pax_global_header00006660000000000000000000000064141255770360014524gustar00rootroot0000000000000052 comment=8b32f3734ff6af7cc7b0fef272591cb80a2d1aae srt-1.4.4/000077500000000000000000000000001412557703600123425ustar00rootroot00000000000000srt-1.4.4/.appveyor.yml000066400000000000000000000030611412557703600150100ustar00rootroot00000000000000configuration: - Release - Debug image: - Visual Studio 2019 - Visual Studio 2015 - Visual Studio 2013 platform: - x64 - x86 build_script: - ps: $VSIMG = $Env:APPVEYOR_BUILD_WORKER_IMAGE; $CNFG = $Env:CONFIGURATION # use a few differing arguments depending on VS version to exercise different options during builds - ps: if ($VSIMG -match '2019' -and $CNFG -eq "Release") { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS ON -UNIT_TESTS ON } - ps: if ($VSIMG -match '2019' -and $CNFG -eq "Debug") { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS ON } - ps: if ($VSIMG -match '2015' -and $CNFG -eq "Release") { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS ON -UNIT_TESTS ON } - ps: if ($VSIMG -match '2015' -and $CNFG -eq "Debug") { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS OFF } - ps: if ($VSIMG -match '2013' -and $CNFG -eq "Release") { .\scripts\build-windows.ps1 -CXX11 OFF -BUILD_APPS ON } - ps: if ($VSIMG -match '2013' -and $CNFG -eq "Debug") { Exit-AppveyorBuild } # just skip 2013 debug build for speed test_script: - ps: if ( $Env:RUN_UNIT_TESTS ) { cd ./_build; ctest -E "TestIPv6.v6_calls_v4" --extra-verbose -C $Env:CONFIGURATION; cd ../ } after_build: - cmd: >- scripts/gather-package.bat 7z a SRT-%APPVEYOR_REPO_BRANCH%-%CONFIGURATION%-Win%PLATFORM%-%VS_VERSION%-%APPVEYOR_BUILD_VERSION%.zip %APPVEYOR_BUILD_FOLDER%\package\* appveyor PushArtifact SRT-%APPVEYOR_REPO_BRANCH%-%CONFIGURATION%-Win%PLATFORM%-%VS_VERSION%-%APPVEYOR_BUILD_VERSION%.zip srt-1.4.4/.clang-format000066400000000000000000000055141412557703600147220ustar00rootroot00000000000000Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -4 # AlignAfterOpenBracket: Align AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: true # AlignEscapedNewlinesLeft: false AlignOperands: true AlignTrailingComments: true # AllowAllArgumentsOnNextLine: true # Requires clang-format v9 and higher # AllowAllConstructorInitializersOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false # AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline # AllowShortIfStatementsOnASingleLine: false # AllowShortLoopsOnASingleLine: false # AlwaysBreakAfterDefinitionReturnType: None # AlwaysBreakAfterReturnType: None # AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: false BinPackParameters: false # BraceWrapping: # AfterClass: false # AfterControlStatement: false # AfterEnum: false # AfterFunction: false # AfterNamespace: false # AfterObjCDeclaration: false # AfterStruct: false # AfterUnion: false # BeforeCatch: false # BeforeElse: false # IndentBraces: false # BreakBeforeBinaryOperators: None BreakBeforeBraces: Allman # BreakInheritanceList: BeforeComma # BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma # BreakAfterJavaFieldAnnotations: false # BreakStringLiterals: true ColumnLimit: 120 # CommentPragmas: '^ IWYU pragma:' # ConstructorInitializerAllOnOneLineOrOnePerLine: false # ConstructorInitializerIndentWidth: 4 # ContinuationIndentWidth: 4 # Cpp11BracedListStyle: true # DerivePointerAlignment: false # DisableFormat: false # ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true # ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] # IncludeIsMainRegex: '$' # IndentCaseLabels: false IndentWidth: 4 # IndentWrappedFunctionNames: false # JavaScriptQuotes: Leave # JavaScriptWrapImports: true # KeepEmptyLinesAtTheStartOfBlocks: true # MacroBlockBegin: '' # MacroBlockEnd: '' # MaxEmptyLinesToKeep: 1 # NamespaceIndentation: None # ObjCBlockIndentWidth: 2 # ObjCSpaceAfterProperty: false # ObjCSpaceBeforeProtocolList: true # PenaltyBreakBeforeFirstCallParameter: 19 # PenaltyBreakComment: 300 # PenaltyBreakFirstLessLess: 120 # PenaltyBreakString: 1000 # PenaltyExcessCharacter: 1000000 # PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Left # ReflowComments: true SortIncludes: false # SpaceAfterCStyleCast: false # SpaceAfterTemplateKeyword: true # SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements # SpaceInEmptyParentheses: false # SpacesBeforeTrailingComments: 1 # SpacesInAngles: false # SpacesInContainerLiterals: true # SpacesInCStyleCastParentheses: false # SpacesInParentheses: false # SpacesInSquareBrackets: false Standard: Cpp03 TabWidth: 4 UseTab: Never srt-1.4.4/.github/000077500000000000000000000000001412557703600137025ustar00rootroot00000000000000srt-1.4.4/.github/ISSUE_TEMPLATE/000077500000000000000000000000001412557703600160655ustar00rootroot00000000000000srt-1.4.4/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000012101412557703600205510ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: 'Type: Bug' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Configure '...' 2. Run '....' 3. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please provide the following information):** - OS: [e.g. Windows, Linux, macOS,...] - SRT Version / commit ID: **Additional context** Add any other context about the problem here. srt-1.4.4/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000012261412557703600216130ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: "[FR]" labels: 'Type: Enhancement' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. For example: "When I try to do X, I am blocked by Y..." **Describe the solution you'd like** A clear and concise description of how you would like to see this feature implemented. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. srt-1.4.4/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000005701412557703600202600ustar00rootroot00000000000000--- name: Question about: Ask a question regarding SRT title: '' labels: 'Type: Question' assignees: '' --- When asking a question please also include where you looked for an answer (so we can update the documentation if needed). You are encouraged to ask general questions regarding SRT in the [SRT Alliance Slack channel](https://slackin-srtalliance.azurewebsites.net/). srt-1.4.4/.github/workflows/000077500000000000000000000000001412557703600157375ustar00rootroot00000000000000srt-1.4.4/.github/workflows/cxx11-macos.yaml000066400000000000000000000007711412557703600206740ustar00rootroot00000000000000name: cxx11 on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: macos runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: configure run: | mkdir _build && cd _build cmake ../ -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=ON -DENABLE_EXPERIMENTAL_BONDING=ON - name: build run: cd _build && cmake --build ./ - name: test run: cd _build && ctest --extra-verbose srt-1.4.4/.github/workflows/cxx11-ubuntu.yaml000066400000000000000000000007421412557703600211120ustar00rootroot00000000000000name: cxx11 on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: ubuntu runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - name: configure run: | mkdir _build && cd _build cmake ../ -DENABLE_STDCXX_SYNC=ON -DENABLE_UNITTESTS=ON -DENABLE_EXPERIMENTAL_BONDING=ON - name: build run: cd _build && cmake --build ./ - name: test run: cd _build && ctest --extra-verbose srt-1.4.4/.github/workflows/cxx11-win.yaml000066400000000000000000000010611412557703600203600ustar00rootroot00000000000000name: cxx11 on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: windows runs-on: windows-latest steps: - uses: actions/checkout@v2 - name: configure run: | md _build && cd _build cmake ../ -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=ON -DENABLE_EXPERIMENTAL_BONDING=ON - name: build run: cd _build && cmake --build ./ --config Release - name: test run: cd _build && ctest -E "TestIPv6.v6_calls_v4" --extra-verbose -C Release srt-1.4.4/.github/workflows/iOS.yaml000066400000000000000000000011041412557703600173110ustar00rootroot00000000000000name: iOS on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: strategy: matrix: cxxstdsync: [OFF, ON] name: iOS-cxxsync${{ matrix.cxxstdsync }} runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: configure run: | mkdir _build && cd _build cmake ../ -DENABLE_ENCRYPTION=OFF -DENABLE_STDCXX_SYNC=${{matrix.cxxstdsync}} -DENABLE_UNITTESTS=OFF -DENABLE_EXPERIMENTAL_BONDING=ON --toolchain scripts/iOS.cmake - name: build run: cd _build && cmake --build ./ srt-1.4.4/.gitignore000066400000000000000000000006651412557703600143410ustar00rootroot00000000000000# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # Ignore any folder starting from underscore _*/ # Ignode Visual Studio Code temp folder .vs/ .vscode/ # Ignore vcpkg submodule vcpkg/ # LSP compile_commands.json srt-1.4.4/.lgtm.yml000066400000000000000000000001721412557703600141060ustar00rootroot00000000000000extraction: cpp: configure: command: - cmake . -DENABLE_HEAVY_LOGGING=1 -DENABLE_EXPERIMENTAL_BONDING=1 srt-1.4.4/.travis.yml000066400000000000000000000102141412557703600144510ustar00rootroot00000000000000language: cpp dist: xenial addons: apt: packages: - tclsh - pkg-config - cmake - libssl-dev - build-essential sonarcloud: organization: "haivision" token: secure: "wJZC0kyyjuf4SZyonZ6p/5Ga9asEqSnKWF9NpRbu6S6ceERO7vbebuSJF5qX3A6ivPuw0TTk5WASOdnvIyfA28FU/D0MWRdH8K7T3w77wdE9EgAEYTUXzdrbzJY18+9pxjljHwWXWALPSGf3MClg4irWrdk1e6uHK+68R39+ZvBGBFpWeeZy/+at9+xwhtAGKBlSHe8zc+3wPxuYdvviLVJ25qbpNmnzkUR0X89G+UBl90raCPSN32EHFdImHZ5DxfEQQJgZFRjzQUY4EW/iYwaMel7jufAq0ClgV4psKujl9Lz8cPqx3WgqRfJyiIthOMTsac7G4zAw8LK2CI0VsssBp0JalLXaumi6vG7o6c3rIwKckzSKccq3pHa7h45praIVVn9s3nq+Q/JGA11FMkKQxdQtmwgFsLhbi6ZxabgsUi5KtWoWY2z6MgpJuROuAjNxZi9XJzUoJs7zSTUtRRW7V8Q2lRiOnknYh25N6TCA5bpyy1EZmRdJErm071YNI9P01gbFz5137FWJFiJzro9TGF0KoHSGiCIdUt3WlMzwr/i/wFLxFBQOZQ2rjTXvhs4hxONxMZV3gzxA1NdLaf9i5Mh6jxVMV+ujaRSV7JmPGzxqiAlpT9cJUhTCYuar9diLLeDrpe7RawEZR8V1xVDQ7yT8ruDNQ78VbSn/sC0=" homebrew: update: false packages: - openssl matrix: include: - os: linux env: - BUILD_TYPE=Debug - BUILD_OPTS='-DENABLE_CODE_COVERAGE=ON -DENABLE_EXPERIMENTAL_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' - RUN_SONARCUBE=1 - RUN_CODECOV=1 - env: - BUILD_TYPE=Debug - BUILD_OPTS='-DENABLE_LOGGING=OFF -DENABLE_MONOTONIC_CLOCK=ON -DENABLE_EXPERIMENTAL_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' - os: linux env: BUILD_TYPE=Release - os: osx osx_image: xcode11.1 env: - BUILD_TYPE=Debug - BUILD_OPTS='-DCMAKE_CXX_FLAGS="-Werror"' - os: osx osx_image: xcode11.1 env: - BUILD_TYPE=Release - BUILD_OPTS='-DCMAKE_CXX_FLAGS="-Werror"' - os: linux compiler: x86_64-w64-mingw32-g++ addons: apt: packages: - gcc-mingw-w64-base - binutils-mingw-w64-x86-64 - gcc-mingw-w64-x86-64 - gcc-mingw-w64 - g++-mingw-w64-x86-64 before_script: - git clone -b OpenSSL_1_1_1-stable https://github.com/openssl/openssl.git openssl - cd openssl - ./Configure --cross-compile-prefix=x86_64-w64-mingw32- mingw64 - make - cd .. env: BUILD_TYPE=Release # Power jobs - os: linux arch: ppc64le env: - BUILD_TYPE=Debug - arch: ppc64le env: - BUILD_TYPE=Release - BUILD_OPTS='-DENABLE_MONOTONIC_CLOCK=ON' script: - TESTS_IPv6="TestMuxer.IPv4_and_IPv6:TestIPv6.v6_calls_v6*:ReuseAddr.ProtocolVersion" ; # Tests to skip due to lack of IPv6 support - if [ "$TRAVIS_COMPILER" == "x86_64-w64-mingw32-g++" ]; then export CC="x86_64-w64-mingw32-gcc"; export CXX="x86_64-w64-mingw32-g++"; cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE $BUILD_OPTS -DENABLE_UNITTESTS="OFF" -DUSE_OPENSSL_PC="OFF" -DOPENSSL_ROOT_DIR="$PWD/openssl" -DCMAKE_SYSTEM_NAME="Windows"; elif [ "$TRAVIS_OS_NAME" == "linux" ]; then cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE $BUILD_OPTS -DENABLE_UNITTESTS="ON"; elif [ "$TRAVIS_OS_NAME" == "osx" ]; then export PKG_CONFIG_PATH=$(brew --prefix openssl)"/lib/pkgconfig"; cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE $BUILD_OPTS -DENABLE_UNITTESTS="ON"; fi - echo "TRAVIS_REPO_SLUG=$TRAVIS_REPO_SLUG" - echo "TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST" - if [[ "$TRAVIS_REPO_SLUG" != "Haivision/srt" || "$TRAVIS_PULL_REQUEST" -gt 0 ]]; then export RUN_SONARCUBE=0; fi - echo "RUN_SONARCUBE=$RUN_SONARCUBE" - if (( "$RUN_SONARCUBE" )); then build-wrapper-linux-x86-64 --out-dir bw-output make; else make -j$(nproc); fi - if [ "$TRAVIS_COMPILER" != "x86_64-w64-mingw32-g++" ]; then ./test-srt --gtest_filter="-$TESTS_IPv6"; fi - if (( "$RUN_CODECOV" )); then source ./scripts/collect-gcov.sh; bash <(curl -s https://codecov.io/bash); fi - if (( "$RUN_SONARCUBE" )); then sonar-scanner -D"sonar.cfamily.gcov.reportPath=."; fi srt-1.4.4/CMakeLists.txt000066400000000000000000001312271412557703600151100ustar00rootroot00000000000000# # SRT - Secure, Reliable, Transport # Copyright (c) 2018 Haivision Systems Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) set (SRT_VERSION 1.4.4) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") include(haiUtil) # needed for set_version_variables # CMake version 3.0 introduced the VERSION option of the project() command # to specify a project version as well as the name. if(${CMAKE_VERSION} VERSION_LESS "3.0.0") project(SRT C CXX) # Sets SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH set_version_variables(SRT_VERSION ${SRT_VERSION}) else() cmake_policy(SET CMP0048 NEW) # Also sets SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH project(SRT VERSION ${SRT_VERSION} LANGUAGES C CXX) endif() include(FindPkgConfig) # XXX See 'if (MINGW)' condition below, may need fixing. include(FindThreads) include(CheckFunctionExists) # Platform shortcuts string(TOLOWER ${CMAKE_SYSTEM_NAME} SYSNAME_LC) set_if(DARWIN (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") OR (${CMAKE_SYSTEM_NAME} MATCHES "iOS") OR (${CMAKE_SYSTEM_NAME} MATCHES "tvOS") OR (${CMAKE_SYSTEM_NAME} MATCHES "watchOS")) set_if(LINUX ${CMAKE_SYSTEM_NAME} MATCHES "Linux") set_if(BSD ${SYSNAME_LC} MATCHES "bsd$") set_if(MICROSOFT WIN32 AND (NOT MINGW AND NOT CYGWIN)) set_if(GNU ${CMAKE_SYSTEM_NAME} MATCHES "GNU") set_if(ANDROID ${SYSNAME_LC} MATCHES "android") set_if(SUNOS "${SYSNAME_LC}" MATCHES "sunos") set_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR (CYGWIN AND CYGWIN_USE_POSIX)) set_if(SYMLINKABLE LINUX OR DARWIN OR BSD OR SUNOS OR CYGWIN OR GNU) # Not sure what to do in case of compiling by MSVC. # This will make installdir in C:\Program Files\SRT then # inside "bin" and "lib64" directories. At least this maintains # the current status. Shall this be not desired, override values # of CMAKE_INSTALL_BINDIR, CMAKE_INSTALL_LIBDIR and CMAKE_INSTALL_INCLUDEDIR. if (NOT DEFINED CMAKE_INSTALL_LIBDIR) include(GNUInstallDirs) endif() # The CMAKE_BUILD_TYPE seems not to be always set, weird. if (NOT DEFINED ENABLE_DEBUG) if (CMAKE_BUILD_TYPE STREQUAL "Debug") set (ENABLE_DEBUG ON) else() set (ENABLE_DEBUG OFF) endif() endif() # Set CMAKE_BUILD_TYPE properly, now that you know # that ENABLE_DEBUG is set as it should. if (ENABLE_DEBUG EQUAL 2) set (CMAKE_BUILD_TYPE "RelWithDebInfo") elseif (ENABLE_DEBUG) # 1, ON, YES, TRUE, Y, or any other non-zero number set (CMAKE_BUILD_TYPE "Debug") else() set (CMAKE_BUILD_TYPE "Release") endif() message(STATUS "BUILD TYPE: ${CMAKE_BUILD_TYPE}") getVarsWith(ENFORCE_ enforcers) foreach(ef ${enforcers}) set (val ${${ef}}) if (NOT val STREQUAL "") set(val =${val}) endif() string(LENGTH ENFORCE_ pflen) string(LENGTH ${ef} eflen) math(EXPR alen ${eflen}-${pflen}) string(SUBSTRING ${ef} ${pflen} ${alen} ef) message(STATUS "FORCED PP VARIABLE: ${ef}${val}") add_definitions(-D${ef}${val}) endforeach() # NOTE: Known options you can change using ENFORCE_ variables: # SRT_ENABLE_ECN 1 /* Early Congestion Notification (for source bitrate control) */ # SRT_DEBUG_TSBPD_OUTJITTER 1 /* Packet Delivery histogram */ # SRT_DEBUG_TSBPD_DRIFT 1 /* Debug Encoder-Decoder Drift) */ # SRT_DEBUG_TSBPD_WRAP 1 /* Debug packet timestamp wraparound */ # SRT_DEBUG_TLPKTDROP_DROPSEQ 1 # SRT_DEBUG_SNDQ_HIGHRATE 1 # SRT_DEBUG_BONDING_STATES 1 # SRT_DEBUG_RTT 1 /* RTT trace */ # SRT_MAVG_SAMPLING_RATE 40 /* Max sampling rate */ # option defaults set(ENABLE_HEAVY_LOGGING_DEFAULT OFF) # Always turn logging on if the build type is debug if (ENABLE_DEBUG) set(ENABLE_HEAVY_LOGGING_DEFAULT ON) endif() set(ENABLE_STDCXX_SYNC_DEFAULT OFF) set(ENABLE_MONOTONIC_CLOCK_DEFAULT OFF) set(MONOTONIC_CLOCK_LINKLIB "") if (MICROSOFT) set(ENABLE_STDCXX_SYNC_DEFAULT ON) elseif (POSIX) test_requires_clock_gettime(ENABLE_MONOTONIC_CLOCK_DEFAULT MONOTONIC_CLOCK_LINKLIB) endif() # options option(CYGWIN_USE_POSIX "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin." OFF) option(ENABLE_CXX11 "Should the c++11 parts (srt-live-transmit) be enabled" ON) option(ENABLE_APPS "Should the Support Applications be Built?" ON) option(ENABLE_EXPERIMENTAL_BONDING "Should the EXPERIMENTAL bonding functionality be enabled?" OFF) option(ENABLE_TESTING "Should the Developer Test Applications be Built?" OFF) option(ENABLE_PROFILE "Should instrument the code for profiling. Ignored for non-GNU compiler." $ENV{HAI_BUILD_PROFILE}) option(ENABLE_LOGGING "Should logging be enabled" ON) option(ENABLE_HEAVY_LOGGING "Should heavy debug logging be enabled" ${ENABLE_HEAVY_LOGGING_DEFAULT}) option(ENABLE_HAICRYPT_LOGGING "Should logging in haicrypt be enabled" 0) option(ENABLE_SHARED "Should libsrt be built as a shared library" ON) option(ENABLE_STATIC "Should libsrt be built as a static library" ON) option(ENABLE_RELATIVE_LIBPATH "Should application contain relative library paths, like ../lib" OFF) option(ENABLE_GETNAMEINFO "In-logs sockaddr-to-string should do rev-dns" OFF) option(ENABLE_UNITTESTS "Enable unit tests" OFF) option(ENABLE_ENCRYPTION "Enable encryption in SRT" ON) option(ENABLE_CXX_DEPS "Extra library dependencies in srt.pc for the CXX libraries useful with C language" ON) option(USE_STATIC_LIBSTDCXX "Should use static rather than shared libstdc++" OFF) option(ENABLE_INET_PTON "Set to OFF to prevent usage of inet_pton when building against modern SDKs while still requiring compatibility with older Windows versions, such as Windows XP, Windows Server 2003 etc." ON) option(ENABLE_CODE_COVERAGE "Enable code coverage reporting" OFF) option(ENABLE_MONOTONIC_CLOCK "Enforced clock_gettime with monotonic clock on GC CV" ${ENABLE_MONOTONIC_CLOCK_DEFAULT}) option(ENABLE_STDCXX_SYNC "Use C++11 chrono and threads for timing instead of pthreads" ${ENABLE_STDCXX_SYNC_DEFAULT}) option(USE_OPENSSL_PC "Use pkg-config to find OpenSSL libraries" ON) option(USE_BUSY_WAITING "Enable more accurate sending times at a cost of potentially higher CPU load" OFF) option(USE_GNUSTL "Get c++ library/headers from the gnustl.pc" OFF) option(ENABLE_SOCK_CLOEXEC "Enable setting SOCK_CLOEXEC on a socket" ON) option(ENABLE_CLANG_TSA "Enable Clang Thread Safety Analysis" OFF) # NOTE: Use ATOMIC_USE_SRT_SYNC_MUTEX and will override the auto-detection of the # Atomic implemetation in srtcore/atomic.h. option(ATOMIC_USE_SRT_SYNC_MUTEX "Use srt::sync::Mutex to Implement Atomics" OFF) if (ATOMIC_USE_SRT_SYNC_MUTEX) add_definitions(-DATOMIC_USE_SRT_SYNC_MUTEX=1) endif() set(TARGET_srt "srt" CACHE STRING "The name for the SRT library") # Use application-defined group reader # (currently the only one implemented) add_definitions(-DSRT_ENABLE_APP_READER) # XXX This was added once as experimental, it is now in force for # write-blocking-mode sockets. Still unclear if all issues around # closing while data still not written are eliminated. add_definitions(-DSRT_ENABLE_CLOSE_SYNCH) if (NOT ENABLE_LOGGING) set (ENABLE_HEAVY_LOGGING OFF) message(STATUS "LOGGING: DISABLED") else() if (ENABLE_HEAVY_LOGGING) message(STATUS "LOGGING: HEAVY") else() message(STATUS "LOGGING: ENABLED") endif() endif() if (USE_BUSY_WAITING) message(STATUS "USE_BUSY_WAITING: ON") list(APPEND SRT_EXTRA_CFLAGS "-DUSE_BUSY_WAITING=1") else() message(STATUS "USE_BUSY_WAITING: OFF (default)") endif() if ( CYGWIN AND NOT CYGWIN_USE_POSIX ) set(WIN32 1) set(CMAKE_LEGACY_CYGWIN_WIN32 1) add_definitions(-DWIN32=1 -DCYGWIN=1) message(STATUS "HAVE CYGWIN. Setting backward compat CMAKE_LEGACY_CYGWIN_WIN32 and -DWIN32") endif() if (NOT USE_ENCLIB) if (USE_GNUTLS) message("NOTE: USE_GNUTLS is deprecated. Use -DUSE_ENCLIB=gnutls instead.") set (USE_ENCLIB gnutls) else() set (USE_ENCLIB openssl) endif() endif() set(USE_ENCLIB "${USE_ENCLIB}" CACHE STRING "The crypto library that SRT uses") set_property(CACHE USE_ENCLIB PROPERTY STRINGS "openssl" "gnutls" "mbedtls") # Make sure DLLs and executabes go to the same path regardles of subdirectory set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) if (NOT DEFINED WITH_COMPILER_TYPE) # This is for a case when you provided the prefix, but you didn't # provide compiler type. This option is in this form predicted to work # only on POSIX systems. Just typical compilers for Linux and Mac are # included. if (DARWIN) set (WITH_COMPILER_TYPE clang) elseif (POSIX) # Posix, but not DARWIN set(WITH_COMPILER_TYPE gcc) else() get_filename_component(WITH_COMPILER_TYPE ${CMAKE_C_COMPILER} NAME) endif() set (USING_DEFAULT_COMPILER_PREFIX 1) endif() if (NOT USING_DEFAULT_COMPILER_PREFIX OR DEFINED WITH_COMPILER_PREFIX) message(STATUS "Handling compiler with PREFIX=${WITH_COMPILER_PREFIX} TYPE=${WITH_COMPILER_TYPE}") parse_compiler_type(${WITH_COMPILER_TYPE} COMPILER_TYPE COMPILER_SUFFIX) if (${COMPILER_TYPE} STREQUAL gcc) set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}gcc${COMPILER_SUFFIX}) set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}g++${COMPILER_SUFFIX}) set (HAVE_COMPILER_GNU_COMPAT 1) elseif (${COMPILER_TYPE} STREQUAL cc) set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}cc${COMPILER_SUFFIX}) set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}c++${COMPILER_SUFFIX}) set (HAVE_COMPILER_GNU_COMPAT 1) elseif (${COMPILER_TYPE} STREQUAL icc) set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}icc${COMPILER_SUFFIX}) set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}icpc${COMPILER_SUFFIX}) set (HAVE_COMPILER_GNU_COMPAT 1) else() # Use blindly for C compiler and ++ for C++. # At least this matches clang. set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}${WITH_COMPILER_TYPE}) set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}${COMPILER_TYPE}++${COMPILER_SUFFIX}) if (${COMPILER_TYPE} STREQUAL clang) set (HAVE_COMPILER_GNU_COMPAT 1) endif() endif() message(STATUS "Compiler type: ${WITH_COMPILER_TYPE}. C: ${CMAKE_C_COMPILER}; C++: ${CMAKE_CXX_COMPILER}") unset(USING_DEFAULT_COMPILER_PREFIX) else() message(STATUS "No WITH_COMPILER_PREFIX - using C++ compiler ${CMAKE_CXX_COMPILER}") endif() if (DEFINED WITH_SRT_TARGET) set (TARGET_haisrt ${WITH_SRT_TARGET}) endif() # When you use crosscompiling, you have to take care that PKG_CONFIG_PATH # and CMAKE_PREFIX_PATH are set properly. # symbol exists in win32, but function does not. if(WIN32) if(ENABLE_INET_PTON) set(CMAKE_REQUIRED_LIBRARIES ws2_32) check_function_exists(inet_pton HAVE_INET_PTON) add_definitions(-D_WIN32_WINNT=0x0600) else() add_definitions(-D_WIN32_WINNT=0x0501) endif() else() check_function_exists(inet_pton HAVE_INET_PTON) endif() if (DEFINED HAVE_INET_PTON) add_definitions(-DHAVE_INET_PTON=1) endif() # Defines HAVE_PTHREAD_GETNAME_* and HAVE_PTHREAD_SETNAME_* include(FindPThreadGetSetName) FindPThreadGetSetName() if (ENABLE_MONOTONIC_CLOCK) if (NOT ENABLE_MONOTONIC_CLOCK_DEFAULT) message(FATAL_ERROR "Your platform does not support CLOCK_MONOTONIC. Build with -DENABLE_MONOTONIC_CLOCK=OFF.") endif() set (WITH_EXTRALIBS "${WITH_EXTRALIBS} ${MONOTONIC_CLOCK_LINKLIB}") add_definitions(-DENABLE_MONOTONIC_CLOCK=1) endif() if (ENABLE_ENCRYPTION) if ("${USE_ENCLIB}" STREQUAL "gnutls") set (SSL_REQUIRED_MODULES "gnutls nettle") if (WIN32) if (MINGW) set (SSL_REQUIRED_MODULES "${SSL_REQUIRED_MODULES} zlib") endif() endif() pkg_check_modules (SSL REQUIRED ${SSL_REQUIRED_MODULES}) add_definitions( -DUSE_GNUTLS=1 ) link_directories( ${SSL_LIBRARY_DIRS} ) elseif ("${USE_ENCLIB}" STREQUAL "mbedtls") add_definitions(-DUSE_MBEDTLS=1) if ("${SSL_LIBRARY_DIRS}" STREQUAL "") set(MBEDTLS_PREFIX "${CMAKE_PREFIX_PATH}" CACHE PATH "The path of mbedtls") find_package(MbedTLS REQUIRED) set (SSL_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR}) set (SSL_LIBRARIES ${MBEDTLS_LIBRARIES}) endif() if ("${SSL_LIBRARIES}" STREQUAL "") set (SSL_LIBRARIES mbedtls mbedcrypto) endif() message(STATUS "SSL enforced mbedtls: -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") foreach(LIB ${SSL_LIBRARIES}) if(IS_ABSOLUTE ${LIB} AND EXISTS ${LIB}) set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${LIB}) else() set(SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} "-l${LIB}") endif() endforeach() else() # openssl add_definitions(-DUSE_OPENSSL=1) set (SSL_REQUIRED_MODULES "openssl libcrypto") # Try using pkg-config method first if enabled, # fall back to find_package method otherwise if (USE_OPENSSL_PC) pkg_check_modules(SSL ${SSL_REQUIRED_MODULES}) endif() if (SSL_FOUND) # We have some cases when pkg-config is improperly configured # When it doesn't ship the -L and -I options, and the CMAKE_PREFIX_PATH # is set (also through `configure`), then we have this problem. If so, # set forcefully the -I and -L contents to prefix/include and # prefix/lib. if ("${SSL_LIBRARY_DIRS}" STREQUAL "") if (NOT "${CMAKE_PREFIX_PATH}" STREQUAL "") message(STATUS "WARNING: pkg-config has incorrect prefix - enforcing target path prefix: ${CMAKE_PREFIX_PATH}") set (SSL_LIBRARY_DIRS ${CMAKE_PREFIX_PATH}/${CMAKE_INSTALL_LIBDIR}) set (SSL_INCLUDE_DIRS ${CMAKE_PREFIX_PATH}/include) endif() endif() link_directories( ${SSL_LIBRARY_DIRS} ) message(STATUS "SSL via pkg-config: -L ${SSL_LIBRARY_DIRS} -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") else() find_package(OpenSSL REQUIRED) set (SSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) set (SSL_LIBRARIES ${OPENSSL_LIBRARIES}) message(STATUS "SSL via find_package(OpenSSL): -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") endif() endif() add_definitions(-DSRT_ENABLE_ENCRYPTION) message(STATUS "ENCRYPTION: ENABLED, using: ${SSL_REQUIRED_MODULES}") message (STATUS "SSL libraries: ${SSL_LIBRARIES}") else() message(STATUS "ENCRYPTION: DISABLED") endif() if (USE_GNUSTL) pkg_check_modules (GNUSTL REQUIRED gnustl) link_directories(${GNUSTL_LIBRARY_DIRS}) include_directories(${GNUSTL_INCLUDE_DIRS}) set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) endif() if (USING_DEFAULT_COMPILER_PREFIX) # Detect if the compiler is GNU compatable for flags if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Intel|Clang|AppleClang") message(STATUS "COMPILER: ${CMAKE_CXX_COMPILER_ID} (${CMAKE_CXX_COMPILER}) - GNU compat") set(HAVE_COMPILER_GNU_COMPAT 1) # See https://gcc.gnu.org/projects/cxx-status.html # At the bottom there's information about C++98, which is default up to 6.1 version. # For all other compilers - including Clang - we state that the default C++ standard is AT LEAST 11. if (${CMAKE_CXX_COMPILER_ID} STREQUAL GNU AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 6.1) message(STATUS "NOTE: GCC ${CMAKE_CXX_COMPILER_VERSION} is detected with default C++98. Forcing C++11 on applications.") set (FORCE_CXX_STANDARD 1) elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang|AppleClang") message(STATUS "NOTE: CLANG ${CMAKE_CXX_COMPILER_VERSION} detected, unsure if >=C++11 is default, forcing C++11 on applications") set (FORCE_CXX_STANDARD 1) else() message(STATUS "NOTE: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} - assuming default C++11.") endif() else() message(STATUS "COMPILER: ${CMAKE_CXX_COMPILER_ID} (${CMAKE_CXX_COMPILER}) - NOT GNU compat") set(HAVE_COMPILER_GNU_COMPAT 0) endif() else() # Compiler altered by WITH_COMPILER_TYPE/PREFIX - can't rely on CMAKE_CXX_* # Force the C++ standard as C++11 # HAVE_COMPILER_GNU_COMPAT was set in the handler of WITH_COMPILER_TYPE set (FORCE_CXX_STANDARD 1) message(STATUS "COMPILER CHANGED TO: ${COMPILER_TYPE} - forcing C++11 standard for apps") endif() # Check for GCC Atomic Intrinsics and C++11 Atomics. # Sets: # HAVE_LIBATOMIC # HAVE_LIBATOMIC_COMPILES # HAVE_LIBATOMIC_COMPILES_STATIC # HAVE_GCCATOMIC_INTRINSICS # HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC include(CheckGCCAtomicIntrinsics) CheckGCCAtomicIntrinsics() # HAVE_CXX_ATOMIC # HAVE_CXX_ATOMIC_STATIC include(CheckCXXAtomic) CheckCXXAtomic() if (DISABLE_CXX11) set (ENABLE_CXX11 0) elseif( DEFINED ENABLE_CXX11 ) else() set (ENABLE_CXX11 1) endif() function (srt_check_cxxstd stdval OUT_STD OUT_PFX) set (STDPFX c++) if (stdval MATCHES "([^+]+\\++)([0-9]*)") set (STDPFX ${CMAKE_MATCH_1}) set (STDCXX ${CMAKE_MATCH_2}) elseif (stdval MATCHES "[0-9]*") set (STDCXX ${stdval}) else() set (STDCXX 0) endif() # Handle C++98 < C++11 # Please fix this around 2070 year. if (${STDCXX} GREATER 80) set (STDCXX 03) endif() # return set (${OUT_STD} ${STDCXX} PARENT_SCOPE) set (${OUT_PFX} ${STDPFX} PARENT_SCOPE) endfunction() if (NOT ENABLE_CXX11) message(WARNING "Parts that require C++11 support will be disabled (srt-live-transmit)") if (ENABLE_STDCXX_SYNC) message(FATAL_ERROR "ENABLE_STDCXX_SYNC is set, but C++11 is disabled by ENABLE_CXX11") endif() elseif (ENABLE_STDCXX_SYNC) add_definitions(-DENABLE_STDCXX_SYNC=1) if (DEFINED USE_CXX_STD) srt_check_cxxstd(${USE_CXX_STD} STDCXX STDPFX) # If defined, make sure it's at least C++11 if (${STDCXX} LESS 11) message(FATAL_ERROR "If ENABLE_STDCXX_SYNC, then USE_CXX_STD must specify at least C++11") endif() else() set (USE_CXX_STD 11) endif() endif() message(STATUS "STDCXX_SYNC: ${ENABLE_STDCXX_SYNC}") message(STATUS "MONOTONIC_CLOCK: ${ENABLE_MONOTONIC_CLOCK}") if (ENABLE_SOCK_CLOEXEC) add_definitions(-DENABLE_SOCK_CLOEXEC=1) endif() if (CMAKE_MAJOR_VERSION LESS 3) set (FORCE_CXX_STANDARD_GNUONLY 1) endif() if (DEFINED USE_CXX_STD) srt_check_cxxstd(${USE_CXX_STD} STDCXX STDPFX) if (${STDCXX} EQUAL 0) message(FATAL_ERROR "USE_CXX_STD: Must specify 03/11/14/17/20 possibly with c++/gnu++ prefix") endif() if (NOT STDCXX STREQUAL "") if (${STDCXX} LESS 11) if (ENABLE_STDCXX_SYNC) message(FATAL_ERROR "If ENABLE_STDCXX_SYNC, then you can't USE_CXX_STD less than 11") endif() # Set back to 98 because cmake doesn't understand 03. set (STDCXX 98) # This enforces C++03 standard on SRT. # Apps still use C++11 # Set this through independent flags set (USE_CXX_STD_LIB ${STDCXX}) set (FORCE_CXX_STANDARD 1) if (NOT ENABLE_APPS) set (USE_CXX_STD_APP ${STDCXX}) message(STATUS "C++ STANDARD: library: C++${STDCXX}, apps disabled (examples will follow C++${STDCXX})") else() set (USE_CXX_STD_APP "") message(STATUS "C++ STANDARD: library: C++${STDCXX}, but apps still at least C++11") endif() elseif (FORCE_CXX_STANDARD_GNUONLY) # CMake is too old to handle CMAKE_CXX_STANDARD, # use bare GNU options. set (FORCE_CXX_STANDARD 1) set (USE_CXX_STD_APP ${STDCXX}) set (USE_CXX_STD_LIB ${STDCXX}) message(STATUS "C++ STANDARD: using C++${STDCXX} for all - GNU only") else() # This enforces this standard on both apps and library, # so set this as global C++ standard option set (CMAKE_CXX_STANDARD ${STDCXX}) unset (FORCE_CXX_STANDARD) # Do not set variables to not duplicate flags set (USE_CXX_STD_LIB "") set (USE_CXX_STD_APP "") message(STATUS "C++ STANDARD: using C++${STDCXX} for all") endif() message(STATUS "C++: Setting C++ standard for gnu compiler: lib: ${USE_CXX_STD_LIB} apps: ${USE_CXX_STD_APP}") endif() else() set (USE_CXX_STD_LIB "") set (USE_CXX_STD_APP "") endif() if (FORCE_CXX_STANDARD) message(STATUS "C++ STD: Forcing C++11 on applications") if (USE_CXX_STD_APP STREQUAL "") set (USE_CXX_STD_APP 11) endif() if (USE_CXX_STD_LIB STREQUAL "" AND ENABLE_STDCXX_SYNC) message(STATUS "C++ STD: Forcing C++11 on library, as C++11 sync requested") set (USE_CXX_STD_LIB 11) endif() endif() # add extra warning flags for gccish compilers if (HAVE_COMPILER_GNU_COMPAT) set (SRT_GCC_WARN "-Wall -Wextra") else() # cpp debugging on Windows :D #set (SRT_GCC_WARN "/showIncludes") endif() if (USE_STATIC_LIBSTDCXX) if (HAVE_COMPILER_GNU_COMPAT) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++") else() message(FATAL_ERROR "On non-GNU-compat compiler it's not known how to use static C++ standard library.") endif() endif() # This options is necessary on some systems; on a cross-ARM compiler it # has been detected, for example, that -lrt is necessary for some applications # because clock_gettime is needed by some functions and it is alternatively # provided by libc, but only in newer versions. This options is rarely necessary, # but may help in several corner cases in unusual platforms. if (WITH_EXTRALIBS) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${WITH_EXTRALIBS}") endif() # CMake has only discovered in 3.3 version that some set-finder is # necessary. Using variables for shortcut to a clumsy check syntax. set (srt_libspec_shared ${ENABLE_SHARED}) set (srt_libspec_static ${ENABLE_STATIC}) set (srtpack_libspec_common) if (srt_libspec_shared) list(APPEND srtpack_libspec_common ${TARGET_srt}_shared) endif() if (srt_libspec_static) list(APPEND srtpack_libspec_common ${TARGET_srt}_static) endif() set (SRT_SRC_HAICRYPT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/haicrypt) set (SRT_SRC_SRTCORE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/srtcore) set (SRT_SRC_COMMON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/common) set (SRT_SRC_TOOLS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools) set (SRT_SRC_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test) if(WIN32) message(STATUS "DETECTED SYSTEM: WINDOWS; WIN32=1; PTW32_STATIC_LIB=1") add_definitions(-DWIN32=1 -DPTW32_STATIC_LIB=1) elseif(DARWIN) message(STATUS "DETECTED SYSTEM: DARWIN") elseif(BSD) message(STATUS "DETECTED SYSTEM: BSD; BSD=1") add_definitions(-DBSD=1) elseif(LINUX) add_definitions(-DLINUX=1) message(STATUS "DETECTED SYSTEM: LINUX; LINUX=1" ) elseif(ANDROID) add_definitions(-DLINUX=1) message(STATUS "DETECTED SYSTEM: ANDROID; LINUX=1" ) elseif(CYGWIN) add_definitions(-DCYGWIN=1) message(STATUS "DETECTED SYSTEM: CYGWIN (posix mode); CYGWIN=1") elseif(GNU) add_definitions(-DGNU=1) message(STATUS "DETECTED SYSTEM: GNU; GNU=1" ) elseif(SUNOS) add_definitions(-DSUNOS=1) message(STATUS "DETECTED SYSTEM: SunOS|Solaris; SUNOS=1" ) else() message(FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}") endif() add_definitions( -D_GNU_SOURCE -DHAI_PATCH=1 -DHAI_ENABLE_SRT=1 -DSRT_VERSION="${SRT_VERSION}" ) if (LINUX) # This is an option supported only on Linux add_definitions(-DSRT_ENABLE_BINDTODEVICE) endif() # This is obligatory include directory for all targets. This is only # for private headers. Installable headers should be exclusively used DIRECTLY. include_directories(${SRT_SRC_COMMON_DIR} ${SRT_SRC_SRTCORE_DIR} ${SRT_SRC_HAICRYPT_DIR}) if (ENABLE_LOGGING) list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_LOGGING=1") if (ENABLE_HEAVY_LOGGING) list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_HEAVY_LOGGING=1") endif() if (ENABLE_HAICRYPT_LOGGING) if (ENABLE_HAICRYPT_LOGGING STREQUAL 2) # Allow value 2 for INSECURE DEBUG logging message(WARNING " *** ENABLED INSECURE HAICRYPT LOGGING - USE FOR TESTING ONLY!!! ***") list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_HAICRYPT_LOGGING=2") else() list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_HAICRYPT_LOGGING=1") endif() endif() endif() if (ENABLE_GETNAMEINFO) list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_GETNAMEINFO=1") endif() if (ENABLE_EXPERIMENTAL_BONDING) list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_EXPERIMENTAL_BONDING=1") endif() if (ENABLE_THREAD_CHECK) add_definitions( -DSRT_ENABLE_THREADCHECK=1 -DFUGU_PLATFORM=1 -I${WITH_THREAD_CHECK_INCLUDEDIR} ) endif() if (ENABLE_CLANG_TSA) list(APPEND SRT_EXTRA_CFLAGS "-Wthread-safety") message(STATUS "Clang TSA: Enabled") endif() if (ENABLE_PROFILE) if (HAVE_COMPILER_GNU_COMPAT) # They are actually cflags, not definitions, but CMake is stupid enough. add_definitions(-g -pg) link_libraries(-g -pg) else() message(FATAL_ERROR "Profiling option is not supported on this platform") endif() endif() if (ENABLE_CODE_COVERAGE) if (HAVE_COMPILER_GNU_COMPAT) add_definitions(-g -O0 --coverage) link_libraries(--coverage) message(STATUS "ENABLE_CODE_COVERAGE: ON") else() message(FATAL_ERROR "ENABLE_CODE_COVERAGE: option is not supported on this platform") endif() endif() # On Linux pthreads have to be linked even when using C++11 threads if (ENABLE_STDCXX_SYNC AND NOT LINUX) message(STATUS "Pthread library: C++11") elseif (PTHREAD_LIBRARY AND PTHREAD_INCLUDE_DIR) message(STATUS "Pthread library: ${PTHREAD_LIBRARY}") message(STATUS "Pthread include dir: ${PTHREAD_INCLUDE_DIR}") elseif (MICROSOFT) find_package(pthreads QUIET) if (NOT PTHREAD_INCLUDE_DIR OR NOT PTHREAD_LIBRARY) #search package folders with GLOB to add as extra hint for headers file(GLOB PTHREAD_PACKAGE_INCLUDE_HINT ./_packages/cinegy.pthreads-win*/sources) if (PTHREAD_PACKAGE_INCLUDE_HINT) message(STATUS "PTHREAD_PACKAGE_INCLUDE_HINT value: ${PTHREAD_PACKAGE_INCLUDE_HINT}") endif() # find pthread header find_path(PTHREAD_INCLUDE_DIR pthread.h HINTS C:/pthread-win32/include ${PTHREAD_PACKAGE_INCLUDE_HINT}) if (PTHREAD_INCLUDE_DIR) message(STATUS "Pthread include dir: ${PTHREAD_INCLUDE_DIR}") else() message(FATAL_ERROR "Failed to find pthread.h. Specify PTHREAD_INCLUDE_DIR.") endif() #search package folders with GLOB to add as extra hint for libs file(GLOB PTHREAD_PACKAGE_LIB_HINT ./_packages/cinegy.pthreads-win*/runtimes/win-*/native/release) if (PTHREAD_PACKAGE_LIB_HINT) message(STATUS "PTHREAD_PACKAGE_LIB_HINT value: ${PTHREAD_PACKAGE_LIB_HINT}") endif() #find pthread library set(PTHREAD_LIB_SUFFIX "") if (ENABLE_DEBUG) set(PTHREAD_LIB_SUFFIX "d") endif () set(PTHREAD_COMPILER_FLAG "") if (MICROSOFT) set(PTHREAD_COMPILER_FLAG "V") elseif (MINGW) set(PTHREAD_COMPILER_FLAG "G") endif () foreach(EXHAND C CE SE) foreach(COMPAT 1 2) list(APPEND PTHREAD_W32_LIBRARY "pthread${PTHREAD_COMPILER_FLAG}${EXHAND}${PTHREAD_LIB_SUFFIX}${COMPAT}") endforeach() endforeach() find_library(PTHREAD_LIBRARY NAMES ${PTHREAD_W32_LIBRARY} pthread pthread_dll pthread_lib HINTS C:/pthread-win32/lib C:/pthread-win64/lib ${PTHREAD_PACKAGE_LIB_HINT}) if (PTHREAD_LIBRARY) message(STATUS "Pthread library: ${PTHREAD_LIBRARY}") else() message(FATAL_ERROR "Failed to find pthread library. Specify PTHREAD_LIBRARY.") endif() endif() else () find_package(Threads REQUIRED) set(PTHREAD_LIBRARY ${CMAKE_THREAD_LIBS_INIT}) endif() # This is required in some projects that add some other sources # to the SRT library to be compiled together (aka "virtual library"). if (DEFINED SRT_EXTRA_LIB_INC) include(${SRT_EXTRA_LIB_INC}.cmake) # Expected to provide variables: # - SOURCES_srt_extra # - EXTRA_stransmit endif() # --------------------------------------------------------------------------- # --- # Target: haicrypt. # Completing sources and installable headers. Flag settings will follow. # --- if (ENABLE_ENCRYPTION) set (HAICRYPT_FILELIST_MAF "filelist-${USE_ENCLIB}.maf") MafReadDir(haicrypt ${HAICRYPT_FILELIST_MAF} SOURCES SOURCES_haicrypt PUBLIC_HEADERS HEADERS_haicrypt PROTECTED_HEADERS HEADERS_haicrypt ) endif() if (WIN32) MafReadDir(common filelist_win32.maf SOURCES SOURCES_common PUBLIC_HEADERS HEADERS_srt_win32 PROTECTED_HEADERS HEADERS_srt_win32 ) message(STATUS "WINDOWS detected: adding compat sources: ${SOURCES_common}") endif() # Make the OBJECT library for haicrypt and srt. Then they'll be bound into # real libraries later, either one common, or separate. # This is needed for Xcode to properly handle CMake OBJECT Libraries # From docs (https://cmake.org/cmake/help/latest/command/add_library.html#object-libraries): # # ... Some native build systems (such as Xcode) may not like targets that have only object files, # so consider adding at least one real source file to any target that references $. set(OBJECT_LIB_SUPPORT "${PROJECT_SOURCE_DIR}/cmake_object_lib_support.c") # NOTE: The "virtual library" is a library specification that cmake # doesn't support (the library of OBJECT type is something in kind of that, # but not fully supported - for example it doesn't support transitive flags, # so this can't be used desired way). It's a private-only dependency type, # where the project isn't compiled into any library file at all - instead, all # of its source files are incorporated directly to the source list of the # project that depends on it. In cmake this must be handled manually. # --- # Target: srt. DEFINITION ONLY. Haicrypt flag settings follow. # --- if (ENABLE_SHARED AND MICROSOFT) #add resource files to shared library, to set DLL metadata on Windows DLLs set (EXTRA_WIN32_SHARED 1) message(STATUS "WIN32: extra resource file will be added") endif() MafReadDir(srtcore filelist.maf SOURCES SOURCES_srt PUBLIC_HEADERS HEADERS_srt PROTECTED_HEADERS HEADERS_srt PRIVATE_HEADERS HEADERS_srt_private ) # Auto generated version file and add it to the HEADERS_srt list. if(DEFINED ENV{APPVEYOR_BUILD_NUMBER}) set(SRT_VERSION_BUILD ON) set(CI_BUILD_NUMBER_STRING $ENV{APPVEYOR_BUILD_NUMBER}) message(STATUS "AppVeyor build environment detected: Adding build number to version header") endif() if(DEFINED ENV{TEAMCITY_VERSION}) set(SRT_VERSION_BUILD ON) set(CI_BUILD_NUMBER_STRING $ENV{CI_BUILD_COUNTER}) message(STATUS "TeamCity build environment detected: Adding build counter to version header") endif() configure_file("srtcore/version.h.in" "version.h" @ONLY) list(INSERT HEADERS_srt 0 "${CMAKE_CURRENT_BINARY_DIR}/version.h") include_directories("${CMAKE_CURRENT_BINARY_DIR}") add_library(srt_virtual OBJECT ${SOURCES_srt} ${SOURCES_srt_extra} ${HEADERS_srt} ${SOURCES_haicrypt} ${SOURCES_common}) if (ENABLE_SHARED) # Set this to sources as well, as it won't be automatically handled set_target_properties(srt_virtual PROPERTIES POSITION_INDEPENDENT_CODE 1) endif() macro(srt_set_stdcxx targetname spec) set (stdcxxspec ${spec}) if (NOT "${stdcxxspec}" STREQUAL "") if (FORCE_CXX_STANDARD_GNUONLY) target_compile_options(${targetname} PRIVATE -std=c++${stdcxxspec}) message(STATUS "C++ STD: ${targetname}: forced C++${stdcxxspec} standard - GNU option: -std=c++${stdcxxspec}") else() set_target_properties(${targetname} PROPERTIES CXX_STANDARD ${stdcxxspec}) message(STATUS "C++ STD: ${targetname}: forced C++${stdcxxspec} standard - portable way") endif() else() message(STATUS "APP: ${targetname}: using default C++ standard") endif() endmacro() srt_set_stdcxx(srt_virtual "${USE_CXX_STD_LIB}") set (VIRTUAL_srt $) if (srt_libspec_shared) add_library(${TARGET_srt}_shared SHARED ${OBJECT_LIB_SUPPORT} ${VIRTUAL_srt}) # shared libraries need PIC set (CMAKE_POSITION_INDEPENDENT_CODE ON) set_property(TARGET ${TARGET_srt}_shared PROPERTY OUTPUT_NAME ${TARGET_srt}) set_target_properties (${TARGET_srt}_shared PROPERTIES VERSION ${SRT_VERSION} SOVERSION ${SRT_VERSION_MAJOR}.${SRT_VERSION_MINOR}) list (APPEND INSTALL_TARGETS ${TARGET_srt}_shared) if (ENABLE_ENCRYPTION) target_link_libraries(${TARGET_srt}_shared PRIVATE ${SSL_LIBRARIES}) endif() if (MICROSOFT) target_link_libraries(${TARGET_srt}_shared PRIVATE ws2_32.lib) if (OPENSSL_USE_STATIC_LIBS) target_link_libraries(${TARGET_srt}_shared PRIVATE crypt32.lib) else() set_target_properties(${TARGET_srt}_shared PROPERTIES LINK_FLAGS "/DELAYLOAD:libeay32.dll") endif() elseif (MINGW) target_link_libraries(${TARGET_srt}_shared PRIVATE wsock32.lib ws2_32.lib) elseif (APPLE) set_property(TARGET ${TARGET_srt}_shared PROPERTY MACOSX_RPATH ON) endif() if (USE_GNUSTL) target_link_libraries(${TARGET_srt}_shared PRIVATE ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) endif() endif() if (srt_libspec_static) add_library(${TARGET_srt}_static STATIC ${OBJECT_LIB_SUPPORT} ${VIRTUAL_srt}) # For Windows, leave the name to be "srt_static.lib". # Windows generates two different library files: # - a usual static library for static linkage # - a shared library exposer, which allows pre-resolution and later dynamic # linkage when running the executable # Both having unfortunately the same names created by MSVC compiler. # It's not the case of Cygwin/MINGW - they are named there libsrt.a and libsrt.dll.a if (MICROSOFT) # Keep _static suffix. By unknown reason, the name must still be set explicitly. set_property(TARGET ${TARGET_srt}_static PROPERTY OUTPUT_NAME ${TARGET_srt}_static) else() set_property(TARGET ${TARGET_srt}_static PROPERTY OUTPUT_NAME ${TARGET_srt}) endif() list (APPEND INSTALL_TARGETS ${TARGET_srt}_static) if (ENABLE_ENCRYPTION) target_link_libraries(${TARGET_srt}_static PRIVATE ${SSL_LIBRARIES}) endif() if (MICROSOFT) target_link_libraries(${TARGET_srt}_static PRIVATE ws2_32.lib) if (OPENSSL_USE_STATIC_LIBS) target_link_libraries(${TARGET_srt}_static PRIVATE crypt32.lib) endif() elseif (MINGW) target_link_libraries(${TARGET_srt}_static PRIVATE wsock32 ws2_32) endif() if (USE_GNUSTL) target_link_libraries(${TARGET_srt}_static PRIVATE ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) endif() endif() target_include_directories(srt_virtual PRIVATE ${SSL_INCLUDE_DIRS}) if (MICROSOFT) if (OPENSSL_USE_STATIC_LIBS) set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ws2_32.lib crypt32.lib) else() set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ws2_32.lib) endif() elseif (MINGW) set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} -lwsock32 -lws2_32) endif() # Applying this to public includes is not transitive enough. # On Windows, apps require this as well, so it's safer to # spread this to all targets. if (PTHREAD_INCLUDE_DIR) include_directories(${PTHREAD_INCLUDE_DIR}) endif() # Link libraries must be applied directly to the derivatives # as virtual libraries (OBJECT-type) cannot have linkage declarations # transitive or not. foreach(tar ${srtpack_libspec_common}) message(STATUS "ADDING TRANSITIVE LINK DEP to:${tar} : ${PTHREAD_LIBRARY} ${dep}") target_link_libraries (${tar} PUBLIC ${PTHREAD_LIBRARY} ${dep}) endforeach() set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${PTHREAD_LIBRARY}) target_compile_definitions(srt_virtual PRIVATE -DSRT_EXPORTS ) if (ENABLE_SHARED) target_compile_definitions(srt_virtual PUBLIC -DSRT_DYNAMIC) endif() if (srt_libspec_shared) if (MICROSOFT) target_link_libraries(${TARGET_srt}_shared PUBLIC Ws2_32.lib) if (OPENSSL_USE_STATIC_LIBS) target_link_libraries(${TARGET_srt}_shared PUBLIC crypt32.lib) endif() endif() endif() # Required by some toolchains when statically linking this library if the # GCC Atomic Intrinsics are being used. if (HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC AND HAVE_LIBATOMIC) if (srt_libspec_static) target_link_libraries(${TARGET_srt}_static PUBLIC atomic) endif() if (srt_libspec_shared) target_link_libraries(${TARGET_srt}_shared PUBLIC atomic) endif() elseif (HAVE_LIBATOMIC AND HAVE_LIBATOMIC_COMPILES_STATIC) # This is a workaround for ANDROID NDK<17 builds, which need to link # to libatomic when linking statically to the SRT library. if (srt_libspec_static) target_link_libraries(${TARGET_srt}_static PUBLIC atomic) endif() endif() # Cygwin installs the *.dll libraries in bin directory and uses PATH. set (INSTALL_SHARED_DIR ${CMAKE_INSTALL_LIBDIR}) if (CYGWIN) set (INSTALL_SHARED_DIR ${CMAKE_INSTALL_BINDIR}) endif() message(STATUS "INSTALL DIRS: bin=${CMAKE_INSTALL_BINDIR} lib=${CMAKE_INSTALL_LIBDIR} shlib=${INSTALL_SHARED_DIR} include=${CMAKE_INSTALL_INCLUDEDIR}") install(TARGETS ${INSTALL_TARGETS} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${INSTALL_SHARED_DIR} ) install(FILES ${HEADERS_srt} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/srt) if (WIN32) install(FILES ${HEADERS_srt_win32} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/srt/win) endif() # --- # That's all for target definition # --- join_arguments(SRT_EXTRA_CFLAGS ${SRT_EXTRA_CFLAGS}) #message(STATUS "Target srt: LIBSPEC: ${srtpack_libspec_common} SOURCES: {${SOURCES_srt}} HEADERS: {${HEADERS_srt}}") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SRT_DEBUG_OPT} ${SRT_EXTRA_CFLAGS} ${SRT_GCC_WARN}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SRT_DEBUG_OPT} ${SRT_EXTRA_CFLAGS} ${SRT_GCC_WARN}") # PC file generation. if (NOT DEFINED INSTALLDIR) set (INSTALLDIR ${CMAKE_INSTALL_PREFIX}) get_filename_component(INSTALLDIR ${INSTALLDIR} ABSOLUTE) endif() # Required if linking a C application. # 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`. if(ENABLE_CXX_DEPS) foreach(LIB ${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES}) if(IS_ABSOLUTE ${LIB} AND EXISTS ${LIB}) set(SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${LIB}) else() set(SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} "-l${LIB}") endif() endforeach() endif() join_arguments(SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE}) # haisrt.pc left temporarily for backward compatibility. To be removed in future! configure_file(scripts/srt.pc.in haisrt.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/haisrt.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) configure_file(scripts/srt.pc.in srt.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/srt.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) # Applications # If static is available, link apps against static one. # Otherwise link against shared one. if (srt_libspec_static) set (srt_link_library ${TARGET_srt}_static) if (ENABLE_RELATIVE_LIBPATH) message(STATUS "ENABLE_RELATIVE_LIBPATH=ON will be ignored due to static linking.") endif() elseif(srt_libspec_shared) set (srt_link_library ${TARGET_srt}_shared) else() message(FATAL_ERROR "Either ENABLE_STATIC or ENABLE_SHARED has to be ON!") endif() macro(srt_add_program_dont_install name) add_executable(${name} ${ARGN}) target_include_directories(${name} PRIVATE apps) target_include_directories(${name} PRIVATE common) endmacro() macro(srt_add_program name) srt_add_program_dont_install(${name} ${ARGN}) install(TARGETS ${name} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endmacro() macro(srt_make_application name) srt_set_stdcxx(${name} "${USE_CXX_STD_APP}") # This is recommended by cmake, but it doesn't work anyway. # What is needed is that this below CMAKE_INSTALL_RPATH (yes, relative) # is added as is. # set (CMAKE_SKIP_RPATH FALSE) # set (CMAKE_SKIP_BUILD_RPATH FALSE) # set (CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) # set (CMAKE_INSTALL_RPATH "../${CMAKE_INSTALL_LIBDIR}") # set (CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) # set (FORCE_RPATH BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH TRUE) if (LINUX AND ENABLE_RELATIVE_LIBPATH AND NOT srt_libspec_static) # This is only needed on Linux, on Windows (including Cygwin) the library file will # be placed into the binrary directory anyway. # XXX not sure about Mac. # See this name used already in install(${TARGET_srt} LIBRARY DESTINATION...). set(FORCE_RPATH LINK_FLAGS -Wl,-rpath,.,-rpath,../${CMAKE_INSTALL_LIBDIR} BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH TRUE) set_target_properties(${name} PROPERTIES ${FORCE_RPATH}) endif() target_link_libraries(${name} ${srt_link_library}) if (USE_GNUSTL) target_link_libraries(${name} PRIVATE ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) endif() if (srt_libspec_static AND CMAKE_DL_LIBS) target_link_libraries(${name} ${CMAKE_DL_LIBS}) endif() endmacro() macro(srt_add_application name) # ARGN=sources... srt_add_program(${name} apps/${name}.cpp ${ARGN}) srt_make_application(${name}) install(TARGETS ${name} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endmacro() ## FIXME: transmitmedia.cpp does not build on OpenBSD ## Issue: https://github.com/Haivision/srt/issues/590 if (BSD AND ${SYSNAME_LC} MATCHES "^openbsd$") set(ENABLE_APPS OFF) endif() ## The applications currently require c++11. if (NOT ENABLE_CXX11) set(ENABLE_APPS OFF) endif() if (ENABLE_APPS) message(STATUS "APPS: ENABLED, std=${USE_CXX_STD_APP}") # Make a virtual library of all shared app files MafReadDir(apps support.maf SOURCES SOURCES_support ) # A special trick that makes the shared application sources # to be compiled once for all applications. Maybe this virtual # library should be changed into a static one and made useful # for users. add_library(srtsupport_virtual OBJECT ${SOURCES_support}) srt_set_stdcxx(srtsupport_virtual "${USE_CXX_STD_APP}") set (VIRTUAL_srtsupport $) # Applications srt_add_application(srt-live-transmit ${VIRTUAL_srtsupport}) if (DEFINED EXTRA_stransmit) set_target_properties(srt-live-transmit PROPERTIES COMPILE_FLAGS "${EXTRA_stransmit}") endif() srt_add_application(srt-file-transmit ${VIRTUAL_srtsupport}) if (MINGW) # FIXME: with MINGW, it fails to build apps that require C++11 # https://github.com/Haivision/srt/issues/177 message(WARNING "On MinGW, some C++11 apps are blocked due to lacking proper C++11 headers for . FIX IF POSSIBLE.") else() # srt-multiplex temporarily blocked #srt_add_application(srt-multiplex ${VIRTUAL_srtsupport}) srt_add_application(srt-tunnel ${VIRTUAL_srtsupport}) target_compile_definitions(srt-tunnel PUBLIC -DSRT_ENABLE_VERBOSE_LOCK) endif() if (ENABLE_TESTING) message(STATUS "DEVEL APPS (testing): ENABLED") macro(srt_add_testprogram name) # Variables in macros are not local. Clear them forcefully. set (SOURCES_app_indir "") set (SOURCES_app "") # Unlike Silvercat, in cmake you must know the full list # of source files at the moment when defining the target # and it can't be altered later. # # For testing applications, every application has its exclusive # list of source files in its own Manifest file. MafReadDir(testing ${name}.maf SOURCES SOURCES_app) srt_add_program(${name} ${SOURCES_app}) endmacro() srt_add_testprogram(utility-test) srt_set_stdcxx(utility-test "${USE_CXX_STD_APP}") if (NOT WIN32) # This program is symlinked under git-cygwin. # Avoid misleading syntax error. srt_add_testprogram(uriparser-test) target_compile_options(uriparser-test PRIVATE -DTEST) srt_set_stdcxx(uriparser-test "${USE_CXX_STD_APP}") endif() srt_add_testprogram(srt-test-live) srt_make_application(srt-test-live) srt_add_testprogram(srt-test-file) srt_make_application(srt-test-file) srt_add_testprogram(srt-test-relay) srt_make_application(srt-test-relay) target_compile_definitions(srt-test-relay PUBLIC -DSRT_ENABLE_VERBOSE_LOCK) srt_add_testprogram(srt-test-multiplex) srt_make_application(srt-test-multiplex) if (ENABLE_EXPERIMENTAL_BONDING) srt_add_testprogram(srt-test-mpbond) srt_make_application(srt-test-mpbond) endif() else() message(STATUS "DEVEL APPS (testing): DISABLED") endif() else() message(STATUS "APPS: DISABLED") endif() if (ENABLE_EXAMPLES) # No examples should need C++11 macro(srt_add_example mainsrc) get_filename_component(name ${mainsrc} NAME_WE) srt_add_program(${name} examples/${mainsrc} ${ARGN}) endmacro() srt_add_example(sendfile.cpp) srt_make_application(sendfile) srt_add_example(recvfile.cpp) srt_make_application(recvfile) srt_add_example(recvlive.cpp) srt_make_application(recvlive) srt_add_example(test-c-client.c) srt_make_application(test-c-client) srt_add_example(example-client-nonblock.c) srt_make_application(example-client-nonblock) srt_add_example(test-c-server.c) srt_make_application(test-c-server) if (ENABLE_EXPERIMENTAL_BONDING) srt_add_example(test-c-client-bonding.c) srt_make_application(test-c-client-bonding) srt_add_example(test-c-server-bonding.c) srt_make_application(test-c-server-bonding) endif() srt_add_example(testcapi-connect.c) target_link_libraries(testcapi-connect ${srt_link_library} ${DEPENDS_srt}) endif() if (ENABLE_UNITTESTS AND ENABLE_CXX11) if (${CMAKE_VERSION} VERSION_LESS "3.10.0") message(STATUS "VERSION < 3.10 -- adding test using the old method") set (USE_OLD_ADD_METHOD 1) else() message(STATUS "VERSION > 3.10 -- using NEW POLICY for in_list operator") cmake_policy(SET CMP0057 NEW) # Support the new IN_LIST operator. endif() set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) find_package(GTest 1.8) if (NOT GTEST_FOUND) message(STATUS "GTEST not found! Fetching from git.") include(googletest) fetch_googletest( ${PROJECT_SOURCE_DIR}/scripts ${PROJECT_BINARY_DIR}/googletest ) set(GTEST_BOTH_LIBRARIES "gtest_main" CACHE STRING "Add gtest_main target") endif() MafReadDir(test filelist.maf SOURCES SOURCES_unittests ) srt_add_program_dont_install(test-srt ${SOURCES_unittests}) srt_make_application(test-srt) target_include_directories(test-srt PRIVATE ${SSL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) target_link_libraries( test-srt ${GTEST_BOTH_LIBRARIES} ${srt_link_library} ${PTHREAD_LIBRARY} ) if (USE_OLD_ADD_METHOD) add_test( NAME test-srt COMMAND ${CMAKE_BINARY_DIR}/test-srt ) else() gtest_add_tests( test-srt "" AUTO ) endif() enable_testing() endif() install(PROGRAMS scripts/srt-ffplay DESTINATION ${CMAKE_INSTALL_BINDIR}) if (DEFINED SRT_EXTRA_APPS_INC) include(${SRT_EXTRA_APPS_INC}.cmake) # No extra variables expected. Just use the variables # already provided and define additional targets. endif() srt-1.4.4/CONTRIBUTING.md000066400000000000000000000046271412557703600146040ustar00rootroot00000000000000## License By contributing code to the [SRT project](https://github.com/Haivision/srt/), you agree to license your contribution under the [MPLv2.0 License](LICENSE). ## 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. Also do not hesitate to ask other users for opinion and discuss the ideas using the ticketing system before you start making your changes. This is especially important in these areas: * the build system and its variables * the SRT library public API * command line tools and their call syntax * the reusable parts (such as utilities) * SRT protocol definitions * portability and platform-specific parts Please follow the [SRT Developer's Guide](docs/dev/developers-guide.md). ## 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. srt-1.4.4/LICENSE000066400000000000000000000405261412557703600133560ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. srt-1.4.4/README.md000066400000000000000000000140711412557703600136240ustar00rootroot00000000000000# Secure Reliable Transport (SRT) Protocol

SRT

[![License: MPLv2.0][license-badge]](./LICENSE) [![Latest release][release-badge]][github releases] [![Debian Badge][debian-badge]][debian-package] [![LGTM Code Quality][lgtm-quality-badge]][lgtm-project] [![LGTM Alerts][lgtm-alerts-badge]][lgtm-project] [![codecov][codecov-badge]][codecov-project] [![Build Status Linux and macOS][travis-badge]][travis] [![Build Status Windows][appveyor-badge]][appveyor] ## Introduction Secure Reliable Transport (SRT) is an open source transport technology that optimizes streaming performance across unpredictable networks, such as the Internet. | | | | --- | --- | | **S**ecure | Encrypts video streams | | **R**eliable | Recovers from severe packet loss | | **T**ransport | Dynamically adapts to changing network conditions | SRT is applied to contribution and distribution endpoints as part of a video stream workflow to deliver the best quality and lowest latency video at all times. As audio/video packets are streamed from a source to a destination device, SRT detects and adapts to the real-time network conditions between the two endpoints. SRT helps compensate for jitter and bandwidth fluctuations due to congestion over noisy networks, such as the Internet. Its error recovery mechanism minimizes the packet loss typical of Internet connections. And SRT supports AES encryption for end-to-end security, keeping your streams safe from prying eyes. [Join the conversation](https://slackin-srtalliance.azurewebsites.net/) in the `#development` channel on [Slack](https://srtalliance.slack.com). ### Guides * [SRT API Documents](docs/API/) * [Using the `srt-live-transmit` App](docs/apps/srt-live-transmit.md) * [SRT Developer's Guide](docs/dev/developers-guide.md) * [Contributing](CONTRIBUTING.md) * [Reporting Issues](docs/dev/making-srt-better.md) * SRT RFC: [Latest IETF Draft](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00), [Latest Working Copy](https://haivision.github.io/srt-rfc/draft-sharabayko-srt.html), [GitHub Repo](https://github.com/Haivision/srt-rfc) * SRT CookBook: [Website](https://srtlab.github.io/srt-cookbook), [GitHub Repo](https://github.com/SRTLab/srt-cookbook) * [SRT Protocol Technical Overview](https://github.com/Haivision/srt/files/2489142/SRT_Protocol_TechnicalOverview_DRAFT_2018-10-17.pdf) * [Why SRT Was Created](docs/misc/why-srt-was-created.md) ## Requirements * C++03 (or above) compliant compiler. * CMake 2.8.12 or above (as build system). * OpenSSL 1.1 (to enable encryption, or build with `-DENABLE_ENCRYPTION=OFF`). * Multithreading is provided by either of the following: * C++11: standard library (`std` by `-DENABLE_STDCXX_SYNC=ON` CMake option); * C++03: Pthreads (for POSIX systems it's built in, for Windows there is a ported library). * Tcl 8.5 (optional, used by `./configure` script or use CMake directly). For a detailed description of the build system and options, please refer to [SRT Build Options](docs/build/build-options.md). ### Build on Linux Install cmake and openssl-devel (or similar name) package. For pthreads there should be -lpthreads linker flag added. Default installation path prefix of `make install` is `/usr/local`. To define a different installation path prefix, use the `--prefix` option with `configure` or [`-DCMAKE_INSTALL_PREFIX`](https://cmake.org/cmake/help/v3.0/variable/CMAKE_INSTALL_PREFIX.html) CMake option. To uninstall, call `make -n install` to list all the dependencies, and then pass the list to `rm`. #### Ubuntu 14 ```shell sudo apt-get update sudo apt-get upgrade sudo apt-get install tclsh pkg-config cmake libssl-dev build-essential ./configure make ``` #### CentOS 7 ```shell sudo yum update sudo yum install tcl pkgconfig openssl-devel cmake gcc gcc-c++ make automake ./configure make ``` #### CentOS 6 ```shell sudo yum update sudo yum install tcl pkgconfig openssl-devel cmake gcc gcc-c++ make automake sudo yum install centos-release-scl-rh devtoolset-3-gcc devtoolset-3-gcc-c++ scl enable devtoolset-3 bash ./configure --use-static-libstdc++ --with-compiler-prefix=/opt/rh/devtoolset-3/root/usr/bin/ make ``` ### Build on Mac (Darwin, iOS) [Homebrew](https://brew.sh/) supports "srt" formula. ```shell brew update brew install srt ``` If you prefer using a head commit of `master` branch, you should add `--HEAD` option to `brew` command. ```shell brew install --HEAD srt ``` Also, SRT can be built with `cmake` and `make` on Mac. 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. ```shell brew install cmake brew install openssl export OPENSSL_ROOT_DIR=$(brew --prefix openssl) export OPENSSL_LIB_DIR=$(brew --prefix openssl)"/lib" export OPENSSL_INCLUDE_DIR=$(brew --prefix openssl)"/include" ./configure make ``` ### Build on Windows Follow the [Building SRT for Windows](docs/build/build-win.md) instructions. [appveyor-badge]: https://img.shields.io/appveyor/ci/Haivision/srt/master.svg?label=Windows [appveyor]: https://ci.appveyor.com/project/Haivision/srt [travis-badge]: https://img.shields.io/travis/Haivision/srt/master.svg?label=Linux/macOS [travis]: https://travis-ci.org/Haivision/srt [license-badge]: https://img.shields.io/badge/License-MPLv2.0-blue [lgtm-alerts-badge]: https://img.shields.io/lgtm/alerts/github/Haivision/srt [lgtm-quality-badge]: https://img.shields.io/lgtm/grade/cpp/github/Haivision/srt [lgtm-project]: https://lgtm.com/projects/g/Haivision/srt/ [codecov-project]: https://codecov.io/gh/haivision/srt [codecov-badge]: https://codecov.io/gh/haivision/srt/branch/master/graph/badge.svg [github releases]: https://github.com/Haivision/srt/releases [release-badge]: https://img.shields.io/github/release/Haivision/srt.svg [debian-badge]: https://badges.debian.net/badges/debian/testing/libsrt1/version.svg [debian-package]: https://packages.debian.org/testing/libsrt1 srt-1.4.4/apps/000077500000000000000000000000001412557703600133055ustar00rootroot00000000000000srt-1.4.4/apps/apputil.cpp000066400000000000000000000510061412557703600154710ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include #include #include #include #include #include #include #include "apputil.hpp" #include "netinet_any.h" #include "srt_compat.h" using namespace std; // 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 #if defined(_WIN32) && !defined(HAVE_INET_PTON) namespace // Prevent conflict in case when still defined { 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 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 // _WIN32 && !HAVE_INET_PTON sockaddr_any CreateAddr(const string& name, unsigned short port, int pref_family) { // Handle empty name. // If family is specified, empty string resolves to ANY of that family. // If not, it resolves to IPv4 ANY (to specify IPv6 any, use [::]). if (name == "") { sockaddr_any result(pref_family == AF_INET6 ? pref_family : AF_INET); result.hport(port); return result; } bool first6 = pref_family != AF_INET; int families[2] = {AF_INET6, AF_INET}; if (!first6) { families[0] = AF_INET; families[1] = AF_INET6; } for (int i = 0; i < 2; ++i) { int family = families[i]; sockaddr_any result (family); // Try to resolve the name by pton first if (inet_pton(family, name.c_str(), result.get_addr()) == 1) { result.hport(port); // same addr location in ipv4 and ipv6 return result; } } // If not, try to resolve by getaddrinfo // This time, use the exact value of pref_family sockaddr_any result; addrinfo fo = { 0, pref_family, 0, 0, 0, 0, NULL, NULL }; addrinfo* val = nullptr; int erc = getaddrinfo(name.c_str(), nullptr, &fo, &val); if (erc == 0) { result.set(val->ai_addr); result.len = result.size(); result.hport(port); // same addr location in ipv4 and ipv6 } freeaddrinfo(val); return result; } string Join(const vector& in, string sep) { if ( in.empty() ) return ""; ostringstream os; os << in[0]; for (auto i = in.begin()+1; i != in.end(); ++i) os << sep << *i; return os.str(); } // OPTION LIBRARY OptionScheme::Args OptionName::DetermineTypeFromHelpText(const std::string& helptext) { if (helptext.empty()) return OptionScheme::ARG_NONE; if (helptext[0] == '<') { // If the argument is , then it's ARG_NONE. // If it's , then it's ARG_VAR. // When closing angle bracket isn't found, fallback to ARG_ONE. size_t pos = helptext.find('>'); if (pos == std::string::npos) return OptionScheme::ARG_ONE; // mistake, but acceptable if (pos >= 4 && helptext.substr(pos-3, 4) == "...>") return OptionScheme::ARG_VAR; // We have < and > without ..., simply one argument return OptionScheme::ARG_ONE; } if (helptext[0] == '[') { // Argument in [] means it is optional; in this case // you should state that the argument can be given or not. return OptionScheme::ARG_VAR; } // Also as fallback return OptionScheme::ARG_NONE; } options_t ProcessOptions(char* const* argv, int argc, std::vector scheme) { using namespace std; string current_key; string extra_arg; size_t vals = 0; OptionScheme::Args type = OptionScheme::ARG_VAR; // This is for no-option-yet or consumed map> params; bool moreoptions = true; for (char* const* p = argv+1; p != argv+argc; ++p) { const char* a = *p; // cout << "*D ARG: '" << a << "'\n"; bool isoption = false; if (a[0] == '-') { isoption = true; // If a[0] isn't NUL - because it is dash - then // we can safely check a[1]. // An expression starting with a dash is not // an option marker if it is a single dash or // a negative number. if (!a[1] || isdigit(a[1])) isoption = false; } if (moreoptions && isoption) { bool arg_specified = false; size_t seppos; // (see goto, it would jump over initialization) current_key = a+1; if ( current_key == "-" ) { // The -- argument terminates the options. // The default key is restored to empty so that // it collects now all arguments under the empty key // (not-option-assigned argument). moreoptions = false; goto EndOfArgs; } // Maintain the backward compatibility with argument specified after : // or with one string separated by space inside. seppos = current_key.find(':'); if (seppos == string::npos) seppos = current_key.find(' '); if (seppos != string::npos) { // Old option specification. extra_arg = current_key.substr(seppos + 1); current_key = current_key.substr(0, 0 + seppos); arg_specified = true; // Prevent eating args from option list } params[current_key].clear(); vals = 0; if (extra_arg != "") { params[current_key].push_back(extra_arg); ++vals; extra_arg.clear(); } // Find the key in the scheme. If not found, treat it as ARG_NONE. for (const auto& s: scheme) { if (s.names().count(current_key)) { // cout << "*D found '" << current_key << "' in scheme type=" << int(s.type) << endl; // If argument was specified using the old way, like // -v:0 or "-v 0", then consider the argument specified and // treat further arguments as either no-option arguments or // new options. if (s.type == OptionScheme::ARG_NONE || arg_specified) { // Anyway, consider it already processed. break; } type = s.type; if ( vals == 1 && type == OptionScheme::ARG_ONE ) { // Argument for one-arg option already consumed, // so set to free args. goto EndOfArgs; } goto Found; } } // Not found: set ARG_NONE. // cout << "*D KEY '" << current_key << "' assumed type NONE\n"; EndOfArgs: type = OptionScheme::ARG_VAR; current_key = ""; Found: continue; } // Collected a value - check if full // cout << "*D COLLECTING '" << a << "' for key '" << current_key << "' (" << vals << " so far)\n"; params[current_key].push_back(a); ++vals; if ( vals == 1 && type == OptionScheme::ARG_ONE ) { // cout << "*D KEY TYPE ONE - resetting to empty key\n"; // Reset the key to "default one". current_key = ""; vals = 0; type = OptionScheme::ARG_VAR; } else { // cout << "*D KEY type VAR - still collecting until the end of options or next option.\n"; } } return params; } string OptionHelpItem(const OptionName& o) { string out = "\t-" + o.main_name; string hlp = o.helptext; string prefix; if (hlp == "") { hlp = " (Undocumented)"; } else if (hlp[0] != ' ') { size_t end = string::npos; if (hlp[0] == '<') { end = hlp.find('>'); } else if (hlp[0] == '[') { end = hlp.find(']'); } if (end != string::npos) { ++end; } else { end = hlp.find(' '); } if (end != string::npos) { prefix = hlp.substr(0, end); //while (hlp[end] == ' ') // ++end; hlp = hlp.substr(end); out += " " + prefix; } } out += " -" + hlp; return out; } // Stats module // Note: std::put_time is supported only in GCC 5 and higher #if !defined(__GNUC__) || defined(__clang__) || (__GNUC__ >= 5) #define HAS_PUT_TIME #endif template inline SrtStatData* make_stat(SrtStatCat cat, const string& name, const string& longname, TYPE CBytePerfMon::*field) { return new SrtStatDataType(cat, name, longname, field); } #define STATX(catsuf, sname, lname, field) s.emplace_back(make_stat(SSC_##catsuf, #sname, #lname, &CBytePerfMon:: field)) #define STAT(catsuf, sname, field) STATX(catsuf, sname, field, field) vector> g_SrtStatsTable; struct SrtStatsTableInit { SrtStatsTableInit(vector>& s) { STATX(GEN, time, Time, msTimeStamp); STAT(WINDOW, flow, pktFlowWindow); STAT(WINDOW, congestion, pktCongestionWindow); STAT(WINDOW, flight, pktFlightSize); STAT(LINK, rtt, msRTT); STAT(LINK, bandwidth, mbpsBandwidth); STAT(LINK, maxBandwidth, mbpsMaxBW); STAT(SEND, packets, pktSent); STAT(SEND, packetsUnique, pktSentUnique); STAT(SEND, packetsLost, pktSndLoss); STAT(SEND, packetsDropped, pktSndDrop); STAT(SEND, packetsRetransmitted, pktRetrans); STAT(SEND, packetsFilterExtra, pktSndFilterExtra); STAT(SEND, bytes, byteSent); STAT(SEND, bytesUnique, byteSentUnique); STAT(SEND, bytesDropped, byteSndDrop); STAT(SEND, byteAvailBuf, byteAvailSndBuf); STAT(SEND, msBuf, msSndBuf); STAT(SEND, mbitRate, mbpsSendRate); STAT(SEND, sendPeriod, usPktSndPeriod); STAT(RECV, packets, pktRecv); STAT(RECV, packetsUnique, pktRecvUnique); STAT(RECV, packetsLost, pktRcvLoss); STAT(RECV, packetsDropped, pktRcvDrop); STAT(RECV, packetsRetransmitted, pktRcvRetrans); STAT(RECV, packetsBelated, pktRcvBelated); STAT(RECV, packetsFilterExtra, pktRcvFilterExtra); STAT(RECV, packetsFilterSupply, pktRcvFilterSupply); STAT(RECV, packetsFilterLoss, pktRcvFilterLoss); STAT(RECV, bytes, byteRecv); STAT(RECV, bytesUnique, byteRecvUnique); STAT(RECV, bytesLost, byteRcvLoss); STAT(RECV, bytesDropped, byteRcvDrop); STAT(RECV, byteAvailBuf, byteAvailRcvBuf); STAT(RECV, msBuf, msRcvBuf); STAT(RECV, mbitRate, mbpsRecvRate); STAT(RECV, msTsbPdDelay, msRcvTsbPdDelay); } } g_SrtStatsTableInit (g_SrtStatsTable); #undef STAT #undef STATX string srt_json_cat_names [] = { "", "window", "link", "send", "recv" }; #ifdef HAS_PUT_TIME // Follows ISO 8601 std::string SrtStatsWriter::print_timestamp() { using namespace std; using namespace std::chrono; const auto systime_now = system_clock::now(); const time_t time_now = system_clock::to_time_t(systime_now); std::ostringstream output; // SysLocalTime returns zeroed tm_now on failure, which is ok for put_time. const tm tm_now = SysLocalTime(time_now); output << std::put_time(&tm_now, "%FT%T.") << std::setfill('0') << std::setw(6); const auto since_epoch = systime_now.time_since_epoch(); const seconds s = duration_cast(since_epoch); output << duration_cast(since_epoch - s).count(); output << std::put_time(&tm_now, "%z"); return output.str(); } #else // This is a stub. The error when not defining it would be too // misleading, so this stub will work if someone mistakenly adds // the item to the output format without checking that HAS_PUT_TIME. string SrtStatsWriter::print_timestamp() { return ""; } #endif // HAS_PUT_TIME class SrtStatsJson : public SrtStatsWriter { static string quotekey(const string& name) { if (name == "") return ""; return R"(")" + name + R"(":)"; } static string quote(const string& name) { if (name == "") return ""; return R"(")" + name + R"(")"; } public: string WriteStats(int sid, const CBytePerfMon& mon) override { std::ostringstream output; string pretty_cr, pretty_tab; if (Option("pretty")) { pretty_cr = "\n"; pretty_tab = "\t"; } SrtStatCat cat = SSC_GEN; // Do general manually output << quotekey(srt_json_cat_names[cat]) << "{" << pretty_cr; // SID is displayed manually output << pretty_tab << quotekey("sid") << sid; // Extra Timepoint is also displayed manually #ifdef HAS_PUT_TIME // NOTE: still assumed SSC_GEN category output << "," << pretty_cr << pretty_tab << quotekey("timepoint") << quote(print_timestamp()); #endif // Now continue with fields as specified in the table for (auto& i: g_SrtStatsTable) { if (i->category == cat) { output << ","; // next item in same cat output << pretty_cr; output << pretty_tab; if (cat != SSC_GEN) output << pretty_tab; } else { if (cat != SSC_GEN) { // DO NOT close if general category, just // enter the depth. output << pretty_cr << pretty_tab << "}"; } cat = i->category; output << ","; output << pretty_cr; if (cat != SSC_GEN) output << pretty_tab; output << quotekey(srt_json_cat_names[cat]) << "{" << pretty_cr << pretty_tab; if (cat != SSC_GEN) output << pretty_tab; } // Print the current field output << quotekey(i->name); i->PrintValue(output, mon); } // Close the previous subcategory if (cat != SSC_GEN) { output << pretty_cr << pretty_tab << "}" << pretty_cr; } // Close the general category entity output << "}" << pretty_cr << endl; return output.str(); } string WriteBandwidth(double mbpsBandwidth) override { std::ostringstream output; output << "{\"bandwidth\":" << mbpsBandwidth << '}' << endl; return output.str(); } }; class SrtStatsCsv : public SrtStatsWriter { private: bool first_line_printed; public: SrtStatsCsv() : first_line_printed(false) {} string WriteStats(int sid, const CBytePerfMon& mon) override { std::ostringstream output; // Header if (!first_line_printed) { #ifdef HAS_PUT_TIME output << "Timepoint,"; #endif output << "Time,SocketID"; for (auto& i: g_SrtStatsTable) { output << "," << i->longname; } output << endl; first_line_printed = true; } // Values #ifdef HAS_PUT_TIME // HDR: Timepoint output << print_timestamp() << ","; #endif // HAS_PUT_TIME // HDR: Time,SocketID output << mon.msTimeStamp << "," << sid; // HDR: the loop of all values in g_SrtStatsTable for (auto& i: g_SrtStatsTable) { output << ","; i->PrintValue(output, mon); } output << endl; return output.str(); } string WriteBandwidth(double mbpsBandwidth) override { std::ostringstream output; output << "+++/+++SRT BANDWIDTH: " << mbpsBandwidth << endl; return output.str(); } }; class SrtStatsCols : public SrtStatsWriter { public: string WriteStats(int sid, const CBytePerfMon& mon) override { std::ostringstream output; output << "======= SRT STATS: sid=" << sid << endl; output << "PACKETS SENT: " << setw(11) << mon.pktSent << " RECEIVED: " << setw(11) << mon.pktRecv << endl; output << "LOST PKT SENT: " << setw(11) << mon.pktSndLoss << " RECEIVED: " << setw(11) << mon.pktRcvLoss << endl; output << "REXMIT SENT: " << setw(11) << mon.pktRetrans << " RECEIVED: " << setw(11) << mon.pktRcvRetrans << endl; output << "DROP PKT SENT: " << setw(11) << mon.pktSndDrop << " RECEIVED: " << setw(11) << mon.pktRcvDrop << endl; output << "FILTER EXTRA TX: " << setw(11) << mon.pktSndFilterExtra << " RX: " << setw(11) << mon.pktRcvFilterExtra << endl; output << "FILTER RX SUPPL: " << setw(11) << mon.pktRcvFilterSupply << " RX LOSS: " << setw(11) << mon.pktRcvFilterLoss << endl; output << "RATE SENDING: " << setw(11) << mon.mbpsSendRate << " RECEIVING: " << setw(11) << mon.mbpsRecvRate << endl; output << "BELATED RECEIVED: " << setw(11) << mon.pktRcvBelated << " AVG TIME: " << setw(11) << mon.pktRcvAvgBelatedTime << endl; output << "REORDER DISTANCE: " << setw(11) << mon.pktReorderDistance << endl; output << "WINDOW FLOW: " << setw(11) << mon.pktFlowWindow << " CONGESTION: " << setw(11) << mon.pktCongestionWindow << " FLIGHT: " << setw(11) << mon.pktFlightSize << endl; output << "LINK RTT: " << setw(9) << mon.msRTT << "ms BANDWIDTH: " << setw(7) << mon.mbpsBandwidth << "Mb/s " << endl; output << "BUFFERLEFT: SND: " << setw(11) << mon.byteAvailSndBuf << " RCV: " << setw(11) << mon.byteAvailRcvBuf << endl; return output.str(); } string WriteBandwidth(double mbpsBandwidth) override { std::ostringstream output; output << "+++/+++SRT BANDWIDTH: " << mbpsBandwidth << endl; return output.str(); } }; shared_ptr SrtStatsWriterFactory(SrtStatsPrintFormat printformat) { switch (printformat) { case SRTSTATS_PROFMAT_JSON: return make_shared(); case SRTSTATS_PROFMAT_CSV: return make_shared(); case SRTSTATS_PROFMAT_2COLS: return make_shared(); default: break; } return nullptr; } SrtStatsPrintFormat ParsePrintFormat(string pf, string& w_extras) { size_t havecomma = pf.find(','); if (havecomma != string::npos) { w_extras = pf.substr(havecomma+1); pf = pf.substr(0, havecomma); } if (pf == "default") return SRTSTATS_PROFMAT_2COLS; if (pf == "json") return SRTSTATS_PROFMAT_JSON; if (pf == "csv") return SRTSTATS_PROFMAT_CSV; return SRTSTATS_PROFMAT_INVALID; } srt-1.4.4/apps/apputil.hpp000066400000000000000000000244431412557703600155030ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_APPCOMMON_H #define INC_SRT_APPCOMMON_H #include #include #include #include #include #include "netinet_any.h" #include "utilities.h" #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 inline bool SysInitializeNetwork() { WORD wVersionRequested = MAKEWORD(2, 2); WSADATA wsaData; return WSAStartup(wVersionRequested, &wsaData) == 0; } inline void SysCleanupNetwork() { WSACleanup(); } #else #include #include #include #include #include // Fixes Android build on NDK r16b and earlier. #if defined(__ANDROID__) && (__ANDROID__ == 1) #include #if !defined(__NDK_MAJOR__) || (__NDK_MAJOR__ <= 16) struct ip_mreq_sourceFIXED { struct in_addr imr_multiaddr; struct in_addr imr_interface; struct in_addr imr_sourceaddr; }; #define ip_mreq_source ip_mreq_sourceFIXED #endif #endif // Nothing needs to be done on POSIX; this is a Windows problem. inline bool SysInitializeNetwork() {return true;} inline void SysCleanupNetwork() {} #endif #include "srt.h" // Required for stats module #ifdef _WIN32 inline int SysError() { return ::GetLastError(); } const int SysAGAIN = WSAEWOULDBLOCK; #else inline int SysError() { return errno; } const int SysAGAIN = EAGAIN; #endif sockaddr_any CreateAddr(const std::string& name, unsigned short port = 0, int pref_family = AF_UNSPEC); std::string Join(const std::vector& in, std::string sep); template struct OnReturnSetter { VarType& var; ValType value; OnReturnSetter(VarType& target, ValType v): var(target), value(v) {} ~OnReturnSetter() { var = value; } }; template OnReturnSetter OnReturnSet(VarType& target, ValType v) { return OnReturnSetter(target, v); } // ---- OPTIONS MODULE inline bool CheckTrue(const std::vector& in) { if (in.empty()) return true; const std::set false_vals = { "0", "no", "off", "false" }; if (false_vals.count(in[0])) return false; return true; //if (in[0] != "false" && in[0] != "off") // return true; //return false; } template static inline Number StrToNumber(const std::string& ) { typename Number::incorrect_version wrong = Number::incorrect_version; return Number(); } #define STON(type, function) \ template<> inline type StrToNumber(const std::string& s) { return function (s, 0, 0); } STON(int, stoi); STON(unsigned long, stoul); STON(unsigned int, stoul); STON(long long, stoll); STON(unsigned long long, stoull); #undef STON typedef std::map> options_t; struct OutList { typedef std::vector type; static type process(const options_t::mapped_type& i) { return i; } }; struct OutString { typedef std::string type; static type process(const options_t::mapped_type& i) { return Join(i, " "); } }; struct NumberAutoConvert { std::string value; NumberAutoConvert(): NumberAutoConvert("") {} NumberAutoConvert(const std::string& arg): NumberAutoConvert(arg.c_str()) {} NumberAutoConvert(const char* arg): value(arg) { if (value.empty()) value = "0"; // Must convert to a default 0 number } template operator Number() { return StrToNumber(value); } }; struct OutNumber { typedef NumberAutoConvert type; static type process(const options_t::mapped_type& i) { // Numbers can't be joined, use the "last overrides" rule. if (i.empty()) return {"0"}; return type { i.back() }; } }; template struct OutNumberAs { typedef Number type; static type process(const options_t::mapped_type& i) { return OutNumber::process(i); } }; struct OutBool { typedef bool type; static type process(const options_t::mapped_type& i) { return CheckTrue(i); } }; struct OptionName; struct OptionScheme { const OptionName* pid; enum Args { ARG_NONE, ARG_ONE, ARG_VAR } type; OptionScheme(const OptionScheme&) = default; OptionScheme(OptionScheme&& src) : pid(src.pid) , type(src.type) { } OptionScheme(const OptionName& id, Args tp); const std::set& names() const; }; struct OptionName { std::string helptext; std::string main_name; std::set names; template OptionName(std::string ht, std::string first, Args... rest) : helptext(ht), main_name(first), names {first, rest...} { } template OptionName(std::vector& sc, OptionScheme::Args type, std::string ht, std::string first, Args... rest) : helptext(ht), main_name(first), names {first, rest...} { sc.push_back(OptionScheme(*this, type)); } template OptionName(std::vector& sc, std::string ht, std::string first, Args... rest) : helptext(ht), main_name(first), names {first, rest...} { OptionScheme::Args type = DetermineTypeFromHelpText(ht); sc.push_back(OptionScheme(*this, type)); } OptionName(std::initializer_list args): main_name(*args.begin()), names(args) {} operator std::set() { return names; } operator const std::set() const { return names; } private: static OptionScheme::Args DetermineTypeFromHelpText(const std::string& helptext); }; inline OptionScheme::OptionScheme(const OptionName& id, Args tp): pid(&id), type(tp) {} inline const std::set& OptionScheme::names() const { return pid->names; } template inline typename OutType::type Option(const options_t&, OutValue deflt=OutValue()) { return deflt; } template inline typename OutType::type Option(const options_t& options, OutValue deflt, std::string key, Args... further_keys) { auto i = options.find(key); if ( i == options.end() ) return Option(options, deflt, further_keys...); return OutType::process(i->second); } template struct OptionTrapType { static TrapType pass(TrapType v) { return v; } }; template<> struct OptionTrapType { static std::string pass(const char* v) { return v; } }; template inline typename OutType::type Option(const options_t& options, OutValue deflt, const OptionName& oname) { (void)OptionTrapType::pass(deflt); for (auto key: oname.names) { auto i = options.find(key); if ( i != options.end() ) { return OutType::process(i->second); } } return deflt; } template inline typename OutType::type Option(const options_t& options, const OptionName& oname) { typedef typename OutType::type out_t; for (auto key: oname.names) { auto i = options.find(key); if ( i != options.end() ) { return OutType::process(i->second); } } return out_t(); } inline bool OptionPresent(const options_t& options, const std::set& keys) { for (auto key: keys) { auto i = options.find(key); if ( i != options.end() ) return true; } return false; } options_t ProcessOptions(char* const* argv, int argc, std::vector scheme); std::string OptionHelpItem(const OptionName& o); // Statistics module enum SrtStatsPrintFormat { SRTSTATS_PROFMAT_INVALID = -1, SRTSTATS_PROFMAT_2COLS = 0, SRTSTATS_PROFMAT_JSON, SRTSTATS_PROFMAT_CSV }; SrtStatsPrintFormat ParsePrintFormat(std::string pf, std::string& w_extras); enum SrtStatCat { SSC_GEN, //< General SSC_WINDOW, // flow/congestion window SSC_LINK, //< Link data SSC_SEND, //< Sending SSC_RECV //< Receiving }; struct SrtStatData { SrtStatCat category; std::string name; std::string longname; SrtStatData(SrtStatCat cat, std::string n, std::string l): category(cat), name(n), longname(l) {} virtual ~SrtStatData() {} virtual void PrintValue(std::ostream& str, const CBytePerfMon& mon) = 0; }; template struct SrtStatDataType: public SrtStatData { typedef TYPE CBytePerfMon::*pfield_t; pfield_t pfield; SrtStatDataType(SrtStatCat cat, const std::string& name, const std::string& longname, pfield_t field) : SrtStatData (cat, name, longname), pfield(field) { } void PrintValue(std::ostream& str, const CBytePerfMon& mon) override { str << mon.*pfield; } }; class SrtStatsWriter { public: virtual std::string WriteStats(int sid, const CBytePerfMon& mon) = 0; virtual std::string WriteBandwidth(double mbpsBandwidth) = 0; virtual ~SrtStatsWriter() { }; // Only if HAS_PUT_TIME. Specified in the imp file. std::string print_timestamp(); void Option(const std::string& key, const std::string& val) { options[key] = val; } bool Option(const std::string& key, std::string* rval = nullptr) { const std::string* out = map_getp(options, key); if (!out) return false; if (rval) *rval = *out; return true; } protected: std::map options; }; extern std::vector> g_SrtStatsTable; std::shared_ptr SrtStatsWriterFactory(SrtStatsPrintFormat printformat); #endif // INC_SRT_APPCOMMON_H srt-1.4.4/apps/logsupport.cpp000066400000000000000000000117021412557703600162300ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include #include #include #include #include #include #include "logsupport.hpp" #include "../srtcore/srt.h" #include "../srtcore/utilities.h" using namespace std; // This is based on codes taken from // This is POSIX standard, so it's not going to change. // Haivision standard only adds one more severity below // DEBUG named DEBUG_TRACE to satisfy all possible needs. map srt_level_names { { "alert", LOG_ALERT }, { "crit", LOG_CRIT }, { "debug", LOG_DEBUG }, { "emerg", LOG_EMERG }, { "err", LOG_ERR }, { "error", LOG_ERR }, /* DEPRECATED */ { "fatal", LOG_CRIT }, // XXX Added for SRT { "info", LOG_INFO }, // WTF? Undefined symbol? { "none", INTERNAL_NOPRI }, /* INTERNAL */ { "notice", LOG_NOTICE }, { "note", LOG_NOTICE }, // XXX Added for SRT { "panic", LOG_EMERG }, /* DEPRECATED */ { "warn", LOG_WARNING }, /* DEPRECATED */ { "warning", LOG_WARNING }, //{ "", -1 } }; srt_logging::LogLevel::type SrtParseLogLevel(string level) { using namespace srt_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); } struct ToLowerFormat { char operator()(char in) { if (islower(in)) return in; if (isupper(in)) return tolower(in); if (in == '_') return '-'; throw std::invalid_argument("Wrong FA name - please check the definition in scripts/generate-logging-defs.tcl file"); } }; void LogFANames::Install(string upname, int value) { string id; transform(upname.begin(), upname.end(), back_inserter(id), ToLowerFormat()); namemap[id] = value; } // See logsupport_appdefs.cpp for log FA definitions LogFANames srt_transmit_logfa_names; const map SrtLogFAList() { return srt_transmit_logfa_names.namemap; } set SrtParseLogFA(string fa, set* punknown) { using namespace srt_logging; set fas; // The split algo won't work on empty string. if ( fa == "" ) return fas; auto& names = srt_transmit_logfa_names.namemap; if ( fa == "all" ) { for (auto entry: names) { // Skip "general", it's always on if (entry.first == "general") continue; fas.insert(entry.second); } 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]; int* pfa = map_getp(names, fa); if (!pfa) { if (punknown) punknown->insert(fa); // If requested, add it back silently else cerr << "ERROR: Invalid log functional area spec: '" << fa << "' - skipping\n"; continue; } fas.insert(*pfa); } return fas; } void ParseLogFASpec(const vector& speclist, string& w_on, string& w_off) { std::ostringstream son, soff; for (auto& s: speclist) { string name; bool on = true; if (s[0] == '+') name = s.substr(1); else if (s[0] == '~') { name = s.substr(1); on = false; } else name = s; if (on) son << "," << name; else soff << "," << name; } const string& sons = son.str(); const string& soffs = soff.str(); w_on = sons.empty() ? string() : sons.substr(1); w_off = soffs.empty() ? string() : soffs.substr(1); } srt-1.4.4/apps/logsupport.hpp000066400000000000000000000017451412557703600162430ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_LOGSUPPORT_HPP #define INC_SRT_LOGSUPPORT_HPP #include #include #include #include "../srtcore/srt.h" #include "../srtcore/logging_api.h" srt_logging::LogLevel::type SrtParseLogLevel(std::string level); std::set SrtParseLogFA(std::string fa, std::set* punknown = nullptr); void ParseLogFASpec(const std::vector& speclist, std::string& w_on, std::string& w_off); const std::map SrtLogFAList(); SRT_API extern std::map srt_level_names; struct LogFANames { std::map namemap; void Install(std::string upname, int value); LogFANames(); }; #endif srt-1.4.4/apps/logsupport_appdefs.cpp000066400000000000000000000026311412557703600177330ustar00rootroot00000000000000/* WARNING: Generated from ../scripts/generate-logging-defs.tcl DO NOT MODIFY. Copyright applies as per the generator script. */ #include "logsupport.hpp" LogFANames::LogFANames() { Install("GENERAL", SRT_LOGFA_GENERAL); Install("SOCKMGMT", SRT_LOGFA_SOCKMGMT); Install("CONN", SRT_LOGFA_CONN); Install("XTIMER", SRT_LOGFA_XTIMER); Install("TSBPD", SRT_LOGFA_TSBPD); Install("RSRC", SRT_LOGFA_RSRC); Install("CONGEST", SRT_LOGFA_CONGEST); Install("PFILTER", SRT_LOGFA_PFILTER); Install("API_CTRL", SRT_LOGFA_API_CTRL); Install("QUE_CTRL", SRT_LOGFA_QUE_CTRL); Install("EPOLL_UPD", SRT_LOGFA_EPOLL_UPD); Install("API_RECV", SRT_LOGFA_API_RECV); Install("BUF_RECV", SRT_LOGFA_BUF_RECV); Install("QUE_RECV", SRT_LOGFA_QUE_RECV); Install("CHN_RECV", SRT_LOGFA_CHN_RECV); Install("GRP_RECV", SRT_LOGFA_GRP_RECV); Install("API_SEND", SRT_LOGFA_API_SEND); Install("BUF_SEND", SRT_LOGFA_BUF_SEND); Install("QUE_SEND", SRT_LOGFA_QUE_SEND); Install("CHN_SEND", SRT_LOGFA_CHN_SEND); Install("GRP_SEND", SRT_LOGFA_GRP_SEND); Install("INTERNAL", SRT_LOGFA_INTERNAL); Install("QUE_MGMT", SRT_LOGFA_QUE_MGMT); Install("CHN_MGMT", SRT_LOGFA_CHN_MGMT); Install("GRP_MGMT", SRT_LOGFA_GRP_MGMT); Install("EPOLL_API", SRT_LOGFA_EPOLL_API); Install("HAICRYPT", SRT_LOGFA_HAICRYPT); Install("APPLOG", SRT_LOGFA_APPLOG); } srt-1.4.4/apps/socketoptions.cpp000066400000000000000000000071321412557703600167200ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include "socketoptions.hpp" #include "verbose.hpp" using namespace std; extern const set true_names = { "1", "yes", "on", "true" }; extern const set false_names = { "0", "no", "off", "false" }; extern const std::map enummap_transtype = { { "live", SRTT_LIVE }, { "file", SRTT_FILE } }; const char* const SocketOption::mode_names[3] = { "listener", "caller", "rendezvous" }; SocketOption::Mode SrtInterpretMode(const string& modestr, const string& host, const string& adapter) { SocketOption::Mode mode = SocketOption::FAILURE; if (modestr == "client" || modestr == "caller") { mode = SocketOption::CALLER; } else if (modestr == "server" || modestr == "listener") { mode = SocketOption::LISTENER; } else if (modestr == "rendezvous") { mode = SocketOption::RENDEZVOUS; } else if (modestr == "default") { // Use the following convention: // 1. Server for source, Client for target // 2. If host is empty, then always server. if ( host == "" ) mode = SocketOption::LISTENER; //else if ( !dir_output ) //mode = "server"; else { // Host is given, so check also "adapter" if (adapter != "") mode = SocketOption::RENDEZVOUS; else mode = SocketOption::CALLER; } } else { mode = SocketOption::FAILURE; } return mode; } SocketOption::Mode SrtConfigurePre(SRTSOCKET socket, string host, map options, vector* failures) { vector dummy; vector& fails = failures ? *failures : dummy; string modestr = "default", adapter; if (options.count("mode")) { modestr = options["mode"]; } if (options.count("adapter")) { adapter = options["adapter"]; } SocketOption::Mode mode = SrtInterpretMode(modestr, host, adapter); if (mode == SocketOption::FAILURE) { fails.push_back("mode"); } if (options.count("linger")) { linger lin; lin.l_linger = stoi(options["linger"]); lin.l_onoff = lin.l_linger > 0 ? 1 : 0; srt_setsockopt(socket, SocketOption::PRE, SRTO_LINGER, &lin, sizeof(linger)); } bool all_clear = true; for (const auto &o: srt_options) { if ( o.binding == SocketOption::PRE && options.count(o.name) ) { string value = options.at(o.name); bool ok = o.apply(socket, value); if ( !ok ) { fails.push_back(o.name); all_clear = false; } } } return all_clear ? mode : SocketOption::FAILURE; } void SrtConfigurePost(SRTSOCKET socket, map options, vector* failures) { vector dummy; vector& fails = failures ? *failures : dummy; for (const auto &o: srt_options) { if ( o.binding == SocketOption::POST && options.count(o.name) ) { string value = options.at(o.name); Verb() << "Setting option: " << o.name << " = " << value; bool ok = o.apply(socket, value); if ( !ok ) fails.push_back(o.name); } } } srt-1.4.4/apps/socketoptions.hpp000066400000000000000000000220121412557703600167170ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_SOCKETOPTIONS_HPP #define INC_SRT_SOCKETOPTIONS_HPP #include #include #include #include #include "../srtcore/srt.h" // Devel path #ifdef _WIN32 #include "winsock2.h" #endif struct OptionValue { std::string s; union { int i; int64_t l; bool b; }; const void* value = nullptr; size_t size = 0; }; extern const std::set false_names, true_names; struct SocketOption { enum Type { STRING = 0, INT, INT64, BOOL, ENUM }; enum Binding { PRE = 0, POST }; enum Domain { SYSTEM, SRT }; enum Mode {FAILURE = -1, LISTENER = 0, CALLER = 1, RENDEZVOUS = 2}; static const char* const mode_names [3]; std::string name; int protocol; int symbol; Binding binding; Type type; const std::map* valmap; template bool apply(Object socket, std::string value) const; template bool applyt(Object socket, std::string value) const; template static int setso(Object socket, int protocol, int symbol, const void* data, size_t size); template bool extract(std::string value, OptionValue& val) const; }; template<> inline int SocketOption::setso(int socket, int /*ignored*/, int sym, const void* data, size_t size) { return srt_setsockopt(socket, 0, SRT_SOCKOPT(sym), data, (int) size); } #if ENABLE_EXPERIMENTAL_BONDING template<> inline int SocketOption::setso(SRT_SOCKOPT_CONFIG* obj, int /*ignored*/, int sym, const void* data, size_t size) { return srt_config_add(obj, SRT_SOCKOPT(sym), data, (int) size); } #endif template<> inline int SocketOption::setso(int socket, int proto, int sym, const void* data, size_t size) { return ::setsockopt(socket, proto, sym, (const char *)data, (int) size); } template<> inline bool SocketOption::extract(std::string value, OptionValue& o) const { o.s = value; o.value = o.s.data(); o.size = o.s.size(); return true; } template<> inline bool SocketOption::extract(std::string value, OptionValue& o) const { try { o.i = stoi(value, 0, 0); o.value = &o.i; o.size = sizeof o.i; return true; } catch (...) // stoi throws { return false; // do not change o } return false; } template<> inline bool SocketOption::extract(std::string value, OptionValue& o) const { 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 true; } catch (...) // stoll throws { return false; } return false; } template<> inline bool SocketOption::extract(std::string value, OptionValue& o) const { bool val; if ( false_names.count(value) ) val = false; else if ( true_names.count(value) ) val = true; else return false; o.b = val; o.value = &o.b; o.size = sizeof o.b; return true; } template<> inline bool SocketOption::extract(std::string value, OptionValue& o) const { if (valmap) { // Search value in the map. If found, set to o. auto p = valmap->find(value); if ( p != valmap->end() ) { o.i = p->second; o.value = &o.i; o.size = sizeof o.i; return true; } } // Fallback: try interpreting it as integer. try { o.i = stoi(value, 0, 0); o.value = &o.i; o.size = sizeof o.i; return true; } catch (...) // stoi throws { return false; // do not change o } return false; } template inline bool SocketOption::applyt(Object socket, std::string value) const { OptionValue o; // common meet point int result = -1; if (extract(value, o)) result = setso(socket, protocol, symbol, o.value, o.size); return result != -1; } template inline bool SocketOption::apply(Object socket, std::string value) const { switch ( type ) { #define SRT_HANDLE_TYPE(ty) case ty: return applyt(socket, value) SRT_HANDLE_TYPE(STRING); SRT_HANDLE_TYPE(INT); SRT_HANDLE_TYPE(INT64); SRT_HANDLE_TYPE(BOOL); SRT_HANDLE_TYPE(ENUM); #undef SRT_HANDLE_TYPE } return false; } extern const std::map enummap_transtype; namespace { const SocketOption srt_options [] { { "transtype", 0, SRTO_TRANSTYPE, SocketOption::PRE, SocketOption::ENUM, &enummap_transtype }, { "maxbw", 0, SRTO_MAXBW, SocketOption::POST, SocketOption::INT64, nullptr}, { "pbkeylen", 0, SRTO_PBKEYLEN, SocketOption::PRE, SocketOption::INT, nullptr}, { "passphrase", 0, SRTO_PASSPHRASE, SocketOption::PRE, SocketOption::STRING, nullptr}, { "mss", 0, SRTO_MSS, SocketOption::PRE, SocketOption::INT, nullptr}, { "fc", 0, SRTO_FC, SocketOption::PRE, SocketOption::INT, nullptr}, { "sndbuf", 0, SRTO_SNDBUF, SocketOption::PRE, SocketOption::INT, nullptr}, { "rcvbuf", 0, SRTO_RCVBUF, SocketOption::PRE, SocketOption::INT, nullptr}, // linger option is handled outside of the common loop, therefore commented out. //{ "linger", 0, SRTO_LINGER, SocketOption::PRE, SocketOption::INT, nullptr}, { "ipttl", 0, SRTO_IPTTL, SocketOption::PRE, SocketOption::INT, nullptr}, { "iptos", 0, SRTO_IPTOS, SocketOption::PRE, SocketOption::INT, nullptr}, { "inputbw", 0, SRTO_INPUTBW, SocketOption::POST, SocketOption::INT64, nullptr}, { "mininputbw", 0, SRTO_MININPUTBW, SocketOption::POST, SocketOption::INT64, nullptr}, { "oheadbw", 0, SRTO_OHEADBW, SocketOption::POST, SocketOption::INT, nullptr}, { "latency", 0, SRTO_LATENCY, SocketOption::PRE, SocketOption::INT, nullptr}, { "tsbpdmode", 0, SRTO_TSBPDMODE, SocketOption::PRE, SocketOption::BOOL, nullptr}, { "tlpktdrop", 0, SRTO_TLPKTDROP, SocketOption::PRE, SocketOption::BOOL, nullptr}, { "snddropdelay", 0, SRTO_SNDDROPDELAY, SocketOption::POST, SocketOption::INT, nullptr}, { "nakreport", 0, SRTO_NAKREPORT, SocketOption::PRE, SocketOption::BOOL, nullptr}, { "conntimeo", 0, SRTO_CONNTIMEO, SocketOption::PRE, SocketOption::INT, nullptr}, { "drifttracer", 0, SRTO_DRIFTTRACER, SocketOption::POST, SocketOption::BOOL, nullptr}, { "lossmaxttl", 0, SRTO_LOSSMAXTTL, SocketOption::POST, SocketOption::INT, nullptr}, { "rcvlatency", 0, SRTO_RCVLATENCY, SocketOption::PRE, SocketOption::INT, nullptr}, { "peerlatency", 0, SRTO_PEERLATENCY, SocketOption::PRE, SocketOption::INT, nullptr}, { "minversion", 0, SRTO_MINVERSION, SocketOption::PRE, SocketOption::INT, nullptr}, { "streamid", 0, SRTO_STREAMID, SocketOption::PRE, SocketOption::STRING, nullptr}, { "congestion", 0, SRTO_CONGESTION, SocketOption::PRE, SocketOption::STRING, nullptr}, { "messageapi", 0, SRTO_MESSAGEAPI, SocketOption::PRE, SocketOption::BOOL, nullptr}, { "payloadsize", 0, SRTO_PAYLOADSIZE, SocketOption::PRE, SocketOption::INT, nullptr}, { "kmrefreshrate", 0, SRTO_KMREFRESHRATE, SocketOption::PRE, SocketOption::INT, nullptr }, { "kmpreannounce", 0, SRTO_KMPREANNOUNCE, SocketOption::PRE, SocketOption::INT, nullptr }, { "enforcedencryption", 0, SRTO_ENFORCEDENCRYPTION, SocketOption::PRE, SocketOption::BOOL, nullptr }, { "ipv6only", 0, SRTO_IPV6ONLY, SocketOption::PRE, SocketOption::INT, nullptr }, { "peeridletimeo", 0, SRTO_PEERIDLETIMEO, SocketOption::PRE, SocketOption::INT, nullptr }, { "packetfilter", 0, SRTO_PACKETFILTER, SocketOption::PRE, SocketOption::STRING, nullptr }, #if ENABLE_EXPERIMENTAL_BONDING { "groupconnect", 0, SRTO_GROUPCONNECT, SocketOption::PRE, SocketOption::INT, nullptr}, #endif #ifdef SRT_ENABLE_BINDTODEVICE { "bindtodevice", 0, SRTO_BINDTODEVICE, SocketOption::PRE, SocketOption::STRING, nullptr}, #endif #if ENABLE_EXPERIMENTAL_BONDING { "groupstabtimeo", 0, SRTO_GROUPSTABTIMEO, SocketOption::POST, SocketOption::INT, nullptr}, #endif { "retransmitalgo", 0, SRTO_RETRANSMITALGO, SocketOption::PRE, SocketOption::INT, nullptr } }; } SocketOption::Mode SrtInterpretMode(const std::string& modestr, const std::string& host, const std::string& adapter); SocketOption::Mode SrtConfigurePre(SRTSOCKET socket, std::string host, std::map options, std::vector* failures = 0); void SrtConfigurePost(SRTSOCKET socket, std::map options, std::vector* failures = 0); #endif srt-1.4.4/apps/srt-file-transmit.cpp000066400000000000000000000547161412557703600174120ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifdef _WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apputil.hpp" #include "uriparser.hpp" #include "logsupport.hpp" #include "socketoptions.hpp" #include "transmitmedia.hpp" #include "verbose.hpp" #ifndef S_ISDIR #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #endif using namespace std; static bool interrupt = false; void OnINT_ForceExit(int) { Verb() << "\n-------- REQUESTED INTERRUPT!\n"; interrupt = true; } struct FileTransmitConfig { unsigned long chunk_size; bool skip_flushing; bool quiet = false; srt_logging::LogLevel::type loglevel = srt_logging::LogLevel::error; set logfas; string logfile; int bw_report = 0; int stats_report = 0; string stats_out; SrtStatsPrintFormat stats_pf = SRTSTATS_PROFMAT_2COLS; bool full_stats = false; string source; string target; }; void PrintOptionHelp(const set &opt_names, const string &value, const string &desc) { cerr << "\t"; int i = 0; for (auto opt : opt_names) { if (i++) cerr << ", "; cerr << "-" << opt; } if (!value.empty()) cerr << ":" << value; cerr << "\t- " << desc << "\n"; } int parse_args(FileTransmitConfig &cfg, int argc, char** argv) { const OptionName o_chunk = { "c", "chunk" }, o_no_flush = { "sf", "skipflush" }, o_bwreport = { "r", "bwreport", "report", "bandwidth-report", "bitrate-report" }, o_statsrep = { "s", "stats", "stats-report-frequency" }, o_statsout = { "statsout" }, o_statspf = { "pf", "statspf" }, o_statsfull = { "f", "fullstats" }, o_loglevel = { "ll", "loglevel" }, o_logfa = { "logfa" }, o_logfile = { "logfile" }, o_quiet = { "q", "quiet" }, o_verbose = { "v", "verbose" }, o_help = { "h", "help" }, o_version = { "version" }; const vector optargs = { { o_chunk, OptionScheme::ARG_ONE }, { o_no_flush, OptionScheme::ARG_NONE }, { o_bwreport, OptionScheme::ARG_ONE }, { o_statsrep, OptionScheme::ARG_ONE }, { o_statsout, OptionScheme::ARG_ONE }, { o_statspf, OptionScheme::ARG_ONE }, { o_statsfull, OptionScheme::ARG_NONE }, { o_loglevel, OptionScheme::ARG_ONE }, { o_logfa, OptionScheme::ARG_ONE }, { o_logfile, OptionScheme::ARG_ONE }, { o_quiet, OptionScheme::ARG_NONE }, { o_verbose, OptionScheme::ARG_NONE }, { o_help, OptionScheme::ARG_NONE }, { o_version, OptionScheme::ARG_NONE } }; options_t params = ProcessOptions(argv, argc, optargs); bool print_help = Option(params, false, o_help); const bool print_version = Option(params, false, o_version); if (params[""].size() != 2 && !print_help && !print_version) { cerr << "ERROR. Invalid syntax. Specify source and target URIs.\n"; if (params[""].size() > 0) { cerr << "The following options are passed without a key: "; copy(params[""].begin(), params[""].end(), ostream_iterator(cerr, ", ")); cerr << endl; } print_help = true; // Enable help to print it further } if (print_help) { cout << "SRT sample application to transmit files.\n"; cerr << "Built with SRT Library version: " << SRT_VERSION << endl; const uint32_t srtver = srt_getversion(); const int major = srtver / 0x10000; const int minor = (srtver / 0x100) % 0x100; const int patch = srtver % 0x100; cerr << "SRT Library version: " << major << "." << minor << "." << patch << endl; cerr << "Usage: srt-file-transmit [options] \n"; cerr << "\n"; PrintOptionHelp(o_chunk, "", "max size of data read in one step"); PrintOptionHelp(o_no_flush, "", "skip output file flushing"); PrintOptionHelp(o_bwreport, "", "bandwidth report frequency"); PrintOptionHelp(o_statsrep, "", "frequency of status report"); PrintOptionHelp(o_statsout, "", "output stats to file"); PrintOptionHelp(o_statspf, "", "stats printing format [json|csv|default]"); PrintOptionHelp(o_statsfull, "", "full counters in stats-report (prints total statistics)"); PrintOptionHelp(o_loglevel, "", "log level [fatal,error,info,note,warning]"); PrintOptionHelp(o_logfa, "", "log functional area [all,general,bstats,control,data,tsbpd,rexmit]"); PrintOptionHelp(o_logfile, "", "write logs to file"); PrintOptionHelp(o_quiet, "", "quiet mode (default off)"); PrintOptionHelp(o_verbose, "", "verbose mode (default off)"); cerr << "\n"; cerr << "\t-h,-help - show this help\n"; cerr << "\t-version - print SRT library version\n"; cerr << "\n"; cerr << "\t - URI specifying a medium to read from\n"; cerr << "\t - URI specifying a medium to write to\n"; cerr << "URI syntax: SCHEME://HOST:PORT/PATH?PARAM1=VALUE&PARAM2=VALUE...\n"; cerr << "Supported schemes:\n"; cerr << "\tsrt: use HOST, PORT, and PARAM for setting socket options\n"; cerr << "\tudp: use HOST, PORT and PARAM for some UDP specific settings\n"; cerr << "\tfile: file URI or file://con to use stdin or stdout\n"; return 2; } if (Option(params, false, o_version)) { cerr << "SRT Library version: " << SRT_VERSION << endl; return 2; } cfg.chunk_size = stoul(Option(params, "1456", o_chunk)); cfg.skip_flushing = Option(params, false, o_no_flush); cfg.bw_report = stoi(Option(params, "0", o_bwreport)); cfg.stats_report = stoi(Option(params, "0", o_statsrep)); cfg.stats_out = Option(params, "", o_statsout); const string pf = Option(params, "default", o_statspf); if (pf == "default") { cfg.stats_pf = SRTSTATS_PROFMAT_2COLS; } else if (pf == "json") { cfg.stats_pf = SRTSTATS_PROFMAT_JSON; } else if (pf == "csv") { cfg.stats_pf = SRTSTATS_PROFMAT_CSV; } else { cfg.stats_pf = SRTSTATS_PROFMAT_2COLS; cerr << "ERROR: Unsupported print format: " << pf << endl; return 1; } cfg.full_stats = Option(params, false, o_statsfull); cfg.loglevel = SrtParseLogLevel(Option(params, "error", o_loglevel)); cfg.logfas = SrtParseLogFA(Option(params, "", o_logfa)); cfg.logfile = Option(params, "", o_logfile); cfg.quiet = Option(params, false, o_quiet); if (Option(params, false, o_verbose)) Verbose::on = !cfg.quiet; cfg.source = params[""].at(0); cfg.target = params[""].at(1); return 0; } void ExtractPath(string path, string& w_dir, string& w_fname) { string directory = path; string filename = ""; struct stat state; stat(path.c_str(), &state); if (!S_ISDIR(state.st_mode)) { // Extract directory as a butlast part of path size_t pos = path.find_last_of("/"); if ( pos == string::npos ) { filename = path; directory = "."; } else { directory = path.substr(0, pos); filename = path.substr(pos+1); } } if (directory[0] != '/') { // Glue in the absolute prefix of the current directory // to make it absolute. This is needed to properly interpret // the fixed uri. static const size_t s_max_path = 4096; // don't care how proper this is char tmppath[s_max_path]; #ifdef _WIN32 const char* gwd = _getcwd(tmppath, s_max_path); #else const char* gwd = getcwd(tmppath, s_max_path); #endif if ( !gwd ) { // Don't bother with that now. We need something better for // that anyway. throw std::invalid_argument("Path too long"); } const string wd = gwd; directory = wd + "/" + directory; } w_dir = directory; w_fname = filename; } bool DoUpload(UriParser& ut, string path, string filename, const FileTransmitConfig &cfg, std::ostream &out_stats) { bool result = false; unique_ptr tar; SRTSOCKET s = SRT_INVALID_SOCK; bool connected = false; int pollid = -1; ifstream ifile(path, ios::binary); if ( !ifile ) { cerr << "Error opening file: '" << path << "'"; goto exit; } pollid = srt_epoll_create(); if ( pollid < 0 ) { cerr << "Can't initialize epoll"; goto exit; } while (!interrupt) { if (!tar.get()) { tar = Target::Create(ut.makeUri()); if (!tar.get()) { cerr << "Unsupported target type: " << ut.uri() << endl; goto exit; } int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR; if (srt_epoll_add_usock(pollid, tar->GetSRTSocket(), &events)) { cerr << "Failed to add SRT destination to poll, " << tar->GetSRTSocket() << endl; goto exit; } srt::setstreamid(tar->GetSRTSocket(), filename); } s = tar->GetSRTSocket(); assert(s != SRT_INVALID_SOCK); SRTSOCKET efd; int efdlen = 1; if (srt_epoll_wait(pollid, 0, 0, &efd, &efdlen, 100, nullptr, nullptr, 0, 0) < 0) { continue; } assert(efd == s); assert(efdlen == 1); SRT_SOCKSTATUS status = srt_getsockstate(s); switch (status) { case SRTS_LISTENING: { if (!tar->AcceptNewClient()) { cerr << "Failed to accept SRT connection" << endl; goto exit; } srt_epoll_remove_usock(pollid, s); s = tar->GetSRTSocket(); int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR; if (srt_epoll_add_usock(pollid, s, &events)) { cerr << "Failed to add SRT client to poll" << endl; goto exit; } cerr << "Target connected (listener)" << endl; connected = true; } break; case SRTS_CONNECTED: { if (!connected) { cerr << "Target connected (caller)" << endl; connected = true; } } break; case SRTS_BROKEN: case SRTS_NONEXIST: case SRTS_CLOSED: { cerr << "Target disconnected" << endl; goto exit; } default: { // No-Op } break; } if (connected) { vector buf(cfg.chunk_size); size_t n = ifile.read(buf.data(), cfg.chunk_size).gcount(); size_t shift = 0; while (n > 0) { int st = tar->Write(buf.data() + shift, n, 0, out_stats); Verb() << "Upload: " << n << " --> " << st << (!shift ? string() : "+" + Sprint(shift)); if (st == SRT_ERROR) { cerr << "Upload: SRT error: " << srt_getlasterror_str() << endl; goto exit; } n -= st; shift += st; } if (ifile.eof()) { cerr << "File sent" << endl; result = true; break; } if ( !ifile.good() ) { cerr << "ERROR while reading file\n"; goto exit; } } } if (result && !cfg.skip_flushing) { assert(s != SRT_INVALID_SOCK); // send-flush-loop result = false; while (!interrupt) { size_t bytes; size_t blocks; int st = srt_getsndbuffer(s, &blocks, &bytes); if (st == SRT_ERROR) { cerr << "Error in srt_getsndbuffer: " << srt_getlasterror_str() << endl; goto exit; } if (bytes == 0) { cerr << "Buffers flushed" << endl; result = true; break; } Verb() << "Sending buffer still: bytes=" << bytes << " blocks=" << blocks; srt::sync::this_thread::sleep_for(srt::sync::milliseconds_from(250)); } } exit: if (pollid >= 0) { srt_epoll_release(pollid); } return result; } bool DoDownload(UriParser& us, string directory, string filename, const FileTransmitConfig &cfg, std::ostream &out_stats) { bool result = false; unique_ptr src; SRTSOCKET s = SRT_INVALID_SOCK; bool connected = false; int pollid = -1; string id; ofstream ofile; SRT_SOCKSTATUS status; SRTSOCKET efd; int efdlen = 1; pollid = srt_epoll_create(); if ( pollid < 0 ) { cerr << "Can't initialize epoll"; goto exit; } while (!interrupt) { if (!src.get()) { src = Source::Create(us.makeUri()); if (!src.get()) { cerr << "Unsupported source type: " << us.uri() << endl; goto exit; } int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; if (srt_epoll_add_usock(pollid, src->GetSRTSocket(), &events)) { cerr << "Failed to add SRT source to poll, " << src->GetSRTSocket() << endl; goto exit; } } s = src->GetSRTSocket(); assert(s != SRT_INVALID_SOCK); if (srt_epoll_wait(pollid, &efd, &efdlen, 0, 0, 100, nullptr, nullptr, 0, 0) < 0) { continue; } assert(efd == s); assert(efdlen == 1); status = srt_getsockstate(s); Verb() << "Event with status " << status << "\n"; switch (status) { case SRTS_LISTENING: { if (!src->AcceptNewClient()) { cerr << "Failed to accept SRT connection" << endl; goto exit; } srt_epoll_remove_usock(pollid, s); s = src->GetSRTSocket(); int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; if (srt_epoll_add_usock(pollid, s, &events)) { cerr << "Failed to add SRT client to poll" << endl; goto exit; } id = srt::getstreamid(s); cerr << "Source connected (listener), id [" << id << "]" << endl; connected = true; continue; } break; case SRTS_CONNECTED: { if (!connected) { id = srt::getstreamid(s); cerr << "Source connected (caller), id [" << id << "]" << endl; connected = true; } } break; // No need to do any special action in case of broken. // The app will just try to read and in worst case it will // get an error. case SRTS_BROKEN: cerr << "Connection closed, reading buffer remains\n"; break; case SRTS_NONEXIST: case SRTS_CLOSED: { cerr << "Source disconnected" << endl; goto exit; } break; default: { // No-Op } break; } if (connected) { MediaPacket packet(cfg.chunk_size); if (!ofile.is_open()) { const char * fn = id.empty() ? filename.c_str() : id.c_str(); directory.append("/"); directory.append(fn); ofile.open(directory.c_str(), ios::out | ios::trunc | ios::binary); if (!ofile.is_open()) { cerr << "Error opening file [" << directory << "]" << endl; goto exit; } cerr << "Writing output to [" << directory << "]" << endl; } int n = src->Read(cfg.chunk_size, packet, out_stats); if (n == SRT_ERROR) { cerr << "Download: SRT error: " << srt_getlasterror_str() << endl; goto exit; } if (n == 0) { result = true; cerr << "Download COMPLETE.\n"; break; } // Write to file any amount of data received Verb() << "Download: --> " << n; ofile.write(packet.payload.data(), n); if (!ofile.good()) { cerr << "Error writing file" << endl; goto exit; } } } exit: if (pollid >= 0) { srt_epoll_release(pollid); } return result; } bool Upload(UriParser& srt_target_uri, UriParser& fileuri, const FileTransmitConfig &cfg, std::ostream &out_stats) { if ( fileuri.scheme() != "file" ) { cerr << "Upload: source accepted only as a file\n"; return false; } // fileuri is source-reading file // srt_target_uri is SRT target string path = fileuri.path(); string directory, filename; ExtractPath(path, (directory), (filename)); Verb() << "Extract path '" << path << "': directory=" << directory << " filename=" << filename; // Set ID to the filename. // Directory will be preserved. // Add some extra parameters. srt_target_uri["transtype"] = "file"; return DoUpload(srt_target_uri, path, filename, cfg, out_stats); } bool Download(UriParser& srt_source_uri, UriParser& fileuri, const FileTransmitConfig &cfg, std::ostream &out_stats) { if (fileuri.scheme() != "file" ) { cerr << "Download: target accepted only as a file\n"; return false; } string path = fileuri.path(), directory, filename; ExtractPath(path, (directory), (filename)); Verb() << "Extract path '" << path << "': directory=" << directory << " filename=" << filename; // Add some extra parameters. srt_source_uri["transtype"] = "file"; return DoDownload(srt_source_uri, directory, filename, cfg, out_stats); } int main(int argc, char** argv) { FileTransmitConfig cfg; const int parse_ret = parse_args(cfg, argc, argv); if (parse_ret != 0) return parse_ret == 1 ? EXIT_FAILURE : 0; // // Set global config variables // if (cfg.chunk_size != SRT_LIVE_MAX_PLSIZE) transmit_chunk_size = cfg.chunk_size; transmit_stats_writer = SrtStatsWriterFactory(cfg.stats_pf); transmit_bw_report = cfg.bw_report; transmit_stats_report = cfg.stats_report; transmit_total_stats = cfg.full_stats; // // Set SRT log levels and functional areas // srt_setloglevel(cfg.loglevel); for (set::iterator i = cfg.logfas.begin(); i != cfg.logfas.end(); ++i) srt_addlogfa(*i); // // SRT log handler // std::ofstream logfile_stream; // leave unused if not set if (!cfg.logfile.empty()) { logfile_stream.open(cfg.logfile.c_str()); if (!logfile_stream) { cerr << "ERROR: Can't open '" << cfg.logfile.c_str() << "' for writing - fallback to cerr\n"; } else { srt::setlogstream(logfile_stream); } } // // SRT stats output // std::ofstream logfile_stats; // leave unused if not set if (cfg.stats_out != "" && cfg.stats_out != "stdout") { logfile_stats.open(cfg.stats_out.c_str()); if (!logfile_stats) { cerr << "ERROR: Can't open '" << cfg.stats_out << "' for writing stats. Fallback to stdout.\n"; return 1; } } else if (cfg.bw_report != 0 || cfg.stats_report != 0) { g_stats_are_printed_to_stdout = true; } ostream &out_stats = logfile_stats.is_open() ? logfile_stats : cout; // File transmission code UriParser us(cfg.source), ut(cfg.target); Verb() << "SOURCE type=" << us.scheme() << ", TARGET type=" << ut.scheme(); signal(SIGINT, OnINT_ForceExit); signal(SIGTERM, OnINT_ForceExit); try { if (us.scheme() == "srt") { if (ut.scheme() != "file") { cerr << "SRT to FILE should be specified\n"; return 1; } Download(us, ut, cfg, out_stats); } else if (ut.scheme() == "srt") { if (us.scheme() != "file") { cerr << "FILE to SRT should be specified\n"; return 1; } Upload(ut, us, cfg, out_stats); } else { cerr << "SRT URI must be one of given media.\n"; return 1; } } catch (std::exception& x) { cerr << "ERROR: " << x.what() << endl; return 1; } return 0; } srt-1.4.4/apps/srt-live-transmit.cpp000066400000000000000000000755371412557703600174360ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ // NOTE: This application uses C++11. // This program uses quite a simple architecture, which is mainly related to // the way how it's invoked: srt-live-transmit (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 (common/socketoptions.hpp) for a list of // all options. // MSVS likes to complain about lots of standard C functions being unsafe. #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS 1 #endif #define REQUIRE_CXX11 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apputil.hpp" // CreateAddr #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" #include "logsupport.hpp" #include "transmitmedia.hpp" #include "verbose.hpp" // NOTE: This is without "haisrt/" because it uses an internal path // to the library. Application using the "installed" library should // use #include #include // This TEMPORARILY contains extra C++-only SRT API. #include using namespace std; struct ForcedExit: public std::runtime_error { ForcedExit(const std::string& arg): std::runtime_error(arg) { } }; struct AlarmExit: public std::runtime_error { AlarmExit(const std::string& arg): std::runtime_error(arg) { } }; volatile bool int_state = false; volatile bool timer_state = false; void OnINT_ForceExit(int) { Verb() << "\n-------- REQUESTED INTERRUPT!\n"; int_state = true; } void OnAlarm_Interrupt(int) { Verb() << "\n---------- INTERRUPT ON TIMEOUT!\n"; int_state = false; // JIC timer_state = true; if ((false)) { throw AlarmExit("Watchdog bites hangup"); } } extern "C" void TestLogHandler(void* opaque, int level, const char* file, int line, const char* area, const char* message); struct LiveTransmitConfig { int timeout = 0; int timeout_mode = 0; int chunk_size = -1; bool quiet = false; srt_logging::LogLevel::type loglevel = srt_logging::LogLevel::error; set logfas; bool log_internal; string logfile; int bw_report = 0; bool srctime = false; size_t buffering = 10; int stats_report = 0; string stats_out; SrtStatsPrintFormat stats_pf = SRTSTATS_PROFMAT_2COLS; bool auto_reconnect = true; bool full_stats = false; string source; string target; }; void PrintOptionHelp(const OptionName& opt_names, const string &value, const string &desc) { cerr << "\t"; int i = 0; for (auto opt : opt_names.names) { if (i++) cerr << ", "; cerr << "-" << opt; } if (!value.empty()) cerr << ":" << value; cerr << "\t- " << desc << "\n"; } int parse_args(LiveTransmitConfig &cfg, int argc, char** argv) { const OptionName o_timeout = { "t", "to", "timeout" }, o_timeout_mode = { "tm", "timeout-mode" }, o_autorecon = { "a", "auto", "autoreconnect" }, o_chunk = { "c", "chunk" }, o_bwreport = { "r", "bwreport", "report", "bandwidth-report", "bitrate-report" }, o_srctime = {"st", "srctime", "sourcetime"}, o_buffering = {"buffering"}, o_statsrep = { "s", "stats", "stats-report-frequency" }, o_statsout = { "statsout" }, o_statspf = { "pf", "statspf" }, o_statsfull = { "f", "fullstats" }, o_loglevel = { "ll", "loglevel" }, o_logfa = { "lfa", "logfa" }, o_log_internal = { "loginternal"}, o_logfile = { "logfile" }, o_quiet = { "q", "quiet" }, o_verbose = { "v", "verbose" }, o_help = { "h", "help" }, o_version = { "version" }; const vector optargs = { { o_timeout, OptionScheme::ARG_ONE }, { o_timeout_mode, OptionScheme::ARG_ONE }, { o_autorecon, OptionScheme::ARG_ONE }, { o_chunk, OptionScheme::ARG_ONE }, { o_bwreport, OptionScheme::ARG_ONE }, { o_srctime, OptionScheme::ARG_ONE }, { o_buffering, OptionScheme::ARG_ONE }, { o_statsrep, OptionScheme::ARG_ONE }, { o_statsout, OptionScheme::ARG_ONE }, { o_statspf, OptionScheme::ARG_ONE }, { o_statsfull, OptionScheme::ARG_NONE }, { o_loglevel, OptionScheme::ARG_ONE }, { o_logfa, OptionScheme::ARG_ONE }, { o_log_internal, OptionScheme::ARG_NONE }, { o_logfile, OptionScheme::ARG_ONE }, { o_quiet, OptionScheme::ARG_NONE }, { o_verbose, OptionScheme::ARG_NONE }, { o_help, OptionScheme::ARG_VAR }, { o_version, OptionScheme::ARG_NONE } }; options_t params = ProcessOptions(argv, argc, optargs); bool print_help = OptionPresent(params, o_help); const bool print_version = OptionPresent(params, o_version); if (params[""].size() != 2 && !print_help && !print_version) { cerr << "ERROR. Invalid syntax. Specify source and target URIs.\n"; if (params[""].size() > 0) { cerr << "The following options are passed without a key: "; copy(params[""].begin(), params[""].end(), ostream_iterator(cerr, ", ")); cerr << endl; } print_help = true; // Enable help to print it further } if (print_help) { string helpspec = Option(params, o_help); if (helpspec == "logging") { cerr << "Logging options:\n"; cerr << " -ll - specify minimum log level\n"; cerr << " -lfa - specify functional areas\n"; cerr << "Where:\n\n"; cerr << " : fatal error note warning debug\n\n"; cerr << "Turns on logs that are at the given log level or any higher level\n"; cerr << "(all to the left in the list above from the selected level).\n"; cerr << "Names from syslog, like alert, crit, emerg, err, info, panic, are also\n"; cerr << "recognized, but they are aligned to those that lie close in the above hierarchy.\n\n"; cerr << " is a coma-separated list of areas to turn on.\n\n"; cerr << "The list may include 'all' to turn all FAs on.\n"; cerr << "Example: `-lfa:sockmgmt,chn-recv` enables only `sockmgmt` and `chn-recv` log FAs.\n"; cerr << "Default: all are on except haicrypt. NOTE: 'general' FA can't be disabled.\n\n"; cerr << "List of functional areas:\n"; map revmap; for (auto entry: SrtLogFAList()) revmap[entry.second] = entry.first; // Each group on a new line int en10 = 0; for (auto entry: revmap) { cerr << " " << entry.second; if (entry.first/10 != en10) { cerr << endl; en10 = entry.first/10; } } cerr << endl; return 1; } cout << "SRT sample application to transmit live streaming.\n"; cerr << "Built with SRT Library version: " << SRT_VERSION << endl; const uint32_t srtver = srt_getversion(); const int major = srtver / 0x10000; const int minor = (srtver / 0x100) % 0x100; const int patch = srtver % 0x100; cerr << "SRT Library version: " << major << "." << minor << "." << patch << endl; cerr << "Usage: srt-live-transmit [options] \n"; cerr << "\n"; #ifndef _WIN32 PrintOptionHelp(o_timeout, "", "exit timer in seconds"); PrintOptionHelp(o_timeout_mode, "", "timeout mode (0 - since app start; 1 - like 0, but cancel on connect"); #endif PrintOptionHelp(o_autorecon, "", "auto-reconnect mode {yes, no}"); PrintOptionHelp(o_chunk, "", "max size of data read in one step, that can fit one SRT packet"); PrintOptionHelp(o_bwreport, "", "bandwidth report frequency"); PrintOptionHelp(o_srctime, "", "Pass packet time from source to SRT output {yes, no}"); PrintOptionHelp(o_buffering, "", "Buffer up to n incoming packets"); PrintOptionHelp(o_statsrep, "", "frequency of status report"); PrintOptionHelp(o_statsout, "", "output stats to file"); PrintOptionHelp(o_statspf, "", "stats printing format {json, csv, default}"); PrintOptionHelp(o_statsfull, "", "full counters in stats-report (prints total statistics)"); PrintOptionHelp(o_loglevel, "", "log level {fatal,error,warn,note,info,debug}"); PrintOptionHelp(o_logfa, "", "log functional area (see '-h logging' for more info)"); //PrintOptionHelp(o_log_internal, "", "use internal logger"); PrintOptionHelp(o_logfile, "", "write logs to file"); PrintOptionHelp(o_quiet, "", "quiet mode (default off)"); PrintOptionHelp(o_verbose, "", "verbose mode (default off)"); cerr << "\n"; cerr << "\t-h,-help - show this help (use '-h logging' for logging system)\n"; cerr << "\t-version - print SRT library version\n"; cerr << "\n"; cerr << "\t - URI specifying a medium to read from\n"; cerr << "\t - URI specifying a medium to write to\n"; cerr << "URI syntax: SCHEME://HOST:PORT/PATH?PARAM1=VALUE&PARAM2=VALUE...\n"; cerr << "Supported schemes:\n"; cerr << "\tsrt: use HOST, PORT, and PARAM for setting socket options\n"; cerr << "\tudp: use HOST, PORT and PARAM for some UDP specific settings\n"; cerr << "\tfile: only as file://con for using stdin or stdout\n"; return 2; } if (print_version) { cerr << "SRT Library version: " << SRT_VERSION << endl; return 2; } cfg.timeout = Option(params, o_timeout); cfg.timeout_mode = Option(params, o_timeout_mode); cfg.chunk_size = Option(params, "-1", o_chunk); cfg.srctime = Option(params, cfg.srctime, o_srctime); const int buffering = Option(params, "10", o_buffering); if (buffering <= 0) { cerr << "ERROR: Buffering value should be positive. Value provided: " << buffering << "." << endl; return 1; } else { cfg.buffering = (size_t) buffering; } cfg.bw_report = Option(params, o_bwreport); cfg.stats_report = Option(params, o_statsrep); cfg.stats_out = Option(params, o_statsout); const string pf = Option(params, "default", o_statspf); string pfext; cfg.stats_pf = ParsePrintFormat(pf, (pfext)); if (cfg.stats_pf == SRTSTATS_PROFMAT_INVALID) { cfg.stats_pf = SRTSTATS_PROFMAT_2COLS; cerr << "ERROR: Unsupported print format: " << pf << " -- fallback to default" << endl; return 1; } cfg.full_stats = OptionPresent(params, o_statsfull); cfg.loglevel = SrtParseLogLevel(Option(params, "warn", o_loglevel)); cfg.logfas = SrtParseLogFA(Option(params, "", o_logfa)); cfg.log_internal = OptionPresent(params, o_log_internal); cfg.logfile = Option(params, o_logfile); cfg.quiet = OptionPresent(params, o_quiet); if (OptionPresent(params, o_verbose)) Verbose::on = !cfg.quiet; cfg.auto_reconnect = Option(params, true, o_autorecon); cfg.source = params[""].at(0); cfg.target = params[""].at(1); return 0; } int main(int argc, char** argv) { srt_startup(); // This is mainly required on Windows to initialize the network system, // for a case when the instance would use UDP. SRT does it on its own, independently. if (!SysInitializeNetwork()) throw std::runtime_error("Can't initialize network!"); // Symmetrically, this does a cleanup; put into a local destructor to ensure that // it's called regardless of how this function returns. struct NetworkCleanup { ~NetworkCleanup() { srt_cleanup(); SysCleanupNetwork(); } } cleanupobj; LiveTransmitConfig cfg; const int parse_ret = parse_args(cfg, argc, argv); if (parse_ret != 0) return parse_ret == 1 ? EXIT_FAILURE : 0; // // Set global config variables // if (cfg.chunk_size > 0) transmit_chunk_size = cfg.chunk_size; transmit_stats_writer = SrtStatsWriterFactory(cfg.stats_pf); transmit_bw_report = cfg.bw_report; transmit_stats_report = cfg.stats_report; transmit_total_stats = cfg.full_stats; // // Set SRT log levels and functional areas // srt_setloglevel(cfg.loglevel); if (!cfg.logfas.empty()) { srt_resetlogfa(nullptr, 0); for (set::iterator i = cfg.logfas.begin(); i != cfg.logfas.end(); ++i) srt_addlogfa(*i); } // // SRT log handler // std::ofstream logfile_stream; // leave unused if not set char NAME[] = "SRTLIB"; if (cfg.log_internal) { 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 (!cfg.logfile.empty()) { logfile_stream.open(cfg.logfile.c_str()); if (!logfile_stream) { cerr << "ERROR: Can't open '" << cfg.logfile.c_str() << "' for writing - fallback to cerr\n"; } else { srt::setlogstream(logfile_stream); } } // // SRT stats output // std::ofstream logfile_stats; // leave unused if not set if (cfg.stats_out != "") { logfile_stats.open(cfg.stats_out.c_str()); if (!logfile_stats) { cerr << "ERROR: Can't open '" << cfg.stats_out << "' for writing stats. Fallback to stdout.\n"; logfile_stats.close(); } } else if (cfg.bw_report != 0 || cfg.stats_report != 0) { g_stats_are_printed_to_stdout = true; } ostream &out_stats = logfile_stats.is_open() ? logfile_stats : cout; #ifdef _WIN32 if (cfg.timeout != 0) { cerr << "ERROR: The -timeout option (-t) is not implemented on Windows\n"; return EXIT_FAILURE; } #else if (cfg.timeout > 0) { signal(SIGALRM, OnAlarm_Interrupt); if (!cfg.quiet) cerr << "TIMEOUT: will interrupt after " << cfg.timeout << "s\n"; alarm(cfg.timeout); } #endif signal(SIGINT, OnINT_ForceExit); signal(SIGTERM, OnINT_ForceExit); if (!cfg.quiet) { cerr << "Media path: '" << cfg.source << "' --> '" << cfg.target << "'\n"; } unique_ptr src; bool srcConnected = false; unique_ptr tar; bool tarConnected = false; int pollid = srt_epoll_create(); if (pollid < 0) { cerr << "Can't initialize epoll"; return 1; } size_t receivedBytes = 0; size_t wroteBytes = 0; size_t lostBytes = 0; size_t lastReportedtLostBytes = 0; std::time_t writeErrorLogTimer(std::time(nullptr)); try { // Now loop until broken while (!int_state && !timer_state) { if (!src.get()) { src = Source::Create(cfg.source); if (!src.get()) { cerr << "Unsupported source type" << endl; return 1; } int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; switch (src->uri.type()) { case UriParser::SRT: if (srt_epoll_add_usock(pollid, src->GetSRTSocket(), &events)) { cerr << "Failed to add SRT source to poll, " << src->GetSRTSocket() << endl; return 1; } break; case UriParser::UDP: if (srt_epoll_add_ssock(pollid, src->GetSysSocket(), &events)) { cerr << "Failed to add UDP source to poll, " << src->GetSysSocket() << endl; return 1; } break; case UriParser::FILE: if (srt_epoll_add_ssock(pollid, src->GetSysSocket(), &events)) { cerr << "Failed to add FILE source to poll, " << src->GetSysSocket() << endl; return 1; } break; default: break; } receivedBytes = 0; } if (!tar.get()) { tar = Target::Create(cfg.target); if (!tar.get()) { cerr << "Unsupported target type" << endl; return 1; } // IN because we care for state transitions only // OUT - to check the connection state changes int events = SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR; switch(tar->uri.type()) { case UriParser::SRT: if (srt_epoll_add_usock(pollid, tar->GetSRTSocket(), &events)) { cerr << "Failed to add SRT destination to poll, " << tar->GetSRTSocket() << endl; return 1; } break; default: break; } wroteBytes = 0; lostBytes = 0; lastReportedtLostBytes = 0; } int srtrfdslen = 2; int srtwfdslen = 2; SRTSOCKET srtrwfds[4] = {SRT_INVALID_SOCK, SRT_INVALID_SOCK , SRT_INVALID_SOCK , SRT_INVALID_SOCK }; int sysrfdslen = 2; SYSSOCKET sysrfds[2]; if (srt_epoll_wait(pollid, &srtrwfds[0], &srtrfdslen, &srtrwfds[2], &srtwfdslen, 100, &sysrfds[0], &sysrfdslen, 0, 0) >= 0) { bool doabort = false; for (size_t i = 0; i < sizeof(srtrwfds) / sizeof(SRTSOCKET); i++) { SRTSOCKET s = srtrwfds[i]; if (s == SRT_INVALID_SOCK) continue; // Remove duplicated sockets for (size_t j = i + 1; j < sizeof(srtrwfds) / sizeof(SRTSOCKET); j++) { const SRTSOCKET next_s = srtrwfds[j]; if (next_s == s) srtrwfds[j] = SRT_INVALID_SOCK; } bool issource = false; if (src && src->GetSRTSocket() == s) { issource = true; } else if (tar && tar->GetSRTSocket() != s) { continue; } const char * dirstring = (issource) ? "source" : "target"; SRT_SOCKSTATUS status = srt_getsockstate(s); switch (status) { case SRTS_LISTENING: { const bool res = (issource) ? src->AcceptNewClient() : tar->AcceptNewClient(); if (!res) { cerr << "Failed to accept SRT connection" << endl; doabort = true; break; } srt_epoll_remove_usock(pollid, s); SRTSOCKET ns = (issource) ? src->GetSRTSocket() : tar->GetSRTSocket(); int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; if (srt_epoll_add_usock(pollid, ns, &events)) { cerr << "Failed to add SRT client to poll, " << ns << endl; doabort = true; } else { if (!cfg.quiet) { cerr << "Accepted SRT " << dirstring << " connection" << endl; } #ifndef _WIN32 if (cfg.timeout_mode == 1 && cfg.timeout > 0) { if (!cfg.quiet) cerr << "TIMEOUT: cancel\n"; alarm(0); } #endif if (issource) srcConnected = true; else tarConnected = true; } } break; case SRTS_BROKEN: case SRTS_NONEXIST: case SRTS_CLOSED: { if (issource) { if (srcConnected) { if (!cfg.quiet) { cerr << "SRT source disconnected" << endl; } srcConnected = false; } } else if (tarConnected) { if (!cfg.quiet) cerr << "SRT target disconnected" << endl; tarConnected = false; } if(!cfg.auto_reconnect) { doabort = true; } else { // force re-connection srt_epoll_remove_usock(pollid, s); if (issource) src.reset(); else tar.reset(); #ifndef _WIN32 if (cfg.timeout_mode == 1 && cfg.timeout > 0) { if (!cfg.quiet) cerr << "TIMEOUT: will interrupt after " << cfg.timeout << "s\n"; alarm(cfg.timeout); } #endif } } break; case SRTS_CONNECTED: { if (issource) { if (!srcConnected) { if (!cfg.quiet) cerr << "SRT source connected" << endl; srcConnected = true; } } else if (!tarConnected) { if (!cfg.quiet) cerr << "SRT target connected" << endl; tarConnected = true; if (tar->uri.type() == UriParser::SRT) { const int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; // Disable OUT event polling when connected if (srt_epoll_update_usock(pollid, tar->GetSRTSocket(), &events)) { cerr << "Failed to add SRT destination to poll, " << tar->GetSRTSocket() << endl; return 1; } } #ifndef _WIN32 if (cfg.timeout_mode == 1 && cfg.timeout > 0) { if (!cfg.quiet) cerr << "TIMEOUT: cancel\n"; alarm(0); } #endif } } default: { // No-Op } break; } } if (doabort) { break; } // read a few chunks at a time in attempt to deplete // read buffers as much as possible on each read event // note that this implies live streams and does not // work for cached/file sources std::list> dataqueue; if (src.get() && src->IsOpen() && (srtrfdslen || sysrfdslen)) { while (dataqueue.size() < cfg.buffering) { std::shared_ptr pkt(new MediaPacket(transmit_chunk_size)); const int res = src->Read(transmit_chunk_size, *pkt, out_stats); if (res == SRT_ERROR && src->uri.type() == UriParser::SRT) { if (srt_getlasterror(NULL) == SRT_EASYNCRCV) break; throw std::runtime_error( string("error: recvmsg: ") + string(srt_getlasterror_str()) ); } if (res == 0 || pkt->payload.empty()) { break; } dataqueue.push_back(pkt); receivedBytes += pkt->payload.size(); } } // if there is no target, let the received data be lost while (!dataqueue.empty()) { std::shared_ptr pkt = dataqueue.front(); if (!tar.get() || !tar->IsOpen()) { lostBytes += pkt->payload.size(); } else if (!tar->Write(pkt->payload.data(), pkt->payload.size(), cfg.srctime ? pkt->time : 0, out_stats)) { lostBytes += pkt->payload.size(); } else { wroteBytes += pkt->payload.size(); } dataqueue.pop_front(); } if (!cfg.quiet && (lastReportedtLostBytes != lostBytes)) { std::time_t now(std::time(nullptr)); if (std::difftime(now, writeErrorLogTimer) >= 5.0) { cerr << lostBytes << " bytes lost, " << wroteBytes << " bytes sent, " << receivedBytes << " bytes received" << endl; writeErrorLogTimer = now; lastReportedtLostBytes = lostBytes; } } } } } catch (std::exception& x) { cerr << "ERROR: " << x.what() << endl; return 255; } return 0; } // Class utilities 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 = SysLocalTime(now); size_t pos = strftime(buf, 1024, "[%c ", &local); #ifdef _MSC_VER // That's something weird that happens on Microsoft Visual Studio 2013 // Trying to keep portability, while every version of MSVS is a different plaform. // On MSVS 2015 there's already a standard-compliant snprintf, whereas _snprintf // is available on backward compatibility and it doesn't work exactly the same way. #define snprintf _snprintf #endif snprintf(buf+pos, 1024-pos, "%s:%d(%s)]{%d} %s", file, line, area, level, message); cerr << buf << endl; } srt-1.4.4/apps/srt-tunnel.cpp000066400000000000000000000733161412557703600161360ustar00rootroot00000000000000// MSVS likes to complain about lots of standard C functions being unsafe. #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS 1 #include #endif #include "platform_sys.h" #define REQUIRE_CXX11 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apputil.hpp" // CreateAddr #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" #include "logsupport.hpp" #include "transmitbase.hpp" // bytevector typedef to avoid collisions #include "verbose.hpp" // NOTE: This is without "haisrt/" because it uses an internal path // to the library. Application using the "installed" library should // use #include #include // This TEMPORARILY contains extra C++-only SRT API. #include #include #include /* # MAF contents for this file. Note that not every file from the support # library is used, but to simplify the build definition it links against # the whole srtsupport library. SOURCES srt-test-tunnel.cpp testmedia.cpp ../apps/verbose.cpp ../apps/socketoptions.cpp ../apps/uriparser.cpp ../apps/logsupport.cpp */ using namespace std; const srt_logging::LogFA SRT_LOGFA_APP = 10; namespace srt_logging { Logger applog(SRT_LOGFA_APP, srt_logger_config, "TUNNELAPP"); } using srt_logging::applog; class Medium { static int s_counter; int m_counter; public: enum ReadStatus { RD_DATA, RD_AGAIN, RD_EOF, RD_ERROR }; enum Mode { LISTENER, CALLER }; protected: UriParser m_uri; size_t m_chunk = 0; map m_options; Mode m_mode; bool m_listener = false; bool m_open = false; bool m_eof = false; bool m_broken = false; std::mutex access; // For closing template static Medium* CreateAcceptor(DerivedMedium* self, const sockaddr_any& sa, SocketType sock, size_t chunk) { string addr = sockaddr_any(sa.get(), sizeof sa).str(); DerivedMedium* m = new DerivedMedium(UriParser(self->type() + string("://") + addr), chunk); m->m_socket = sock; return m; } public: string uri() { return m_uri.uri(); } string id() { std::ostringstream os; os << type() << m_counter; return os.str(); } Medium(const UriParser& u, size_t ch): m_counter(s_counter++), m_uri(u), m_chunk(ch) {} Medium(): m_counter(s_counter++) {} virtual const char* type() = 0; virtual bool IsOpen() = 0; virtual void CloseInternal() = 0; void CloseState() { m_open = false; m_broken = true; } // External API for this class that allows to close // the entity on request. The CloseInternal should // redirect to a type-specific function, the same that // should be also called in destructor. void Close() { CloseState(); CloseInternal(); } virtual bool End() = 0; virtual int ReadInternal(char* output, int size) = 0; virtual bool IsErrorAgain() = 0; ReadStatus Read(bytevector& output); virtual void Write(bytevector& portion) = 0; virtual void CreateListener() = 0; virtual void CreateCaller() = 0; virtual unique_ptr Accept() = 0; virtual void Connect() = 0; static std::unique_ptr Create(const std::string& url, size_t chunk, Mode); virtual bool Broken() = 0; virtual size_t Still() { return 0; } class ReadEOF: public std::runtime_error { public: ReadEOF(const std::string& fn): std::runtime_error( "EOF while reading file: " + fn ) { } }; class TransmissionError: public std::runtime_error { public: TransmissionError(const std::string& fn): std::runtime_error( fn ) { } }; static void Error(const string& text) { throw TransmissionError("ERROR (internal): " + text); } virtual ~Medium() { CloseState(); } protected: void InitMode(Mode m) { m_mode = m; Init(); if (m_mode == LISTENER) { CreateListener(); m_listener = true; } else { CreateCaller(); } m_open = true; } virtual void Init() {} }; class Engine { Medium* media[2]; std::thread thr; class Tunnel* parent_tunnel; std::string nameid; int status = 0; Medium::ReadStatus rdst = Medium::RD_ERROR; UDT::ERRORINFO srtx; public: enum Dir { DIR_IN, DIR_OUT }; int stat() { return status; } Engine(Tunnel* p, Medium* m1, Medium* m2, const std::string& nid) : #ifdef HAVE_FULL_CXX11 media {m1, m2}, #endif parent_tunnel(p), nameid(nid) { #ifndef HAVE_FULL_CXX11 // MSVC is not exactly C++11 compliant and complains around // initialization of an array. // Leaving this method of initialization for clarity and // possibly more preferred performance. media[0] = m1; media[1] = m2; #endif } void Start() { Verb() << "START: " << media[DIR_IN]->uri() << " --> " << media[DIR_OUT]->uri(); const std::string thrn = media[DIR_IN]->id() + ">" + media[DIR_OUT]->id(); srt::ThreadName tn(thrn); thr = thread([this]() { Worker(); }); } void Stop() { // If this thread is already stopped, don't stop. if (thr.joinable()) { LOGP(applog.Debug, "Engine::Stop: Closing media:"); // Close both media as a hanged up reading thread // will block joining. media[0]->Close(); media[1]->Close(); LOGP(applog.Debug, "Engine::Stop: media closed, joining engine thread:"); if (thr.get_id() == std::this_thread::get_id()) { // If this is this thread which called this, no need // to stop because this thread will exit by itself afterwards. // You must, however, detach yourself, or otherwise the thr's // destructor would kill the program. thr.detach(); LOGP(applog.Debug, "DETACHED."); } else { thr.join(); LOGP(applog.Debug, "Joined."); } } } void Worker(); }; struct Tunnelbox; class Tunnel { Tunnelbox* parent_box; std::unique_ptr med_acp, med_clr; Engine acp_to_clr, clr_to_acp; volatile bool running = true; std::mutex access; public: string show() { return med_acp->uri() + " <-> " + med_clr->uri(); } Tunnel(Tunnelbox* m, std::unique_ptr&& acp, std::unique_ptr&& clr): parent_box(m), med_acp(move(acp)), med_clr(move(clr)), acp_to_clr(this, med_acp.get(), med_clr.get(), med_acp->id() + ">" + med_clr->id()), clr_to_acp(this, med_clr.get(), med_acp.get(), med_clr->id() + ">" + med_acp->id()) { } void Start() { acp_to_clr.Start(); clr_to_acp.Start(); } // This is to be called by an Engine from Engine::Worker // thread. // [[affinity = acp_to_clr.thr || clr_to_acp.thr]]; void decommission_engine(Medium* which_medium) { // which_medium is the medium that failed. // Upon breaking of one medium from the pair, // the other needs to be closed as well. Verb() << "Medium broken: " << which_medium->uri(); bool stop = true; /* { lock_guard lk(access); if (acp_to_clr.stat() == -1 && clr_to_acp.stat() == -1) { Verb() << "Tunnel: Both engine decommissioned, will stop the tunnel."; // Both engines are down, decommission the tunnel. // Note that the status -1 means that particular engine // is not currently running and you can safely // join its thread. stop = true; } else { Verb() << "Tunnel: Decommissioned one engine, waiting for the other one to report"; } } */ if (stop) { // First, stop all media. med_acp->Close(); med_clr->Close(); // Then stop the tunnel (this is only a signal // to a cleanup thread to delete it). Stop(); } } void Stop(); bool decommission_if_dead(bool forced); // [[affinity = g_tunnels.thr]] }; void Engine::Worker() { bytevector outbuf; Medium* which_medium = media[DIR_IN]; for (;;) { try { which_medium = media[DIR_IN]; rdst = media[DIR_IN]->Read((outbuf)); switch (rdst) { case Medium::RD_DATA: { which_medium = media[DIR_OUT]; // We get the data, write them to the output media[DIR_OUT]->Write((outbuf)); } break; case Medium::RD_EOF: status = -1; throw Medium::ReadEOF(""); case Medium::RD_AGAIN: // Theoreticall RD_AGAIN should not be reported // because it should be taken care of internally by // repeated sending - unless we get m_broken set. // If it is, however, it should be handled just like error. case Medium::RD_ERROR: status = -1; Medium::Error("Error while reading"); } } catch (Medium::ReadEOF&) { Verb() << "EOF. Exiting engine."; break; } catch (Medium::TransmissionError& er) { Verb() << er.what() << " - interrupting engine: " << nameid; break; } } // This is an engine thread and it should simply // tell the parent_box Tunnel that it is no longer // operative. It's not necessary to inform it which // of two engines is decommissioned - it should only // know that one of them got down. It will then check // if both are down here and decommission the whole // tunnel if so. parent_tunnel->decommission_engine(which_medium); } class SrtMedium: public Medium { SRTSOCKET m_socket = SRT_ERROR; friend class Medium; public: #ifdef HAVE_FULL_CXX11 using Medium::Medium; #else // MSVC and gcc 4.7 not exactly support C++11 SrtMedium(UriParser u, size_t ch): Medium(u, ch) {} #endif bool IsOpen() override { return m_open; } bool End() override { return m_eof; } bool Broken() override { return m_broken; } void CloseSrt() { Verb() << "Closing SRT socket for " << uri(); lock_guard lk(access); if (m_socket == SRT_ERROR) return; srt_close(m_socket); m_socket = SRT_ERROR; } // Forwarded in order to separate the implementation from // the virtual function so that virtual function is not // being called in destructor. void CloseInternal() override { return CloseSrt(); } const char* type() override { return "srt"; } int ReadInternal(char* output, int size) override; bool IsErrorAgain() override; void Write(bytevector& portion) override; void CreateListener() override; void CreateCaller() override; unique_ptr Accept() override; void Connect() override; protected: void Init() override; void ConfigurePre(); void ConfigurePost(SRTSOCKET socket); using Medium::Error; static void Error(UDT::ERRORINFO& ri, const string& text) { throw TransmissionError("ERROR: " + text + ": " + ri.getErrorMessage()); } ~SrtMedium() override { CloseState(); CloseSrt(); } }; class TcpMedium: public Medium { int m_socket = -1; friend class Medium; public: #ifdef HAVE_FULL_CXX11 using Medium::Medium; #else // MSVC not exactly supports C++11 TcpMedium(UriParser u, size_t ch): Medium(u, ch) {} #endif #ifdef _WIN32 static int tcp_close(int socket) { return ::closesocket(socket); } enum { DEF_SEND_FLAG = 0 }; #elif defined(LINUX) || defined(GNU) || defined(CYGWIN) static int tcp_close(int socket) { return ::close(socket); } enum { DEF_SEND_FLAG = MSG_NOSIGNAL }; #else static int tcp_close(int socket) { return ::close(socket); } enum { DEF_SEND_FLAG = 0 }; #endif bool IsOpen() override { return m_open; } bool End() override { return m_eof; } bool Broken() override { return m_broken; } void CloseTcp() { Verb() << "Closing TCP socket for " << uri(); lock_guard lk(access); if (m_socket == -1) return; tcp_close(m_socket); m_socket = -1; } void CloseInternal() override { return CloseTcp(); } const char* type() override { return "tcp"; } int ReadInternal(char* output, int size) override; bool IsErrorAgain() override; void Write(bytevector& portion) override; void CreateListener() override; void CreateCaller() override; unique_ptr Accept() override; void Connect() override; protected: void ConfigurePre() { #if defined(__APPLE__) int optval = 1; setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)); #endif } void ConfigurePost(int) { } using Medium::Error; static void Error(int verrno, const string& text) { char rbuf[1024]; throw TransmissionError("ERROR: " + text + ": " + SysStrError(verrno, rbuf, 1024)); } virtual ~TcpMedium() { CloseState(); CloseTcp(); } }; void SrtMedium::Init() { // This function is required due to extra option // check need if (m_options.count("mode")) Error("No option 'mode' is required, it defaults to position of the argument"); if (m_options.count("blocking")) Error("Blocking is not configurable here."); // XXX // Look also for other options that should not be here. // Enforce the transtype = file m_options["transtype"] = "file"; } void SrtMedium::ConfigurePre() { vector fails; m_options["mode"] = "caller"; SrtConfigurePre(m_socket, "", m_options, &fails); if (!fails.empty()) { cerr << "Failed options: " << Printable(fails) << endl; } } void SrtMedium::ConfigurePost(SRTSOCKET so) { vector fails; SrtConfigurePost(so, m_options, &fails); if (!fails.empty()) { cerr << "Failed options: " << Printable(fails) << endl; } } void SrtMedium::CreateListener() { int backlog = 5; // hardcoded! m_socket = srt_create_socket(); ConfigurePre(); sockaddr_any sa = CreateAddr(m_uri.host(), m_uri.portno()); int stat = srt_bind(m_socket, sa.get(), sizeof sa); if ( stat == SRT_ERROR ) { srt_close(m_socket); Error(UDT::getlasterror(), "srt_bind"); } stat = srt_listen(m_socket, backlog); if ( stat == SRT_ERROR ) { srt_close(m_socket); Error(UDT::getlasterror(), "srt_listen"); } m_listener = true; }; void TcpMedium::CreateListener() { int backlog = 5; // hardcoded! sockaddr_any sa = CreateAddr(m_uri.host(), m_uri.portno()); m_socket = socket(sa.get()->sa_family, SOCK_STREAM, IPPROTO_TCP); ConfigurePre(); int stat = ::bind(m_socket, sa.get(), sa.size()); if (stat == -1) { tcp_close(m_socket); Error(errno, "bind"); } stat = listen(m_socket, backlog); if ( stat == -1 ) { tcp_close(m_socket); Error(errno, "listen"); } m_listener = true; } unique_ptr SrtMedium::Accept() { sockaddr_any sa; SRTSOCKET s = srt_accept(m_socket, (sa.get()), (&sa.len)); if (s == SRT_ERROR) { Error(UDT::getlasterror(), "srt_accept"); } ConfigurePost(s); // Configure 1s timeout int timeout_1s = 1000; srt_setsockflag(m_socket, SRTO_RCVTIMEO, &timeout_1s, sizeof timeout_1s); unique_ptr med(CreateAcceptor(this, sa, s, m_chunk)); Verb() << "accepted a connection from " << med->uri(); return med; } unique_ptr TcpMedium::Accept() { sockaddr_any sa; int s = ::accept(m_socket, (sa.get()), (&sa.syslen())); if (s == -1) { Error(errno, "accept"); } // Configure 1s timeout timeval timeout_1s { 1, 0 }; int st SRT_ATR_UNUSED = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout_1s, sizeof timeout_1s); timeval re; socklen_t size = sizeof re; int st2 SRT_ATR_UNUSED = getsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&re, &size); LOGP(applog.Debug, "Setting SO_RCVTIMEO to @", m_socket, ": ", st == -1 ? "FAILED" : "SUCCEEDED", ", read-back value: ", st2 == -1 ? int64_t(-1) : (int64_t(re.tv_sec)*1000000 + re.tv_usec)/1000, "ms"); unique_ptr med(CreateAcceptor(this, sa, s, m_chunk)); Verb() << "accepted a connection from " << med->uri(); return med; } void SrtMedium::CreateCaller() { m_socket = srt_create_socket(); ConfigurePre(); // XXX setting up outgoing port not supported } void TcpMedium::CreateCaller() { m_socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); ConfigurePre(); } void SrtMedium::Connect() { sockaddr_any sa = CreateAddr(m_uri.host(), m_uri.portno()); int st = srt_connect(m_socket, sa.get(), sizeof sa); if (st == SRT_ERROR) Error(UDT::getlasterror(), "srt_connect"); ConfigurePost(m_socket); // Configure 1s timeout int timeout_1s = 1000; srt_setsockflag(m_socket, SRTO_RCVTIMEO, &timeout_1s, sizeof timeout_1s); } void TcpMedium::Connect() { sockaddr_any sa = CreateAddr(m_uri.host(), m_uri.portno()); int st = ::connect(m_socket, sa.get(), sa.size()); if (st == -1) Error(errno, "connect"); ConfigurePost(m_socket); // Configure 1s timeout timeval timeout_1s { 1, 0 }; setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout_1s, sizeof timeout_1s); } int SrtMedium::ReadInternal(char* w_buffer, int size) { int st = -1; do { st = srt_recv(m_socket, (w_buffer), size); if (st == SRT_ERROR) { int syserr; if (srt_getlasterror(&syserr) == SRT_EASYNCRCV && !m_broken) continue; } break; } while (true); return st; } int TcpMedium::ReadInternal(char* w_buffer, int size) { int st = -1; LOGP(applog.Debug, "TcpMedium:recv @", m_socket, " - begin"); do { st = ::recv(m_socket, (w_buffer), size, 0); if (st == -1) { if ((errno == EAGAIN || errno == EWOULDBLOCK)) { if (!m_broken) { LOGP(applog.Debug, "TcpMedium: read:AGAIN, repeating"); continue; } LOGP(applog.Debug, "TcpMedium: read:AGAIN, not repeating - already broken"); } else { LOGP(applog.Debug, "TcpMedium: read:ERROR: ", errno); } } break; } while (true); LOGP(applog.Debug, "TcpMedium:recv @", m_socket, " - result: ", st); return st; } bool SrtMedium::IsErrorAgain() { return srt_getlasterror(NULL) == SRT_EASYNCRCV; } bool TcpMedium::IsErrorAgain() { return errno == EAGAIN; } // The idea of Read function is to get the buffer that // possibly contains some data not written to the output yet, // but the time has come to read. We can't let the buffer expand // more than the size of the chunk, so if the buffer size already // exceeds it, don't return any data, but behave as if they were read. // This will cause the worker loop to redirect to Write immediately // thereafter and possibly will flush out the remains of the buffer. // It's still possible that the buffer won't be completely purged Medium::ReadStatus Medium::Read(bytevector& w_output) { // Don't read, but fake that you read if (w_output.size() > m_chunk) { Verb() << "BUFFER EXCEEDED"; return RD_DATA; } // Resize to maximum first size_t shift = w_output.size(); if (shift && m_eof) { // You have nonempty buffer, but eof was already // encountered. Report as if something was read. // // Don't read anything because this will surely // result in error since now. return RD_DATA; } size_t pred_size = shift + m_chunk; w_output.resize(pred_size); int st = ReadInternal((w_output.data() + shift), m_chunk); if (st == -1) { if (IsErrorAgain()) return RD_AGAIN; return RD_ERROR; } if (st == 0) { m_eof = true; if (shift) { // If there's 0 (eof), but you still have data // in the buffer, fake that they were read. Only // when the buffer was empty at entrance should this // result with EOF. // // Set back the size this buffer had before we attempted // to read into it. w_output.resize(shift); return RD_DATA; } w_output.clear(); return RD_EOF; } w_output.resize(shift+st); return RD_DATA; } void SrtMedium::Write(bytevector& w_buffer) { int st = srt_send(m_socket, w_buffer.data(), w_buffer.size()); if (st == SRT_ERROR) { Error(UDT::getlasterror(), "srt_send"); } // This should be ==, whereas > is not possible, but // this should simply embrace this case as a sanity check. if (st >= int(w_buffer.size())) w_buffer.clear(); else if (st == 0) { Error("Unexpected EOF on Write"); } else { // Remove only those bytes that were sent w_buffer.erase(w_buffer.begin(), w_buffer.begin()+st); } } void TcpMedium::Write(bytevector& w_buffer) { int st = ::send(m_socket, w_buffer.data(), w_buffer.size(), DEF_SEND_FLAG); if (st == -1) { Error(errno, "send"); } // This should be ==, whereas > is not possible, but // this should simply embrace this case as a sanity check. if (st >= int(w_buffer.size())) w_buffer.clear(); else if (st == 0) { Error("Unexpected EOF on Write"); } else { // Remove only those bytes that were sent w_buffer.erase(w_buffer.begin(), w_buffer.begin()+st); } } std::unique_ptr Medium::Create(const std::string& url, size_t chunk, Medium::Mode mode) { UriParser uri(url); std::unique_ptr out; // Might be something smarter, but there are only 2 types. if (uri.scheme() == "srt") { out.reset(new SrtMedium(uri, chunk)); } else if (uri.scheme() == "tcp") { out.reset(new TcpMedium(uri, chunk)); } else { Error("Medium not supported"); } out->InitMode(mode); return out; } struct Tunnelbox { list> tunnels; std::mutex access; condition_variable decom_ready; bool main_running = true; thread thr; void signal_decommission() { lock_guard lk(access); decom_ready.notify_one(); } void install(std::unique_ptr&& acp, std::unique_ptr&& clr) { lock_guard lk(access); Verb() << "Tunnelbox: Starting tunnel: " << acp->uri() << " <-> " << clr->uri(); tunnels.emplace_back(new Tunnel(this, move(acp), move(clr))); // Note: after this instruction, acp and clr are no longer valid! auto& it = tunnels.back(); it->Start(); } void start_cleaner() { thr = thread( [this]() { CleanupWorker(); } ); } void stop_cleaner() { if (thr.joinable()) thr.join(); } private: void CleanupWorker() { unique_lock lk(access); while (main_running) { decom_ready.wait(lk); // Got a signal, find a tunnel ready to cleanup. // We just get the signal, but we don't know which // tunnel has generated it. for (auto i = tunnels.begin(), i_next = i; i != tunnels.end(); i = i_next) { ++i_next; // Bound in one call the check if the tunnel is dead // and decommissioning because this must be done in // the one critical section - make sure no other thread // is accessing it at the same time and also make join all // threads that might have been accessing it. After // exiting as true (meaning that it was decommissioned // as expected) it can be safely deleted. if ((*i)->decommission_if_dead(main_running)) { tunnels.erase(i); } } } } }; void Tunnel::Stop() { // Check for running must be done without locking // because if the tunnel isn't running if (!running) return; // already stopped lock_guard lk(access); // Ok, you are the first to make the tunnel // not running and inform the tunnelbox. running = false; parent_box->signal_decommission(); } bool Tunnel::decommission_if_dead(bool forced) { lock_guard lk(access); if (running && !forced) return false; // working, not to be decommissioned // Join the engine threads, make sure nothing // is running that could use the data. acp_to_clr.Stop(); clr_to_acp.Stop(); // Done. The tunnelbox after calling this can // safely delete the decommissioned tunnel. return true; } int Medium::s_counter = 1; Tunnelbox g_tunnels; std::unique_ptr main_listener; size_t default_chunk = 4096; int OnINT_StopService(int) { g_tunnels.main_running = false; g_tunnels.signal_decommission(); // Will cause the Accept() block to exit. main_listener->Close(); return 0; } int main( int argc, char** argv ) { if (!SysInitializeNetwork()) { cerr << "Fail to initialize network module."; return 1; } size_t chunk = default_chunk; OptionName o_loglevel = { "ll", "loglevel" }, o_logfa = { "lf", "logfa" }, o_chunk = {"c", "chunk" }, o_verbose = {"v", "verbose" }, o_noflush = {"s", "skipflush" }; // Options that expect no arguments (ARG_NONE) need not be mentioned. vector optargs = { { o_loglevel, OptionScheme::ARG_ONE }, { o_logfa, OptionScheme::ARG_ONE }, { o_chunk, OptionScheme::ARG_ONE } }; options_t params = ProcessOptions(argv, argc, optargs); /* cerr << "OPTIONS (DEBUG)\n"; for (auto o: params) { cerr << "[" << o.first << "] "; copy(o.second.begin(), o.second.end(), ostream_iterator(cerr, " ")); cerr << endl; } */ vector args = params[""]; if ( args.size() < 2 ) { cerr << "Usage: " << argv[0] << " \n"; return 1; } string loglevel = Option(params, "error", o_loglevel); string logfa = Option(params, "", o_logfa); srt_logging::LogLevel::type lev = SrtParseLogLevel(loglevel); srt::setloglevel(lev); if (logfa == "") { srt::addlogfa(SRT_LOGFA_APP); } else { // Add only selected FAs set unknown_fas; set fas = SrtParseLogFA(logfa, &unknown_fas); srt::resetlogfa(fas); // The general parser doesn't recognize the "app" FA, we check it here. if (unknown_fas.count("app")) srt::addlogfa(SRT_LOGFA_APP); } string verbo = Option(params, "no", o_verbose); if ( verbo == "" || !false_names.count(verbo) ) { Verbose::on = true; Verbose::cverb = &std::cout; } string chunks = Option(params, "", o_chunk); if ( chunks!= "" ) { chunk = stoi(chunks); } string listen_node = args[0]; string call_node = args[1]; UriParser ul(listen_node), uc(call_node); // It is allowed to use both media of the same type, // but only srt and tcp are allowed. set allowed = {"srt", "tcp"}; if (!allowed.count(ul.scheme())|| !allowed.count(uc.scheme())) { cerr << "ERROR: only tcp and srt schemes supported"; return -1; } Verb() << "LISTEN type=" << ul.scheme() << ", CALL type=" << uc.scheme(); g_tunnels.start_cleaner(); main_listener = Medium::Create(listen_node, chunk, Medium::LISTENER); // The main program loop is only to catch // new connections and manage them. Also takes care // of the broken connections. for (;;) { try { Verb() << "Waiting for connection..."; std::unique_ptr accepted = main_listener->Accept(); if (!g_tunnels.main_running) { Verb() << "Service stopped. Exiting."; break; } Verb() << "Connection accepted. Connecting to the relay..."; // Now call the target address. std::unique_ptr caller = Medium::Create(call_node, chunk, Medium::CALLER); caller->Connect(); Verb() << "Connected. Establishing pipe."; // No exception, we are free to pass :) g_tunnels.install(move(accepted), move(caller)); } catch (...) { Verb() << "Connection reported, but failed"; } } g_tunnels.stop_cleaner(); return 0; } srt-1.4.4/apps/support.maf000066400000000000000000000012251412557703600155060ustar00rootroot00000000000000# IMPORTANT! # # This file contains information about ALL files existing in this directory # and belonging to the shared file between official applications # so that the build definition file can take them all to link against the app. # Applications in the 'apps' directory will use them all. # Appliecaions in the 'testing' directory may use some of them and they will # take selectively whichever parts they need. SOURCES apputil.cpp logsupport.cpp logsupport_appdefs.cpp socketoptions.cpp transmitmedia.cpp uriparser.cpp verbose.cpp PRIVATE HEADERS apputil.hpp logsupport.hpp socketoptions.hpp transmitbase.hpp transmitmedia.hpp uriparser.hpp verbose.hpp srt-1.4.4/apps/transmitbase.hpp000066400000000000000000000051631412557703600165170ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_COMMON_TRANMITBASE_HPP #define INC_SRT_COMMON_TRANMITBASE_HPP #include #include #include #include #include #include "srt.h" #include "uriparser.hpp" #include "apputil.hpp" typedef std::vector bytevector; extern bool transmit_total_stats; extern bool g_stats_are_printed_to_stdout; extern volatile bool transmit_throw_on_interrupt; extern unsigned long transmit_bw_report; extern unsigned long transmit_stats_report; extern unsigned long transmit_chunk_size; struct MediaPacket { bytevector payload; int64_t time = 0; MediaPacket(bytevector&& src) : payload(std::move(src)) {} MediaPacket(bytevector&& src, int64_t stime) : payload(std::move(src)), time(stime) {} MediaPacket(size_t payload_size) : payload(payload_size), time(0) {} MediaPacket(const bytevector& src) : payload(src) {} MediaPacket(const bytevector& src, int64_t stime) : payload(src), time(stime) {} MediaPacket() {} }; extern std::shared_ptr transmit_stats_writer; class Location { public: UriParser uri; Location() {} }; class Source: public Location { public: virtual int Read(size_t chunk, MediaPacket& pkt, std::ostream &out_stats = std::cout) = 0; virtual bool IsOpen() = 0; virtual bool End() = 0; static std::unique_ptr Create(const std::string& url); virtual void Close() {} virtual ~Source() {} class ReadEOF: public std::runtime_error { public: ReadEOF(const std::string& fn): std::runtime_error( "EOF while reading file: " + fn ) { } }; virtual SRTSOCKET GetSRTSocket() const { return SRT_INVALID_SOCK; }; virtual int GetSysSocket() const { return -1; }; virtual bool AcceptNewClient() { return false; } }; class Target: public Location { public: virtual int Write(const char* data, size_t size, int64_t src_time, std::ostream &out_stats = std::cout) = 0; virtual bool IsOpen() = 0; virtual bool Broken() = 0; virtual void Close() {} virtual size_t Still() { return 0; } static std::unique_ptr Create(const std::string& url); virtual ~Target() {} virtual SRTSOCKET GetSRTSocket() const { return SRT_INVALID_SOCK; } virtual int GetSysSocket() const { return -1; } virtual bool AcceptNewClient() { return false; } }; #endif srt-1.4.4/apps/transmitmedia.cpp000066400000000000000000000777411412557703600166720ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ // Just for formality. This file should be used #include #include #include #include #include #include #include #include #include #include #if !defined(_WIN32) #include #else #include #include #endif #if defined(SUNOS) #include #endif #include "netinet_any.h" #include "apputil.hpp" #include "socketoptions.hpp" #include "uriparser.hpp" #include "transmitmedia.hpp" #include "srt_compat.h" #include "verbose.hpp" using namespace std; bool g_stats_are_printed_to_stdout = false; bool transmit_total_stats = false; unsigned long transmit_bw_report = 0; unsigned long transmit_stats_report = 0; unsigned long transmit_chunk_size = SRT_LIVE_MAX_PLSIZE; class FileSource: public Source { ifstream ifile; string filename_copy; public: FileSource(const string& path): ifile(path, ios::in | ios::binary), filename_copy(path) { if ( !ifile ) throw std::runtime_error(path + ": Can't open file for reading"); } int Read(size_t chunk, MediaPacket& pkt, ostream & ignored SRT_ATR_UNUSED = cout) override { if (pkt.payload.size() < chunk) pkt.payload.resize(chunk); pkt.time = 0; ifile.read(pkt.payload.data(), chunk); size_t nread = ifile.gcount(); if (nread < pkt.payload.size()) pkt.payload.resize(nread); if (pkt.payload.empty()) { return 0; } return (int) nread; } bool IsOpen() override { return bool(ifile); } bool End() override { return ifile.eof(); } }; class FileTarget: public Target { ofstream ofile; public: FileTarget(const string& path): ofile(path, ios::out | ios::trunc | ios::binary) {} int Write(const char* data, size_t size, int64_t time SRT_ATR_UNUSED, ostream & ignored SRT_ATR_UNUSED = cout) override { ofile.write(data, size); return !(ofile.bad()) ? (int) size : 0; } bool IsOpen() override { return !!ofile; } bool Broken() override { return !ofile.good(); } //~FileTarget() { ofile.close(); } void Close() override { ofile.close(); } }; template struct File; template <> struct File { typedef FileSource type; }; template <> struct File { typedef FileTarget type; }; template Iface* CreateFile(const string& name) { return new typename File::type (name); } shared_ptr transmit_stats_writer; void SrtCommon::InitParameters(string host, map par) { // Application-specific options: mode, blocking, timeout, adapter if (Verbose::on && !par.empty()) { Verb() << "SRT parameters specified:\n"; for (map::iterator i = par.begin(); i != par.end(); ++i) { cerr << "\t" << i->first << " = '" << i->second << "'\n"; } } string adapter; if (par.count("adapter")) { adapter = par.at("adapter"); } m_mode = "default"; if (par.count("mode")) { m_mode = par.at("mode"); } SocketOption::Mode mode = SrtInterpretMode(m_mode, host, adapter); if (mode == SocketOption::FAILURE) { Error("Invalid mode"); } // Fix the mode name after successful interpretation m_mode = SocketOption::mode_names[mode]; par.erase("mode"); if (par.count("timeout")) { m_timeout = stoi(par.at("timeout"), 0, 0); par.erase("timeout"); } if (par.count("adapter")) { m_adapter = par.at("adapter"); par.erase("adapter"); } else if (m_mode == "listener") { // For listener mode, adapter is taken from host, // if 'adapter' parameter is not given m_adapter = host; } if (par.count("tsbpd") && false_names.count(par.at("tsbpd"))) { m_tsbpdmode = false; } if (par.count("port")) { m_outgoing_port = stoi(par.at("port"), 0, 0); par.erase("port"); } // That's kinda clumsy, but it must rely on the defaults. // Default mode is live, so check if the file mode was enforced if ((par.count("transtype") == 0 || par["transtype"] != "file") && transmit_chunk_size > SRT_LIVE_DEF_PLSIZE) { if (transmit_chunk_size > SRT_LIVE_MAX_PLSIZE) throw std::runtime_error("Chunk size in live mode exceeds 1456 bytes; this is not supported"); par["payloadsize"] = Sprint(transmit_chunk_size); } // Assign the others here. m_options = par; } void SrtCommon::PrepareListener(string host, int port, int backlog) { m_bindsock = srt_create_socket(); if ( m_bindsock == SRT_ERROR ) Error("srt_create_socket"); int stat = ConfigurePre(m_bindsock); if ( stat == SRT_ERROR ) Error("ConfigurePre"); sockaddr_any sa = CreateAddr(host, port); sockaddr* psa = sa.get(); Verb() << "Binding a server on " << host << ":" << port << " ..."; stat = srt_bind(m_bindsock, psa, sizeof sa); if ( stat == SRT_ERROR ) { srt_close(m_bindsock); Error("srt_bind"); } Verb() << " listen..."; stat = srt_listen(m_bindsock, backlog); if ( stat == SRT_ERROR ) { srt_close(m_bindsock); Error("srt_listen"); } } void SrtCommon::StealFrom(SrtCommon& src) { // This is used when SrtCommon class designates a listener // object that is doing Accept in appropriate direction class. // The new object should get the accepted socket. m_output_direction = src.m_output_direction; m_timeout = src.m_timeout; m_tsbpdmode = src.m_tsbpdmode; m_options = src.m_options; m_bindsock = SRT_INVALID_SOCK; // no listener m_sock = src.m_sock; src.m_sock = SRT_INVALID_SOCK; // STEALING } bool SrtCommon::AcceptNewClient() { sockaddr_any scl; Verb() << " accept... "; m_sock = srt_accept(m_bindsock, scl.get(), &scl.len); if ( m_sock == SRT_INVALID_SOCK ) { srt_close(m_bindsock); m_bindsock = SRT_INVALID_SOCK; Error("srt_accept"); } // we do one client connection at a time, // so close the listener. srt_close(m_bindsock); m_bindsock = SRT_INVALID_SOCK; Verb() << " connected."; // ConfigurePre is done on bindsock, so any possible Pre flags // are DERIVED by sock. ConfigurePost is done exclusively on sock. int stat = ConfigurePost(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePost"); return true; } void SrtCommon::Init(string host, int port, map par, bool dir_output) { m_output_direction = dir_output; InitParameters(host, par); Verb() << "Opening SRT " << (dir_output ? "target" : "source") << " " << m_mode << " on " << host << ":" << port; if ( m_mode == "caller" ) OpenClient(host, port); else if ( m_mode == "listener" ) OpenServer(m_adapter, port); else if ( m_mode == "rendezvous" ) OpenRendezvous(m_adapter, host, port); else { throw std::invalid_argument("Invalid 'mode'. Use 'client' or 'server'"); } } int SrtCommon::ConfigurePost(SRTSOCKET sock) { bool no = false; int result = 0; if ( m_output_direction ) { result = srt_setsockopt(sock, 0, SRTO_SNDSYN, &no, sizeof no); 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, &no, sizeof no); if ( result == -1 ) return result; if ( m_timeout ) return srt_setsockopt(sock, 0, SRTO_RCVTIMEO, &m_timeout, sizeof m_timeout); } SrtConfigurePost(sock, m_options); for (const 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 ( !ok ) Verb() << "WARNING: failed to set '" << o.name << "' (post, " << (m_output_direction? "target":"source") << ") to " << value; else Verb() << "NOTE: SRT/post::" << o.name << "=" << value; } } return 0; } int SrtCommon::ConfigurePre(SRTSOCKET sock) { int result = 0; bool no = false; if ( !m_tsbpdmode ) { result = srt_setsockopt(sock, 0, SRTO_TSBPDMODE, &no, sizeof no); if ( result == -1 ) return result; } result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &no, sizeof no); if ( result == -1 ) return result; // host is only checked for emptiness and depending on that the connection mode is selected. // Here we are not exactly interested with that information. vector failures; // NOTE: here host = "", so the 'connmode' will be returned as LISTENER always, // but it doesn't matter here. We don't use 'connmode' for anything else than // checking for failures. SocketOption::Mode conmode = SrtConfigurePre(sock, "", m_options, &failures); if ( conmode == SocketOption::FAILURE ) { if ( Verbose::on ) { cerr << "WARNING: failed to set options: "; copy(failures.begin(), failures.end(), ostream_iterator(cerr, ", ")); cerr << endl; } return SRT_ERROR; } return 0; } void SrtCommon::SetupAdapter(const string& host, int port) { sockaddr_any localsa = CreateAddr(host, port); sockaddr* psa = localsa.get(); int stat = srt_bind(m_sock, psa, sizeof localsa); if ( stat == SRT_ERROR ) Error("srt_bind"); } void SrtCommon::OpenClient(string host, int port) { PrepareClient(); if ( m_outgoing_port ) { SetupAdapter("", m_outgoing_port); } ConnectClient(host, port); } void SrtCommon::PrepareClient() { m_sock = srt_create_socket(); if ( m_sock == SRT_ERROR ) Error("srt_create_socket"); int stat = ConfigurePre(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePre"); } void SrtCommon::ConnectClient(string host, int port) { sockaddr_any sa = CreateAddr(host, port); sockaddr* psa = sa.get(); Verb() << "Connecting to " << host << ":" << port; int stat = srt_connect(m_sock, psa, sizeof sa); if ( stat == SRT_ERROR ) { srt_close(m_sock); Error("srt_connect"); } stat = ConfigurePost(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePost"); } void SrtCommon::Error(string src) { int errnov = 0; int result = srt_getlasterror(&errnov); string message = srt_getlasterror_str(); Verb() << "\nERROR #" << result << "." << errnov << ": " << message; throw TransmissionError("error: " + src + ": " + message); } void SrtCommon::OpenRendezvous(string adapter, string host, int port) { m_sock = srt_create_socket(); if ( m_sock == SRT_ERROR ) Error("srt_create_socket"); bool yes = true; srt_setsockopt(m_sock, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); int stat = ConfigurePre(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePre"); sockaddr_any sa = CreateAddr(host, port); if (sa.family() == AF_UNSPEC) { Error("OpenRendezvous: invalid target host specification: " + host); } const int outport = m_outgoing_port ? m_outgoing_port : port; sockaddr_any localsa = CreateAddr(adapter, outport, sa.family()); Verb() << "Binding a server on " << adapter << ":" << outport; stat = srt_bind(m_sock, localsa.get(), sizeof localsa); if ( stat == SRT_ERROR ) { srt_close(m_sock); Error("srt_bind"); } Verb() << "Connecting to " << host << ":" << port; stat = srt_connect(m_sock, sa.get(), sizeof sa); if ( stat == SRT_ERROR ) { srt_close(m_sock); Error("srt_connect"); } stat = ConfigurePost(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePost"); } void SrtCommon::Close() { Verb() << "SrtCommon: DESTROYING CONNECTION, closing sockets (rt%" << m_sock << " ls%" << m_bindsock << ")..."; if ( m_sock != SRT_INVALID_SOCK ) { srt_close(m_sock); m_sock = SRT_INVALID_SOCK; } if ( m_bindsock != SRT_INVALID_SOCK ) { srt_close(m_bindsock); m_bindsock = SRT_INVALID_SOCK ; } Verb() << "SrtCommon: ... done."; } SrtCommon::~SrtCommon() { Close(); } SrtSource::SrtSource(string host, int port, const map& par) { Init(host, port, par, false); ostringstream os; os << host << ":" << port; hostport_copy = os.str(); } int SrtSource::Read(size_t chunk, MediaPacket& pkt, ostream &out_stats) { static unsigned long counter = 1; if (pkt.payload.size() < chunk) pkt.payload.resize(chunk); SRT_MSGCTRL ctrl; const int stat = srt_recvmsg2(m_sock, pkt.payload.data(), (int) chunk, &ctrl); if (stat <= 0) { pkt.payload.clear(); return stat; } pkt.time = ctrl.srctime; chunk = size_t(stat); if (chunk < pkt.payload.size()) pkt.payload.resize(chunk); const bool need_bw_report = transmit_bw_report && (counter % transmit_bw_report) == transmit_bw_report - 1; const bool need_stats_report = transmit_stats_report && (counter % transmit_stats_report) == transmit_stats_report - 1; if (need_bw_report || need_stats_report) { CBytePerfMon perf; srt_bstats(m_sock, &perf, need_stats_report && !transmit_total_stats); if (transmit_stats_writer != nullptr) { if (need_bw_report) cerr << transmit_stats_writer->WriteBandwidth(perf.mbpsBandwidth) << std::flush; if (need_stats_report) out_stats << transmit_stats_writer->WriteStats(m_sock, perf) << std::flush; } } ++counter; return stat; } int SrtTarget::ConfigurePre(SRTSOCKET sock) { int result = SrtCommon::ConfigurePre(sock); if ( result == -1 ) return result; int yes = 1; // This is for the HSv4 compatibility; if both parties are HSv5 // (min. version 1.2.1), then this setting simply does nothing. // In HSv4 this setting is obligatory; otherwise the SRT handshake // extension will not be done at all. result = srt_setsockopt(sock, 0, SRTO_SENDER, &yes, sizeof yes); if ( result == -1 ) return result; return 0; } int SrtTarget::Write(const char* data, size_t size, int64_t src_time, ostream &out_stats) { static unsigned long counter = 1; SRT_MSGCTRL ctrl = srt_msgctrl_default; ctrl.srctime = src_time; int stat = srt_sendmsg2(m_sock, data, (int) size, &ctrl); if (stat == SRT_ERROR) { return stat; } const bool need_bw_report = transmit_bw_report && (counter % transmit_bw_report) == transmit_bw_report - 1; const bool need_stats_report = transmit_stats_report && (counter % transmit_stats_report) == transmit_stats_report - 1; if (need_bw_report || need_stats_report) { CBytePerfMon perf; srt_bstats(m_sock, &perf, need_stats_report && !transmit_total_stats); if (transmit_stats_writer != nullptr) { if (need_bw_report) cerr << transmit_stats_writer->WriteBandwidth(perf.mbpsBandwidth) << std::flush; if (need_stats_report) out_stats << transmit_stats_writer->WriteStats(m_sock, perf) << std::flush; } } ++counter; return stat; } SrtModel::SrtModel(string host, int port, map par) { InitParameters(host, par); if (m_mode == "caller") is_caller = true; else if (m_mode != "listener") throw std::invalid_argument("Only caller and listener modes supported"); m_host = host; m_port = port; } void SrtModel::Establish(std::string& w_name) { // This does connect or accept. // When this returned true, the caller should create // a new SrtSource or SrtTaget then call StealFrom(*this) on it. // If this is a connector and the peer doesn't have a corresponding // medium, it should send back a single byte with value 0. This means // that agent should stop connecting. if (is_caller) { // Establish a connection PrepareClient(); if (w_name != "") { Verb() << "Connect with requesting stream [" << w_name << "]"; srt::setstreamid(m_sock, w_name); } else { Verb() << "NO STREAM ID for SRT connection"; } if (m_outgoing_port) { Verb() << "Setting outgoing port: " << m_outgoing_port; SetupAdapter("", m_outgoing_port); } ConnectClient(m_host, m_port); if (m_outgoing_port == 0) { // Must rely on a randomly selected one. Extract the port // so that it will be reused next time. sockaddr_any s(AF_INET); int namelen = s.size(); if ( srt_getsockname(Socket(), s.get(), &namelen) == SRT_ERROR ) { Error("srt_getsockname"); } m_outgoing_port = s.hport(); Verb() << "Extracted outgoing port: " << m_outgoing_port; } } else { // Listener - get a socket by accepting. // Check if the listener is already created first if (Listener() == SRT_INVALID_SOCK) { Verb() << "Setting up listener: port=" << m_port << " backlog=5"; PrepareListener(m_adapter, m_port, 5); } Verb() << "Accepting a client..."; AcceptNewClient(); // This rewrites m_sock with a new SRT socket ("accepted" socket) w_name = srt::getstreamid(m_sock); Verb() << "... GOT CLIENT for stream [" << w_name << "]"; } } 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() { #ifdef _WIN32 // The default stdin mode on windows is text. // We have to set it to the binary mode _setmode(_fileno(stdin), _O_BINARY); #endif } int Read(size_t chunk, MediaPacket& pkt, ostream & ignored SRT_ATR_UNUSED = cout) override { if (pkt.payload.size() < chunk) pkt.payload.resize(chunk); bool st = cin.read(pkt.payload.data(), chunk).good(); chunk = cin.gcount(); if (chunk == 0 || !st) { pkt.payload.clear(); return 0; } // Save this time to potentially use it for SRT target. pkt.time = srt_time_now(); if (chunk < pkt.payload.size()) pkt.payload.resize(chunk); return (int) chunk; } bool IsOpen() override { return cin.good(); } bool End() override { return cin.eof(); } int GetSysSocket() const override { return 0; }; }; class ConsoleTarget: public Target { public: ConsoleTarget() { #ifdef _WIN32 // The default stdout mode on windows is text. // We have to set it to the binary mode _setmode(_fileno(stdout), _O_BINARY); #endif } virtual ~ConsoleTarget() { cout.flush(); } int Write(const char* data, size_t len, int64_t src_time SRT_ATR_UNUSED, ostream & ignored SRT_ATR_UNUSED = cout) override { cout.write(data, len); return (int) len; } bool IsOpen() override { return cout.good(); } bool Broken() override { return cout.eof(); } int GetSysSocket() const override { return 0; }; }; 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 [] { { "iptos", IPPROTO_IP, IP_TOS, SocketOption::PRE, SocketOption::INT, nullptr }, // IP_TTL and IP_MULTICAST_TTL are handled separately by a common option, "ttl". { "mcloop", IPPROTO_IP, IP_MULTICAST_LOOP, SocketOption::PRE, SocketOption::INT, nullptr }, { "sndbuf", SOL_SOCKET, SO_SNDBUF, SocketOption::PRE, SocketOption::INT, nullptr}, { "rcvbuf", SOL_SOCKET, SO_RCVBUF, SocketOption::PRE, SocketOption::INT, nullptr} }; static inline bool IsMulticast(in_addr adr) { unsigned char* abytes = (unsigned char*)&adr.s_addr; unsigned char c = abytes[0]; return c >= 224 && c <= 239; } class UdpCommon { protected: int m_sock = -1; sockaddr_any sadr; string adapter; map m_options; void Setup(string host, int port, map attr) { m_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (m_sock == -1) Error(SysError(), "UdpCommon::Setup: socket"); int yes = 1; ::setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof yes); // set non-blocking mode #if defined(_WIN32) unsigned long ulyes = 1; if (ioctlsocket(m_sock, FIONBIO, &ulyes) == SOCKET_ERROR) #else if (ioctl(m_sock, FIONBIO, (const char *)&yes) < 0) #endif { Error(SysError(), "UdpCommon::Setup: ioctl FIONBIO"); } sadr = CreateAddr(host, port); bool is_multicast = false; if (attr.count("multicast")) { // XXX: Here provide support for IPv6 multicast #1479 if (sadr.family() != AF_INET) { throw std::runtime_error("UdpCommon: Multicast on IPv6 is not yet supported"); } if (!IsMulticast(sadr.sin.sin_addr)) { throw std::runtime_error("UdpCommon: requested multicast for a non-multicast-type IP address"); } is_multicast = true; } else if (sadr.family() == AF_INET && IsMulticast(sadr.sin.sin_addr)) { is_multicast = true; } if (is_multicast) { ip_mreq_source mreq_ssm; ip_mreq mreq; sockaddr_any maddr (AF_INET); int opt_name; void* mreq_arg_ptr; socklen_t mreq_arg_size; adapter = attr.count("adapter") ? attr.at("adapter") : string(); if ( adapter == "" ) { Verb() << "Multicast: home address: INADDR_ANY:" << port; maddr.sin.sin_family = AF_INET; maddr.sin.sin_addr.s_addr = htonl(INADDR_ANY); maddr.sin.sin_port = htons(port); // necessary for temporary use } else { Verb() << "Multicast: home address: " << adapter << ":" << port; maddr = CreateAddr(adapter, port); } if (attr.count("source")) { #ifdef IP_ADD_SOURCE_MEMBERSHIP /* this is an ssm. we need to use the right struct and opt */ opt_name = IP_ADD_SOURCE_MEMBERSHIP; mreq_ssm.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; mreq_ssm.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; inet_pton(AF_INET, attr.at("source").c_str(), &mreq_ssm.imr_sourceaddr); mreq_arg_size = sizeof(mreq_ssm); mreq_arg_ptr = &mreq_ssm; #else throw std::runtime_error("UdpCommon: source-filter multicast not supported by OS"); #endif } else { opt_name = IP_ADD_MEMBERSHIP; mreq.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; mreq.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; mreq_arg_size = sizeof(mreq); mreq_arg_ptr = &mreq; } #ifdef _WIN32 const char* mreq_arg = (const char*)mreq_arg_ptr; const auto status_error = SOCKET_ERROR; #else const void* mreq_arg = mreq_arg_ptr; const auto status_error = -1; #endif #if defined(_WIN32) || defined(__CYGWIN__) // On Windows it somehow doesn't work when bind() // is called with multicast address. Write the address // that designates the network device here. // Also, sets port sharing when working with multicast sadr = maddr; int reuse = 1; int shareAddrRes = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuse), sizeof(reuse)); if (shareAddrRes == status_error) { throw runtime_error("marking socket for shared use failed"); } Verb() << "Multicast(Windows): will bind to home address"; #else Verb() << "Multicast(POSIX): will bind to IGMP address: " << host; #endif int res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg, mreq_arg_size); if ( res == status_error ) { Error(errno, "adding to multicast membership failed"); } attr.erase("multicast"); attr.erase("adapter"); } // The "ttl" options is handled separately, it maps to both IP_TTL // and IP_MULTICAST_TTL so that TTL setting works for both uni- and multicast. if (attr.count("ttl")) { int ttl = stoi(attr.at("ttl")); int res = setsockopt(m_sock, IPPROTO_IP, IP_TTL, (const char*)&ttl, sizeof ttl); if (res == -1) Verb() << "WARNING: failed to set 'ttl' (IP_TTL) to " << ttl; res = setsockopt(m_sock, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof ttl); if (res == -1) Verb() << "WARNING: failed to set 'ttl' (IP_MULTICAST_TTL) to " << ttl; attr.erase("ttl"); } 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 ( !ok ) Verb() << "WARNING: failed to set '" << o.name << "' to " << value; } } } void Error(int err, string src) { char buf[512]; string message = SysStrError(err, buf, 512u); cerr << "\nERROR #" << err << ": " << message << endl; throw TransmissionError("error: " + src + ": " + message); } ~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, sadr.get(), sadr.size()); if ( stat == -1 ) Error(SysError(), "Binding address for UDP"); eof = false; } int Read(size_t chunk, MediaPacket& pkt, ostream & ignored SRT_ATR_UNUSED = cout) override { if (pkt.payload.size() < chunk) pkt.payload.resize(chunk); sockaddr_any sa(sadr.family()); socklen_t si = sa.size(); int stat = recvfrom(m_sock, pkt.payload.data(), (int) chunk, 0, sa.get(), &si); if (stat < 1) { if (SysError() != EWOULDBLOCK) eof = true; pkt.payload.clear(); return stat; } sa.len = si; // Save this time to potentially use it for SRT target. pkt.time = srt_time_now(); chunk = size_t(stat); if (chunk < pkt.payload.size()) pkt.payload.resize(chunk); return stat; } bool IsOpen() override { return m_sock != -1; } bool End() override { return eof; } int GetSysSocket() const override { return m_sock; }; }; class UdpTarget: public Target, public UdpCommon { public: UdpTarget(string host, int port, const map& attr ) { if (host.empty()) cerr << "\nWARN Host for UDP target is not provided. Will send to localhost:" << port << ".\n"; Setup(host, port, attr); if (adapter != "") { sockaddr_any maddr = CreateAddr(adapter, 0); if (maddr.family() != AF_INET) { Error(0, "UDP/target: IPv6 multicast not supported in the application"); } in_addr addr = maddr.sin.sin_addr; int res = setsockopt(m_sock, IPPROTO_IP, IP_MULTICAST_IF, reinterpret_cast(&addr), sizeof(addr)); if (res == -1) { Error(SysError(), "setsockopt/IP_MULTICAST_IF: " + adapter); } } } int Write(const char* data, size_t len, int64_t src_time SRT_ATR_UNUSED, ostream & ignored SRT_ATR_UNUSED = cout) override { int stat = sendto(m_sock, data, (int) len, 0, sadr.get(), sadr.size()); if ( stat == -1 ) { if ((false)) Error(SysError(), "UDP Write/sendto"); return stat; } return stat; } bool IsOpen() override { return m_sock != -1; } bool Broken() override { return false; } int GetSysSocket() const override { return m_sock; }; }; template struct Udp; template <> struct Udp { typedef UdpSource type; }; template <> struct Udp { typedef UdpTarget type; }; template Iface* CreateUdp(const string& host, int port, const map& par) { return new typename Udp::type (host, port, par); } template inline bool IsOutput() { return false; } template<> inline bool IsOutput() { return true; } template extern unique_ptr CreateMedium(const string& uri) { unique_ptr ptr; UriParser u(uri); int iport = 0; switch ( u.type() ) { default: break; // do nothing, return nullptr case UriParser::FILE: if (u.host() == "con" || u.host() == "console") { if (IsOutput() && ( (Verbose::on && Verbose::cverb == &cout) || g_stats_are_printed_to_stdout)) { cerr << "ERROR: file://con with -v or -r or -s 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()); } // Disable regular file support for the moment #if 0 else ptr.reset( CreateFile(u.path())); #endif break; case UriParser::SRT: iport = atoi(u.port().c_str()); if ( iport < 1024 ) { cerr << "Port value invalid: " << iport << " - must be >=1024\n"; throw invalid_argument("Invalid port number"); } ptr.reset( CreateSrt(u.host(), iport, u.parameters()) ); break; case UriParser::UDP: iport = atoi(u.port().c_str()); if ( iport < 1024 ) { cerr << "Port value invalid: " << iport << " - must be >=1024\n"; throw invalid_argument("Invalid port number"); } ptr.reset( CreateUdp(u.host(), iport, u.parameters()) ); break; } if (ptr.get()) ptr->uri = move(u); return ptr; } std::unique_ptr Source::Create(const std::string& url) { return CreateMedium(url); } std::unique_ptr Target::Create(const std::string& url) { return CreateMedium(url); } srt-1.4.4/apps/transmitmedia.hpp000066400000000000000000000125371412557703600166670ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_COMMON_TRANSMITMEDIA_HPP #define INC_SRT_COMMON_TRANSMITMEDIA_HPP #include #include #include #include "transmitbase.hpp" #include // Needs access to CUDTException using namespace std; // Trial version of an exception. Try to implement later an official // interruption mechanism in SRT using this. struct TransmissionError: public std::runtime_error { TransmissionError(const std::string& arg): std::runtime_error(arg) { } }; class SrtCommon { protected: bool m_output_direction = false; //< Defines which of SND or RCV option variant should be used, also to set SRT_SENDER for output int m_timeout = 0; //< enforces using SRTO_SNDTIMEO or SRTO_RCVTIMEO, depending on @a m_output_direction bool m_tsbpdmode = true; int m_outgoing_port = 0; string m_mode; string m_adapter; map m_options; // All other options, as provided in the URI SRTSOCKET m_sock = SRT_INVALID_SOCK; SRTSOCKET m_bindsock = SRT_INVALID_SOCK; bool IsUsable() { SRT_SOCKSTATUS st = srt_getsockstate(m_sock); return st > SRTS_INIT && st < SRTS_BROKEN; } bool IsBroken() { return srt_getsockstate(m_sock) > SRTS_CONNECTED; } public: void InitParameters(string host, map par); void PrepareListener(string host, int port, int backlog); void StealFrom(SrtCommon& src); bool AcceptNewClient(); SRTSOCKET Socket() const { return m_sock; } SRTSOCKET Listener() const { return m_bindsock; } void Close(); protected: void Error(string src); void Init(string host, int port, map par, bool dir_output); virtual int ConfigurePost(SRTSOCKET sock); virtual int ConfigurePre(SRTSOCKET sock); void OpenClient(string host, int port); void PrepareClient(); void SetupAdapter(const std::string& host, int port); void ConnectClient(string host, int port); void OpenServer(string host, int port) { PrepareListener(host, port, 1); } void OpenRendezvous(string adapter, string host, int port); virtual ~SrtCommon(); }; class SrtSource: public Source, public SrtCommon { std::string hostport_copy; public: SrtSource(std::string host, int port, const std::map& par); SrtSource() { // Do nothing - create just to prepare for use } int Read(size_t chunk, MediaPacket& pkt, ostream& out_stats = cout) override; /* In this form this isn't needed. Unblock if any extra settings have to be made. virtual int ConfigurePre(UDTSOCKET sock) override { int result = SrtCommon::ConfigurePre(sock); if ( result == -1 ) return result; return 0; } */ bool IsOpen() override { return IsUsable(); } bool End() override { return IsBroken(); } SRTSOCKET GetSRTSocket() const override { SRTSOCKET socket = SrtCommon::Socket(); if (socket == SRT_INVALID_SOCK) socket = SrtCommon::Listener(); return socket; } bool AcceptNewClient() override { return SrtCommon::AcceptNewClient(); } }; class SrtTarget: public Target, public SrtCommon { public: SrtTarget(std::string host, int port, const std::map& par) { Init(host, port, par, true); } SrtTarget() {} int ConfigurePre(SRTSOCKET sock) override; int Write(const char* data, size_t size, int64_t src_time, ostream &out_stats = cout) override; bool IsOpen() override { return IsUsable(); } bool Broken() override { return IsBroken(); } size_t Still() override { size_t bytes; int st = srt_getsndbuffer(m_sock, nullptr, &bytes); if (st == -1) return 0; return bytes; } SRTSOCKET GetSRTSocket() const override { SRTSOCKET socket = SrtCommon::Socket(); if (socket == SRT_INVALID_SOCK) socket = SrtCommon::Listener(); return socket; } bool AcceptNewClient() override { return SrtCommon::AcceptNewClient(); } }; // This class is used when we don't know yet whether the given URI // designates an effective listener or caller. So we create it, initialize, // then we know what mode we'll be using. // // When caller, then we will do connect() using this object, then clone out // a new object - of a direction specific class - which will steal the socket // from this one and then roll the data. After this, this object is ready // to connect again, and will create its own socket for that occasion, and // the whole procedure repeats. // // When listener, then this object will be doing accept() and with every // successful acceptation it will clone out a new object - of a direction // specific class - which will steal just the connection socket from this // object. This object will still live on and accept new connections and // so on. class SrtModel: public SrtCommon { public: bool is_caller = false; string m_host; int m_port = 0; SrtModel(string host, int port, map par); void Establish(std::string& name); }; #endif srt-1.4.4/apps/uriparser.cpp000066400000000000000000000215041412557703600160270ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ // STL includes #include #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["udp"] = UriParser::UDP; types["tcp"] = UriParser::TCP; types["srt"] = UriParser::SRT; types["rtmp"] = UriParser::RTMP; types["http"] = UriParser::HTTP; types["rtp"] = UriParser::RTP; types[""] = UriParser::UNKNOWN; } } g_uriparser_init; UriParser::UriParser(const string& strUrl, DefaultExpect exp) { m_expect = exp; Parse(strUrl, exp); } UriParser::~UriParser(void) { } string UriParser::makeUri() { // Reassemble parts into the URI string prefix = ""; if (m_proto != "") { prefix = m_proto + "://"; } std::ostringstream out; out << prefix << m_host; if ((m_port == "" || m_port == "0") && m_expect == EXPECT_FILE) { // Do not add port } else { out << ":" << m_port; } if (m_path != "") { if (m_path[0] != '/') out << "/"; out << m_path; } if (!m_mapQuery.empty()) { out << "?"; query_it i = m_mapQuery.begin(); for (;;) { out << i->first << "=" << i->second; ++i; if (i == m_mapQuery.end()) break; out << "&"; } } m_origUri = out.str(); return m_origUri; } string UriParser::proto(void) const { return m_proto; } UriParser::Type UriParser::type() const { return m_uriType; } string UriParser::host(void) const { return m_host; } string UriParser::port(void) const { return m_port; } unsigned short int UriParser::portno(void) const { // 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) const { return m_path; } string UriParser::queryValue(const string& strKey) const { return m_mapQuery.at(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); transform(m_proto.begin(), m_proto.end(), m_proto.begin(), [](char c){ return tolower(c); }); m_host = m_host.substr(idx + 3, m_host.size() - (idx + 3)); } // Handle the IPv6 specification in square brackets. // This actually handles anything specified in [] so potentially // you can also specify the usual hostname here as well. If the // whole host results to have [] at edge positions, they are stripped, // otherwise they remain. In both cases the search for the colon // separating the port specification starts only after ]. const size_t i6pos = m_host.find("["); size_t i6end = string::npos; // Search for the "path" part only behind the closed bracket, // if both open and close brackets were found size_t path_since = 0; if (i6pos != string::npos) { i6end = m_host.find("]", i6pos); if (i6end != string::npos) path_since = i6end; } idx = m_host.find("/", path_since); 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; } bool stripbrackets = false; size_t hostend = 0; if (i6pos != string::npos) { // IPv6 IP address. Find the terminating ] hostend = m_host.find("]", i6pos); idx = m_host.rfind(":"); if (hostend != string::npos) { // Found the end. But not necessarily it was // at the beginning. If it was at the beginning, // strip them from the host name. size_t lasthost = idx; if (idx != string::npos && idx < hostend) { idx = string::npos; lasthost = m_host.size(); } if (i6pos == 0 && hostend == lasthost - 1) { stripbrackets = true; } } } else { idx = m_host.rfind(":"); } if (idx != string::npos) { m_port = m_host.substr(idx + 1, m_host.size() - (idx + 1)); // Extract host WITHOUT stripping brackets m_host = m_host.substr(0, idx); } if (stripbrackets) { if (!hostend) hostend = m_host.size() - 1; m_host = m_host.substr(1, hostend - 1); } 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) m_origUri = strUrl; } #ifdef TEST #include using namespace std; int main( int argc, char** argv ) { if ( argc < 2 ) { return 0; } UriParser parser (argv[1], UriParser::EXPECT_HOST); std::vector args; if (argc > 2) { copy(argv+2, argv+argc, back_inserter(args)); } (void)argc; cout << "PARSING URL: " << argv[1] << endl; cout << "SCHEME INDEX: " << int(parser.type()) << endl; cout << "PROTOCOL: " << parser.proto() << endl; cout << "HOST: " << parser.host() << endl; cout << "PORT (string): " << parser.port() << endl; cout << "PORT (numeric): " << parser.portno() << endl; cout << "PATH: " << parser.path() << endl; cout << "PARAMETERS:\n"; for (auto& p: parser.parameters()) { cout << "\t" << p.first << " = " << p.second << endl; } if (!args.empty()) { for (string& s: args) { vector keyval; Split(s, '=', back_inserter(keyval)); if (keyval.size() < 2) keyval.push_back(""); parser[keyval[0]] = keyval[1]; } cout << "REASSEMBLED: " << parser.makeUri() << endl; } return 0; } #endif srt-1.4.4/apps/uriparser.hpp000066400000000000000000000037141412557703600160370ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_URL_PARSER_H #define INC_SRT_URL_PARSER_H #include #include #include #include "utilities.h" //++ // UriParser //-- class UriParser { // Construction public: enum DefaultExpect { EXPECT_FILE, EXPECT_HOST }; enum Type { UNKNOWN, FILE, UDP, TCP, SRT, RTMP, HTTP, RTP }; UriParser(const std::string& strUrl, DefaultExpect exp = EXPECT_FILE); UriParser(): m_uriType(UNKNOWN) {} virtual ~UriParser(void); // Some predefined types Type type() const; typedef MapProxy ParamProxy; // Operations public: std::string uri() const { return m_origUri; } std::string proto() const; std::string scheme() const { return proto(); } std::string host() const; std::string port() const; unsigned short int portno() const; std::string hostport() const { return host() + ":" + port(); } std::string path() const; std::string queryValue(const std::string& strKey) const; std::string makeUri(); ParamProxy operator[](const std::string& key) { return ParamProxy(m_mapQuery, key); } const std::map& parameters() const { return m_mapQuery; } typedef std::map::const_iterator query_it; 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; DefaultExpect m_expect; std::map m_mapQuery; }; //#define TEST1 1 #endif // INC_SRT_URL_PARSER_H srt-1.4.4/apps/verbose.cpp000066400000000000000000000041351412557703600154610ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include "verbose.hpp" namespace Verbose { bool on = false; std::ostream* cverb = &std::cerr; #if SRT_ENABLE_VERBOSE_LOCK std::mutex vlock; #endif Log& Log::operator<<(LogNoEol) { noeol = true; if (on) { (*cverb) << std::flush; } return *this; } #if SRT_ENABLE_VERBOSE_LOCK Log& Log::operator<<(LogLock) { lockline = true; return *this; } #endif Log::~Log() { if (on && !noeol) { #if SRT_ENABLE_VERBOSE_LOCK if (lockline) { // Lock explicitly, as requested, and wait for the opportunity. vlock.lock(); } else if (vlock.try_lock()) { // Successfully locked, so unlock immediately, locking wasn't requeted. vlock.unlock(); } else { // Failed to lock, which means that some other thread has locked it first. // This means that some other thread wants to print the whole line and doesn't // want to be disturbed during this process. Lock the thread then as this is // the only way to wait until it's unlocked. However, do not block your printing // with locking, because you were not requested to lock (treat this mutex as // an entry semaphore, which may only occasionally block the whole line). vlock.lock(); vlock.unlock(); } #endif (*cverb) << std::endl; #if SRT_ENABLE_VERBOSE_LOCK // If lockline is set, the lock was requested and WAS DONE, so unlock. // Otherwise locking WAS NOT DONE. if (lockline) vlock.unlock(); #endif } } } srt-1.4.4/apps/verbose.hpp000066400000000000000000000037561412557703600154760ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_VERBOSE_HPP #define INC_SRT_VERBOSE_HPP #include #if SRT_ENABLE_VERBOSE_LOCK #include #endif namespace Verbose { extern bool on; extern std::ostream* cverb; struct LogNoEol { LogNoEol() {} }; #if SRT_ENABLE_VERBOSE_LOCK struct LogLock { LogLock() {} }; #endif class Log { bool noeol = false; #if SRT_ENABLE_VERBOSE_LOCK bool lockline = false; #endif // Disallow creating dynamic objects void* operator new(size_t); public: template Log& operator<<(const V& arg) { // Template - must be here; extern template requires // predefined specializations. if (on) (*cverb) << arg; return *this; } Log& operator<<(LogNoEol); #if SRT_ENABLE_VERBOSE_LOCK Log& operator<<(LogLock); #endif ~Log(); }; class ErrLog: public Log { public: template ErrLog& operator<<(const V& arg) { // Template - must be here; extern template requires // predefined specializations. if (on) (*cverb) << arg; else std::cerr << arg; return *this; } }; // terminal inline void Print(Log& ) {} template inline void Print(Log& out, Arg1&& arg1, Args&&... args) { out << arg1; Print(out, args...); } } inline Verbose::Log Verb() { return Verbose::Log(); } inline Verbose::ErrLog Verror() { return Verbose::ErrLog(); } template inline void Verb(Args&&... args) { Verbose::Log log; Verbose::Print(log, args...); } // Manipulator tags static const Verbose::LogNoEol VerbNoEOL; #if SRT_ENABLE_VERBOSE_LOCK static const Verbose::LogLock VerbLock; #endif #endif srt-1.4.4/cmake_object_lib_support.c000066400000000000000000000007351412557703600175430ustar00rootroot00000000000000// DO NOT DELETE // This file is needed for Xcode to properly handle CMake OBJECT Libraries // From docs (https://cmake.org/cmake/help/latest/command/add_library.html#object-libraries): // // ... Some native build systems (such as Xcode) may not like targets that have only object files, // so consider adding at least one real source file to any target that references $. // Just a dummy symbol to avoid compiler warnings int srt_object_lib_dummy = 0; srt-1.4.4/codecov.yml000066400000000000000000000002001412557703600144770ustar00rootroot00000000000000coverage: status: project: default: target: 40% threshold: null patch: false changes: false srt-1.4.4/common/000077500000000000000000000000001412557703600136325ustar00rootroot00000000000000srt-1.4.4/common/filelist_win32.maf000066400000000000000000000002341412557703600171530ustar00rootroot00000000000000 PUBLIC HEADERS win/syslog_defs.h # # These are included by platform_sys.h header contained in ../srtcore/filelist.maf # win/unistd.h SOURCES win_time.cpp srt-1.4.4/common/win/000077500000000000000000000000001412557703600144275ustar00rootroot00000000000000srt-1.4.4/common/win/syslog_defs.h000066400000000000000000000023001412557703600171140ustar00rootroot00000000000000#ifndef INC_SRT_WINDOWS_SYSLOG_DEFS_H #define INC_SRT_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 srt-1.4.4/common/win/unistd.h000066400000000000000000000032321412557703600161060ustar00rootroot00000000000000#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 */ srt-1.4.4/common/win/wintime.h000066400000000000000000000026431412557703600162610ustar00rootroot00000000000000#ifndef INC_SRT_WIN_WINTIME #define INC_SRT_WIN_WINTIME #include #include // HACK: This include is a workaround for a bug in the MinGW headers // where pthread.h, which defines _POSIX_THREAD_SAFE_FUNCTIONS, // has to be included before time.h so that time.h defines // localtime_r correctly #include #ifdef __cplusplus extern "C" { #endif #if !defined(_MSC_VER) #define SRTCOMPAT_WINTIME_STATIC_INLINE_DECL static inline #else // NOTE: MVC Does not like static inline for C functions in some versions. // so just use static for MVC. #define SRTCOMPAT_WINTIME_STATIC_INLINE_DECL static #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 SRTCompat_timeradd( struct timeval *a, struct timeval *b, struct timeval *result); SRTCOMPAT_WINTIME_STATIC_INLINE_DECL void timeradd( struct timeval *a, struct timeval *b, struct timeval *result) { SRTCompat_timeradd(a, b, result); } int SRTCompat_gettimeofday( struct timeval* tp, struct timezone* tz); SRTCOMPAT_WINTIME_STATIC_INLINE_DECL int gettimeofday( struct timeval* tp, struct timezone* tz) { return SRTCompat_gettimeofday(tp, tz); } #undef SRTCOMPAT_WINTIME_STATIC_INLINE_DECL #ifdef __cplusplus } #endif #endif // INC_SRT_WIN_WINTIME srt-1.4.4/common/win_time.cpp000066400000000000000000000020031412557703600161440ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #include "win/wintime.h" #include void SRTCompat_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 SRTCompat_gettimeofday(struct timeval* tp, struct timezone*) { struct timeb tb; ftime(&tb); tp->tv_sec = (long)tb.time; tp->tv_usec = 1000*tb.millitm; return 0; } srt-1.4.4/configure000077500000000000000000000145041412557703600142550ustar00rootroot00000000000000#!/usr/bin/tclsh # # SRT - Secure, Reliable, Transport # Copyright (c) 2018 Haivision Systems Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # This is a general-purpose configure script, which is a user-friendly # wrapper to call the "cmake" command. # There are two options that are handled specifically: # # --help: show the list of official options # --prefix: alias to --cmake-install-prefix # The processing done automatically on all options by default is: # Every option like: # --long-c++-option # --cmake-special-option=ON # Turns into: # -DLONG_CXX_OPTION=1 # -DCMAKE_SPECIAL_OPTION=ON # # In the configuration file, "configure-data.tcl", you can add # special processing for options and define explicit options # in the "::options" dictionary. Explicit options (in contrast # to "blind" options) have additional properties: # # - only those options are mentioned with --help # - you can pass a value for this option without = character # - you can specify --disable-option instead of --enable-option=0 # # In "configure-data.tcl", beside ::options, you can define "preprocess" and # "postprocess" procedures. In "preprocess", use ::optval array to modify the # list of options to be processed further. Additionally in "postprocess" # procedure you can influence directly the options for "cmake" command in # ::cmakeopt variable (modifying ::optval in "postprocess" is useless). # 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] } # Check if a --disable option has its --enable counterpart. If so, # then just invert the option. proc resolve_disablers {} { set enablers "" set optkeys_len [llength $::optkeys] for {set pos 0} {$pos < $optkeys_len} {incr pos} { set opt [lindex $::optkeys $pos] if { [string match --disable-* $opt] } { set inverted enable-[string range $opt 10 end] if { $inverted in [dict keys $::options] } { lset ::optkeys $pos --$inverted set val $::optval($opt) unset ::optval($opt) if { $val == "" || ![string is boolean $val] } { set ::optval(--$inverted) 0 } else { set ::optval(--$inverted) [expr {!$val}] } puts "NOTE: $opt changed into --$inverted=$::optval(--$inverted)" } } } } foreach {o desc} $options { lassign [resolve $o] optname optvar opttype optmark set opt($optname) [list $optvar $opttype $optmark] set info($optname) $desc } if { $argv == "--help" || $argv == "-h" } { 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)" } puts stderr "NOTE1: Option list may be incomplete. Refer to variables in CMakeLists.txt" puts stderr "NOTE2: Non-internal options turn e.g. --enable-c++11 into cmake -DENABLE_CXX11=1" puts stderr "NOTE3: You can use --disable-x instead of --enable-x=0 for the above options." 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 { [set a1 [string first = $a]] != -1 } { # Do not split. Options may include =. set val [string range $a $a1+1 end] set a [string range $a 0 $a1-1] } 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" } # Save the original call into config-status.sh set ofd [open config-status.sh w] puts $ofd "#!/bin/bash" puts -nonewline $ofd "$argv0 " foreach a $argv { set len 1 if {[catch {llength $a} len] || $len > 1 } { puts -nonewline $ofd "'$a' " } else { puts -nonewline $ofd "$a " } } puts $ofd "" close $ofd file attributes config-status.sh -permissions +x set cmakeopt "" resolve_disablers 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)" } srt-1.4.4/configure-data.tcl000066400000000000000000000305651412557703600157470ustar00rootroot00000000000000# # SRT - Secure, Reliable, Transport # Copyright (c) 2018 Haivision Systems Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # 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 # Options processed here internally, not passed to cmake set internal_options { with-compiler-prefix= "set C/C++ toolchains gcc and g++" with-compiler-type= "compiler type: gcc(default), cc, others simply add ++ for C++" with-srt-name= "Override srt library name" with-haicrypt-name= "Override haicrypt library name (if compiled separately)" } # Options that refer directly to variables used in CMakeLists.txt set cmake_options { cygwin-use-posix "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin. (default: OFF)" enable-encryption "Should encryption features be enabled (default: ON)" enable-c++11 "Should the c++11 parts (srt-live-transmit) be enabled (default: ON)" enable-apps "Should the Support Applications be Built? (default: ON)" enable-testing "Should developer testing applications be built (default: OFF)" enable-c++-deps "Extra library dependencies in srt.pc for C language (default: OFF)" enable-heavy-logging "Should heavy debug logging be enabled (default: OFF)" enable-logging "Should logging be enabled (default: ON)" enable-debug=<0,1,2> "Enable debug mode (0=disabled, 1=debug, 2=rel-with-debug)" enable-haicrypt-logging "Should logging in haicrypt be enabled (default: OFF)" enable-inet-pton "Set to OFF to prevent usage of inet_pton when building against modern SDKs (default: ON)" enable-code-coverage "Enable code coverage reporting (default: OFF)" enable-monotonic-clock "Enforced clock_gettime with monotonic clock on GC CV /temporary fix for #729/ (default: OFF)" enable-profile "Should instrument the code for profiling. Ignored for non-GNU compiler. (default: OFF)" enable-relative-libpath "Should applications contain relative library paths, like ../lib (default: OFF)" enable-shared "Should libsrt be built as a shared library (default: ON)" enable-static "Should libsrt be built as a static library (default: ON)" enable-suflip "Should suflip tool be built (default: OFF)" enable-getnameinfo "In-logs sockaddr-to-string should do rev-dns (default: OFF)" enable-unittests "Enable unit tests (default: OFF)" enable-thread-check "Enable #include that implements THREAD_* macros" openssl-crypto-library= "Path to a library." openssl-include-dir= "Path to a file." openssl-ssl-library= "Path to a library." pkg-config-executable= "pkg-config executable" pthread-include-dir= "Path to a file." pthread-library= "Path to a library." use-busy-waiting "Enable more accurate sending times at a cost of potentially higher CPU load (default: OFF)" use-gnustl "Get c++ library/headers from the gnustl.pc" use-enclib "Encryption library to be used: openssl(default), gnutls, mbedtls" use-gnutls "DEPRECATED. Use USE_ENCLIB=openssl|gnutls|mbedtls instead" use-openssl-pc "Use pkg-config to find OpenSSL libraries (default: ON)" use-static-libstdc++ "Should use static rather than shared libstdc++ (default: OFF)" } set options $internal_options$cmake_options # 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 } set haicrypt_name "" set srt_name "" 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: } # Alias to old name --with-gnutls, which enforces using gnutls instead of openssl if { [info exists ::optval(--with-gnutls)] } { unset ::optval(--with-gnutls) set ::optval(--use-enclib) gnutls puts "WARNING: --with-gnutls is a deprecated alias to --use-enclib=gnutls, please use the latter one" } # Alias to old name --use-gnutls, which enforces using gnutls instead of openssl if { [info exists ::optval(--use-gnutls)] } { unset ::optval(--use-gnutls) set ::optval(--use-enclib) gnutls puts "WARNING: --use-gnutls is a deprecated alias to --use-enclib=gnutls, please use the latter one" } if { [info exists ::optval(--with-target-path)] } { set ::target_path $::optval(--with-target-path) unset ::optval(--with-target-path) puts "NOTE: Explicit target path: $::target_path" } if { "--with-srt-name" in $::optkeys } { set ::srt_name $::optval(--with-srt-name) unset ::optval(--with-srt-name) } if { "--with-haicrypt-name" in $::optkeys } { set ::haicrypt_name $::optval(--with-haicrypt-name) unset ::optval(--with-haicrypt-name) } } 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) set compiler gcc if { [info exists ::optval(--with-compiler-type)] } { set compiler $::optval(--with-compiler-type) } if { [info exists ::optval(--with-compiler-prefix)] } { set prefix $::optval(--with-compiler-prefix) return ${prefix}$compiler } else { return $compiler } 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 --with-compiler-type --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 } } set cygwin_posix 0 if { "--cygwin-use-posix" in $all_options } { # Will enforce OpenSSL autodetection set cygwin_posix 1 } if { $toolchain_changed } { # Check characteristics of the compiler - in particular, whether the target is different # than the current target. set compiler_path "" set target_platform "" set cmd [GetCompilerCommand] if { $cmd != "" } { set gcc_version [exec $cmd -v 2>@1] set target "" set compiler_path [file dirname $cmd] foreach l [split $gcc_version \n] { if { [string match Target:* $l] } { set target [lindex $l 1] ;# [0]Target: [1]x86_64-some-things-further set target_platform [lindex [split $target -] 0] ;# [0]x86_64 [1]redhat [2]linux break } } if { $target_platform == "" } { puts "NOTE: can't obtain target from '[file tail $cmd] -v': $l - ASSUMING HOST compiler" } else { if { $target_platform != $::tcl_platform(machine) } { puts "NOTE: foreign target type detected ($target)" ;# - setting CROSSCOMPILING flag" #lappend ::cmakeopt "-DHAVE_CROSSCOMPILER=1" set iscross 1 } } } else { puts "CONFIGURE: default compiler used" } } if { $::srt_name != "" } { lappend ::cmakeopt "-DTARGET_srt=$::srt_name" } if { $::haicrypt_name != "" } { lappend ::cmakeopt "-DTARGET_haicrypt=$::haicrypt_name" } set have_openssl 0 if { [lsearch -glob $::optkeys --openssl*] != -1 } { set have_openssl 1 } set have_gnutls 0 if { [lsearch -glob $::optkeys --use-gnutls] != -1 } { set have_gnutls 1 } if { $have_openssl && $have_gnutls } { puts "NOTE: SSL library is exclusively selectable. Thus, --use-gnutls option will be ignored" set have_gnutls 0 } if { $have_gnutls } { lappend ::cmakeopt "-DUSE_GNUTLS=ON" } if {$iscross} { proc check-target-path {path} { puts "Checking path '$path'" if { [file isdir $path] && [file isdir $path/bin] && [file isdir $path/include] && ([file isdir $path/lib] || [file isdir $path/lib64]) } { return yes } return no } if { ![info exists ::target_path] } { # Try to autodetect the target path by having the basic 3 directories. set target_path "" set compiler_prefix [file dirname $compiler_path] ;# strip 'bin' directory puts "NOTE: no --with-target-path found, will try to autodetect at $compiler_path" foreach path [list $compiler_path $compiler_prefix/$target] { if { [check-target-path $path] } { set target_path $path puts "NOTE: target path detected: $target_path" break } } if { $target_path == "" } { puts "ERROR: Can't determine compiler's platform files root path (using compiler command path). Specify --with-target-path." exit 1 } } else { set target_path $::target_path # Still, check if correct. if { ![check-target-path $target_path] } { puts "ERROR: path in --with-target-path does not contain typical subdirectories" exit 1 } puts "NOTE: Using explicit target path: $target_path" } # Add this for cmake, should it need for something lappend ::cmakeopt "-DCMAKE_PREFIX_PATH=$target_path" # Add explicitly the path for pkg-config # which lib if { [file isdir $target_path/lib64/pkgconfig] } { set ::env(PKG_CONFIG_PATH) $target_path/lib64/pkgconfig puts "PKG_CONFIG_PATH: Found pkgconfig in lib64 for '$target_path' - using it" } elseif { [file isdir $target_path/lib/pkgconfig] } { set ::env(PKG_CONFIG_PATH) $target_path/lib/pkgconfig puts "PKG_CONFIG_PATH: Found pkgconfig in lib for '$target_path' - using it" } else { puts "PKG_CONFIG_PATH: NOT changed, no pkgconfig in '$target_path'" } # Otherwise don't set PKG_CONFIG_PATH and we'll see. } set use_brew 0 if { $::HAVE_DARWIN && !$toolchain_changed } { set use_brew 1 } if { $use_brew } { foreach item $::cmakeopt { if { [string first "Android" $item] != -1 } { set use_brew 0 break } } } if { $use_brew } { if { $have_gnutls } { # Use gnutls explicitly, as found in brew set er [catch {exec brew info gnutls} res] if { $er } { error "Cannot find gnutls in brew" } } else { # 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 "-DOPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include" lappend ::cmakeopt "-DOPENSSL_LIBRARIES=/usr/local/opt/openssl/lib/libcrypto.a" } } } } srt-1.4.4/docs/000077500000000000000000000000001412557703600132725ustar00rootroot00000000000000srt-1.4.4/docs/API/000077500000000000000000000000001412557703600137035ustar00rootroot00000000000000srt-1.4.4/docs/API/API-functions.md000066400000000000000000005365711412557703600166650ustar00rootroot00000000000000# SRT API Functions

Library Initialization

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_startup](#srt_startup) | Called at the start of an application that uses the SRT library | | [srt_cleanup](#srt_cleanup) | Cleans up global SRT resources before exiting an application | | | |

Creating and Configuring Sockets

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_socket](#srt_socket) | Deprecated | | [srt_create_socket](#srt_create_socket) | Creates an SRT socket | | [srt_bind](#srt_bind) | Binds a socket to a local address and port | | [srt_bind_acquire](#srt_bind_acquire) | Acquires a given UDP socket instead of creating one | | [srt_getsockstate](#srt_getsockstate) | Gets the current status of the socket | | [srt_getsndbuffer](#srt_getsndbuffer) | Retrieves information about the sender buffer | | [srt_close](#srt_close) | Closes the socket or group and frees all used resources | | | |

Connecting

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_listen](#srt_listen) | Sets up the listening state on a socket | | [srt_accept](#srt_accept) | Accepts a connection; creates/returns a new socket or group ID | | [srt_accept_bond](#srt_accept_bond) | Accepts a connection pending on any sockets passed in the `listeners` array
of `nlisteners` size | | [srt_listen_callback](#srt_listen_callback) | Installs/executes a callback hook on a socket created to handle the incoming connection
on a listening socket | | [srt_connect](#srt_connect) | Connects a socket or a group to a remote party with a specified address and port | | [srt_connect_bind](#srt_connect_bind) | Same as [`srt_bind`](#srt_bind) then [`srt_connect`](#srt_connect) if called with socket [`u`](#u) | | [srt_connect_debug](#srt_connect_debug) | Same as [`srt_connect`](#srt_connect)but allows specifying ISN (developers only) | | [srt_rendezvous](#srt_rendezvous) | Performs a rendezvous connection | | [srt_connect_callback](#srt_connect_callback) | Installs/executes a callback hook on socket/group [`u`](#u) after connection resolution/failure | | | |

Socket Group Management

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [SRT_GROUP_TYPE](#SRT_GROUP_TYPE) | Group types collected in an [`SRT_GROUP_TYPE`](#SRT_GROUP_TYPE) enum | | [SRT_SOCKGROUPCONFIG](#SRT_SOCKGROUPCONFIG) | Structure used to define entry points for connections for [`srt_connect_group`](#srt_connect_group) | | [SRT_SOCKGROUPDATA](#SRT_SOCKGROUPDATA) | Most important structure for group member status | | [SRT_MEMBERSTATUS](#SRT_MEMBERSTATUS) | Enumeration type that defines the state of a member connection in the group | | [srt_create_group](#srt_create_group) | Creates a new group of type `type` | | [srt_include](#srt_include) | Adds a socket to a group | | [srt_exclude](#srt_exclude) | Removes a socket from a group to which it currently belongs | | [srt_groupof](#srt_groupof) | Returns the group ID of a socket, or `SRT_INVALID_SOCK` | | [srt_group_data](#srt_group_data) | Obtains the current member state of the group specified in `socketgroup` | | [srt_connect_group](#srt_connect_group) | Similar to calling [`srt_connect`](#srt_connect) or [`srt_connect_bind`](#srt_connect_bind)
in a loop for every item in an array. | | [srt_prepare_endpoint](#srt_prepare_endpoint) | Prepares a default [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) object as an element of
an array for [`srt_connect_group`](#srt_connect_group) | | [srt_create_config](#srt_create_config) | Creates a dynamic object for specifying socket options | | [srt_delete_config](#srt_delete_config) | Deletes the configuration object | | [srt_config_add](#srt_config_add) | Adds a configuration option to the configuration object | | | |

Options and Properties

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_getpeername](#srt_getpeername) | Retrieves the remote address to which the socket is connected | | [srt_getsockname](#srt_getsockname) | Extracts the address to which the socket was bound | | [srt_getsockopt](#srt_getsockopt) | Gets the value of the given socket option (from a socket or a group) | | [srt_getsockflag](#srt_getsockflag) | Gets the value of the given socket option (from a socket or a group) | | [srt_setsockopt](#srt_setsockopt) | Sets a value for a socket option in the socket or group | | [srt_setsockflag](#srt_setsockflag) | Sets a value for a socket option in the socket or group | | [srt_getversion](#srt_getversion) | Get SRT version value | | | |

Helper Data Types for Transmission

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [SRT_MSGCTRL](#SRT_MSGCTRL) | Used in [`srt_sendmsg2`](#srt_sendmsg) and [`srt_recvmsg2`](#srt_recvmsg2) calls;
specifies some extra parameters | | | |

Transmission

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_send](#srt_send) | Sends a payload to a remote party over a given socket | | [srt_sendmsg](#srt_sendmsg) | Sends a payload to a remote party over a given socket | | [srt_sendmsg2](#srt_sendmsg2) | Sends a payload to a remote party over a given socket | | [srt_recv](#srt_recv) | Extracts the payload waiting to be received | | [srt_recvmsg](#srt_recvmsg) | Extracts the payload waiting to be received | | [srt_recvmsg2](#srt_recvmsg2) | Extracts the payload waiting to be received | | [srt_sendfile](#srt_sendfile) | Function dedicated to sending a file | | [srt_recvfile](#srt_recvfile) | Function dedicated to receiving a file | | | |

Performance Tracking

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_bstats](#srt_bstats) | Reports the current statistics | | [srt_bistats](#srt_bistats) | Reports the current statistics | | | |

Asynchronous Operations (Epoll)

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_epoll_create](#srt_epoll_create) | Creates a new epoll container | | [srt_epoll_add_usock](#srt_epoll_add_usock) | Adds a user socket to a container, or updates an existing socket subscription | | [srt_epoll_add_ssock](#srt_epoll_add_ssock) | Adds a system socket to a container, or updates an existing socket subscription | | [srt_epoll_update_usock](#srt_epoll_update_usock) | Adds a user socket to a container, or updates an existing socket subscription | | [srt_epoll_update_ssock](#srt_epoll_update_ssock) | Adds a system socket to a container, or updates an existing socket subscription | | [srt_epoll_remove_usock](#srt_epoll_remove_usock) | Removes a specified user socket from an epoll container; clears all readiness states for that socket | | [srt_epoll_remove_ssock](#srt_epoll_remove_ssock) | Removes a specified system socket from an epoll container; clears all readiness states for that socket | | [srt_epoll_wait](#srt_epoll_wait) | Blocks the call until any readiness state occurs in the epoll container | | [srt_epoll_uwait](#srt_epoll_uwait) | Blocks a call until any readiness state occurs in the epoll container | | [srt_epoll_clear_usocks](#srt_epoll_clear_usocks) | removes all SRT ("user") socket subscriptions from the epoll container identified by [`eid`](#eid) | | [srt_epoll_set](#srt_epoll_set) | Allows setting or retrieving flags that change the default behavior of the epoll functions | | [srt_epoll_release](#srt_epoll_release) | Deletes the epoll container | | | |

Logging Control

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_setloglevel](#srt_setloglevel) | Sets the minimum severity for logging | | [srt_addlogfa](#srt_addlogfa) | Add a functional area (FA), which is an additional filtering mechanism for logging | | [srt_dellogfa](#srt_dellogfa) | Delete a functional area (FA), which is an additional filtering mechanism for logging | | [srt_resetlogfa](#srt_resetlogfa) | Reset a functional area (FA), which is an additional filtering mechanism for logging | | [srt_setloghandler](#srt_setloghandler) | Replaces default standard stream for error logging | | [srt_setlogflags](#srt_setlogflags) | Allows configuring parts of log information that are not to be passed | | | |

Time Access

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_time_now](#srt_time_now) | Get time in microseconds elapsed since epoch using SRT internal clock
(steady or monotonic clock) | | [srt_connection_time](#srt_connection_time) | Get connection time in microseconds elapsed since epoch using SRT internal clock
(steady or monotonic clock) | | [srt_clock_type](#srt_clock_type) | Get the type of clock used internally by SRT | | | |

Diagnostics

| *Function / Structure* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_getlasterror](#srt_getlasterror) | Get the numeric code of the last error | | [srt_strerror](#srt_strerror) | Returns a string message that represents a given SRT error code and possibly
the `errno` value, if not 0 | | [srt_getlasterror_str](#srt_getlasterror_str) | Gets the text message for the last error | | [srt_clearlasterror](#srt_clearlasterror) | Clears the last error | | [srt_rejectreason_str](#srt_rejectreason_str) | Returns a constant string for the reason of the connection rejected, as per given code ID | | [srt_setrejectreason](#srt_setrejectreason) | Sets the rejection code on the socket | | [srt_getrejectreason](#srt_getrejectreason) | Provides a detailed reason for a failed connection attempt | | | |

Rejection Reasons

| *Rejection Reason* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [SRT_REJ_UNKNOWN](#SRT_REJ_UNKNOWN) | A fallback value for cases when there was no connection rejected | | [SRT_REJ_SYSTEM](#SRT_REJ_SYSTEM) | A system function reported a failure | | [SRT_REJ_PEER](#SRT_REJ_PEER) | The connection has been rejected by peer, but no further details are available | | [SRT_REJ_RESOURCE](#SRT_REJ_RESOURCE) | A problem with resource allocation (usually memory) | | [SRT_REJ_ROGUE](#SRT_REJ_ROGUE) | The data sent by one party to another cannot be properly interpreted | | [SRT_REJ_BACKLOG](#SRT_REJ_BACKLOG) | The listener's backlog has exceeded | | [SRT_REJ_IPE](#SRT_REJ_IPE) | Internal Program Error | | [SRT_REJ_CLOSE](#SRT_REJ_CLOSE) | The listener socket received a request as it is being closed | | [SRT_REJ_VERSION](#SRT_REJ_VERSION) | A party did not satisfy the minimum version requirement that had been set up for a connection | | [SRT_REJ_RDVCOOKIE](#SRT_REJ_RDVCOOKIE) | Rendezvous cookie collision | | [SRT_REJ_BADSECRET](#SRT_REJ_BADSECRET) | Both parties have defined a passprhase for connection and they differ | | [SRT_REJ_UNSECURE](#SRT_REJ_UNSECURE) | Only one connection party has set up a password | | [SRT_REJ_MESSAGEAPI](#SRT_REJ_MESSAGEAPI) | The value for [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) flag is different on both connection parties | | [SRT_REJ_FILTER](#SRT_REJ_FILTER) | The [`SRTO_PACKETFILTER`](API-socket-options.md#SRTO_PACKETFILTER) option has been set differently on both connection parties | | [SRT_REJ_GROUP](#SRT_REJ_GROUP) | The group type or some group settings are incompatible for both connection parties | | [SRT_REJ_TIMEOUT](#SRT_REJ_TIMEOUT) | The connection wasn't rejected, but it timed out | | | |

Error Codes

| *Error Code* | *Description* | |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | [`SRT_EUNKNOWN`](#srt_eunknown) | Internal error when setting the right error code | [`SRT_SUCCESS`](#srt_success) | The value set when the last error was cleared and no error has occurred since then | [`SRT_ECONNSETUP`](#srt_econnsetup) | General setup error resulting from internal system state | [`SRT_ENOSERVER`](#srt_enoserver) | Connection timed out while attempting to connect to the remote address | [`SRT_ECONNREJ`](#srt_econnrej) | Connection has been rejected | [`SRT_ESOCKFAIL`](#srt_esockfail) | An error occurred when trying to call a system function on an internally used UDP socket | [`SRT_ESECFAIL`](#srt_esecfail) | A possible tampering with the handshake packets was detected, or encryption request
wasn't properly fulfilled. | [`SRT_ESCLOSED`](#srt_esclosed) | A socket that was vital for an operation called in blocking mode has been closed
during the operation | [`SRT_ECONNFAIL`](#srt_econnfail) | General connection failure of unknown details | [`SRT_ECONNLOST`](#srt_econnlost) | The socket was properly connected, but the connection has been broken | [`SRT_ENOCONN`](#srt_enoconn) | The socket is not connected | [`SRT_ERESOURCE`](#srt_eresource) | System or standard library error reported unexpectedly for unknown purpose | [`SRT_ETHREAD`](#srt_ethread) | System was unable to spawn a new thread when requried | [`SRT_ENOBUF`](#srt_enobuf) | System was unable to allocate memory for buffers | [`SRT_ESYSOBJ`](#srt_esysobj) | System was unable to allocate system specific objects | [`SRT_EFILE`](#srt_efile) | General filesystem error (for functions operating with file transmission) | [`SRT_EINVRDOFF`](#srt_einvrdoff) | Failure when trying to read from a given position in the file | [`SRT_ERDPERM`](#srt_erdperm) | Read permission was denied when trying to read from file | [`SRT_EINVWROFF`](#srt_einvwroff) | Failed to set position in the written file | [`SRT_EWRPERM`](#srt_ewrperm) | Write permission was denied when trying to write to a file | [`SRT_EINVOP`](#srt_einvop) | Invalid operation performed for the current state of a socket | [`SRT_EBOUNDSOCK`](#srt_eboundsock) | The socket is currently bound and the required operation cannot be performed in this state | [`SRT_ECONNSOCK`](#srt_econnsock) | The socket is currently connected and therefore performing the required operation is not possible | [`SRT_EINVPARAM`](#srt_einvparam) | Call parameters for API functions have some requirements that were not satisfied | [`SRT_EINVSOCK`](#srt_einvsock) | The API function required an ID of an entity (socket or group) and it was invalid | [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock) | The operation to be performed on a socket requires that it first be explicitly bound | [`SRT_ENOLISTEN`](#srt_enolisten) | The socket passed for the operation is required to be in the listen state | [`SRT_ERDVNOSERV`](#srt_erdvnoserv) | The required operation cannot be performed when the socket is set to rendezvous mode | [`SRT_ERDVUNBOUND`](#srt_erdvunbound) | An attempt was made to connect to a socket set to rendezvous mode that was not first bound | [`SRT_EINVALMSGAPI`](#srt_einvalmsgapi) | The function was used incorrectly in the message API | [`SRT_EINVALBUFFERAPI`](#srt_einvalbufferapi) | The function was used incorrectly in the stream (buffer) API | [`SRT_EDUPLISTEN`](#srt_eduplisten) | The port tried to be bound for listening is already busy | [`SRT_ELARGEMSG`](#srt_elargemsg) | Size exceeded | [`SRT_EINVPOLLID`](#srt_einvpollid) | The epoll ID passed to an epoll function is invalid | [`SRT_EPOLLEMPTY`](#srt_epollempty) | The epoll container currently has no subscribed sockets | [`SRT_EASYNCFAIL`](#srt_easyncfail) | General asynchronous failure (not in use currently) | [`SRT_EASYNCSND`](#srt_easyncsnd) | Sending operation is not ready to perform | [`SRT_EASYNCRCV`](#srt_easyncrcv) | Receiving operation is not ready to perform | [`SRT_ETIMEOUT`](#srt_etimeout) | The operation timed out | [`SRT_ECONGEST`](#srt_econgest) | With [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) and [`SRTO_TLPKTDROP`](API-socket-options.md#SRTO_TLPKTDROP) set to true,
some packets were dropped by sender | [`SRT_EPEERERR`](#srt_epeererr) | Receiver peer is writing to a file that the agent is sending | | | | ## Library Initialization * [srt_startup](#srt_startup) * [srt_cleanup](#srt_cleanup) ### srt_startup ``` int srt_startup(void); ``` This function shall be called at the start of an application that uses the SRT library. It provides all necessary platform-specific initializations, sets up global data, and starts the SRT GC thread. If this function isn't explicitly called, it will be called automatically when creating the first socket. However, relying on this behavior is strongly discouraged. | Returns | | |:----------------------------- |:--------------------------------------------------------------- | | 0 | Successfully run, or already started | | 1 | This is the first startup, but the GC thread is already running | | -1 | Failed | | | | | Errors | | |:----------------------------- |:--------------------------------------------------------------- | | [`SRT_ECONNSETUP`](#srt_econnsetup) | With error code set, reported when required system resource(s) failed to initialize.
This is currently used only on Windows to report a failure from `WSAStartup`. | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_cleanup ``` int srt_cleanup(void); ``` This function cleans up all global SRT resources and shall be called just before exiting the application that uses the SRT library. This cleanup function will still be called from the C++ global destructor, if not called by the application, although relying on this behavior is strongly discouraged. | Returns | | |:----------------------------- |:--------------------------------------------------------------- | | 0 | A possibility to return other values is reserved for future use | | | | **IMPORTANT**: Note that the startup/cleanup calls have an instance counter. This means that if you call [`srt_startup`](#srt_startup) multiple times, you need to call the `srt_cleanup` function exactly the same number of times. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Creating and Configuring Sockets * [srt_socket](#srt_socket) * [srt_create_socket](#srt_create_socket) * [srt_bind](#srt_bind) * [srt_bind_acquire](#srt_bind_acquire) * [srt_getsockstate](#srt_getsockstate) * [srt_getsndbuffer](#srt_getsndbuffer) * [srt_close](#srt_close) ### srt_socket ``` SRTSOCKET srt_socket(int af, int type, int protocol); ``` Old and deprecated version of [`srt_create_socket`](#srt_create_socket). All arguments are ignored. **NOTE** changes with respect to UDT version: * In UDT (and SRT versions before 1.4.2) the `af` parameter was specifying the socket family (`AF_INET` or `AF_INET6`). This is now not required; this parameter is decided at the call of [`srt_connect`](#srt_connect) or [`srt_bind`](#srt_bind). * In UDT the `type` parameter was used to specify the file or message mode using `SOCK_STREAM` or `SOCK_DGRAM` symbols (with the latter being misleading, as the message mode has nothing to do with UDP datagrams and it's rather similar to the SCTP protocol). In SRT these two modes are available by setting [`SRTO_TRANSTYPE`](API-socket-options.md#SRTO_TRANSTYPE). The default is `SRTT_LIVE`. If, however, you set [`SRTO_TRANSTYPE`](API-socket-options.md#SRTO_TRANSTYPE) to `SRTT_FILE` for file mode, you can then leave the [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) option as false (default), which corresponds to "stream" mode (TCP-like), or set it to true, which corresponds to "message" mode (SCTP-like). [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_create_socket ``` SRTSOCKET srt_create_socket(); ``` Creates an SRT socket. Note that socket IDs always have the `SRTGROUP_MASK` bit clear. | Returns | | |:----------------------------- |:------------------------------------------------------- | | Socket ID | A valid socket ID on success | | `SRT_INVALID_SOCK` | (`-1`) on error | | | | | Errors | | |:----------------------------- |:------------------------------------------------------------ | | [`SRT_ENOBUF`](#srt_enobuf) | Not enough memory to allocate required resources . | | | | **NOTE:** This is probably a design flaw (:warning:   **BUG?**). Usually underlying system errors are reported by [`SRT_ECONNSETUP`](#srt_econnsetup). [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_bind ``` int srt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen); ``` Binds a socket to a local address and port. Binding specifies the local network interface and the UDP port number to be used for the socket. When the local address is a form of `INADDR_ANY`, then it's bound to all interfaces. When the port number is 0, then the port number will be system-allocated if necessary. This call is obligatory for a listening socket before calling [`srt_listen`](#srt_listen) and for rendezvous mode before calling [`srt_connect`](#srt_connect); otherwise it's optional. For a listening socket it defines the network interface and the port where the listener should expect a call request. In the case of rendezvous mode (when the socket has set [`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) to true both parties connect to one another) it defines the network interface and port from which packets will be sent to the peer, and the port to which the peer is expected to send packets. For a connecting socket this call can set up the outgoing port to be used in the communication. It is allowed that multiple SRT sockets share one local outgoing port, as long as [`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDRS) is set to *true* (default). Without this call the port will be automatically selected by the system. **NOTE**: This function cannot be called on a socket group. If you need to have the group-member socket bound to the specified source address before connecting, use [`srt_connect_bind`](#srt_connect_bind) for that purpose. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | `SRT_ERROR` | (-1) on error, otherwise 0 | | | | | Errors | | |:---------------------------------------- |:-------------------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket passed as [`u`](#u) designates no valid socket | | [`SRT_EINVOP`](#srt_einvop) | Socket already bound | | [`SRT_ECONNSETUP`](#srt_econnsetup) | Internal creation of a UDP socket failed | | [`SRT_ESOCKFAIL`](#srt_esockfail) | Internal configuration of a UDP socket (`bind`, `setsockopt`) failed | | [`SRT_EBINDCONFLICT`](#srt_ebindconflict)| Binding specification conflicts with existing one | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_bind_acquire ``` int srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock); ``` A version of [`srt_bind`](#srt_bind) that acquires a given UDP socket instead of creating one. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_getsockstate ``` SRT_SOCKSTATUS srt_getsockstate(SRTSOCKET u); ``` Gets the current status of the socket. Possible states are: | State | Description | |:----------------------------------------------- |:----------------------------------------------------------------- | | `SRTS_INIT` | Created, but not bound. | | `SRTS_OPENED` | Created and bound, but not in use yet. | | `SRTS_LISTENING` | Socket is in listening state. | | `SRTS_CONNECTING` | The connect operation was initiated, but not yet finished. This may also mean that it has timed out;
you can only know that after getting a socket error report from [`srt_epoll_wait`](#srt_epoll_wait). In blocking mode
it's not possible because [`srt_connect`](#srt_connect) does not return until the socket is connected or failed due
to timeout or interrupted call. | | `SRTS_CONNECTED` | The socket is connected and ready for transmission. | | `SRTS_BROKEN` | The socket was connected, but the connection was broken. | | `SRTS_CLOSING` | The socket may still be open and active, but closing is requested, so no further operations will
be accepted (active operations will be completed before closing) | | `SRTS_CLOSED` | The socket has been closed, but not yet removed by the GC thread. | | `SRTS_NONEXIST` | The specified number does not correspond to a valid socket. | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_getsndbuffer ``` int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes); ``` Retrieves information about the sender buffer. **Arguments**: * `sock`: Socket to test * `blocks`: Written information about buffer blocks in use * `bytes`: Written information about bytes in use This function can be used for diagnostics. It is especially useful when the socket needs to be closed asynchronously. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_close ``` int srt_close(SRTSOCKET u); ``` Closes the socket or group and frees all used resources. Note that underlying UDP sockets may be shared between sockets, so these are freed only with the last user closed. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | `SRT_ERROR` | (-1) in case of error, otherwise 0 | | | | | Errors | | |:------------------------------- |:----------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Connecting * [srt_listen](#srt_listen) * [srt_accept](#srt_accept) * [srt_accept_bond](#srt_accept_bond) * [srt_listen_callback](#srt_listen_callback) * [srt_connect](#srt_connect) * [srt_connect_bind](#srt_connect_bind) * [srt_connect_debug](#srt_connect_debug) * [srt_rendezvous](#srt_rendezvous) * [srt_connect_callback](#srt_connect_callback) ### srt_listen ``` int srt_listen(SRTSOCKET u, int backlog); ``` This sets up the listening state on a socket with a backlog setting that defines how many sockets may be allowed to wait until they are accepted (excessive connection requests are rejected in advance). The following important options may change the behavior of the listener socket and the [`srt_accept`](#srt_accept) function: * [`srt_listen_callback`](#srt_listen_callback) installs a user function that will be called before [`srt_accept`](#srt_accept) can happen * [`SRTO_GROUPCONNECT`](API-socket-options.md#SRTO_GROUPCONNECT) option allows the listener socket to accept group connections | Returns | | |:----------------------------- |:--------------------------------------------------------- | | `SRT_ERROR` | (-1) in case of error, otherwise 0. | | | | | Errors | | |:--------------------------------------- |:-------------------------------------------------------------------------------------------- | | [`SRT_EINVPARAM`](#srt_einvparam) | Value of `backlog` is 0 or negative. | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid SRT socket. | | [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock) | [`srt_bind`](#srt_bind) has not yet been called on that socket. | | [`SRT_ERDVNOSERV`](#srt_erdvnoserv) | [`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) flag is set to true on specified socket. | | [`SRT_EINVOP`](#srt_einvop) | Internal error (should not happen when [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock) is reported). | | [`SRT_ECONNSOCK`](#srt_econnsock) | The socket is already connected. | | [`SRT_EDUPLISTEN`](#srt_eduplisten) | The address used in [`srt_bind`](#srt_bind) by this socket is already occupied by another listening socket.
Binding multiple sockets to one IP address and port is allowed, as long as
[`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDRS) is set to true, but only one of these sockets can be set up as a listener. | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_accept ``` SRTSOCKET srt_accept(SRTSOCKET lsn, struct sockaddr* addr, int* addrlen); ``` Accepts a pending connection, then creates and returns a new socket or group ID that handles this connection. The group and socket can be distinguished by checking the `SRTGROUP_MASK` bit on the returned ID. * `lsn`: the listener socket previously configured by [`srt_listen`](#srt_listen) * `addr`: the IP address and port specification for the remote party * `addrlen`: INPUT: size of `addr` pointed object. OUTPUT: real size of the returned object **NOTE:** `addr` is allowed to be NULL, in which case it's understood that the application is not interested in the address from which the connection originated. Otherwise `addr` should specify an object into which the address will be written, and `addrlen` must also specify a variable to contain the object size. Note also that in the case of group connection only the initial connection that establishes the group connection is returned, together with its address. As member connections are added or broken within the group, you can obtain this information through [`srt_group_data`](#srt_group_data) or the data filled by [`srt_sendmsg2`](#srt_sendmsg) and [`srt_recvmsg2`](#srt_recvmsg2). If the `lsn` listener socket is configured for blocking mode ([`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) set to true, default), the call will block until the incoming connection is ready. Otherwise, the call always returns immediately. The `SRT_EPOLL_IN` epoll event should be checked on the `lsn` socket prior to calling this function in that case. If the pending connection is a group connection (initiated on the peer side by calling the connection function using a group ID, and permitted on the listener socket by the [`SRTO_GROUPCONNECT`](API-socket-options.md#SRTO_GROUPCONNECT) flag), then the value returned is a group ID. This function then creates a new group, as well as a new socket for this connection, that will be added to the group. Once the group is created this way, further connections within the same group, as well as sockets for them, will be created in the background. The [`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) event is raised on the `lsn` socket when a new background connection is attached to the group, although it's usually for internal use only. | Returns | | |:----------------------------- |:----------------------------------------------------------------------- | | socket/group ID | On success, a valid SRT socket or group ID to be used for transmission. | | `SRT_ERROR` | (-1) on failure | | | | | Errors | | |:--------------------------------- |:----------------------------------------------------------------------- | | [`SRT_EINVPARAM`](#srt_einvparam) | NULL specified as `addrlen`, when `addr` is not NULL | | [`SRT_EINVSOCK`](#srt_einvsock) | `lsn` designates no valid socket ID. | | [`SRT_ENOLISTEN`](#srt_enolisten) | `lsn` is not set up as a listener ([`srt_listen`](#srt_listen) not called). | | [`SRT_EASYNCRCV`](#srt_easyncrcv) | No connection reported so far. This error is reported only in the non-blocking mode | | [`SRT_ESCLOSED`](#srt_esclosed) | The `lsn` socket has been closed while the function was blocking the call. Including when the socket was closed just at the
moment when a connection was made (i.e., the socket got closed during processing) | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_accept_bond ``` SRTSOCKET srt_accept_bond(const SRTSOCKET listeners[], int nlisteners, int msTimeOut); ``` Accepts a pending connection, like [`srt_accept`](#srt_accept), but pending on any of the listener sockets passed in the `listeners` array of `nlisteners` size. **Arguments**: * `listeners`: array of listener sockets (all must be setup by [`srt_listen`](#srt_listen)) * `nlisteners`: size of the `listeners` array * `msTimeOut`: timeout in [ms] or -1 to block forever This function is for blocking mode only - for non-blocking mode you should simply call [`srt_accept`](#srt_accept) on the first listener socket that reports readiness, and this function is actually a friendly shortcut that uses waiting on epoll and [`srt_accept`](#srt_accept) internally. This function supports an important use case for accepting a group connection, for which every member connection is expected to be established over a different listener socket. Note that there's no special set of settings required or rejected for this function. Group-member connections for the same group can always be established over various different listener sockets when all those listeners are hosted by the same application. The group management is global for the application, so a connection reporting in for an already connected group gets discovered, and the connection will be handled in the background. This occurs regardless of which listener socket the call was made to, as long as the connection is accepted according to any additional conditions. This function has nothing to do with the groups. You can use it in any case when you have one service that accepts connections to multiple endpoints. Note also that the settings as to whether listeners should accept or reject socket or group connections should be applied to the listener sockets appropriately prior to calling this function. | Returns | | |:----------------------------- |:---------------------------------------------------------------------- | | SRT socket
group ID | On success, a valid SRT socket or group ID to be used for transmission | | `SRT_ERROR` | (-1) on failure | | | | | Errors | | |:--------------------------------- |:------------------------------------------------------------ | | [`SRT_EINVPARAM`](#srt_einvparam) | NULL specified as `listeners` or `nlisteners` < 1 | | [`SRT_EINVSOCK`](#srt_einvsock) | Any socket in `listeners` designates no valid socket ID. Can also mean *Internal Error* when
an error occurred while creating an accepted socket (:warning:   **BUG?**) | | [`SRT_ENOLISTEN`](#srt_enolisten) | Any socket in `listeners` is not set up as a listener ([`srt_listen`](#srt_listen) not called, or the listener socket
has already been closed) | | [`SRT_ETIMEOUT`](#srt_etimeout) | No connection reported on any listener socket as the timeout has been reached. This error is only
reported when `msTimeOut` is not -1 | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_listen_callback ``` int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook_fn, void* hook_opaque); ``` This call installs a callback hook, which will be executed on a socket that is automatically created to handle the incoming connection on the listening socket (and is about to be returned by [`srt_accept`](#srt_accept)), but before the connection has been accepted. **Arguments**: * `lsn`: Listening socket where you want to install the callback hook * `hook_fn`: The callback hook function pointer * `hook_opaque`: The pointer value that will be passed to the callback function | Returns | | |:----------------------------- |:---------------------------------------------------------- | | 0 | Successful | | -1 | Error | | | | | Errors | | |:--------------------------------- |:----------------------------------------- | | [`SRT_EINVPARAM`](#srt_einvparam) | Reported when `hook_fn` is a null pointer | | | | The callback function has the signature as per this type definition: ``` typedef int srt_listen_callback_fn(void* opaque, SRTSOCKET ns, int hs_version const struct sockaddr* peeraddr, const char* streamid); ``` The callback function gets the following parameters passed: * `opaque`: The pointer passed as `hook_opaque` when registering * `ns`: The freshly created socket to handle the incoming connection * `hs_version`: The handshake version (usually 5, pre-1.3 versions of SRT use 4) * `peeraddr`: The address of the incoming connection * `streamid`: The value set to [`SRTO_STREAMID`](API-socket-options.md#SRTO_STREAMID) option set on the peer side Note that SRT versions that use handshake version 4 are incapable of using any extensions, such as `streamid`. However they do support encryption. Note also that the SRT version isn't extracted at this point. However you can prevent connections with versions that are too old by using the [`SRTO_MINVERSION`](API-socket-options.md#SRTO_MINVERSION) option. The callback function is given an opportunity to: * use the passed information (`streamid` and peer address) to decide what to do with this connection * alter any options on the socket, which could not be set properly beforehand on the listening socket to be derived by the accepted socket, and won't be allowed to be altered after the socket is returned by [`srt_accept`](#srt_accept) Note that normally the returned socket has already set all derived options from the listener socket. The moment when this callback is called is when the conclusion handshake has been already received from the caller party, but not yet interpreted (the `streamid` field is extracted from it prematurely). When, for example, you set a passphrase on the socket at this point, the Key Material processing will happen against this passphrase, after the callback function is finished. The callback function shall return 0, if the connection is to be accepted. If you return -1, **or** if the function throws an exception, this will be understood as a request to reject the incoming connection. In this case the about-to-be-accepted socket will be silently deleted and [`srt_accept`](#srt_accept) will not report it. Note that in case of non-blocking mode the epoll bits for read-ready on the listener socket will not be set if the connection is rejected, including when rejected from this user function. **IMPORTANT**: This function is called in the receiver worker thread, which means that it must do its checks and operations as quickly as possible. Every delay you create in this function will burden the processing of the incoming data on the associated UDP socket. In the case of a listener socket this means the listener socket itself and every socket accepted off this listener socket. Avoid any extensive search operations. It is best to cache in memory whatever database you have to check against the data received in `streamid` or `peeraddr`. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_connect ``` int srt_connect(SRTSOCKET u, const struct sockaddr* name, int namelen); ``` Connects a socket or a group to a remote party with a specified address and port. **Arguments**: * [`u`](#u): can be an SRT socket or SRT group, both freshly created and not yet used for any connection, except possibly [`srt_bind`](#srt_bind) on the socket * `name`: specification of the remote address and port * `namelen`: size of the object passed by `name` **NOTES:** 1. The socket used here may be [bound by `srt_bind`](#srt_bind) before connecting, or binding and connection can be done in one function ([`srt_connect_bind`](#srt_connect_bind)), such that it uses a predefined network interface or local outgoing port. This is optional in the case of a caller-listener arrangement, but obligatory for a rendezvous arrangement. If not used, the binding will be done automatically to `INADDR_ANY` (which binds on all interfaces) and port 0 (which makes the system assign the port automatically). 2. This function is used for both connecting to the listening peer in a caller-listener arrangement, and calling the peer in rendezvous mode. For the latter, the [`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) flag must be set to true prior to calling this function, and binding, as described in #1, is in this case obligatory (see `SRT_ERDVUNBOUND` below). 3. When [`u`](#u) is a group, then this call can be done multiple times, each time for another member connection, and a new member SRT socket will be created automatically for every call of this function. 4. If you want to connect a group to multiple links at once and use blocking mode, you might want to use [`srt_connect_group`](#srt_connect_group) instead. This function also allows you to use additional settings, available only for groups. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | `SRT_ERROR` | (-1) in case of error | | 0 | In case when used for [`u`](#u) socket | | Socket ID | Created for connection for [`u`](#u) group | | | | | Errors | | |:------------------------------------- |:----------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | | [`SRT_ERDVUNBOUND`](#srt_erdvunbound) | Socket [`u`](#u) is in rendezvous mode, but it wasn't bound (see note #2) | | [`SRT_ECONNSOCK`](#srt_econnsock) | Socket [`u`](#u) is already connected | | [`SRT_ECONNREJ`](#srt_econnrej) | Connection has been rejected | | [`SRT_ENOSERVER`](#srt_enoserver) | Connection has been timed out (see [`SRTO_CONNTIMEO`](API-socket-options.md#SRTO_CONNTIMEO)) | | [`SRT_ESCLOSED`](#srt_esclosed) | The socket [`u`](#u) has been closed while the function was blocking the call | | | | If the `u` socket is configured for blocking mode (when [`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) is set to true, default), the call will block until the connection succeeds or fails. The "early" errors [`SRT_EINVSOCK`](#srt_einvsock), [`SRT_ERDVUNBOUND`](#srt_erdvunbound) and [`SRT_ECONNSOCK`](#srt_econnsock) are reported in both modes immediately. Other errors are "late" failures and can only be reported in blocking mode. In non-blocking mode, a successful connection can be recognized by the `SRT_EPOLL_OUT` epoll event flag and a "late" failure by the `SRT_EPOLL_ERR` flag. Note that the socket state in the case of a failed connection remains `SRTS_CONNECTING` in that case. In the case of "late" failures you can additionally call [`srt_getrejectreason`](#srt_getrejectreason) to get detailed error information. Note that in blocking mode only for the `SRT_ECONNREJ` error this function may return any additional information. In non-blocking mode a detailed "late" failure cannot be distinguished, and therefore it can also be obtained from this function. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_connect_bind ``` int srt_connect_bind(SRTSOCKET u, const struct sockaddr* source, const struct sockaddr* target, int len); ``` This function does the same as first [`srt_bind`](#srt_bind) then [`srt_connect`](#srt_connect), if called with [`u`](#u) being a socket. If [`u`](#u) is a group, then it will execute [`srt_bind`](#srt_bind) first on the automatically created socket for the connection. **Arguments**: * [`u`](#u): Socket or group to connect * `source`: Address to bind [`u`](#u) to * `target`: Address to connect * `len`: size of the original structure of `source` and `target` | Returns | | |:----------------------------- |:-------------------------------------------------------- | | `SRT_ERROR` | (-1) in case of error | | 0 | In case when used for [`u`](#u) socket | | Socket ID | Created for connection for [`u`](#u) group | | | | | Errors | | |:---------------------------------------- |:-------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket passed as [`u`](#u) designates no valid socket | | [`SRT_EINVOP`](#srt_einvop) | Socket already bound | | [`SRT_ECONNSETUP`](#srt_econnsetup) | Internal creation of a UDP socket failed | | [`SRT_ESOCKFAIL`](#srt_esockfail) | Internal configuration of a UDP socket (`bind`, `setsockopt`) failed | | [`SRT_ERDVUNBOUND`](#srt_erdvunbound) | Internal error ([`srt_connect`](#srt_connect) should not report it after [`srt_bind`](#srt_bind) was called) | | [`SRT_ECONNSOCK`](#srt_econnsock) | Socket [`u`](#u) is already connected | | [`SRT_ECONNREJ`](#srt_econnrej) | Connection has been rejected | | [`SRT_EBINDCONFLICT`](#srt_ebindconflict)| Binding specification conflicts with existing one | | | | **IMPORTANT**: It's not allowed to bind and connect the same socket to two different families (that is, both `source` and `target` must be `AF_INET` or `AF_INET6`), although you may mix links over IPv4 and IPv6 in one group. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_connect_debug ``` int srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); ``` This function is for developers only and can be used for testing. It does the same thing as [`srt_connect`](#srt_connect), with the exception that it allows specifying the Initial Sequence Number for data transmission. Normally this value is generated randomly. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_rendezvous ``` int srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen); ``` Performs a rendezvous connection. This is a shortcut for doing bind locally, setting the [`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) option to true, and doing [`srt_connect`](#srt_connect). **Arguments**: * [`u`](#u): socket to connect * `local_name`: specifies the local network interface and port to bind * `remote_name`: specifies the remote party's IP address and port | Returns | | |:----------------------------- |:-------------------------------------------------------- | | `SRT_ERROR` | (-1) in case of error, otherwise 0 | | | | | Errors | | |:------------------------------------- |:-------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket passed as [`u`](#u) designates no valid socket | | [`SRT_EINVOP`](#srt_einvop) | Socket already bound | | [`SRT_ECONNSETUP`](#srt_econnsetup) | Internal creation of a UDP socket failed | | [`SRT_ESOCKFAIL`](#srt_esockfail) | Internal configuration of a UDP socket (`bind`, `setsockopt`) failed | | [`SRT_ERDVUNBOUND`](#srt_erdvunbound) | Internal error ([`srt_connect`](#srt_connect) should not report it after [`srt_bind`](#srt_bind) was called) | | [`SRT_ECONNSOCK`](#srt_econnsock) | Socket [`u`](#u) is already connected | | [`SRT_ECONNREJ`](#srt_econnrej) | Connection has been rejected | | | | **IMPORTANT**: Establishing a rendezvous connection to two different families is not allowed (that is, both `local_name` and `remote_name` must be `AF_INET` or `AF_INET6`). [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_connect_callback ``` int srt_connect_callback(SRTSOCKET u, srt_connect_callback_fn* hook_fn, void* hook_opaque); ``` This call installs a callback hook, which will be executed on a given [`u`](#u) socket or all member sockets of a [`u`](#u) group, just after a pending connection in the background has been resolved and the connection has failed. Note that this function is not guaranteed to be called if the [`u`](#u) socket is set to blocking mode ([`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) option set to true). It is guaranteed to be called when a socket is in non-blocking mode, or when you use a group. This function is mainly intended to be used with group connections. Note that even if you use a group connection in blocking mode, after the group is considered connected the member connections still continue in background. Also, when some connections are still pending and others have failed, the blocking call for [`srt_connect_group`](#srt_connect_group) will not exit until at least one of them succeeds or all fail - in such a case those failures also happen only in the background, while the connecting function blocks until all connections are resolved. When all links fail, you will only get a general error code for the group. This mechanism allows you to get individual errors for particular member connection failures. **Arguments**: * [`u`](#u): Socket or group that will be used for connecting and for which the hook is installed * `hook_fn`: The callback hook function pointer * `hook_opaque`: The pointer value that will be passed to the callback function | Returns | | |:----------------------------- |:--------------------------------------------------------- | | 0 | Successful | | -1 | Error | | | | | Errors | | |:---------------------------------- |:------------------------------------------| | [`SRT_EINVPARAM`](#srt_einvparam) | Reported when `hook_fn` is a null pointer | | | | The callback function signature has the following type definition: ``` typedef void srt_connect_callback_fn(void* opaq, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token); ``` **Arguments**: * `opaq`: The pointer passed as `hook_opaque` when registering * `ns`: The socket for which the connection process was resolved * [`errorcode`](#error-codes): The error code, same as for [`srt_connect`](#srt_connect) for blocking mode * `peeraddr`: The target address passed to [`srt_connect`](#srt_connect) call * `token`: The token value, if it was used for group connection, otherwise -1 [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Socket Group Management * [SRT_GROUP_TYPE](#SRT_GROUP_TYPE) * [SRT_SOCKGROUPCONFIG](#SRT_SOCKGROUPCONFIG) * [SRT_SOCKGROUPDATA](#SRT_SOCKGROUPDATA) * [SRT_MEMBERSTATUS](#SRT_MEMBERSTATUS) ### SRT_GROUP_TYPE The following group types are collected in an [`SRT_GROUP_TYPE`](#SRT_GROUP_TYPE) enum: * `SRT_GTYPE_BROADCAST`: broadcast type, all links are actively used at once * `SRT_GTYPE_BACKUP`: backup type, idle links take over connection on disturbance * `SRT_GTYPE_BALANCING`: balancing type, share bandwidth usage between links [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### SRT_SOCKGROUPCONFIG This structure is used to define entry points for connections for the [`srt_connect_group`](#srt_connect_group) function: ``` typedef struct SRT_GroupMemberConfig_ { SRTSOCKET id; struct sockaddr_storage srcaddr; struct sockaddr_storage peeraddr; int weight; SRT_SOCKOPT_CONFIG* config; int errorcode; int token; } SRT_SOCKGROUPCONFIG; ``` where: * `id`: member socket ID (filled back as output) * `srcaddr`: address to which `id` should be bound * `peeraddr`: address to which `id` should be connected * `weight`: the weight parameter for the link (group-type dependent) * `config`: the configuration object, if used (see [`srt_create_config()`](#srt_create_config)) * [`errorcode`](#error-codes): status of the connecting operation * `token`: An integer value unique for every connection, or -1 if unused The `srt_prepare_endpoint` sets these fields to default values. After that you can change the value of `weight` and `config` and `token` fields. The `weight` parameter's meaning is dependent on the group type: * BROADCAST: not used * BACKUP: positive value of link priority (the greater, the more preferred) * BALANCING: relative expected load on this link for fixed algorithm In any case, the allowed value for `weight` is between 0 and 32767. The `config` parameter is used to provide options to be set separately on a socket for a particular connection (see [`srt_create_config()`](#srt_create_config)). The `token` value is intended to allow the application to more easily identify a particular connection. If you don't use it and leave the default value of -1, the library will set a unique value for the next connection (a 32-bit unsigned number that will overflow by itself; the default value will be skipped). The application can also set a unique value by itself and keep the same value for the same connection. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### SRT_SOCKGROUPDATA The most important structure for the group member status is [`SRT_SOCKGROUPDATA`](#SRT_SOCKGROUPDATA): ```c++ typedef struct SRT_SocketGroupData_ { SRTSOCKET id; struct sockaddr_storage peeraddr; SRT_SOCKSTATUS sockstate; uint16_t weight; SRT_MEMBERSTATUS memberstate; int result; int token; } SRT_SOCKGROUPDATA; ``` where: * `id`: member socket ID * `peeraddr`: address to which `id` should be connected * `sockstate`: current connection status (see [`srt_getsockstate`](#srt_getsockstate) * `weight`: current weight value set on the link * `memberstate`: current state of the member (see below) * `result`: result of the operation (if this operation recently updated this structure) * `token`: A token value set for that connection (see [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG)) [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### SRT_MEMBERSTATUS The enumeration type that defines the state of a member connection in a group: * `SRT_GST_PENDING`: The connection is in progress, so the socket is not currently being used for transmission, even potentially, and still has a chance to fail and transit into `SRT_GST_BROKEN` without turning into `SRT_GST_IDLE` * `SRT_GST_IDLE`: The connection is established and ready to take over transmission, but it's not used for transmission at the moment. This state may last for a short moment in the case of broadcast or balancing groups. In backup groups this state defines a backup link that is ready to take over when the currently active (running) link becomes unstable. * `SRT_GST_RUNNING`: The connection is established and at least one packet has already been sent or received over it. * `SRT_GST_BROKEN`: The connection was broken. Broken connections are not to be revived. Note also that it is only possible to see this state if it is read by [`srt_sendmsg2`](#srt_sendmsg) or [`srt_recvmsg2`](#srt_recvmsg2) just after the link failure has been detected. Otherwise, the broken link simply disappears from the member list. Note that internally the member state is separate for sending and receiving. If the `memberstate` field of [`SRT_SOCKGROUPDATA`](#SRT_SOCKGROUPDATA) is `SRT_GST_RUNNING`, it means that this is the state in at least one direction, while in the other direction it may be `SRT_GST_IDLE`. In all other cases the states should be the same in both directions. States should normally start with `SRT_GST_PENDING` and then turn into `SRT_GST_IDLE`. Once a new link is used for sending data, the state becomes `SRT_GST_RUNNING`. In the case of the `SRT_GTYPE_BACKUP` type group, if a link is in the `SRT_GST_RUNNING` state, but another link is chosen to remain as the only active one, this link will be "silenced" (its state will become `SRT_GST_IDLE`). [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### Functions to Be Used on Groups * [srt_create_group](#srt_create_group) * [srt_include](#srt_include) * [srt_exclude](#srt_exclude) * [srt_groupof](#srt_groupof) * [srt_group_data](#srt_group_data) * [srt_connect_group](#srt_connect_group) * [srt_prepare_endpoint](#srt_prepare_endpoint) * [srt_create_config](#srt_create_config) * [srt_delete_config](#srt_delete_config) * [srt_config_add](#srt_config_add) #### srt_create_group ``` SRTSOCKET srt_create_group(SRT_GROUP_TYPE type); ``` Creates a new group of type `type`. This is typically called on the caller side to be next used for connecting to the listener peer side. The group ID is of the same domain as the socket ID, with the exception that the `SRTGROUP_MASK` bit is set on it, unlike for socket ID. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- #### srt_include ``` int srt_include(SRTSOCKET socket, SRTSOCKET group); ``` This function adds a socket to a group. This is only allowed for unmanaged groups. No such group type is currently implemented. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- #### srt_exclude ``` int srt_exclude(SRTSOCKET socket); ``` This function removes a socket from a group to which it currently belongs. This is only allowed for unmanaged groups. No such group type is currently implemented. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- #### srt_groupof ``` SRTSOCKET srt_groupof(SRTSOCKET socket); ``` Returns the group ID of the socket, or `SRT_INVALID_SOCK` if the socket doesn't exist or it's not a member of any group. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- #### srt_group_data ``` int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA output[], size_t* inoutlen); ``` **Arguments**: * `socketgroup` an existing socket group ID * `output` points to an output array * `inoutlen` points to a variable that stores the size of the `output` array, and is set to the filled array's size This function obtains the current member state of the group specified in `socketgroup`. The `output` should point to an array large enough to hold all the elements. The `inoutlen` should point to a variable initially set to the size of the `output` array. The current number of members will be written back to `inoutlen`. If the size of the `output` array is enough for the current number of members, the `output` array will be filled with group data and the function will return the number of elements filled. Otherwise the array will not be filled and `SRT_ERROR` will be returned. This function can be used to get the group size by setting `output` to `NULL`, and providing `socketgroup` and `inoutlen`. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | # of elements | The number of data elements filled, on success | | -1 | Error | | | | | Errors | | |:---------------------------------- |:--------------------------------------------------------- | | [`SRT_EINVPARAM`](#srt_einvparam) | Reported if `socketgroup` is not an existing group ID | | [`SRT_ELARGEMSG`](#srt_elargemsg) | Reported if `inoutlen` if less than the size of the group | | | | | in:output | in:inoutlen | returns | out:output | out:inoutlen | Error | |:---------:|:--------------:|:------------:|:----------:|:------------:|:---------------------------------:| | NULL | NULL | -1 | NULL | NULL | [`SRT_EINVPARAM`](#srt_einvparam) | | NULL | ptr | 0 | NULL | group.size() | âœ–ï¸ | | ptr | NULL | -1 | âœ–ï¸ | NULL | [`SRT_EINVPARAM`](#srt_einvparam) | | ptr | ≥ group.size | group.size() | group.data | group.size | âœ–ï¸ | | ptr | < group.size | -1 | âœ–ï¸ | group.size | [`SRT_ELARGEMSG`](#srt_elargemsg) | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- #### srt_connect_group ``` int srt_connect_group(SRTSOCKET group, SRT_SOCKGROUPCONFIG name [], int arraysize); ``` This function does almost the same as calling [`srt_connect`](#srt_connect) or [`srt_connect_bind`](#srt_connect_bind) (when the source was specified for [`srt_prepare_endpoint`](#srt_prepare_endpoint)) in a loop for every item specified in `name` array. However if you did this in blocking mode, the first call to [`srt_connect`](#srt_connect) would block until the connection is established, whereas this function blocks until any of the specified connections is established. If you set the group nonblocking mode ([`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) option), there's no difference, except that the [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure allows you to add extra configuration data used by groups. Note also that this function accepts only groups, not sockets. The elements of the `name` array need to be prepared with the use of the [`srt_prepare_endpoint`](#srt_prepare_endpoint) function. Note that it is **NOT** required that every target address you specify for it is of the same family. Return value and errors in this function are the same as in [`srt_connect`](#srt_connect), although this function reports success when at least one connection has succeeded. If none has succeeded, this function reports an [`SRT_ECONNLOST`](#srt_econnlost) error. Particular connection states can be obtained from the `name` array upon return from the [`errorcode`](#error-codes) field. The fields of [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure have the following meaning: **Input**: * `id`: unused, should be -1 (default when created by [`srt_prepare_endpoint`](#srt_prepare_endpoint)) * `srcaddr`: address to bind before connecting, if specified (see below for details) * `peeraddr`: target address to connect * `weight`: weight value to be set on the link * `config`: socket options to be set on the socket before connecting * [`errorcode`](#error-codes): unused, should be [`SRT_SUCCESS`](#srt_success) (default) * `token`: An integer value unique for every connection, or -1 if unused **Output**: * `id`: The socket created for that connection (-1 if failed to create) * `srcaddr`: unchanged * `peeraddr`: unchanged * `weight`: unchanged * `config`: unchanged (the object should be manually deleted upon return) * [`errorcode`](#error-codes): status of connection for that link ([`SRT_SUCCESS`](#srt_success) if succeeded) * `token`: same as in input, or a newly created token value if input was -1 The procedure of connecting for every connection definition specified in the `name` array is performed the following way: 1. The socket for this connection is first created 2. Socket options derived from the group are set on that socket. 3. If `config` is not NULL, configuration options stored there are set on that socket. 4. If source address is specified (that is `srcaddr` value is **not** default empty, as described in [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG), then the binding operation is done on the socket (see [`srt_bind`](#srt_bind)). 5. The socket is added to the group as a member. 6. The socket is connected to the target address, as specified in the `peeraddr` field. During this process there can be errors at any stage. There are two possibilities as to what may happen in this case: 1. If creation of a new socket has failed, which may only happen due to problems with system resources, then the whole loop is interrupted and no further items in the array are processed. All sockets that got created until then, and for which the connection attempt has at least successfully started, remain group members, although the function will return immediately with an error status (that is, without waiting for the first successful connection). If your application wants to do any partial recovery from this situation, it can only use the epoll mechanism to wait for readiness. 2. In any other case, if an error occurs at any stage of the above process, the processing is interrupted for this very array item only, the socket used for it is immediately closed, and the processing of the next elements continues. In the case of a connection process, it also passes two stages - parameter check and the process itself. Failure at the parameter check breaks this process, while if the check passes, this item is considered correctly processed, even if the connection attempt is going to fail later. If this function is called in blocking mode, it then blocks until at least one connection reports success, or if all of them fail. The status of connections that continue in the background after this function exits can then be checked by [`srt_group_data`](#srt_group_data). As member socket connections are running in the background, for determining if a particular connection has succeeded or failed it is recommended to use [`srt_connect_callback`](#srt_connect_callback). In this case the `token` callback function parameter will be the same as the `token` value used for the particular item in the `name` connection table. The `token` value doesn't have any limitations except that the -1 value is a "trap representation", that is, when set on input it will make the internals define a unique value for the `token`. Your application can also set unique values, in which case the `token` value will be preserved. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- #### srt_prepare_endpoint ``` SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src /*nullable*/, const struct sockaddr* adr, int namelen); ``` This function prepares a default [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) object as an element of the array you can prepare for [`srt_connect_group`](#srt_connect_group) function, filled with additional data: **Arguments**: * `src`: address to which the newly created socket should be bound * `adr`: address to which the newly created socket should connect * `namelen`: size of both `src` and `adr` The following fields are set by this function: * `id`: -1 (unused for input) * `srcaddr`: default empty (see below) or copied from `src` * `peeraddr`: copied from `adr` * `weight`: 0 * `config`: `NULL` * [`errorcode`](#error-codes): [`SRT_SUCCESS`](#srt_success) The default empty `srcaddr` is set the following way: * `ss_family` set to the same value as `adr->sa_family` * empty address (`INADDR_ANY` for IPv4 and `in6addr_any` for IPv6) * port number 0 If `src` is not NULL, then `srcaddr` is copied from `src`. Otherwise it will remain as default empty. The `adr` parameter is obligatory. If `src` parameter is not NULL, then both `adr` and `src` must have the same value of `sa_family`. Note though that this function has no possibility of reporting errors - these would be reported only by [`srt_connect_group`](#srt_connect_group), separately for every individual connection, and the status can be obtained from the [`errorcode`](#error-codes) field. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- #### srt_create_config ``` SRT_SOCKOPT_CONFIG* srt_create_config(); ``` Creates a dynamic object for specifying the socket options. You can add options to be set on the socket by [`srt_config_add`](#srt_config_add) and then mount this object into the `config` field in [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) object for that particular connection. After the object is no longer needed, you should delete it using [`srt_delete_config`](#srt_delete_config). | Returns | | |:----------------------------- |:------------------------------------------------------------------ | | Pointer | The pointer to the created object (memory allocation errors apply) | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- #### srt_delete_config ``` void srt_delete_config(SRT_SOCKOPT_CONFIG* c); ``` Deletes the configuration object. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- #### srt_config_add ``` int srt_config_add(SRT_SOCKOPT_CONFIG* c, SRT_SOCKOPT opt, void* val, int len); ``` Adds a configuration option to the configuration object. Parameters have meanings similar to [`srt_setsockflag`](#srt_setsockflag). Note that not every option is allowed to be set this way. However, the option (if allowed) isn't checked if it doesn't violate other preconditions. This will be checked when the option is being set on the socket, which may fail as a part of the connection process done by [`srt_connect_group`](#srt_connect_group). This function should be used when this option must be set individually on a socket and differently for a particular link. If you need to set some option the same way on every socket, you should instead set this option on the whole group. The following options are allowed to be set on the member socket: * [`SRTO_SNDBUF`](API-socket-options.md#SRTO_SNDBUF): Allows for larger sender buffer for slower links * [`SRTO_RCVBUF`](API-socket-options.md#SRTO_RCVBUF): Allows for larger receiver buffer for longer recovery * [`SRTO_UDP_RCVBUF`](API-socket-options.md#SRTO_UDP_RCVBUF): UDP receiver buffer, if this link has a big flight window * [`SRTO_UDP_SNDBUF`](API-socket-options.md#SRTO_UDP_SNDBUF): UDP sender buffer, if this link has a big flight window * [`SRTO_SNDDROPDELAY`](API-socket-options.md#SRTO_SNDDROPDELAY): When particular link tends to drop too eagerly * [`SRTO_NAKREPORT`](API-socket-options.md#SRTO_NAKREPORT): If you don't want NAKREPORT to work for this link * [`SRTO_CONNTIMEO`](API-socket-options.md#SRTO_CONNTIMEO): If you want to give more time to connect on this link * [`SRTO_LOSSMAXTTL`](API-socket-options.md#SRTO_LOSSMAXTTL): If this link tends to suffer from UDP reordering * [`SRTO_PEERIDLETIMEO`](API-socket-options.md#SRTO_PEERIDLETIMEO): If you want to be more tolerant for temporary outages * [`SRTO_GROUPSTABTIMEO`](API-socket-options.md#SRTO_GROUPSTABTIMEO): To set ACK jitter tolerance per individual link | Returns | | |:----------------------------- |:--------------------------------------------------------- | | 0 | Success | | -1 | Failure | | | | | Errors | | |:---------------------------------- |:--------------------------------------------------------------------- | | [`SRT_EINVPARAM`](#srt_einvparam) | This option is not allowed to be set on a socket being a group member | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Options and Properties * [srt_getpeername](#srt_getpeername) * [srt_getsockname](#srt_getsockname) * [srt_getsockopt, srt_getsockflag](#srt_getsockopt-srt_getsockflag) * [srt_setsockopt, srt_setsockflag](#srt_setsockopt-srt_setsockflag) * [srt_getversion](#srt_getversion) **NOTE**: For more information, see [SRT API Socket Options, Getting and Setting Options](API-socket-options.md#getting-and-setting-options). ### srt_getpeername ``` int srt_getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); ``` Retrieves the remote address to which the socket is connected. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | `SRT_ERROR` | (-1) in case of error, otherwise 0 | | | | | Errors | | |:------------------------------- |:------------------------------------------------------------------------ | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | | [`SRT_ENOCONN`](#srt_enoconn) | Socket [`u`](#u) isn't connected, so there's no remote address to return | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_getsockname ``` int srt_getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); ``` Extracts the address to which the socket was bound. Although you should know the address(es) that you have used for binding yourself, this function can be useful for extracting the local outgoing port number when it was specified as 0 with binding for system autoselection. With this function you can extract the port number after it has been autoselected. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | `SRT_ERROR` | (-1) in case of error, otherwise 0 | | | | | Errors | | |:------------------------------- |:---------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | | [`SRT_ENOCONN`](#srt_enoconn) | Socket [`u`](#u) isn't bound, so there's no local address to return
(:warning:   **BUG?** It should rather be [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock)) | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_getsockopt ### srt_getsockflag ```c++ int srt_getsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, void* optval, int* optlen); int srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); ``` Gets the value of the given socket option (from a socket or a group). The first version ([`srt_getsockopt`](#srt_getsockopt)) follows the BSD socket API convention, although the "level" parameter is ignored. The second version ([`srt_getsockflag`](#srt_getsockflag)) omits the "level" parameter completely. Options correspond to various data types (see [API-socket-options.md](./API-socket-options.md)). A variable `optval` of the appropriate data type has to be passed. The integer value of `optlen` should originally contain the size of the `optval` type provided; on return, it will be set to the size of the value returned. For most options, it will be the size of an integer. Some options, however, use types `bool`, `int64_t`, `C string`, etc. (see [API-socket-options.md](./API-socket-options.md#sockopt_types)). The application is responsible for allocating sufficient memory space as defined and pointed to by `optval`. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | `SRT_ERROR` | (-1) in case of error, otherwise 0 | | | | | Errors | | |:-------------------------------- |:---------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | | [`SRT_EINVOP`](#srt_einvop) | Option `opt` indicates no valid option | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_setsockopt ### srt_setsockflag ```c++ int srt_setsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, const void* optval, int optlen); int srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); ``` Sets a value for a socket option in the socket or group. The first version ([`srt_setsockopt`](#srt_setsockopt)) follows the BSD socket API convention, although the "level" parameter is ignored. The second version ([`srt_setsockflag`](#srt_setsockflag)) omits the "level" parameter completely. Options correspond to various data types, so you need to know what data type is assigned to a particular option, and to pass a variable of the appropriate data type with the option value to be set. Please note that some of the options can only be set on sockets or only on groups, although most of the options can be set on the groups so that they are then derived by the member sockets. | Returns | | |:----------------------------- |:----------------------------------------------- | | `SRT_ERROR` | (-1) in case of error, otherwise 0 | | | | | Errors | | |:----------------------------------- |:--------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | | [`SRT_EINVPARAM`](#srt_einvparam) | Option `opt` indicates no valid option | | [`SRT_EBOUNDSOCK`](#srt_eboundsock) | Tried to set an option with PRE_BIND restriction on a bound socket. | | [`SRT_ECONNSOCK`](#srt_econnsock) | Tried to set an option with PRE_BIND or PRE restriction on a socket in connecting/listening/connected state. | | | | **NOTE*: Various other errors may result from problems when setting a specific option (see option description in [API-socket-options.md](./API-socket-options.md) for details). [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_getversion ``` uint32_t srt_getversion(); ``` Get SRT version value. The version format in hex is 0xXXYYZZ for x.y.z in human readable form, where x = ("%d", (version>>16) & 0xff), etc. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | SRT Version | Unsigned 32-bit integer | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Helper Data Types for Transmission * [SRT_MSGCTRL](#SRT_MSGCTRL) **NOTE:** There might be a difference in terminology used in [SRT RFC](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00) and current documentation. Please consult [Data Transmission Modes](https://tools.ietf.org/html/draft-sharabayko-srt-00#section-4.2) and [Best Practices and Configuration Tips for Data Transmission via SRT](https://tools.ietf.org/html/draft-sharabayko-srt-00#page-71) sections of the RFC additionally. The current section is going to be reworked accordingly. ### SRT_MSGCTRL The [`SRT_MSGCTRL`](#SRT_MSGCTRL) structure: ```c++ typedef struct SRT_MsgCtrl_ { int flags; // Left for future int msgttl; // TTL for a message, default -1 (no TTL limitation) int inorder; // Whether a message is allowed to supersede a partially lost one. Unused in stream and live mode int boundary; // 0:mid pkt, 1(01b):end of frame, 2(11b):complete frame, 3(10b): start of frame int64_t srctime; // Source time, in microseconds since SRT internal clock epoch int32_t pktseq; // Sequence number of the first packet in received message (unused for sending) int32_t msgno; // Message number (output value for both sending and receiving) SRT_SOCKGROUPDATA* grpdata; // Pointer to group data array size_t grpdata_size; // Size of the group array } SRT_MSGCTRL; ``` The [`SRT_MSGCTRL`](#SRT_MSGCTRL) structure is used in [`srt_sendmsg2`](#srt_sendmsg) and [`srt_recvmsg2`](#srt_recvmsg2) calls and specifies some special extra parameters: - `flags`: [IN, OUT]. RESERVED FOR FUTURE USE (should be 0). This is intended to specify some special options controlling the details of how the called function should work. - `msgttl`: [IN]. In **message** and **live mode** only, specifies the TTL for sending messages (in `[ms]`). Not used for receiving messages. If this value is not negative, it defines the maximum time up to which this message should stay scheduled for sending for the sake of later retransmission. A message is always sent for the first time, but the UDP packet carrying it may be (also partially) lost, and if so, lacking packets will be retransmitted. If the message is not successfully resent before TTL expires, further retransmission is given up and the message is discarded. - `inorder`: [IN]. In **message mode** only, specifies that sent messages should be extracted by the receiver in the order of sending. This can be meaningful if a packet loss has happened, and a particular message must wait for retransmission so that it can be reassembled and then delivered. When this flag is false, the message can be delivered even if there are any previous messages still waiting for completion. - `boundary`: RESERVED FOR FUTURE USE. Intended to be used in a special mode when you are allowed to send or retrieve a part of the message. - `srctime`: - [OUT] Receiver only. Specifies the time when the packet was intended to be delivered to the receiving application (in microseconds since SRT clock epoch). - [IN] Sender only. Specifies the application-provided timestamp to be associated with the packet. If not provided (specified as 0), the current time of SRT internal clock is used. - For details on how to use `srctime` please refer to the (Time Access)[#time-access] section. - `pktseq`: Receiver only. Reports the sequence number for the packet carrying out the payload being returned. If the payload is carried out by more than one UDP packet, only the sequence of the first one is reported. Note that in **live mode** there's always one UDP packet per message. - `msgno`: Message number that can be sent by both sender and receiver, although it is required that this value remain monotonic in subsequent send calls. Normally message numbers start with 1 and increase with every message sent. - `grpdata` and `grpdata_size`: Pointer and size of the group array. For single socket connections these values should remain NULL and 0 respectively. When you call [`srt_sendmsg2`](#srt_sendmsg) or [`srt_recvmsg2`](#srt_recvmsg2) function for a group, you should pass an array here so that you can retrieve the status of particular member sockets. If you pass an array that is too small, your `grpdata_size` field will be rewritten with the current number of members, but without filling in the array. For details, see the [SRT Connection Bonding](../features/bonding-intro.md) and [SRT Socket Groups](../features/socket-groups.md) documents. **Helpers for [`SRT_MSGCTRL`](#SRT_MSGCTRL):** ``` void srt_msgctrl_init(SRT_MSGCTRL* mctrl); const SRT_MSGCTRL srt_msgctrl_default; ``` Helpers for getting an object of [`SRT_MSGCTRL`](#SRT_MSGCTRL) type ready to use. The first is a function that fills the object with default values. The second is a constant object and can be used as a source for assignment. Note that you cannot pass this constant object into any of the API functions because they require it to be mutable, as they use some fields to output values. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Transmission * [srt_send, srt_sendmsg, srt_sendmsg2](#srt_send-srt_sendmsg-srt_sendmsg2) * [srt_recv, srt_recvmsg, srt_recvmsg2](#srt_recv-srt_recvmsg-srt_recvmsg2) * [srt_sendfile, srt_recvfile](#srt_sendfile-srt_recvfile) **NOTE:** There might be a difference in terminology used in [SRT RFC](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00) and current documentation. Please consult [Data Transmission Modes](https://tools.ietf.org/html/draft-sharabayko-srt-00#section-4.2) and [Best Practices and Configuration Tips for Data Transmission via SRT](https://tools.ietf.org/html/draft-sharabayko-srt-00#page-71) sections of the RFC additionally. The current section is going to be reworked accordingly. ### srt_send ### srt_sendmsg ### srt_sendmsg2 ``` int srt_send(SRTSOCKET u, const char* buf, int len); int srt_sendmsg(SRTSOCKET u, const char* buf, int len, int ttl/* = -1*/, int inorder/* = false*/); int srt_sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL *mctrl); ``` Sends a payload to a remote party over a given socket. **Arguments**: * [`u`](#u): Socket used to send. The socket must be connected for this operation. * `buf`: Points to the buffer containing the payload to send. * `len`: Size of the payload specified in `buf`. * `ttl`: Time (in `[ms]`) to wait for a successful delivery. See description of the [`SRT_MSGCTRL::msgttl`](#SRT_MSGCTRL) field. * `inorder`: Required to be received in the order of sending. See [`SRT_MSGCTRL::inorder`](#SRT_MSGCTRL). * `mctrl`: An object of [`SRT_MSGCTRL`](#SRT_MSGCTRL) type that contains extra parameters, including `ttl` and `inorder`. The way this function works is determined by the mode set in options, and it has specific requirements: 1. In **file/stream mode**, the payload is byte-based. You are not required to know the size of the data, although they are only guaranteed to be received in the same byte order. 2. In **file/message mode**, the payload that you send using this function is a single message that you intend to be received as a whole. In other words, a single call to this function determines a message's boundaries. 3. In **live mode**, you are only allowed to send up to the length of `SRTO_PAYLOADSIZE`, which can't be larger than 1456 bytes (1316 default). | Returns | | |:----------------------------- |:--------------------------------------------------------- | | Size | Size of the data sent, if successful | | `SRT_ERROR` | In case of error (-1) | | | | **NOTE**: Note that in **file/stream mode** the returned size may be less than `len`, which means that it didn't send the whole contents of the buffer. You would need to call this function again with the rest of the buffer next time to send it completely. In both **file/message** and **live mode** the successful return is always equal to `len`. | Errors | | |:--------------------------------------------- |:------------------------------------------------------------------------------------------------------------------- | | [`SRT_ENOCONN`](#srt_enoconn) | Socket [`u`](#u) used when the operation is not connected. | | [`SRT_ECONNLOST`](#srt_econnlost) | Socket [`u`](#u) used for the operation has lost its connection. | | [`SRT_EINVALMSGAPI`](#srt_einvalmsgapi) | Incorrect API usage in **message mode**:
**live mode**: trying to send more bytes at once than `SRTO_PAYLOADSIZE` or wrong source time
was provided. | | [`SRT_EINVALBUFFERAPI`](#srt_einvalbufferapi) | Incorrect API usage in **stream mode** (reserved for future use):
The congestion controller object used for this mode doesn't use any restrictions on this call,
but this may change. | | [`SRT_ELARGEMSG`](#srt_elargemsg) | Message to be sent can't fit in the sending buffer (that is, it exceeds the current total space in the
sending buffer in bytes). This means that the sender buffer is too small, or the application is
trying to send a larger message than initially predicted. | | [`SRT_EASYNCSND`](#srt_easyncsnd) | There's no free space currently in the buffer to schedule the payload. This is only reported in
non-blocking mode ([`SRTO_SNDSYN`](API-socket-options.md#SRTO_SNDSYN) set to false); in blocking mode the call is blocked until
enough free space in the sending buffer becomes available. | | [`SRT_ETIMEOUT`](#srt_etimeout) | The condition described above still persists and the timeout has passed. This is only reported in
blocking mode when [`SRTO_SNDTIMEO`](API-socket-options.md#SRTO_SNDTIMEO) is set to a value other than -1. | | [`SRT_EPEERERR`](#srt_epeererr) | This is reported only in the case where, as a stream is being received by a peer, the
[`srt_recvfile`](#srt_recvfile) function encounters an error during a write operation on a file. This is reported by
a `UMSG_PEERERROR` message from the peer, and the agent sets the appropriate flag internally.
This flag persists up to the moment when the connection is broken or closed. | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_recv ### srt_recvmsg ### srt_recvmsg2 ``` int srt_recv(SRTSOCKET u, char* buf, int len); int srt_recvmsg(SRTSOCKET u, char* buf, int len); int srt_recvmsg2(SRTSOCKET u, char *buf, int len, SRT_MSGCTRL *mctrl); ``` Extracts the payload waiting to be received. Note that [`srt_recv`](#srt_recv) and [`srt_recvmsg`](#srt_recvmsg) are identical functions, two different names being kept for historical reasons. In the UDT predecessor the application was required to use either the `UDT::recv` version for **stream mode** and `UDT::recvmsg` for **message mode**. In SRT this distinction is resolved internally by the [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) flag. **Arguments**: * [`u`](#u): Socket used to send. The socket must be connected for this operation. * `buf`: Points to the buffer to which the payload is copied. * `len`: Size of the payload specified in `buf`. * `mctrl`: An object of [`SRT_MSGCTRL`](#SRT_MSGCTRL) type that contains extra parameters. The way this function works is determined by the mode set in options, and it has specific requirements: 1. In **file/stream mode**, as many bytes as possible are retrieved, that is, only so many bytes that fit in the buffer and are currently available. Any data that is available but not extracted this time will be available next time. 2. In **file/message mode**, exactly one message is retrieved, with the boundaries defined at the moment of sending. If some parts of the messages are already retrieved, but not the whole message, nothing will be received (the function blocks or returns [`SRT_EASYNCRCV`](#srt_easyncrcv)). If the message to be returned does not fit in the buffer, nothing will be received and the error is reported. 3. In **live mode**, the function behaves as in **file/message mode**, although the number of bytes retrieved will be at most the maximum payload of one MTU. The [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) value configured by the sender is not negotiated, and not known to the receiver. The [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) value set on the SRT receiver is mainly used for heuristics. However, the receiver is prepared to receive the whole MTU as configured with [`SRTO_MSS`](API-socket-options.md#SRTO_MSS). In this mode, however, with default settings of [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) and [`SRTO_TLPKTDROP`](API-socket-options.md#SRTO_TLPKTDROP), the message will be received only when its time to play has come, and until then it will be kept in the receiver buffer. Also, when the time to play has come for a message that is next to the currently lost one, it will be delivered and the lost one dropped. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | Size | Size (\>0) of the data received, if successful. | | 0 | If the connection has been closed | | `SRT_ERROR` | (-1) when an error occurs | | | | | Errors | | |:--------------------------------------------- |:--------------------------------------------------------- | | [`SRT_ENOCONN`](#srt_enoconn) | Socket [`u`](#u) used for the operation is not connected. | | [`SRT_ECONNLOST`](#srt_econnlost) | Socket [`u`](#u) used for the operation has lost connection (this is reported only if the connection
was unexpectedly broken, not when it was closed by the foreign host). | | [`SRT_EINVALMSGAPI`](#srt_einvalmsgapi) | Incorrect API usage in **message mode**:
-- **live mode**: size of the buffer is less than [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) | | [`SRT_EINVALBUFFERAPI`](#srt_einvalbufferapi) | Incorrect API usage in **stream mode**:
• Currently not in use. File congestion control used for **stream mode** does not restrict
the parameters. :warning:   **???** | | [`SRT_ELARGEMSG`](#srt_elargemsg) | Message to be sent can't fit in the sending buffer (that is, it exceeds the current total space in
the sending buffer in bytes). This means that the sender buffer is too small, or the application
is trying to send a larger message than initially intended. | | [`SRT_EASYNCRCV`](#srt_easyncrcv) | There are no data currently waiting for delivery. This happens only in non-blocking mode
(when [`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) is set to false). In blocking mode the call is blocked until the data are ready.
How this is defined, depends on the mode:
• In **live mode** (with [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) on), at least one packet must be present in the receiver
buffer and its time to play be in the past
• In **file/message mode**, one full message must be available, the next one waiting if there are no
messages with `inorder` = false, or possibly the first message ready with `inorder` = false
• In **file/stream mode**, it is expected to have at least one byte of data still not extracted | | [`SRT_ETIMEOUT`](#srt_etimeout) | The readiness condition described above is still not achieved and the timeout has passed.
This is only reported in blocking mode when[`SRTO_RCVTIMEO`](API-socket-options.md#SRTO_RCVTIMEO) is set to a value other than -1. | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_sendfile ### srt_recvfile ``` int64_t srt_sendfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block); int64_t srt_recvfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block); ``` These are functions dedicated to sending and receiving a file. You need to call this function just once for the whole file, although you need to know the size of the file prior to sending, and also define the size of a single block that should be internally retrieved and written into a file in a single step. This influences only the performance of the internal operations; from the application perspective you just have one call that exits only when the transmission is complete. **Arguments**: * [`u`](#u): Socket used for transmission. The socket must be connected. * `path`: Path to the file that should be read or written. * `offset`: Needed to pass or retrieve the offset used to read or write to a file * `size`: Size of transfer (file size, if offset is at 0) * `block`: Size of the single block to read at once before writing it to a file The following values are recommended for the `block` parameter: ``` #define SRT_DEFAULT_SENDFILE_BLOCK 364000 #define SRT_DEFAULT_RECVFILE_BLOCK 7280000 ``` You need to pass them to the [`srt_sendfile`](#srt_sendfile) or [`srt_recvfile`](#srt_recvfile) function if you don't know what value to chose. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | Size | The size (\>0) of the transmitted data of a file. It may be less than `size`, if the size was greater
than the free space in the buffer, in which case you have to send rest of the file next time. | | -1 | in case of error | | | | | Errors | | |:--------------------------------------------- |:----------------------------------------------------------------------------- | | [`SRT_ENOCONN`](#srt_enoconn) | Socket [`u`](#u) used for the operation is not connected. | | [`SRT_ECONNLOST`](#srt_econnlost) | Socket [`u`](#u) used for the operation has lost its connection. | | [`SRT_EINVALBUFFERAPI`](#srt_einvalbufferapi) | When socket has [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) = true or [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) = true.
(:warning:   **BUG?**: Looxlike MESSAGEAPI isn't checked) | | [`SRT_EINVRDOFF`](#srt_einvrdoff) | There is a mistake in `offset` or `size` parameters, which should match the index availability
and size of the bytes available since `offset` index. This is actually reported for [`srt_sendfile`](#srt_sendfile)
when the `seekg` or `tellg` operations resulted in error. | | [`SRT_EINVWROFF`](#srt_einvwroff) | Like above, reported for [`srt_recvfile`](#srt_recvfile) and `seekp`/`tellp`. | | [`SRT_ERDPERM`](#srt_erdperm) | The read from file operation has failed ([`srt_sendfile`](#srt_sendfile)). | | [`SRT_EWRPERM`](#srt_ewrperm) | The write to file operation has failed ([`srt_recvfile`](#srt_recvfile)). | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Performance Tracking * [srt_bstats, srt_bistats](#srt_bstats-srt_bistats) **Sequence Numbers:** The sequence numbers used in SRT are 32-bit "circular numbers" with the most significant bit not included. For example 0x7FFFFFFF shifted forward by 3 becomes 2. As far as any comparison is concerned, it can be thought of as a "distance" which is an integer value expressing an offset to be added to one sequence number in order to get the second one. This distance is only valid as long as the threshold value isn't exceeded, so it's understood that all sequence numbers that are anywhere taken into account are systematically updated and kept in the range between 0 and half of the maximum 0x7FFFFFFF. Hence, the distance counting procedure always assumes that the sequence number are in the required range already, so for a numbers like 0x7FFFFFF0 and 0x10, for which the "numeric difference" would be 0x7FFFFFE0, the "distance" is 0x20. ### srt_bstats ### srt_bistats ``` // Performance monitor with Byte counters for better bitrate estimation. int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); // Performance monitor with Byte counters and instantaneous stats instead of moving averages for Snd/Rcvbuffer sizes. int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous); ``` Reports the current statistics **Arguments**: * [`u`](#u): Socket from which to get statistics * `perf`: Pointer to an object to be written with the statistics * `clear`: 1 if the statistics should be cleared after retrieval * `instantaneous`: 1 if the statistics should use instant data, not moving averages `SRT_TRACEBSTATS` is an alias to `struct CBytePerfMon`. For a complete description of the fields please refer to [SRT Statistics](statistics.md). [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Asynchronous Operations (Epoll) * [srt_epoll_create](#srt_epoll_create) * [srt_epoll_add_usock, srt_epoll_add_ssock, srt_epoll_update_usock, srt_epoll_update_ssock](#srt_epoll_add_usock-srt_epoll_add_ssock-srt_epoll_update_usock-srt_epoll_update_ssock) * [srt_epoll_remove_usock, srt_epoll_remove_ssock](#srt_epoll_remove_usock-srt_epoll_remove_ssock) * [srt_epoll_wait](#srt_epoll_wait) * [srt_epoll_uwait](#srt_epoll_uwait) * [srt_epoll_clear_usocks](#srt_epoll_clear_usocks) * [srt_epoll_set](#srt_epoll_set) * [srt_epoll_release](#srt_epoll_release) The epoll system is currently the only method for using multiple sockets in one thread with having the blocking operation moved to epoll waiting so that it can block on multiple sockets at once. That is, instead of blocking a single reading or writing operation, as it's in blocking mode, it blocks until at least one of the sockets subscribed for a single waiting call in given operation mode is ready to do this operation without blocking. It's usually combined with setting the nonblocking mode on a socket. In SRT this is set separately for reading and writing ([`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) and [`SRTO_SNDSYN`](API-socket-options.md#SRTO_SNDSYN) respectively). This is to ensure that if there is internal error in the application (or even possibly a bug in SRT that has reported a spurious readiness report) the operation will end up with an error rather than cause blocking, which would be more dangerous for the application ([`SRT_EASYNCRCV`](#srt_easyncrcv) and [`SRT_EASYNCSND`](#srt_easyncsnd) respectively). The epoll system, similar to the one on Linux, relies on [`eid`](#eid) objects managed internally in SRT, which can be subscribed to particular sockets and the readiness status of particular operations. The [`srt_epoll_wait`](#srt_epoll_wait) function can then be used to block until any readiness status in the whole [`eid`](#eid) is set. ### srt_epoll_create ``` int srt_epoll_create(void); ``` Creates a new epoll container. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | valid EID | Success | | -1 | Failure | | | | | Errors | | |:----------------------------------- |:--------------------------------------------------------------------- | | [`SRT_ECONNSETUP`](#srt_econnsetup) | System operation failed or not enough space to create a new epoll. System error might happen on
systems that use a special method for the system part of epoll (`epoll_create()`, `kqueue()`),
and therefore associated resources, like epoll on Linux. | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_epoll_add_usock ### srt_epoll_add_ssock ### srt_epoll_update_usock ### srt_epoll_update_ssock ``` int srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); int srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); ``` Adds a socket to a container, or updates an existing socket subscription. The `_usock` suffix refers to a user socket (SRT socket). The `_ssock` suffix refers to a system socket. The `_add_` functions add new sockets. The `_update_` functions act on a socket that is in the container already and just allow changes in the subscription details. For example, if you have already subscribed a socket with [`SRT_EPOLL_OUT`](#SRT_EPOLL_OUT) to wait until it's connected, to change it into poll for read-readiness, you use this function on that same socket with a variable set to [`SRT_EPOLL_IN`](#SRT_EPOLL_IN). This will not only change the event type which is polled on the socket, but also remove any readiness status for flags that are no longer set. It is discouraged to perform socket removal and adding back (instead of using `_update_`) because this way you may miss an event that could happen in the brief moment between these two calls. **Arguments**: * `eid`: epoll container id * `u`: SRT socket * `s`(#s): system socket * `events`: points to * a variable set to epoll flags (see below) to use only selected events * NULL if you want to subscribe a socket for all events in level-triggered mode Possible epoll flags are the following: * `SRT_EPOLL_IN`: report readiness for reading or incoming connection on a listener socket * `SRT_EPOLL_OUT`: report readiness for writing or a successful connection * `SRT_EPOLL_ERR`: report errors on the socket * `SRT_EPOLL_UPDATE`: an important event has happened that requires attention * `SRT_EPOLL_ET`: the event will be edge-triggered All flags except [`SRT_EPOLL_ET`](#SRT_EPOLL_ET) are event type flags (important for functions that expect only event types and not other flags). The [`SRT_EPOLL_IN`](#SRT_EPOLL_IN), [`SRT_EPOLL_OUT`](#SRT_EPOLL_OUT) and [`SRT_EPOLL_ERR`](#SRT_EPOLL_ERR) events are by default **level-triggered**. With [`SRT_EPOLL_ET`](#SRT_EPOLL_ET) flag they become **edge-triggered**. The [`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) flag is always edge-triggered. It designates a special event that happens on a group, or on a listener socket that has the [`SRTO_GROUPCONNECT`](API-socket-options.md#SRTO_GROUPCONNECT) flag set to allow group connections. This flag is triggered in the following situations: * for group connections, when a new link has been established for a group that is already connected (that is, has at least one connection established), [`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) is reported for the listener socket accepting the connection. This is intended for internal use only. An initial connection results in reporting the group connection on that listener. But when the group is already connected, [`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) is reported instead. * when one of a group's member connection has been broken Note that at this time the edge-triggered mode is supported only for SRT sockets, not for system sockets. In the **edge-triggered** mode the function will only return socket states that have changed since the last call of the waiting function. All events reported in a particular call of the waiting function will be cleared in the internal flags and will not be reported until the internal signalling logic clears this state and raises it again. In the **level-triggered** mode the function will always return the readiness state as long as it lasts, until the internal signalling logic clears it. Note that when you use [`SRT_EPOLL_ET`](#SRT_EPOLL_ET) flag in one subscription call, it defines edge-triggered mode for all events passed together with it. However, if you want to have some events reported as edge-triggered and others as level-triggered, you can do two separate subscriptions for the same socket. **IMPORTANT**: The [`srt_epoll_wait`](#srt_epoll_wait) function does not report [`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) events. If you need the ability to get any possible flag, you must use [`srt_epoll_wait`](#srt_epoll_wait). Note that this function doesn't work with system file descriptors. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | 0 | Success | | -1 | Failure | | | | | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | | | | :warning:   **BUG?**: for `add_ssock` the system error results in an empty `CUDTException()` call which actually results in [`SRT_SUCCESS`](#srt_success). For cases like that the [`SRT_ECONNSETUP`](#srt_econnsetup) code is predicted. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_epoll_remove_usock ### srt_epoll_remove_ssock ``` int srt_epoll_remove_usock(int eid, SRTSOCKET u); int srt_epoll_remove_ssock(int eid, SYSSOCKET s); ``` Removes a specified socket from an epoll container and clears all readiness states recorded for that socket. The `_usock` suffix refers to a user socket (SRT socket). The `_ssock` suffix refers to a system socket. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | 0 | Success | | -1 | Failure | | | | | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_epoll_wait ``` 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); ``` Blocks the call until any readiness state occurs in the epoll container. Readiness can be on a socket in the container for the event type as per subscription. Note that in the case when a particular event was subscribed with [`SRT_EPOLL_ET`](#SRT_EPOLL_ET) flag, this event, once reported in this function, will be cleared internally. The first readiness state causes this function to exit, but all ready sockets are reported. This function blocks until the timeout specified in the `msTimeOut` parameter. If timeout is 0, it exits immediately after checking. If timeout is -1, it blocks indefinitely until a readiness state occurs. **Arguments**: * `eid`: epoll container * `readfds` and `rnum`: A pointer and length of an array to write SRT sockets that are read-ready * `writefds` and `wnum`: A pointer and length of an array to write SRT sockets that are write-ready * `msTimeOut`: Timeout specified in milliseconds, or special values (0 or -1) * `lwfds` and `lwnum`: A pointer and length of an array to write system sockets that are read-ready * `lwfds` and `lwnum`: A pointer and length of an array to write system sockets that are write-ready Note that the following flags are reported: * [`SRT_EPOLL_IN`](#SRT_EPOLL_IN) as read-ready (also a listener socket ready to accept) * [`SRT_EPOLL_OUT`](#SRT_EPOLL_OUT) as write-ready (also a connected socket) * [`SRT_EPOLL_ERR`](#SRT_EPOLL_ERR) as both read-ready and write-ready * [`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) is not reported There is no space here to report sockets for which it's already known that the operation will end up with error (although such a state is known internally). If an error occurred on a socket then that socket is reported in both read-ready and write-ready arrays, regardless of what event types it was subscribed for. Usually then you subscribe the given socket for only read readiness, for example ([`SRT_EPOLL_IN`](#SRT_EPOLL_IN)), but pass both arrays for read and write readiness.This socket will not be reported in the write readiness array even if it's write ready (because this isn't what it was subscribed for), but it will be reported there, if the next operation on this socket is about to be erroneous. On such sockets you can still perform an operation, just you should expect that it will always report an error. On the other hand that's the only way to know what kind of error has occurred on the socket. | Returns | | |:----------------------------- |:------------------------------------------------------------ | | Number | The number (\>0) of ready sockets, of whatever kind (if any) | | -1 | Error | | | | | Errors | | |:----------------------------------- |:------------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | | [`SRT_ETIMEOUT`](#srt_etimeout) | Up to `msTimeOut` no sockets subscribed in [`eid`](#eid) were ready. This is reported only if `msTimeOut`
was \>=0, otherwise the function waits indefinitely. | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_epoll_uwait ``` int srt_epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); ``` This function blocks a call until any readiness state occurs in the epoll container. Unlike [`srt_epoll_wait`](#srt_epoll_wait), it can only be used with [`eid`](#eid) subscribed to user sockets (SRT sockets), not system sockets. This function blocks until the timeout specified in `msTimeOut` parameter. If timeout is 0, it exits immediately after checking. If timeout is -1, it blocks indefinitely until a readiness state occurs. **Arguments**: * `eid`: epoll container * `fdsSet` : A pointer to an array of `SRT_EPOLL_EVENT` * `fdsSize` : The size of the fdsSet array * `msTimeOut` : Timeout specified in milliseconds, or special values (0 or -1): * 0: Don't wait, return immediately (report any sockets currently ready) * -1: Wait indefinitely. | Returns | | |:----------------------------- |:-------------------------------------------------------------------------------------------------------------------------------------- | | Number | The number of user socket (SRT socket) state changes that have been reported in `fdsSet`,
if this number isn't greater than `fdsSize` | | `fdsSize` + 1 | This means that there was not enough space in the output array to report all events.
For events subscribed with the [`SRT_EPOLL_ET`](#SRT_EPOLL_ET) flag only those will be cleared that were reported.
Others will wait for the next call. | | 0 | If no readiness state was found on any socket and the timeout has passed
(this is not possible when waiting indefinitely) | | -1 | Error | | | | | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | | [`SRT_EINVPARAM`](#srt_einvparam) | One of possible usage errors:
* `fdsSize` is < 0
* `fdsSize` is > 0 and `fdsSet` is a null pointer
* [`eid`](#eid) was subscribed to any system socket | | | | **IMPORTANT**: This function reports timeout by returning 0, not by [`SRT_ETIMEOUT`](#srt_etimeout) error. The `SRT_EPOLL_EVENT` structure: ``` typedef struct SRT_EPOLL_EVENT_ { SRTSOCKET fd; int events; } SRT_EPOLL_EVENT; ``` * `fd`: the user socket (SRT socket) * [`events`](#events): event flags that report readiness of this socket - a combination of [`SRT_EPOLL_IN`](#SRT_EPOLL_IN), [`SRT_EPOLL_OUT`](#SRT_EPOLL_OUT) and [`SRT_EPOLL_ERR`](#SRT_EPOLL_ERR). See [srt_epoll_add_usock](#srt_epoll_add_usock) for details. Note that when [`SRT_EPOLL_ERR`](#SRT_EPOLL_ERR) is set, the underlying socket error can't be retrieved with `srt_getlasterror()`. The socket will be automatically closed and its state can be verified with a call to [`srt_getsockstate`](#srt_getsockstate). [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_epoll_clear_usocks ``` int srt_epoll_clear_usocks(int eid); ``` This function removes all SRT ("user") socket subscriptions from the epoll container identified by [`eid`](#eid). | Returns | | |:----------------------------- |:--------------------------------------------------------- | | 0 | Success | | -1 | Failure | | | | | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_epoll_set ``` int32_t srt_epoll_set(int eid, int32_t flags); ``` This function allows setting or retrieving flags that change the default behavior of the epoll functions. All default values for these flags are 0. The following flags are available: * `SRT_EPOLL_ENABLE_EMPTY`: allows the [`srt_epoll_wait`](#srt_epoll_wait) and [`srt_epoll_uwait`](#srt_epoll_uwait) functions to be called with the EID not subscribed to any socket. The default behavior of these function is to report error in this case. * `SRT_EPOLL_ENABLE_OUTPUTCHECK`: Forces the [`srt_epoll_wait`](#srt_epoll_wait) and [`srt_epoll_uwait`](#srt_epoll_uwait) functions to check if the output array is not empty. For [`srt_epoll_wait`](#srt_epoll_wait) it is still allowed that either system or user array is empty, as long as EID isn't subscribed to this type of socket/fd. [`srt_epoll_uwait`](#srt_epoll_uwait) only checks if the general output array is not empty. **Arguments**: * `eid`: the epoll container id * `flags`: a nonzero set of the above flags, or special values: * 0: clear all flags (set all defaults) * -1: do not modify any flags | Returns | | |:----------------------------- |:-------------------------------------------------------------------------- | | | This function returns the state of the flags at the time before the call | | -1 | Special value in case when an error occurred | | | | | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_epoll_release ``` int srt_epoll_release(int eid); ``` Deletes the epoll container. | Returns | | |:----------------------------- |:-------------------------------------------------------------- | | | The number (\>0) of ready sockets, of whatever kind (if any) | | -1 | Error | | | | | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Logging Control * [srt_setloglevel](#srt_setloglevel) * [srt_addlogfa, srt_dellogfa, srt_resetlogfa](#srt_addlogfa-srt_dellogfa-srt_resetlogfa) * [srt_setloghandler](#srt_setloghandler) * [srt_setlogflags](#srt_setlogflags) SRT has a widely used system of logs, as this is usually the only way to determine how the internals are working without changing the rules by the act of tracing. Logs are split into levels (5 levels out of those defined by syslog are in use) and additional filtering is possible on an FA (functional area). By default only entries up to the *Note* log level are displayed and from all FAs. Logging can only be manipulated globally, with no regard to a specific socket. This is because lots of operations in SRT are not dedicated to any particular socket, and some are shared between sockets. ### srt_setloglevel ``` void srt_setloglevel(int ll); ``` Sets the minimum severity for logging. A particular log entry is displayed only if it has a severity greater than or equal to the minimum. Setting this value to `LOG_DEBUG` turns on all levels. The constants for this value are those from `` (for Windows, refer to `common/win/syslog_defs.h`). The only meaningful ones are: * `LOG_DEBUG`: Highly detailed and very frequent messages * `LOG_NOTICE`: Occasionally displayed information * `LOG_WARNING`: Unusual behavior * `LOG_ERR`: Abnormal behavior * `LOG_CRIT`: Error that makes the current socket unusable [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_addlogfa ### srt_dellogfa ### srt_resetlogfa ```c++ void srt_addlogfa(int fa); void srt_dellogfa(int fa); void srt_resetlogfa(const int* fara, size_t fara_size); ``` A functional area (FA) is an additional filtering mechanism for logging. You can set up logging to display logs only from selected FAs. The list of FAs is collected in the `srt.h` file, as identified by the `SRT_LOGFA_` prefix. They are not enumerated here because they may be changed very often. All FAs are turned on by default, except potentially dangerous ones (such as `SRT_LOGFA_HAICRYPT`). The reason is that they may display either some security information that shall remain in memory only (so, only if strictly required for development), or some duplicated information (so you may want to turn one FA on, while turning off the others). [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_setloghandler ```c++ void srt_setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler); typedef void SRT_LOG_HANDLER_FN(void* opaque, int level, const char* file, int line, const char* area, const char* message); ``` By default logs are printed to standard error stream. This function replaces the sending to a stream with a handler function that will receive them. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_setlogflags ```c++ void srt_setlogflags(int flags); ``` When you set a log handler with [`srt_setloghandler`](#srt_setloghandler), you may also want to configure which parts of the log information you do not wish to be passed in the log line (the `message` parameter). A user's logging facility may, for example, not wish to get the current time or log level marker, as it will provide this information on its own. The following flags are available, as collected in the `logging_api.h` public header: - `SRT_LOGF_DISABLE_TIME`: Do not provide the time in the header - `SRT_LOGF_DISABLE_THREADNAME`: Do not provide the thread name in the header - `SRT_LOGF_DISABLE_SEVERITY`: Do not provide severity information in the header - `SRT_LOGF_DISABLE_EOL`: Do not add the end-of-line character to the log line [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Time Access * [srt_time_now](#srt_time_now) * [srt_connection_time](#srt_connection_time) The following set of functions is intended to retrieve timestamps from the clock used by SRT. The sender can pass the timestamp in `MSGCTRL::srctime` of the `srt_sendmsg2(..)` function together with the packet being submitted to SRT. If the `srctime` value is not provided (the default value of 0 is set), SRT will use the internal clock and assign packet submission time as the packet timestamp. If the sender wants to explicitly assign a timestamp to a certain packet this timestamp MUST be taken from SRT Time Access functions. The time value provided MUST equal or exceed the connection start time (`srt_connection_time(..)`) of the SRT socket passed to `srt_sendmsg2(..)`. The current time value of the SRT internal clock can be retrieved using the `srt_time_now()` function. There are two known cases where you might want to use `srctime`: 1. SRT passthrough (for stream gateways). You may wish to simply retrieve packets from an SRT source and pass them transparently to an SRT output (possibly re-encrypting). In that case, every packet you read should preserve the original value of `srctime` as obtained from [`srt_recvmsg2`](#srt_recvmsg2), and the original `srctime` for each packet should be then passed to [`srt_sendmsg2`](#srt_sendmsg). This mechanism could be used to avoid jitter resulting from varying differences between the time of receiving and sending the same packet. 2. Stable timing source. In the case of a live streaming procedure, when spreading packets evenly into the stream, you might want to predefine times for every single packet to keep time intervals perfectly equal. Or, if you believe that your input signal delivers packets at the exact times that should be assigned to them, you might want to preserve these times at the SRT receiving side to avoid jitter that may result from varying time differences between the packet arrival and the moment when sending it over SRT. In such cases you might do the following: - At the packet arrival time, grab the current time at that moment using `srt_time_now()`. - When you want a pre-calculated packet time, use a private relative time counter set at the moment when the connection was made. From the moment when your first packet is ready, start pre-calculating packet times relative to the connection start time obtained from `srt_connection_time()`. Although you still have to synchronize sending times with these predefined times, by explicitly specifying the source time you avoid the jitter resulting from a lost accuracy due to waiting time and unfortunate thread scheduling. Note that `srctime` uses an internally defined clock that is intended to be monotonic (the definition depends on the build flags, see below). Because of that **the application should not define this time basing on values obtained from the system functions for getting the current system time** (such as `time`, `ftime` or `gettimeofday`). To avoid problems and misunderstanding you should rely exclusively on time values provided by the `srt_time_now()` and `srt_connection_time()` functions. The clock used by the SRT internal clock is determined by the following build flags: - `ENABLE_MONOTONIC` makes use of `CLOCK_MONOTONIC` with `clock_gettime` function. - `ENABLE_STDXXX_SYNC` makes use of `std::chrono::steady_clock`. The default is currently to use the system clock as the internal SRT clock, although it's highly recommended to use one of the above monotonic clocks, as system clock is vulnerable to time modifications during transmission. ### srt_time_now ```c++ int64_t srt_time_now(); ``` Get time in microseconds elapsed since epoch using SRT internal clock (steady or monotonic clock). | Returns | | |:----------------------------- |:------------------------------------------------------------------------ | | | Current time in microseconds elapsed since epoch of SRT internal clock. | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_connection_time ```c++ int64_t srt_connection_time(SRTSOCKET sock); ``` Get connection time in microseconds elapsed since epoch using SRT internal clock (steady or monotonic clock). The connection time represents the time when SRT socket was open to establish a connection. Milliseconds elapsed since connection start time can be determined using [**Performance tracking**](#Performance-tracking) functions and `msTimeStamp` value of the `SRT_TRACEBSTATS` (see [SRT Statistics](statistics.md)). | Returns | | |:----------------------------- |:--------------------------------------------------------------------------- | | | Connection time in microseconds elapsed since epoch of SRT internal clock | | -1 | Error | | | | | Errors | | |:--------------------------------- |:---------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket `sock` is not an ID of a valid SRT socket | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_clock_type ```c int srt_clock_type(void); ``` Get the type of clock used internally by SRT to be used only for informtational peurpose. Using any time source except for [`srt_time_now()`](#srt_time_now) and [`srt_connection_time(SRTSOCKET)`](#srt_connection_time) to timestamp packets submitted to SRT is not recommended and must be done with awareness and at your own risk. | Returns | Clock Type | Description | | :------ | :---------------------------------- | :------------------------------------------| | 0 | `SRT_SYNC_CLOCK_STDCXX_STEADY` | C++11 `std::chrono::steady_clock` | | 1 | `SRT_SYNC_CLOCK_GETTIME_MONOTONIC` | `clock_gettime` with `CLOCK_MONOTONIC` | | 2 | `SRT_SYNC_CLOCK_WINQPC` | Windows `QueryPerformanceCounter(..)` | | 3 | `SRT_SYNC_CLOCK_MACH_ABSTIME` | `mach_absolute_time()` | | 4 | `SRT_SYNC_CLOCK_POSIX_GETTIMEOFDAY` | POSIX `gettimeofday(..)` | | 5 | `SRT_SYNC_CLOCK_AMD64_RDTSC` | `asm("rdtsc" ..)` | | 6 | `SRT_SYNC_CLOCK_IA32_RDTSC` | `asm volatile("rdtsc" ..)` | | 7 | `SRT_SYNC_CLOCK_IA64_ITC` | `asm("mov %0=ar.itc" ..)` | | Errors | | |:--------------------------------- |:---------------------------------------------------------- | | None | | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ## Diagnostics * [srt_getlasterror_str](#srt_getlasterror_str) * [srt_getlasterror](#srt_getlasterror) * [srt_strerror](#srt_strerror) * [srt_clearlasterror](#srt_clearlasterror) * [srt_getrejectreason](#srt_getrejectreason) * [srt_rejectreason_str](#srt_rejectreason_str) * [srt_setrejectreason](#srt_setrejectreason) General notes concerning the `getlasterror` diagnostic functions: when an API function ends up with error, this error information is stored in a thread-local storage. This means that you'll get the error of the operation that was last performed as long as you call this diagnostic function just after the failed function has returned. In any other situation the information provided by the diagnostic function is undefined. **NOTE**: There are lists of rejection reasons and error codes at the bottom of this section. ### srt_getlasterror ``` int srt_getlasterror(int* errno_loc); ``` Get the numeric code of the last error. Additionally, in the variable passed as `errno_loc` the system error value is returned, or 0 if there was no system error associated with the last error. The system error is: * On POSIX systems, the value from `errno` * On Windows, the result from `GetLastError()` call [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_strerror ``` const char* srt_strerror(int code, int errnoval); ``` Returns a string message that represents a given SRT error code and possibly the `errno` value, if not 0. **NOTE:** *This function isn't thread safe. It uses a static variable to hold the error description. There's no problem with using it in a multithreaded environment, as long as only one thread in the whole application calls this function at the moment* [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_getlasterror_str ``` const char* srt_getlasterror_str(void); ``` Get the text message for the last error. It's a shortcut to calling first `srt_getlasterror` and then passing the returned value into [`srt_strerror`](#srt_strerror). Note that, contrary to [`srt_strerror`](#srt_strerror), this function is thread safe. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_clearlasterror ``` void srt_clearlasterror(void); ``` This function clears the last error. After this call, `srt_getlasterror` will report a "successful" code. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_rejectreason_str ``` const char* srt_rejectreason_str(enum SRT_REJECT_REASON id); ``` Returns a constant string for the reason of the connection rejected, as per given code ID. It provides a system-defined message for values below `SRT_REJ_E_SIZE`. For other values below `SRT_REJC_PREDEFINED` it returns the string for [`SRT_REJ_UNKNOWN`](#SRT_REJ_UNKNOWN). For values since `SRT_REJC_PREDEFINED` on, returns "Application-defined rejection reason". The actual messages assigned to the internal rejection codes, that is, less than `SRT_REJ_E_SIZE`, can be also obtained from the `srt_rejectreason_msg` array. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_setrejectreason ``` int srt_setrejectreason(SRTSOCKET sock, int value); ``` Sets the rejection code on the socket. This call is only useful in the listener callback. The code from `value` set this way will be set as a rejection reason for the socket. After the callback rejects the connection, the code will be passed back to the caller peer with the handshake response. Note that allowed values for this function begin with `SRT_REJC_PREDEFINED` (that is, you cannot set a system rejection code). For example, your application can inform the calling side that the resource specified under the `r` key in the StreamID string (see [`SRTO_STREAMID`](API-socket-options.md#SRTO_STREAMID)) is not available - it then sets the value to `SRT_REJC_PREDEFINED + 404`. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | 0 | Error | | -1 | Success | | | | | Errors | | |:--------------------------------- |:-------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket `sock` is not an ID of a valid socket | | [`SRT_EINVPARAM`](#srt_einvparam) | `value` is less than `SRT_REJC_PREDEFINED` | | | | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### srt_getrejectreason ``` int srt_getrejectreason(SRTSOCKET sock); ``` This function provides a more detailed reason for a failed connection attempt. It shall be called after a connecting function (such as [`srt_connect`](#srt_connect)) has returned an error, the code for which is [`SRT_ECONNREJ`](#srt_econnrej). If [`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) has been set on the socket used for the connection, the function should also be called when the [`SRT_EPOLL_ERR`](#SRT_EPOLL_ERR) event is set for this socket. It returns a numeric code, which can be translated into a message by [`srt_rejectreason_str`](#srt_rejectreason_str). [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### Rejection Reasons #### SRT_REJ_UNKNOWN A fallback value for cases when there was no connection rejected. #### SRT_REJ_SYSTEM One system function reported a failure. Usually this means some system error or lack of system resources to complete the task. #### SRT_REJ_PEER The connection has been rejected by peer, but no further details are available. This usually means that the peer doesn't support rejection reason reporting. #### SRT_REJ_RESOURCE A problem with resource allocation (usually memory). #### SRT_REJ_ROGUE The data sent by one party to another cannot be properly interpreted. This should not happen during normal usage, unless it's a bug, or some weird events are happening on the network. #### SRT_REJ_BACKLOG The listener's backlog has exceeded its queue limit (there are many other callers waiting for the opportunity of being connected and wait in the queue, which has reached its limit). #### SRT_REJ_IPE Internal Program Error. This should not happen during normal usage and it usually means a bug in the software (although this can be reported by both local and foreign host). #### SRT_REJ_CLOSE The listener socket was able to receive your request, but at this moment it is being closed. It's likely that your next attempt will result in a timeout. #### SRT_REJ_VERSION One party of the connection has set up a minimum version that is required for that connection, but the other party didn't satisfy this requirement. #### SRT_REJ_RDVCOOKIE Rendezvous cookie collision. This normally should never happen, or the probability that it will happen is negligible. However this can also be a result of a misconfiguration in that you are trying to make a rendezvous connection where both parties try to bind to the same IP address, or both are local addresses of the same host. In such a case the sent handshake packets are returning to the same host as if they were sent by the peer (i.e. a party is sending to itself). When this happens, this reject reason will be reported by every attempt. #### SRT_REJ_BADSECRET Both parties have defined a passphrase for connection, but they differ. #### SRT_REJ_UNSECURE Only one connection party has set up a password. See also the [`SRTO_ENFORCEDENCRYPTION`](API-socket-options.md#SRTO_ENFORCEDENCRYPTION) flag. #### SRT_REJ_MESSAGEAPI The value of the [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) flag is different on both connection parties. #### SRT_REJ_CONGESTION The [`SRTO_CONGESTION`](API-socket-options.md#SRTO_CONGESTION)option has been set up differently on both connection parties. #### SRT_REJ_FILTER The [`SRTO_PACKETFILTER`](API-socket-options.md#SRTO_PACKETFILTER) option has been set differently on both connection parties. #### SRT_REJ_GROUP The group type or some group settings are incompatible for both connection parties. While every connection within a bonding group may have different target addresses, they should all designate the same endpoint and the same SRT application. If this condition isn't satisfied, then the peer will respond with a different peer group ID for the connection that is trying to contact a machine/application that is completely different from the existing connections in the bonding group. #### SRT_REJ_TIMEOUT The connection wasn't rejected, but it timed out. This code is always set on connection timeout, but this is the only way to get this state in non-blocking mode (see [`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN)). There may also be server and user rejection codes, as defined by the `SRT_REJC_INTERNAL`, `SRT_REJC_PREDEFINED` and `SRT_REJC_USERDEFINED` constants. Note that the number space from the value of `SRT_REJC_PREDEFINED` and above is reserved for "predefined codes" (`SRT_REJC_PREDEFINED` value plus adopted HTTP codes). Values above `SRT_REJC_USERDEFINED` are freely defined by the application. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- ### Error Codes All functions that return the status via `int` value return -1 (designated as `SRT_ERROR`) always when the call has failed (in case of resource creation functions an appropriate symbol is defined, like `SRT_INVALID_SOCK` for `SRTSOCKET`). When this happens, the error code can be obtained from the `srt_getlasterror` function. The values for the error are collected in an `SRT_ERRNO` enum: #### SRT_EUNKNOWN Internal error when setting the right error code. #### SRT_SUCCESS The value set when the last error was cleared and no error has occurred since then. #### SRT_ECONNSETUP General setup error resulting from internal system state. #### SRT_ENOSERVER Connection timed out while attempting to connect to the remote address. Note that when this happens, [`srt_getrejectreason`](#srt_getrejectreason) also reports the timeout reason. #### SRT_ECONNREJ Connection has been rejected. Additional reject reason can be obtained through [`srt_getrejectreason`](#srt_getrejectreason) (see above). #### SRT_ESOCKFAIL An error occurred when trying to call a system function on an internally used UDP socket. Note that the detailed system error is available in the extra variable passed by pointer to `srt_getlasterror`. #### SRT_ESECFAIL A possible tampering with the handshake packets was detected, or an encryption request wasn't properly fulfilled. #### SRT_ESCLOSED A socket that was vital for an operation called in blocking mode has been closed during the operation. Please note that this situation is handled differently than the system calls for `connect` and `accept` functions for TCP, which simply block indefinitely (or until the standard timeout) when the key socket was closed during an operation. When this error is reported, it usually means that the socket passed as the first parameter to [`srt_connect*`](#srt_connect) or [`srt_accept`](#srt_accept) is no longer usable. #### SRT_ECONNFAIL General connection failure of unknown details. #### SRT_ECONNLOST The socket was properly connected, but the connection has been broken. This specialization is reported from the transmission functions. #### SRT_ENOCONN The socket is not connected. This can be reported also when the connection was broken for a function that checks some characteristic socket data. #### SRT_ERESOURCE System or standard library error reported unexpectedly for unknown purpose. Usually it means some internal error. #### SRT_ETHREAD System was unable to spawn a new thread when required. #### SRT_ENOBUF System was unable to allocate memory for buffers. #### SRT_ESYSOBJ System was unable to allocate system specific objects (such as sockets, mutexes or condition variables). #### SRT_EFILE General filesystem error (for functions operating with file transmission). #### SRT_EINVRDOFF Failure when trying to read from a given position in the file (file could be modified while it was read from). #### SRT_ERDPERM Read permission was denied when trying to read from file. #### SRT_EINVWROFF Failed to set position in the written file. #### SRT_EWRPERM Write permission was denied when trying to write to a file. #### SRT_EINVOP Invalid operation performed for the current state of a socket. This mainly concerns performing `srt_bind*` operations on a socket that is already bound. Once a socket has been been bound, it cannot be bound again. #### SRT_EBOUNDSOCK The socket is currently bound and the required operation cannot be performed in this state. Usually it's about an option that can only be set on the socket before binding (`srt_bind*`). Note that a socket that is currently connected is also considered bound. #### SRT_ECONNSOCK The socket is currently connected and therefore performing the required operation is not possible. Usually concerns setting an option that must be set before connecting (although it is allowed to be altered after binding), or when trying to start a connecting operation ([`srt_connect*`](#srt_connect)) while the socket isn't in a state that allows it (only [`SRTS_INIT`](#SRTS_INIT) or [`SRTS_OPENED`](#SRTS_OPENED) are allowed). #### SRT_EINVPARAM This error is reported in a variety of situations when call parameters for API functions have some requirements defined and these were not satisfied. This error should be reported after an initial check of the parameters of the call before even performing any operation. This error can be easily avoided if you set the values correctly. #### SRT_EINVSOCK The API function required an ID of an entity (socket or group) and it was invalid. Note that some API functions work only with socket or only with group, so they would also return this error if inappropriate type of entity was passed, even if it was valid. #### SRT_EUNBOUNDSOCK The operation to be performed on a socket requires that it first be explicitly bound (using [`srt_bind*`](#srt_bind) functions). Currently it applies when calling [`srt_listen`](#srt_listen), which cannot work with an implicitly bound socket. #### SRT_ENOLISTEN The socket passed for the operation is required to be in the listen state ([`srt_listen`](#srt_listen) must be called first). #### SRT_ERDVNOSERV The required operation cannot be performed when the socket is set to rendezvous mode ([`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) set to true). Usually applies when trying to call [`srt_listen`](#srt_listen) on such a socket. #### SRT_ERDVUNBOUND An attempt was made to connect to a socket set to rendezvous mode ([`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) set to true) that was not first bound. A rendezvous connection requires setting up two addresses and ports on both sides of the connection, then setting the local one with [`srt_bind`](#srt_bind) and using the remote one with [`srt_connect`](#srt_connect) (or you can simply use [`srt_rendezvous`](#srt_rendezvous)). Calling [`srt_connect*`](#srt_connect) on an unbound socket (in [`SRTS_INIT`](#SRTS_INIT) state) that is to be bound implicitly is only allowed for regular caller sockets (not rendezvous). #### SRT_EINVALMSGAPI The function was used incorrectly in the message API. This can happen if: * The parameters specific for the message API in [`SRT_MSGCTRL`](#SRT_MSGCTRL) type parameter were incorrectly specified. * The extra parameter check performed by the congestion controller has failed. * The socket is a member of a self-managing group, therefore you should perform the operation on the group, not on this socket. #### SRT_EINVALBUFFERAPI The function was used incorrectly in the stream (buffer) API, that is, either the stream-only functions were used with set message API ([`srt_sendfile`](#srt_sendfile)/[`srt_recvfile`](#srt_recvfile)) or TSBPD mode was used with buffer API ([`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) set to true) or the congestion controller has failed to check call parameters. #### SRT_EDUPLISTEN The port tried to be bound for listening is already busy. Note that binding to the same port is allowed in general (when [`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDRS) is true on every socket that has bound it), but only one such socket can be a listener. #### SRT_ELARGEMSG Size exceeded. This is reported in the following situations: * Trying to receive a message, but the read-ready message is larger than the buffer passed to the receiving function. * Trying to send a message, but the size of this message exceeds the size of the preset sender buffer, so it cannot be stored in the sender buffer. * When getting group data, the array to be filled is too small. #### SRT_EINVPOLLID The epoll ID passed to an epoll function is invalid. #### SRT_EPOLLEMPTY The epoll container currently has no subscribed sockets. This is reported by an epoll waiting function that would in this case block forever. This problem might be reported both in a situation where you have created a new epoll container and didn't subscribe any sockets to it, or you did, but these sockets have been closed (including when closed in a separate thread while the waiting function was blocking). Note that this situation can be prevented by setting the `SRT_EPOLL_ENABLE_EMPTY` flag, which may be useful when you use multiple threads and start waiting without subscribed sockets, so that you can subscribe them later from another thread. #### `SRT_EBINDCONFLICT` The binding you are attempting to set up a socket with cannot be completed because it conflicts with another existing binding. This is because an intersecting binding was found that cannot be reused according to the specification in `srt_bind` call. A binding is considered intersecting if the existing binding has the same port and covers at least partially the range as that of the attempted binding. These ranges can be split in three groups: 1. An explicitly specified IP address (both IPv4 and IPv6) covers this address only. 2. An IPv4 wildcard 0.0.0.0 covers all IPv4 addresses (but not IPv6). 3. An IPv6 wildcard :: covers: * if `SRTO_IPV6ONLY` is true - all IPv6 addresses (but not IPv4) * if `SRTO_IPV6ONLY` is false - all IP addresses. Example 1: * Socket 1: bind to IPv4 0.0.0.0 * Socket 2: bind to IPv6 :: with `SRTO_IPV6ONLY` = true * Result: NOT intersecting Example 2: * Socket 1: bind to IPv4 1.2.3.4 * Socket 2: bind to IPv4 0.0.0.0 * Result: intersecting (and conflicting) Example 3: * Socket 1: bind to IPv4 1.2.3.4 * Socket 2: bind to IPv6 :: with `SRTO_IPV6ONLY` = false * Result: intersecting (and conflicting) If any common range coverage is found between the attempted binding specification (in `srt_bind` call) and the found existing binding with the same port number, then all of the following conditions must be satisfied between them: 1. The `SRTO_REUSEADDR` must be true (default) in both. 2. The IP address specification (in case of IPv6, also including the value of `SRTO_IPV6ONLY` flag) must be exactly identical. 3. The UDP-specific settings must be identical. If any of these conditions isn't satisfied, the `srt_bind` function results in conflict and report this error. #### SRT_EASYNCFAIL General asynchronous failure (not in use currently). #### SRT_EASYNCSND Sending operation is not ready to perform. This error is reported when trying to perform a sending operation on a socket that is not ready for sending, but [`SRTO_SNDSYN`](API-socket-options.md#SRTO_SNDSYN) was set to false (when true, the function would block the call otherwise). #### SRT_EASYNCRCV Receiving operation is not ready to perform. This error is reported when trying to perform a receiving operation or accept a new socket from the listener socket, when the socket is not ready for that operation, but [`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) was set to false (when true, the function would block the call otherwise). #### SRT_ETIMEOUT The operation timed out. This can happen if you have a timeout set by an option ([`SRTO_RCVTIMEO`](API-socket-options.md#SRTO_RCVTIMEO) or [`SRTO_SNDTIMEO`](API-socket-options.md#SRTO_SNDTIMEO)), or passed as an extra argument ([`srt_epoll_wait`](#srt_epoll_wait) or [`srt_accept_bond`](#srt_accept_bond)) and the function call was blocking, but the required timeout time has passed. #### SRT_ECONGEST **NOTE**: This error is used only in an experimental version that requires setting the `SRT_ENABLE_ECN` macro at compile time. Otherwise the situation described below results in the usual successful report. This error should be reported by the sending function when, with [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) and [`SRTO_TLPKTDROP`](API-socket-options.md#SRTO_TLPKTDROP) set to true, some packets were dropped at the sender side (see the description of [`SRTO_TLPKTDROP`](API-socket-options.md#SRTO_TLPKTDROP) for details). This doesn't concern the data that were passed for sending by the sending function (these data are placed at the back of the sender buffer, while the dropped packets are at the front). In other words, the operation done by the sending function is successful, but the application might want to slow down the sending rate to avoid congestion. #### SRT_EPEERERR This error is reported when a receiver peer is writing to a file that an agent is sending. When the peer encounters an error when writing the received data to a file, it sends the `UMSG_PEERERROR` message back to the sender, and the sender reports this error from the API sending function. [Return to Top of Page](#SRT-API-Functions) srt-1.4.4/docs/API/API-socket-options.md000066400000000000000000002352271412557703600176300ustar00rootroot00000000000000# SRT API Socket Options There is a general method of setting options on a socket in the SRT C API, similar to the system `setsockopt/getsockopt` functions. - [Types Used in Socket Options](#types-used-in-socket-options) - [Getting and Setting Options](#getting-and-setting-options) - [List of Options](#list-of-options) ## Types Used in Socket Options Possible types of socket options are: - `int32_t` - a 32-bit integer. On most systems similar to `int`. In some cases the value is expressed using an enumeration type (see [Enumeration types...](#enumeration_types) section below). - `int64_t` - a 64-bit integer. - `bool` - a Boolean type (`` for C, or built-in for C++). When *setting* an option, passing the value through an `int` type is also properly recognized. When *getting* an option, however, the`bool` type should be used. It is also possible to pass a variable of `int` type initialized with 0 and then comparing the resulting value with 0 (just don't compare the result with 1 or `true`). - `string` - a C string. When *setting* an option, a `const char*` character array pointer is expected to be passed in `optval` and the array length in `optlen` **without the terminating NULL character**. When *getting*, an array is expected to be passed in `optval` with a sufficient size **with an extra space for the terminating NULL character** provided in `optlen`. The return value of `optlen` does not count the terminating NULL character. - `linger` - Linger structure. Used exclusively with `SRTO_LINGER`. ### Enumeration Types Used in Options #### `SRT_TRANSTYPE` Used by `SRTO_TRANSTYPE` option: - `SRTT_LIVE`: Live mode. - `SRTT_FILE`: File mode. See [Transmission Types](API.md#transmission-types) for details. #### `SRT_KM_STATE` The defined encryption state as performed by the Key Material Exchange, used by `SRTO_RCVKMSTATE`, `SRTO_SNDKMSTATE` and `SRTO_KMSTATE` options: - `SRT_KM_S_UNSECURED`: no encryption/decryption. If this state is only on the receiver, received encrypted packets will be dropped. - `SRT_KM_S_SECURING`: pending security (HSv4 only). This is a temporary state used only if the connection uses HSv4 and the Key Material Exchange is not finished yet. On HSv5 this is not possible because the Key Material Exchange for the initial key is done in the handshake. - `SRT_KM_S_SECURED`: KM exchange was successful and the data will be sent encrypted and will be decrypted by the receiver. This state is only possible on both sides in both directions simultaneously. - `SRT_KM_S_NOSECRET`: If this state is in the sending direction (`SRTO_SNDKMSTATE`), then it means that the sending party has set a passphrase, but the peer did not. In this case the sending party can receive unencrypted packets from the peer, but packets it sends to the peer will be encrypted and the peer will not be able to decrypt them. This state is only possible in HSv5. - `SRT_KM_S_BADSECRET`: The password is wrong (set differently on each party); encrypted payloads won't be decrypted in either direction. Note that with the default value of `SRTO_ENFORCEDENCRYPTION` option (true), the state is equal on both sides in both directions, and it can be only `SRT_KM_S_UNSECURED` or `SRT_KM_S_SECURED` (in other cases the connection is rejected). Otherwise it may happen that either both sides have different passwords and the state is `SRT_KM_S_BADSECRET` in both directions, or only one party has set a password, in which case the KM state is as follows: | | `SRTO_RCVKMSTATE` | `SRTO_SNDKMSTATE` | |--------------------------|----------------------|----------------------| | Party with no password: | `SRT_KM_S_NOSECRET` | `SRT_KM_S_UNSECURED` | | Party with password: | `SRT_KM_S_UNSECURED` | `SRT_KM_S_NOSECRET` | ## Getting and Setting Options Legacy version: int srt_getsockopt(SRTSOCKET socket, int level, SRT_SOCKOPT optName, void* optval, int& optlen); int srt_setsockopt(SRTSOCKET socket, int level, SRT_SOCKOPT optName, const void* optval, int optlen); New version: int srt_getsockflag(SRTSOCKET socket, SRT_SOCKOPT optName, void* optval, int& optlen); int srt_setsockflag(SRTSOCKET socket, SRT_SOCKOPT optName, const void* optval, int optlen); In the legacy version, there's an additional unused `level` parameter. It was there in the original UDT API just to mimic the system `setsockopt` function, but it's ignored. Some options require a value of type `bool` while others require type `integer`, which is not the same -- they differ in size, and mistaking them may end up causing a crash. This must be kept in mind especially in any C wrapper. For convenience, the *setting* option function may accept both `int32_t` and `bool` types, but this is not so in the case of *getting* an option value. **UDT project legacy note**: Almost all options from the UDT library are derived (there are a few deleted, including some deprecated already in UDT). Many new SRT options have been added. All options are available exclusively with the `SRTO_` prefix. Old names are provided as alias names in the `udt.h` legacy/C++ API file. Note the translation rules: * `UDT_` prefix from UDT options was changed to the prefix `SRTO_` * `UDP_` prefix from UDT options was changed to the prefix `SRTO_UDP_` * `SRT_` prefix in older SRT versions was changed to `SRTO_` The [table below](#list-of-options) provides a complete list of SRT options and their characteristics according to the following legend: 1. **Since**: Defines the SRT version when this option was first introduced. If this field is empty, it's an option derived from UDT. "Version 0.0.0" is the oldest version of SRT ever created and put into use. 2. **Restrict**: Defines restrictions on setting the option. The field is empty if the option is not settable (see **Dir** column): - `pre-bind`: The option cannot be altered on a socket that is already bound (by calling `srt_bind()` or any other function doing this, including automatic binding when trying to connect, as well as accepted sockets). In other words, once an SRT socket has transitioned from `SRTS_INIT` to `SRTS_OPENED` socket state. - `pre`: The option cannot be altered on a socket that is in `SRTS_LISTENING`, `SRTS_CONNECTING` or `SRTS_CONNECTED` state. If an option was set on a listener socket, it will be inherited by a socket returned by `srt_accept()` (except for `SRTO_STREAMID`). - `post`: The option is unrestricted and can be altered at any time, including when the socket is connected, as well as on an accepted socket. The setting of this flag on a listening socket is usually derived by the accepted socket, but this isn't a rule for all options. Note though that there are some unrestricted options that have an important meaning when set prior to connecting (different one than for a connected socket). **NOTE**: The `pre-bind` characteristic applies exclusively to options that: - Change the behavior and functionality of the `srt_bind` call - Concern or set an option on the internally used UDP socket - Concern any kind of resource used by the multiplexer 3. **Type**: The data type of the option (see above). 4. **Units**: Roughly specified unit, if the value defines things like length or time. It can also define more precisely what kind of specialization can be used when the type is integer: - `enum`: the possible values are defined in an enumeration type - `flags`: the integer value is a collection of bit flags - `B/s` - bytes per second. 5. **Default**: The exact default value, if it can be easily specified. A more complicated default state of a particular option will be explained in the [description](#option-descriptions) (when marked by asterisk). For non-settable options this field is empty. 6. **Range**: If a value of an integer type has a limited range, or only a certain value allowed, it will be specified here (otherwise empty). A range value can be specified as: - `X-... `: specifies only a minimum value - `X-Y,Z `: values between X and Y are allowed, and additionally Z - If the value is of `string` type, this field will contain its maximum size in square brackets. - If the range contains additionally an asterisk, it means that more elaborate restrictions on the value apply, as explained in the [description](#option-descriptions). 7. **Dir**: Option direction: W if can be set, R if can be retrieved, RW if both. 8. **Entity**: This describes whether the option can be set on the socket or the group. The G and S options may appear together, in which case both possibilities apply. The D and I options, mutually exclusive, appear always with G. The + marker can only coexist with GS. Possible specifications are: - S: This option can be set on a single socket (exclusively, if not GS) - G: This option can be set on a group (exclusively, if not GS) - D: If set on a group, it will be derived by the member socket - I: If set on a group, it will be taken and managed exclusively by the group - +: This option is also allowed to be set individually on a group member socket through a configuration object in `SRT_SOCKGROUPCONFIG` prepared by `srt_create_config`. Note that this setting may override the setting derived from the group. ## List of Options The following table lists SRT API socket options in alphabetical order. Option details are given further below. | Option Name | Since | Restrict | Type | Units | Default | Range | Dir |Entity | | :----------------------------------------------------- | :---: | :------: | :-------: | :-----: | :---------------: | :------: |:---:|:-----:| | [`SRTO_BINDTODEVICE`](#SRTO_BINDTODEVICE) | 1.4.2 | pre-bind | `string` | | | | RW | GSD+ | | [`SRTO_CONGESTION`](#SRTO_CONGESTION) | 1.3.0 | pre | `string` | | "live" | \* | W | S | | [`SRTO_CONNTIMEO`](#SRTO_CONNTIMEO) | 1.1.2 | pre | `int32_t` | ms | 3000 | 0.. | W | GSD+ | | [`SRTO_DRIFTTRACER`](#SRTO_DRIFTTRACER) | 1.4.2 | post | `bool` | | true | | RW | GSD | | [`SRTO_ENFORCEDENCRYPTION`](#SRTO_ENFORCEDENCRYPTION) | 1.3.2 | pre | `bool` | | true | | W | GSD | | [`SRTO_EVENT`](#SRTO_EVENT) | | | `int32_t` | flags | | | R | S | | [`SRTO_FC`](#SRTO_FC) | | pre | `int32_t` | pkts | 25600 | 32.. | RW | GSD | | [`SRTO_GROUPCONNECT`](#SRTO_GROUPCONNECT) | 1.5.0 | pre | `int32_t` | | 0 | 0...1 | W | S | | [`SRTO_GROUPSTABTIMEO`](#SRTO_GROUPSTABTIMEO) | 1.5.0 | pre | `int32_t` | ms | 80 | 10-... | W | GSD+ | | [`SRTO_GROUPTYPE`](#SRTO_GROUPTYPE) | 1.5.0 | | `int32_t` | enum | | | R | S | | [`SRTO_INPUTBW`](#SRTO_INPUTBW) | 1.0.5 | post | `int64_t` | B/s | 0 | 0.. | RW | GSD | | [`SRTO_IPTOS`](#SRTO_IPTOS) | 1.0.5 | pre-bind | `int32_t` | | (system) | 0..255 | RW | GSD | | [`SRTO_IPTTL`](#SRTO_IPTTL) | 1.0.5 | pre-bind | `int32_t` | hops | (system) | 1..255 | RW | GSD | | [`SRTO_IPV6ONLY`](#SRTO_IPV6ONLY) | 1.4.0 | pre-bind | `int32_t` | | (system) | -1..1 | RW | GSD | | [`SRTO_ISN`](#SRTO_ISN) | 1.3.0 | | `int32_t` | | | | R | S | | [`SRTO_KMPREANNOUNCE`](#SRTO_KMPREANNOUNCE) | 1.3.2 | pre | `int32_t` | pkts | 0: 212 | 0.. \* | RW | GSD | | [`SRTO_KMREFRESHRATE`](#SRTO_KMREFRESHRATE) | 1.3.2 | pre | `int32_t` | pkts | 0: 224 | 0.. | RW | GSD | | [`SRTO_KMSTATE`](#SRTO_KMSTATE) | 1.0.2 | | `int32_t` | enum | | | R | S | | [`SRTO_LATENCY`](#SRTO_LATENCY) | 1.0.2 | pre | `int32_t` | ms | 120 \* | 0.. | RW | GSD | | [`SRTO_LINGER`](#SRTO_LINGER) | | post | `linger` | s | on, 180 | 0.. | RW | GSD | | [`SRTO_LOSSMAXTTL`](#SRTO_LOSSMAXTTL) | 1.2.0 | post | `int32_t` | packets | 0 | 0.. | RW | GSD+ | | [`SRTO_MAXBW`](#SRTO_MAXBW) | | post | `int64_t` | B/s | -1 | -1.. | RW | GSD | | [`SRTO_MESSAGEAPI`](#SRTO_MESSAGEAPI) | 1.3.0 | pre | `bool` | | true | | W | GSD | | [`SRTO_MININPUTBW`](#SRTO_MININPUTBW) | 1.4.3 | post | `int64_t` | B/s | 0 | 0.. | RW | GSD | | [`SRTO_MINVERSION`](#SRTO_MINVERSION) | 1.3.0 | pre | `int32_t` | version | 0x010000 | \* | RW | GSD | | [`SRTO_MSS`](#SRTO_MSS) | | pre-bind | `int32_t` | bytes | 1500 | 76.. | RW | GSD | | [`SRTO_NAKREPORT`](#SRTO_NAKREPORT) | 1.1.0 | pre | `bool` | | \* | | RW | GSD+ | | [`SRTO_OHEADBW`](#SRTO_OHEADBW) | 1.0.5 | post | `int32_t` | % | 25 | 5..100 | RW | GSD | | [`SRTO_PACKETFILTER`](#SRTO_PACKETFILTER) | 1.4.0 | pre | `string` | | "" | [512] | RW | GSD | | [`SRTO_PASSPHRASE`](#SRTO_PASSPHRASE) | 0.0.0 | pre | `string` | | "" | [10..79] | W | GSD | | [`SRTO_PAYLOADSIZE`](#SRTO_PAYLOADSIZE) | 1.3.0 | pre | `int32_t` | bytes | \* | 0.. \* | W | GSD | | [`SRTO_PBKEYLEN`](#SRTO_PBKEYLEN) | 0.0.0 | pre | `int32_t` | bytes | 0 | \* | RW | GSD | | [`SRTO_PEERIDLETIMEO`](#SRTO_PEERIDLETIMEO) | 1.3.3 | pre | `int32_t` | ms | 5000 | 0.. | RW | GSD+ | | [`SRTO_PEERLATENCY`](#SRTO_PEERLATENCY) | 1.3.0 | pre | `int32_t` | ms | 0 | 0.. | RW | GSD | | [`SRTO_PEERVERSION`](#SRTO_PEERVERSION) | 1.1.0 | | `int32_t` | * | | | R | GS | | [`SRTO_RCVBUF`](#SRTO_RCVBUF) | | pre-bind | `int32_t` | bytes | 8192 payloads | \* | RW | GSD+ | | [`SRTO_RCVDATA`](#SRTO_RCVDATA) | | | `int32_t` | pkts | | | R | S | | [`SRTO_RCVKMSTATE`](#SRTO_RCVKMSTATE) | 1.2.0 | | `int32_t` | enum | | | R | S | | [`SRTO_RCVLATENCY`](#SRTO_RCVLATENCY) | 1.3.0 | pre | `int32_t` | msec | \* | 0.. | RW | GSD | | [`SRTO_RCVSYN`](#SRTO_RCVSYN) | | post | `bool` | | true | | RW | GSI | | [`SRTO_RCVTIMEO`](#SRTO_RCVTIMEO) | | post | `int32_t` | ms | -1 | -1, 0.. | RW | GSI | | [`SRTO_RENDEZVOUS`](#SRTO_RENDEZVOUS) | | pre | `bool` | | false | | RW | S | | [`SRTO_RETRANSMITALGO`](#SRTO_RETRANSMITALGO) | 1.4.2 | pre | `int32_t` | | 1 | [0, 1] | RW | GSD | | [`SRTO_REUSEADDR`](#SRTO_REUSEADDR) | | pre-bind | `bool` | | true | | RW | GSD | | [`SRTO_SENDER`](#SRTO_SENDER) | 1.0.4 | pre | `bool` | | false | | W | S | | [`SRTO_SNDBUF`](#SRTO_SNDBUF) | | pre-bind | `int32_t` | bytes | 8192 payloads | \* | RW | GSD+ | | [`SRTO_SNDDATA`](#SRTO_SNDDATA) | | | `int32_t` | pkts | | | R | S | | [`SRTO_SNDDROPDELAY`](#SRTO_SNDDROPDELAY) | 1.3.2 | post | `int32_t` | ms | \* | -1.. | W | GSD+ | | [`SRTO_SNDKMSTATE`](#SRTO_SNDKMSTATE) | 1.2.0 | | `int32_t` | enum | | | R | S | | [`SRTO_SNDSYN`](#SRTO_SNDSYN) | | post | `bool` | | true | | RW | GSI | | [`SRTO_SNDTIMEO`](#SRTO_SNDTIMEO) | | post | `int32_t` | ms | -1 | -1.. | RW | GSI | | [`SRTO_STATE`](#SRTO_STATE) | | | `int32_t` | enum | | | R | S | | [`SRTO_STREAMID`](#SRTO_STREAMID) | 1.3.0 | pre | `string` | | "" | [512] | RW | GSD | | [`SRTO_TLPKTDROP`](#SRTO_TLPKTDROP) | 1.0.6 | pre | `bool` | | \* | | RW | GSD | | [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE) | 1.3.0 | pre | `int32_t` | enum |`SRTT_LIVE` | \* | W | S | | [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE) | 0.0.0 | pre | `bool` | | \* | | W | S | | [`SRTO_UDP_RCVBUF`](#SRTO_UDP_RCVBUF) | | pre-bind | `int32_t` | bytes | 8192 payloads | \* | RW | GSD+ | | [`SRTO_UDP_SNDBUF`](#SRTO_UDP_SNDBUF) | | pre-bind | `int32_t` | bytes | 65536 | \* | RW | GSD+ | | [`SRTO_VERSION`](#SRTO_VERSION) | 1.1.0 | | `int32_t` | | | | R | S | ### Option Descriptions #### SRTO_BINDTODEVICE | OptName | Since | Restrict | Type | Units | Default | Range | Dir |Entity| | --------------------- | ----- | -------- | -------- | ------ | -------- | ------ |-----|------| | `SRTO_BINDTODEVICE` | 1.4.2 | pre-bind | `string` | | | | RW | GSD+ | Refers to the `SO_BINDTODEVICE` system socket option for `SOL_SOCKET` level. This effectively limits the packets received by this socket to only those that are targeted to that device. The device is specified by name passed as string. The setting becomes effective after binding the socket (including default-binding when connecting). NOTE: This option is only available on Linux and available there by default. On all other platforms setting this option will always fail. NOTE: With the default system configuration, this option is only available for a process that runs as root. Otherwise the function that applies the setting (`srt_bind`, `srt_connect` etc.) will fail. [Return to list](#list-of-options) --- #### SRTO_CONGESTION | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_CONGESTION` | 1.3.0 | pre | `string` | | "live" | * | W | S | The type of congestion controller used for the transmission for that socket. Its type must be exactly the same on both connecting parties, otherwise the connection is rejected - **however** you may also change the value of this option for the accepted socket in the listener callback (see `srt_listen_callback`) if an appropriate instruction was given in the Stream ID. Currently supported congestion controllers are designated as "live" and "file" Note that it is not recommended to change this option manually, but you should rather change the whole set of options using the [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE) option. [Return to list](#list-of-options) --- #### SRTO_CONNTIMEO | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ------------------ | ----- | -------- | --------- | ------ | -------- | ------ | --- | ------ | | `SRTO_CONNTIMEO` | 1.1.2 | pre | `int32_t` | msec | 3000 | 0.. | W | GSD+ | Connect timeout. This option applies to the caller and rendezvous connection modes. For the rendezvous mode (see `SRTO_RENDEZVOUS`) the effective connection timeout will be 10 times the value set with `SRTO_CONNTIMEO`. [Return to list](#list-of-options) --- #### SRTO_DRIFTTRACER | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | --------- | ------ | -------- | ------ | --- | ------ | | `SRTO_DRIFTTRACER`| 1.4.2 | post | `bool` | | true | | RW | GSD | Enables or disables time drift tracer (receiver). [Return to list](#list-of-options) --- #### SRTO_ENFORCEDENCRYPTION | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_ENFORCEDENCRYPTION` | 1.3.2 | pre | `bool` | | true | | W | GSD | This option enforces that both connection parties have the same passphrase set, or both do not set the passphrase, otherwise the connection is rejected. When this option is set to FALSE **on both connection parties**, the connection is allowed even if the passphrase differs on both parties, or it was set only on one party. Note that the party that has set a passphrase is still allowed to send data over the network. However, the receiver will not be able to decrypt that data and will not deliver it to the application. The party that has set no passphrase can send (unencrypted) data that will be successfully received by its peer. This option can be used in some specific situations when the user knows both parties of the connection, so there's no possible situation of a rogue sender and can be useful in situations where it is important to know whether a connection is possible. The inability to decrypt an incoming transmission can be then reported as a different kind of problem. **IMPORTANT**: There is unusual and unobvious behavior when this flag is TRUE on the caller and FALSE on the listener, and the passphrase was mismatched. On the listener side the connection will be established and broken right after, resulting in a short-lived "spurious" connection report on the listener socket. This way, a socket will be available for retrieval from an `srt_accept` call for a very short time, after which it will be removed from the listener backlog just as if no connection attempt was made at all. If the application is fast enough to react on an incoming connection, it will retrieve it, only to learn that it is already broken. This also makes possible a scenario where `SRT_EPOLL_IN` is reported on a listener socket, but then an `srt_accept` call reports an `SRT_EASYNCRCV` error. How fast the connection gets broken depends on the network parameters -- in particular, whether the `UMSG_SHUTDOWN` message sent by the caller is delivered (which takes one RTT in this case) or missed during the interval from its creation up to the connection timeout (default = 5 seconds). It is therefore strongly recommended that you only set this flag to FALSE on the listener when you are able to ensure that it is also set to FALSE on the caller side. [Return to list](#list-of-options) --- #### SRTO_EVENT | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | --------- | ------ | -------- | ------ | --- | ------ | | `SRTO_EVENT` | | | `int32_t` | flags | | | R | S | Returns bit flags set according to the current active events on the socket. Possible values are those defined in `SRT_EPOLL_OPT` enum (a combination of `SRT_EPOLL_IN`, `SRT_EPOLL_OUT` and `SRT_EPOLL_ERR`). [Return to list](#list-of-options) --- #### SRTO_FC | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | --------- | ------ | -------- | ------ | --- | ------ | | `SRTO_FC` | | pre | `int32_t` | pkts | 25600 | 32.. | RW | GSD | Flow Control limits the maximum number of packets "in flight" - payload (data) packets that were sent but reception is not yet acknowledged with an ACK control packet. It also includes data packets already received, but that can't be acknowledged due to loss of preceding data packet(s). In other words, if a data packet with sequence number `A` was lost, then acknowledgement of the following `SRTO_FC` packets is blocked until packet `A` is either successfully retransmitted or dropped by the [Too-Late Packet Drop mechanism](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-4.6). Thus the sender will have `SRTO_FC` packets in flight, and will not be allowed to send further data packets. Therefore, when establishing the value of `SRTO_FC`, it is recommend taking into consideration possible delays due to packet loss and retransmission. There is a restriction that the receiver buffer size ([SRTO_RCVBUF](#SRTO_RCVBUF)) must not be greater than `SRTO_FC` ([#700](https://github.com/Haivision/srt/issues/700)). Therefore, it is recommended to set the value of `SRTO_FC` first, and then the value of `SRTO_RCVBUF`. The default flow control window size is 25600 packets. It is approximately: - 270 Mbits of payload in the default live streaming configuration with an SRT payload size of 1316 bytes; - 300 Mbits of payload with an SRT payload size of 1456 bytes. The minimum number of packets in flight should be (assuming max payload size): `FCmin = bps / 8 × RTTsec / (MSS - 44)`, where - `bps` - is the payload bitrate of the stream in bits per second; - `RTTsec` - RTT of the network connection in seconds; - `MSS` - Maximum segment size (aka MTU), see [SRTO_MSS](#SRTO_MSS); - 44 - size of headers (20 bytes IPv4 + 8 bytes of UDP + 16 bytes of SRT packet header). To avoid blocking the sending of further packets in case of packet loss, the recommended flow control window is `FC = bps / 8 × (RTTsec + latency_sec) / (MSS - 44)`, where `latency_sec` is the receiver buffering delay ([SRTO_RCVLATENCY](#SRTO_RCVLATENCY)) **in seconds**. [Return to list](#list-of-options) --- #### SRTO_GROUPCONNECT | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | --------- | ------ | -------- | ------ | --- | ------ | | `SRTO_GROUPCONNECT` | 1.5.0 | pre | `int32_t` | | 0 | 0...1 | W | S | When this flag is set to 1 on a listener socket, it allows this socket to accept group connections. When set to the default 0, group connections will be rejected. Keep in mind that if the `SRTO_GROUPCONNECT` flag is set to 1 (i.e. group connections are allowed) `srt_accept` may return a socket **or** a group ID. A call to `srt_accept` on a listener socket that has group connections allowed must take this into consideration. It's up to the caller of this function to make this distinction and to take appropriate action depending on the type of entity returned. When this flag is set to 1 on an accepted socket that is passed to the listener callback handler, it means that this socket is created for a group connection and it will become a member of a group. Note that in this case only the first connection within the group will result in reporting from `srt_accept` (further connections are handled in the background), and this function will return the group, not this socket ID. [Return to list](#list-of-options) --- #### SRTO_GROUPSTABTIMEO | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | --------------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_GROUPSTABTIMEO` | 1.5.0 | pre | `int32_t` | ms | 80 | 10-... | W | GSD+ | **Not in use at the moment. Is to be repurposed in SRT v1.4.3!** This setting is used for groups of type `SRT_GTYPE_BACKUP`. It defines the stability timeout, which is the maximum interval between two consecutive packets retrieved from the peer on the currently active link. These two packets can be of any type, but this setting usually refers to control packets while the agent is a sender. Idle links exchange only keepalive messages once per second, so they do not count. Note that this option is meaningless on sockets that are not members of the Backup-type group. This value should be set with a thoroughly selected balance and correspond to the maximum stretched response time between two consecutive ACK messages. By default ACK messages are sent every 10ms (so this interval is not dependent on the network latency), and so should be the interval between two consecutive received ACK messages. Note, however, that the network jitter on the public internet causes these intervals to be stretched, even to multiples of that interval. Both large and small values of this option have consequences: Large values of this option prevent overreaction on highly stretched response times, but introduce a latency penalty - the latency must be greater than this value (otherwise switching to another link won't preserve smooth signal sending). Large values will also contribute to higher packet bursts sent at the moment when an idle link is activated. Smaller values of this option respect low latency requirements very well, but may cause overreaction on even slightly stretched response times. This is unwanted, as a link switch should ideally happen only when the currently active link is really broken, as every link switch costs extra overhead (it counts for 100% for a time of one ACK interval). Note that the value of this option is not allowed to exceed the value of `SRTO_PEERIDLETIMEO`. Usually it is only meaningful if you change the latter option, as the default value of it is way above any sensible value of `SRTO_GROUPSTABTIMEO`. [Return to list](#list-of-options) --- #### SRTO_GROUPTYPE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_GROUPTYPE` | 1.5.0 | | `int32_t` | enum | | | R | S | This option is read-only and it is intended to be called inside the listener callback handler (see `srt_listen_callback`). Possible values are defined in the `SRT_GROUP_TYPE` enumeration type. This option returns the group type that is declared in the incoming connection. If the incoming connection is not going to make a group-member connection, then the value returned is `SRT_GTYPE_UNDEFINED`. If this option is read in any other context than inside the listener callback handler, the value is undefined. [Return to list](#list-of-options) --- #### SRTO_INPUTBW | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ---------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_INPUTBW` | 1.0.5 | post | `int64_t` | B/s | 0 | 0.. | RW | GSD | This option is effective only if [`SRTO_MAXBW`](#SRTO_MAXBW) is set to 0 (relative). It controls the maximum bandwidth together with [`SRTO_OHEADBW`](#SRTO_OHEADBW) option according to the formula: `MAXBW = INPUTBW * (100 + OHEADBW) / 100`. When this option is set to 0 (automatic) then the real INPUTBW value will be estimated from the rate of the input (cases when the application calls the `srt_send*` function) during transmission. The minimum allowed estimate value is restricted by [`SRTO_MININPUTBW`](#SRTO_MININPUTBW), meaning `INPUTBW = MAX(INPUTBW_ESTIMATE; MININPUTBW)`. *Recommended: set this option to the anticipated bitrate of your live stream and keep the default 25% value for `SRTO_OHEADBW`*. [Return to list](#list-of-options) --- #### SRTO_MININPUTBW | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_MININPUTBW` | 1.4.3 | post | `int64_t` | B/s | 0 | 0.. | RW | GSD | This option is effective only if both `SRTO_MAXBW` and `SRTO_INPUTBW` are set to 0. It controls the minimum allowed value of the input bitrate estimate. See [`SRTO_INPUTBW`](#SRTO_INPUTBW). [Return to list](#list-of-options) --- #### SRTO_IPTOS | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ---------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_IPTOS` | 1.0.5 | pre-bind | `int32_t` | | (system) | 0..255 | RW | GSD | IPv4 Type of Service (see `IP_TOS` option for IP) or IPv6 Traffic Class (see `IPV6_TCLASS` of IPv6) depending on socket address family. Applies to sender only. When *getting*, the returned value is the user preset for non-connected sockets and the actual value for connected sockets. *Sender*: user configurable, default: `0xB8`. [Return to list](#list-of-options) --- #### SRTO_IPTTL | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ---------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_IPTTL` | 1.0.5 | pre-bind | `int32_t` | hops | (system) | 1..255 | RW | GSD | IPv4 Time To Live (see `IP_TTL` option for IP) or IPv6 unicast hops (see `IPV6_UNICAST_HOPS` for IPv6) depending on socket address family. Applies to sender only. When *getting*, the returned value is the user preset for non-connected sockets and the actual value for connected sockets. *Sender*: user configurable, default: 64 [Return to list](#list-of-options) --- #### SRTO_IPV6ONLY | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ---------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_IPV6ONLY` | 1.4.0 | pre-bind | `int32_t` | | (system) | -1..1 | RW | GSD | Set system socket flag `IPV6_V6ONLY`. When set to 0 a listening socket binding an IPv6 address accepts also IPv4 clients (their addresses will be formatted as IPv4-mapped IPv6 addresses). By default (-1) this option is not set and the platform default value is used. [Return to list](#list-of-options) --- #### SRTO_ISN | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ---------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_ISN` | 1.3.0 | | `int32_t` | | | | R | S | The value of the ISN (Initial Sequence Number), which is the first sequence number put on the first UDP packets sent that are carrying an SRT data payload. *This value is useful for developers of some more complicated methods of flow control, possibly with multiple SRT sockets at a time. It is not intended to be used in any regular development.* [Return to list](#list-of-options) --- #### SRTO_KMPREANNOUNCE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | --------------------- | ----- | -------- | ---------- | ------ | ----------------- | ------ | --- | ------ | | `SRTO_KMPREANNOUNCE` | 1.3.2 | pre | `int32_t` | pkts | 0: 212 | 0.. * | RW | GSD | The interval (defined in packets) between when a new Stream Encrypting Key (SEK) is sent and when switchover occurs. This value also applies to the subsequent interval between when switchover occurs and when the old SEK is decommissioned. At `SRTO_KMPREANNOUNCE` packets before switchover the new key is sent (repeatedly, if necessary, until it is confirmed by the receiver). At the switchover point (see `SRTO_KMREFRESHRATE`), the sender starts encrypting and sending packets using the new key. The old key persists in case it is needed to decrypt packets that were in the flight window, or retransmitted packets. The old key is decommissioned at `SRTO_KMPREANNOUNCE` packets after switchover. **The allowed range** for this value is between 1 and half of the current value of `SRTO_KMREFRESHRATE`. The minimum value should never be less than the flight window [`SRTO_FC`](#SRTO_FC) (i.e. the number of packets that have already left the sender but have not yet arrived at the receiver). The value of `SRTO_KMPREANNOUNCE must not exceed `(SRTO_KMREFRESHRATE - 1) / 2`. **Default value:** `0` - corresponds to 4096 packets (212 or 0x1000). [Return to list](#list-of-options) --- #### SRTO_KMREFRESHRATE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | --------------------- | ----- | -------- | ---------- | ------ | ---------------- | ------ | --- | ------ | | `SRTO_KMREFRESHRATE` | 1.3.2 | pre | `int32_t` | pkts | 0: 224| 0.. | RW | GSD | The number of packets to be transmitted after which the Stream Encryption Key (SEK), used to encrypt packets, will be switched to the new one. Note that the old and new keys live in parallel for a certain period of time (see `SRTO_KMPREANNOUNCE`) before and after the switchover. Having a preannounce period before switchover ensures the new SEK is installed at the receiver before the first packet encrypted with the new SEK is received. The old key remains active after switchover in order to decrypt packets that might still be in flight, or packets that have to be retransmitted. **Default value:** `0` - corresponds to 16777216 packets (224 or 0x1000000). [Return to list](#list-of-options) --- #### SRTO_KMSTATE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | --------------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_KMSTATE` | 1.0.2 | | `int32_t` | enum | | | R | S | Keying Material state. This is a legacy option that is equivalent to `SRTO_SNDKMSTATE`, if the socket has set `SRTO_SENDER` to true, and `SRTO_RCVKMSTATE` otherwise. This option is then equal to `SRTO_RCVKMSTATE` always if your application disregards possible cooperation with a peer older than 1.3.0, but then with the default value of `SRTO_ENFORCEDENCRYPTION` the value returned by both options is always the same. See [`SRT_KM_STATE`](#2-srt_km_state) for more details. [Return to list](#list-of-options) --- #### SRTO_LATENCY | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | --------------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_LATENCY` | 1.0.2 | pre | `int32_t` | ms | 120 * | 0.. | RW | GSD | This option sets both [`SRTO_RCVLATENCY`](#SRTO_RCVLATENCY) and [`SRTO_PEERLATENCY`](#SRTO_PEERLATENCY) to the same value specified. Prior to SRT version 1.3.0 `SRTO_LATENCY` was the only option to set the latency. However it is effectively equivalent to setting `SRTO_PEERLATENCY` in the sending direction (see [`SRTO_SENDER`](#SRTO_SENDER)), and `SRTO_RCVLATENCY` in the receiving direction. SRT version 1.3.0 and higher support bidirectional transmission, so that each side can be sender and receiver at the same time, and `SRTO_SENDER` became redundant. [Return to list](#list-of-options) --- #### SRTO_LINGER | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_LINGER` | | pre | `linger` | s | on, 180 | 0.. | RW | GSD | Linger time on close (see [SO\_LINGER](http://man7.org/linux/man-pages/man7/socket.7.html)). *SRT recommended value*: off (0). [Return to list](#list-of-options) --- #### SRTO_LOSSMAXTTL | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_LOSSMAXTTL` | 1.2.0 | post | `int32_t` | packets | 0 | 0.. | RW | GSD+ | The value up to which the *Reorder Tolerance* may grow. The *Reorder Tolerance* is the number of packets that must follow the experienced "gap" in sequence numbers of incoming packets so that the loss report is sent (in the hope that the gap is due to packet reordering rather than because of loss). The value of *Reorder Tolerance* starts from 0 and is set to a greater value when packet reordering is detected This happens when a "belated" packet, with sequence number older than the latest received, has been received, but without retransmission flag. When this is detected the *Reorder Tolerance* is set to the value of the interval between latest sequence and this packet's sequence, but not more than the value set by `SRTO_LOSSMAXTTL`. By default this value is set to 0, which means that this mechanism is off. [Return to list](#list-of-options) --- #### SRTO_MAXBW | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_MAXBW` | 1.0.5 | post | `int64_t` | B/s | -1 | -1.. | RW | GSD | Maximum send bandwidth: - `-1`: infinite (the limit in Live Mode is 1 Gbps); - `0`: relative to input rate (see [`SRTO_INPUTBW`](#SRTO_INPUTBW)); - `>0`: absolute limit in B/s. **NOTE**: This option has a default value of -1, regardless of the mode. For live streams it is typically recommended to set the value 0 here and rely on `SRTO_INPUTBW` and `SRTO_OHEADBW` options. However, if you want to do so, you should make sure that your stream has a fairly constant bitrate, or that changes are not abrupt, as high bitrate changes may work against the measurement. SRT cannot ensure that this is always the case for a live stream, therefore the default -1 remains even in live mode. [Return to list](#list-of-options) --- #### SRTO_MESSAGEAPI | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_MESSAGEAPI` | 1.3.0 | pre | `bool` | | true | | W | GSD | When set, this socket uses the Message API[\*], otherwise it uses the Stream API. Note that in live mode (see [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE) option) only the Message API is available. In File mode you can chose to use one of two modes (note that the default for this option is changed with `SRTO_TRANSTYPE` option): - **Stream API** (default for file mode): In this mode you may send as many data as you wish with one sending instruction, or even use dedicated functions that operate directly on a file. The internal facility will take care of any speed and congestion control. When receiving, you can also receive as many data as desired. The data not extracted will be waiting for the next call. There is no boundary between data portions in Stream mode. - **Message API**: In this mode your single sending instruction passes exactly one piece of data that has boundaries (a message). Contrary to Live mode, this message may span multiple UDP packets, and the only size limitation is that it shall fit as a whole in the sending buffer. The receiver shall use as large a buffer as necessary to receive the message, otherwise reassembling and delivering the message might not be possible. When the message is not complete (not all packets received or there was a packet loss) it will not be copied to the application's buffer. Messages that are sent later, but were earlier reassembled by the receiver, will be delivered once ready, if the `inorder` flag was set to false. See [`srt_sendmsg`](API.md#sending-and-receiving). As a comparison to the standard system protocols, the Stream API does transmission similar to TCP, whereas the Message API functions like the SCTP protocol. [Return to list](#list-of-options) --- #### SRTO_MINVERSION | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_MINVERSION` | 1.3.0 | pre | `int32_t` | version | 0x010000 | * | RW | GSD | The minimum SRT version that is required from the peer. A connection to a peer that does not satisfy the minimum version requirement will be rejected. See [`SRTO_VERSION`](#SRTO_VERSION) for the version format. The default value is 0x010000 (SRT v1.0.0). [Return to list](#list-of-options) --- #### SRTO_MSS | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_MSS` | | pre-bind | `int32_t` | bytes | 1500 | 76.. | RW | GSD | Maximum Segment Size. Used for buffer allocation and rate calculation using packet counter assuming fully filled packets. Each party can set its own MSS value independently. During a handshake the parties exchange MSS values, and the lowest is used. *Generally on the internet MSS is 1500 by default. This is the maximum size of a UDP packet and can be only decreased, unless you have some unusual dedicated network settings. MSS is not to be confused with the size of the UDP payload or SRT payload - this size is the size of the IP packet, including the UDP and SRT headers* THe value of `SRTO_MSS` must not exceed `SRTO_UDP_SNDBUF` or `SRTO_UDP_RCVBUF`. [Return to list](#list-of-options) --- #### SRTO_NAKREPORT | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_NAKREPORT` | 1.1.0 | pre | `bool` | | * | | RW | GSD+ | When set to true, every report for a detected loss will be repeated when the timeout for the expected retransmission of this loss has expired and the missing packet still wasn't recovered, or wasn't conditionally dropped (see [`SRTO_TLPKTDROP`](#SRTO_TLPKTDROP)). The default is true for Live mode, and false for File mode (see [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE)). [Return to list](#list-of-options) --- #### SRTO_OHEADBW | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_OHEADBW` | 1.0.5 | post | `int32_t` | % | 25 | 5..100 | RW | GSD | Recovery bandwidth overhead above input rate (see [`SRTO_INPUTBW`](#SRTO_INPUTBW)), in percentage of the input rate. It is effective only if `SRTO_MAXBW` is set to 0. *Sender*: user configurable, default: 25%. Recommendations: - Overhead is intended to give you extra bandwidth for the case when a packet has taken part of the bandwidth, but then was lost and has to be retransmitted. Therefore the effective maximum bandwidth should be appropriately higher than your stream's bitrate so that there's some room for retransmission, but still limited so that the retransmitted packets don't cause the bandwidth usage to skyrocket when larger groups of packets are lost - Don't configure it too low and avoid 0 in the case when you have the `SRTO_INPUTBW` option set to 0 (automatic). Otherwise your stream will choke and break quickly at any rise in packet loss. - ***To do: set-only; get should be supported.*** [Return to list](#list-of-options) --- #### SRTO_PACKETFILTER | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_PACKETFILTER` | 1.4.0 | pre | `string` | | "" | [512] | RW | GSD | Set up the packet filter. The string must match appropriate syntax for packet filter setup. Note also that: * The configuration is case-sentitive (e.g. "FEC,Cols:20" is not valid). * Setting this option will fail if you use an unknown filter type. An empty value for this option means that for this connection the filter isn't required, but it will accept any filter settings if provided by the peer. If this option is changed by both parties simultaneously, the result will be a configuration integrating parameters from both parties, that is: * parameters provided by both parties are accepted, if they are identical * parameters that are set only on one side will have the value defined by that side * parameters not set in either side will be set as default The connection will be rejected with `SRT_REJ_FILTER` code in the following cases: * both sides define a different packet filter type * for the same key two different values were provided by both sides * mandatory parameters weren't provided by either side In case of the built-in `fec` filter, the mandatory parameter is `cols`, all others have their default values. For example, the configuration specified as `fec,cols:10` is `fec,cols:10,rows:1,arq:onreq,layout:even`. See how to configure the FEC filter in [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md#configuring-the-fec-filter). Below in the table are examples for the built-in `fec` filter. Note that the negotiated config need not have parameters in the given order. Cases when negotiation succeeds: | Peer A | Peer B | Negotiated Config |----------------------|---------------------|------------------------------------------------------ | (no filter) | (no filter) | | fec,cols:10 | fec | fec,cols:10,rows:1,arq:onreq,layout:even | fec,cols:10 | fec,cols:10,rows:20 | fec,cols:10,rows:20,arq:onreq,layout:even | fec,layout:staircase | fec,cols:10 | fec,cols:10,rows:1,arq:onreq,layout:staircase In these cases the configuration is rejected with SRT_REJ_FILTER code: | Peer A | Peer B | Error reason |-----------------------|---------------------|-------------------------- | fec | (no filter) | missing `cols` parameter | fec,rows:20,arq:never | fec,layout:even | missing `cols` parameter | fec,cols:20 | fec,cols:10 | `cols` parameter value conflict | fec,cols:20,rows:20 | fec,cols:20,rows:10 | `rows` parameter value conflict In general it is recommended that one party defines the full configuration, while the other keeps this value empty. Reading this option after the connection is established will return the full configuration that has been agreed upon by both parties (including default values). For details, see [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md). [Return to list](#list-of-options) --- #### SRTO_PASSPHRASE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_PASSPHRASE` | 0.0.0 | pre | `string` | | "" |[10..79]| W | GSD | Sets the passphrase for encryption. This enables encryption on this party (or disables it, if an empty passphrase is passed). The password must be minimum 10 and maximum 79 characters long. The passphrase is the shared secret between the sender and the receiver. It is used to generate the Key Encrypting Key using [PBKDF2](http://en.wikipedia.org/wiki/PBKDF2) (Password-Based Key Derivation Function 2). When a socket with configured passphrase is being connected, the peer must have the same password set, or the connection is rejected. This behavior can be changed by [`SRTO_ENFORCEDENCRYPTION`](#SRTO_ENFORCEDENCRYPTION). Note that since the introduction of bidirectional support, there's only one initial encryption key to encrypt the stream (new keys after refreshing will be updated independently), and there's no distinction between "service party that defines the password" and "client party that is required to set matching password" - both parties are equivalent, and in order to have a working encrypted connection, they have to simply set the same passphrase. [Return to list](#list-of-options) --- #### SRTO_PAYLOADSIZE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_PAYLOADSIZE` | 1.3.0 | pre | `int32_t` | bytes | \* | 0.. \* | W | GSD | Sets the maximum declared size of a single call to sending function in Live mode. When set to 0, there's no limit for a single sending call. For Live mode: Default value is 1316, but can be increased up to 1456. Note that with the `SRTO_PACKETFILTER` option additional header space is usually required, which decreases the maximum possible value for `SRTO_PAYLOADSIZE`. For File mode: Default value is 0 and it's recommended not to be changed. [Return to list](#list-of-options) --- #### SRTO_PBKEYLEN | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_PBKEYLEN` | 0.0.0 | pre | `int32_t` | bytes | 0 | * | RW | GSD | Encryption key length. Possible values: - 0 =`PBKEYLEN` (default value) - 16 = AES-128 (effective value) - 24 = AES-192 - 32 = AES-256 The use is slightly different in 1.2.0 (HSv4), and since 1.3.0 (HSv5): - **HSv4**: This is set on the sender and enables encryption, if not 0. The receiver shall not set it and will agree on the length as defined by the sender. - **HSv5**: The "default value" for `PBKEYLEN` is 0, which means that the `PBKEYLEN` won't be advertised. The "effective value" for `PBKEYLEN` is 16, but this applies only when neither party has set the value explicitly (i.e. when both are initially at the default value of 0). If any party *has* set an explicit value (16, 24, 32) it will be advertised in the handshake. If the other party remains at the default 0, it will accept the peer's value. The situation where both parties set a value should be treated carefully. Actually there are three intended methods of defining it, and all other uses are considered undefined behavior: - **Unidirectional**: the sender shall set `PBKEYLEN` and the receiver shall not alter the default value 0. The effective `PBKEYLEN` will be the one set on the sender. The receiver need not know the sender's `PBKEYLEN`, just the passphrase, `PBKEYLEN` will be correctly passed. - **Bidirectional in Caller-Listener arrangement**: it is recommended to use a rule whereby you will be setting the `PBKEYLEN` exclusively either on the Listener or on the Caller. The value set on the Listener will win, if set on both parties. - **Bidirectional in Rendezvous arrangement**: you have to know the passphrases for both parties, as well as `PBKEYLEN`. Set `PBKEYLEN` to the same value on both parties (or leave the default value on both parties, which will result in 16) - **Unwanted behavior cases**: if both parties set `PBKEYLEN` and the value on both sides is different, the effective `PBKEYLEN` will be the one that is set on the Responder party, which may also override the `PBKEYLEN` 32 set by the sender to value 16 if such value was used by the receiver. The Responder party is the Listener in a Caller-Listener arrangement. In Rendezvous it's a matter of luck which party becomes the Responder. [Return to list](#list-of-options) --- #### SRTO_PEERIDLETIMEO | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_PEERIDLETIMEO` | 1.3.3 | pre | `int32_t` | ms | 5000 | 0.. | RW | GSD+ | The maximum time in `[ms]` to wait until another packet is received from a peer since the last such packet reception. If this time is passed, the connection is considered broken on timeout. [Return to list](#list-of-options) --- #### SRTO_PEERLATENCY | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_PEERLATENCY` | 1.3.0 | pre | `int32_t` | ms | 0 | 0.. | RW | GSD | The latency value (as described in [`SRTO_RCVLATENCY`](#SRTO_RCVLATENCY)) provided by the sender side as a minimum value for the receiver. Reading the value of the option on an unconnected socket reports the configured value. Reading the value on a connected socket reports the effective receiver buffering latency of the peer. **The `SRTO_PEERLATENCY` option in versions prior to 1.3.0 is only available as** [`SRTO_LATENCY`](#SRTO_LATENCY). See also [`SRTO_LATENCY`](#SRTO_LATENCY). [Return to list](#list-of-options) --- #### SRTO_PEERVERSION | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | | `SRTO_PEERVERSION` | 1.1.0 | | `int32_t` | * | | | R | GS | SRT version used by the peer. The value 0 is returned if not connected, SRT handshake not yet performed (HSv4 only), or if peer is not SRT. See [`SRTO_VERSION`](#SRTO_VERSION) for the version format. [Return to list](#list-of-options) --- #### SRTO_RCVBUF | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | ---------- | ------ | --- | ------ | | `SRTO_RCVBUF` | | pre-bind | `int32_t` | bytes | 8192 bufs | * | RW | GSD+ | Receive Buffer Size, in bytes. Note, however, that the internal setting of this value is in the number of buffers, each one of size equal to SRT payload size, which is the value of `SRTO_MSS` decreased by UDP and SRT header sizes (28 and 16). The value set here will be effectively aligned to the multiple of payload size. - **Minimum value**: 32 buffers (46592 with default value of `SRTO_MSS`). - **Maximum value**: [`SRTO_FC`](#SRTO_FC) number of buffers (receiver buffer must not be greater than the Flight Flag size). [Return to list](#list-of-options) --- #### SRTO_RCVDATA | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | ---------- | ------ | --- | ------ | | `SRTO_RCVDATA` | | | `int32_t` | pkts | | | R | S | Size of the available data in the receive buffer. [Return to list](#list-of-options) --- #### SRTO_RCVKMSTATE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | ---------- | ------ | --- | ------ | | `SRTO_RCVKMSTATE` | 1.2.0 | | `int32_t` | enum | | | R | S | KM state on the agent side when it's a receiver. Values defined in enum [`SRT_KM_STATE`](#srt_km_state). [Return to list](#list-of-options) --- #### SRTO_RCVLATENCY | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | ---------- | ------ | --- | ------ | | `SRTO_RCVLATENCY` | 1.3.0 | pre | `int32_t` | ms | * | 0.. | RW | GSD | The latency value in the receiving direction of the socket. This value is only significant when [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE) is enabled. **Default value**: 120 ms in Live mode, 0 in File mode (see [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE)). The latency value defines the **minimum** receiver buffering delay before delivering an SRT data packet from a receiving SRT socket to a receiving application. The provided value is used in the connection establishment (handshake exchange) stage to fix the end-to-end latency of the transmission. The effective end-to-end latency `L` will be fixed as the network transmission time of the final handshake packet (~1/2 RTT) plus the **negotiated** latency value `Ln`. Data packets will stay in the receiver buffer for at least `L` microseconds since the timestamp of the packet, independent of the actual network transmission times (RTT variations) of these packets. The actual value of the receiver buffering delay `Ln` (the negotiated latency) used on a connection is determined by the negotiation in the connection establishment (handshake exchange) phase as the maximum of the `SRTO_RCVLATENCY` value and the value of [`SRTO_PEERLATENCY`](#SRTO_PEERLATENCY) set by the peer. Reading the `SRTO_RCVLATENCY` value on a socket after the connection is established provides the actual (negotiated) latency value `Ln`. The receiver's buffer must be large enough to store the `L` segment of the stream, i.e. `L × Bitrate` bytes. Refer to [`SRTO_RCVBUF`](#SRTO_RCVBUF). The sender's buffer must be large enough to store a packet up until it is either delivered (and acknowledged) or dropped by the sender due to it becoming too late to be delivered. In other words, `D × Bitrate` bytes, where `D` is the sender's drop delay value configured with [`SRTO_SNDDROPDELAY`](#SRTO_SNDDROPDELAY). Buffering of data packets on the receiving side makes it possible to recover from packet losses using the ARQ (Automatic Repeat Request) technique, and to deal with varying RTT times (network jitter) in the network, providing a (close to) **constant end-to-end latency of the transmission**. See also [`SRTO_LATENCY`](#SRTO_LATENCY). [Return to list](#list-of-options) --- #### SRTO_RCVSYN | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | ---------- | ------ | --- | ------ | | `SRTO_RCVSYN` | | post | `bool` | | true | | RW | GSI | When true, sets blocking mode on reading function when it's not ready to perform the operation. When false ("non-blocking mode"), the reading function will in this case report error `SRT_EASYNCRCV` and return immediately. Details depend on the tested entity: On a connected socket or group this applies to a receiving function (`srt_recv` and others) and a situation when there are no data available for reading. The readiness state for this operation can be tested by checking the `SRT_EPOLL_IN` flag on the aforementioned socket or group. On a freshly created socket or group that is about to be connected to a peer listener this applies to any `srt_connect` call (and derived), which in "non-blocking mode" always returns immediately. The connected state for that socket or group can be tested by checking the `SRT_EPOLL_OUT` flag. Note that a socket that failed to connect doesn't change the `SRTS_CONNECTING` state and can be found out only by testing the `SRT_EPOLL_ERR` flag. On a listener socket this applies to `srt_accept` call. The readiness state for this operation can be tested by checking the `SRT_EPOLL_IN` flag on this listener socket. This flag is also derived from the listener socket by the accepted socket or group, although the meaning of this flag is effectively different. Note that when this flag is set only on a group, it applies to a specific receiving operation being done on that group (i.e. it is not derived from the socket of which the group is a member). [Return to list](#list-of-options) --- #### SRTO_RCVTIMEO | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | ---------- | ------ | --- | ------ | | `SRTO_RCVTIMEO` | | post | `int32_t` | ms | -1 | -1, 0..| RW | GSI | Limits the time up to which the receiving operation will block (see [`SRTO_RCVSYN`](#SRTO_RCVSYN) for details), such that when this time is exceeded, it will behave as if in "non-blocking mode". The -1 value means no time limit. [Return to list](#list-of-options) --- #### SRTO_RENDEZVOUS | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | ---------- | ------ | --- | ------ | | `SRTO_RENDEZVOUS` | | pre | `bool` | | false | | RW | S | Use Rendezvous connection mode (both sides must set this and both must use the procedure of `srt_bind` and then `srt_connect` (or `srt_rendezvous`) to one another. [Return to list](#list-of-options) --- #### SRTO_RETRANSMITALGO | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | --------------------- | ----- | -------- | --------- | ------ | ------- | ------ | --- | ------ | | `SRTO_RETRANSMITALGO` | 1.4.2 | pre | `int32_t` | | 1 | [0, 1] | RW | GSD | An SRT sender option to choose between two retransmission algorithms: - 0 - an intensive retransmission algorithm (default until SRT v1.4.4), and - 1 - a new efficient retransmission algorithm (introduced in SRT v1.4.2; default since SRT v1.4.4). The intensive retransmission algorithm causes the SRT sender to schedule a packet for retransmission each time it receives a negative acknowledgement (NAK). On a network characterized by low packet loss levels and link capacity high enough to accommodate extra retransmission overhead, this algorithm increases the chances of recovering from packet loss with a minimum delay, and may better suit end-to-end latency constraints. The new efficient algorithm optimizes the bandwidth usage by producing fewer retransmissions per lost packet. It takes SRT statistics into account to determine if a retransmitted packet is still in flight and could reach the receiver in time, so that some of the NAK reports are ignored by the sender. This algorithm better fits general use cases, as well as cases where channel bandwidth is limited. NOTE: This option is effective only on the sending side. It influences the decision as to whether a particular reported lost packet should be retransmitted at a certain time or not. NOTE: The efficient retransmission algorithm can only be used when a receiver sends Periodic NAK reports. See [SRTO_NAKREPORT](#SRTO_NAKREPORT). [Return to list](#list-of-options) --- #### SRTO_REUSEADDR | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | ---------- | ------ | --- | ------ | | `SRTO_REUSEADDR` | | pre-bind | `bool` | | true | | RW | GSD | When true, allows the SRT socket to use the binding address used already by another SRT socket in the same application. Note that SRT socket uses an intermediate object called Multiplexer to access the underlying UDP sockets, so multiple SRT sockets may share one UDP socket, and the packets received by this UDP socket will be correctly dispatched to the SRT socket to which they are currently destined. This has some similarities to the `SO_REUSEADDR` system socket option, although it's only used inside SRT. *TODO: This option weirdly only allows the socket used in **bind()** to use the local address that another socket is already using, but not to disallow another socket in the same application to use the binding address that the current socket is already using. What it actually changes is that when given an address in **bind()** is already used by another socket, this option will make the binding fail instead of adding the socket to the shared group of that socket that already has bound this address - but it will not disallow another socket to reuse its address.* [Return to list](#list-of-options) --- #### SRTO_SENDER | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | ---------- | ------ | --- | ------ | | `SRTO_SENDER` | 1.0.4 | pre | `bool` | | false | | W | S | Set sender side. The side that sets this flag is expected to be a sender. This flag is only required when communicating with a receiver that uses SRT version less than 1.3.0 (and hence *HSv4* handshake), in which case if not set properly, the TSBPD mode (see [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE)) or encryption will not work. Setting `SRTO_MINVERSION` to 1.3.0 is therefore recommended. [Return to list](#list-of-options) --- #### SRTO_SNDBUF | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_SNDBUF` | | pre-bind | `int32_t` | bytes |8192 bufs | * | RW | GSD+ | Sender Buffer Size. See [`SRTO_RCVBUF`](#SRTO_RCVBUF) for more information. [Return to list](#list-of-options) --- #### SRTO_SNDDATA | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_SNDDATA` | | | `int32_t` | pkts | | | R | S | Size of the unacknowledged data in send buffer. [Return to list](#list-of-options) --- #### SRTO_SNDDROPDELAY | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_SNDDROPDELAY` | 1.3.2 | post | `int32_t` | ms | * | -1.. | W | GSD+ | Sets an extra delay before `TLPKTDROP` is triggered on the data sender. This delay is added to the default drop delay time interval value. Keep in mind that the longer the delay, the more probable it becomes that packets would be retransmitted uselessly because they will be dropped by the receiver anyway. `TLPKTDROP` discards packets reported as lost if it is already too late to send them (the receiver would discard them even if received). The delay before the `TLPKTDROP` mechanism is triggered consists of the SRT latency (`SRTO_PEERLATENCY`), plus `SRTO_SNDDROPDELAY`, plus `2 * interval between sending ACKs` (where the default `interval between sending ACKs` is 10 milliseconds). The minimum delay is `1000 + 2 * interval between sending ACKs` milliseconds. **Special value -1**: Do not drop packets on the sender at all (retransmit them always when requested). **Default:** 0 in Live mode, -1 in File mode. [Return to list](#list-of-options) --- #### SRTO_SNDKMSTATE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_SNDKMSTATE` | 1.2.0 | | `int32_t` | enum | | | R | S | Peer KM state on receiver side for `SRTO_KMSTATE` Values defined in enum [`SRT_KM_STATE`](#srt_km_state). [Return to list](#list-of-options) --- #### SRTO_SNDSYN | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_SNDSYN` | | post | `bool` | | true | | RW | GSI | When true, sets blocking mode on writing function when it's not ready to perform the operation. When false ("non-blocking mode"), the writing function will in this case report error `SRT_EASYNCSND` and return immediately. On a connected socket or group this applies to a sending function (`srt_send` and others) and a situation when there's no free space in the sender buffer, caused by inability to send all the scheduled data over the network. Readiness for this operation can be tested by checking the `SRT_EPOLL_OUT` flag. On a freshly created socket or group it will have no effect until the socket enters a connected state. On a listener socket it will be derived by the accepted socket or group, but will have no effect on the listener socket itself. [Return to list](#list-of-options) --- #### SRTO_SNDTIMEO | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_SNDTIMEO` | | post | `int32_t` | ms | -1 | -1.. | RW | GSI | limit the time up to which the sending operation will block (see `SRTO_SNDSYN` for details), so when this time is exceeded, it will behave as if in "non-blocking mode". The -1 value means no time limit. [Return to list](#list-of-options) --- #### SRTO_STATE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_STATE` | | | `int32_t` | enum | | | R | S | Returns the current socket state, same as `srt_getsockstate`. [Return to list](#list-of-options) --- #### SRTO_STREAMID | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_STREAMID` | 1.3.0 | pre | `string` | | "" | [512] | RW | GSD | - A string that can be set on the socket prior to connecting. The listener side will be able to retrieve this stream ID from the socket that is returned from `srt_accept` (for a connected socket with that stream ID). You usually use SET on the socket used for `srt_connect`, and GET on the socket retrieved from `srt_accept`. This string can be used completely free-form. However, it's highly recommended to follow the [SRT Access Control (Stream ID) Guidlines](../features/access-control.md). - As this uses internally the `std::string` type, there are additional functions for it in the legacy/C++ API (udt.h): `srt::setstreamid` and `srt::getstreamid`. - This option is not useful for a Rendezvous connection, since one side would override the value from the other side resulting in an arbitrary winner. Also in this connection both peers are known to one another and both have equivalent roles in the connection. - **IMPORTANT**: This option is not derived by the accepted socket from the listener socket, and setting it on a listener socket (see `srt_listen` function) doesn't influence anything. [Return to list](#list-of-options) --- #### SRTO_TLPKTDROP | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_TLPKTDROP` | 1.0.6 | pre | `bool` | | * | | RW | GSD | Too-late Packet Drop. When enabled on receiver, it skips missing packets that have not been delivered in time and delivers the subsequent packets to the application when their time-to-play has come. It also sends a fake ACK to the sender. When enabled on sender and enabled on the receiving peer, sender drops the older packets that have no chance to be delivered in time. It is automatically enabled in sender if receiver supports it. **Default:** true in Live mode, false in File mode (see [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE)) [Return to list](#list-of-options) --- #### SRTO_TRANSTYPE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_TRANSTYPE` | 1.3.0 | pre | `int32_t` | enum |`SRTT_LIVE`| \* | W | S | Sets the transmission type for the socket, in particular, setting this option sets multiple other parameters to their default values as required for a particular transmission type. Values defined by enum `SRT_TRANSTYPE` (see above for possible values) [Return to list](#list-of-options) --- #### SRTO_TSBPDMODE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_TSBPDMODE` | 0.0.0 | pre | `bool` | | \* | | W | S | When true, use Timestamp-based Packet Delivery mode. In this mode the packet's time is assigned at the sending time (or allowed to be predefined), transmitted in the packet's header, and then restored on the receiver side so that the time intervals between consecutive packets are preserved when delivering to the application. **Default:** true in Live mode, false in File mode (see [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE)). [Return to list](#list-of-options) --- #### SRTO_UDP_RCVBUF | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_UDP_RCVBUF` | | pre-bind | `int32_t` | bytes | 8192 bufs | * | RW | GSD+ | UDP Socket Receive Buffer Size. Configured in bytes, maintained in packets based on MSS value. Receive buffer must not be greater than FC size. [Return to list](#list-of-options) --- #### SRTO_UDP_SNDBUF | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_UDP_SNDBUF` | | pre-bind | `int32_t` | bytes | 65536 | * | RW | GSD+ | UDP Socket Send Buffer Size. Configured in bytes, maintained in packets based on `SRTO_MSS` value. [Return to list](#list-of-options) --- #### SRTO_VERSION | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | ----------------- | ----- | -------- | ---------- | ------- | --------- | ------ | --- | ------ | | `SRTO_VERSION` | 1.1.0 | | `int32_t` | | | | R | S | Local SRT version. This is the highest local version supported if not connected, or the highest version supported by the peer if connected. The version format in hex is `0x00XXYYZZ` for x.y.z in human readable form. For example, version 1.4.2 is encoded as `0x010402`. [Return to list](#list-of-options) --- srt-1.4.4/docs/API/API.md000066400000000000000000001030361412557703600146410ustar00rootroot00000000000000# SRT API The SRT C API (defined in `srt.h` file) is largely based in design on the legacy UDT API, with some important changes. The `udt.h` file contains the legacy UDT API plus some minor optional functions that require the C++ standard library to be used. There are a few optional C++ API functions stored there, as there is no real C++ API for SRT. These functions may be useful in certain situations. There are some example applications so that you can see how the API is being used, including `srt-live-transmit` and `srt-file-transmit`. All SRT related material is contained in `transmitmedia.*` files in the `apps` directory which is used by all applications. See `SrtSource::Read` and `SrtTarget::Write` as examples of how data are read and written in SRT. - [Setup and Teardown](#setup-and-teardown) - [Creating and Destroying a Socket](#creating-and-destroying-a-socket) - [Binding and Connecting](#binding-and-connecting) - [Sending and Receiving](#sending-and-receiving) - [Blocking and Non-blocking Modes](#blocking-and-non-blocking-mode) - [EPoll (Non-blocking Mode Events)](#epoll-non-blocking-mode-events) - [Transmission Types](#transmission-types) - [Transmission Method: Live](#transmission-method-live) - [Transmission Method: Buffer](#transmission-method-buffer) - [Transmission Method: Message](#transmission-method-message) ## Setup and Teardown Before any part of the SRT C API can be used, the user should call the `srt_startup()` function. Likewise, before the application exits, the `srt_cleanup()` function should be called. Note that one of the things the startup function does is to create a new thread, so choose the point of execution for these functions carefully. ## Creating and Destroying a Socket To do anything with SRT, you first have to create an SRT socket. The term "socket" in this case is used because of its logical similarity to system-wide sockets. An SRT socket is not directly related to system sockets, but like a system socket it is used to define a point of communication. ### Synopsis ```c++ SRTSOCKET srt_create_socket(); int srt_close(SRTSOCKET s); ``` Note that `SRTSOCKET` is just an alias for `int`; this is a legacy naming convention from UDT, which is here only for clarity. ### Usage ```c++ sock = srt_create_socket(); ``` This creates a socket, which can next be configured and then used for communication. ```c++ srt_close(sock); ``` This closes the socket and frees all its resources. Note that the true life of the socket does not end exactly after this function exits - some details are being finished in a separate "SRT GC" thread. Still, at least all shared system resources (such as listener port) should be released after this function exits. ### Important Remarks 1. SRT uses the system UDP protocol as an underlying communication layer, and so it uses also UDP sockets. The underlying communication layer is used only instrumentally, and SRT manages UDP sockets as its own system resource as it pleases - so in some cases it may be reasonable for multiple SRT sockets to share one UDP socket, or for one SRT socket to use multiple UDP sockets. 2. The term "port" used in SRT is occasionally identical to the term "UDP port". However SRT offers more flexibility than UDP (or TCP, the more logical similarity) because it manages ports as its own resources. For example, one port may be shared between various services. ## Binding and Connecting Connections are established using the same philosophy as TCP, using functions with names and signatures similar to the BSD Socket API. What is new here is the _rendezvous_ mode. ### Synopsis ```c++ int srt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen); int srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock); ``` This function sets up the "sockname" for the socket, that is, the local IP address of the network device (use `INADDR_ANY` for using any device) and port. Note that this can be done on both listening and connecting sockets; for the latter it will define the outgoing port. If you don't set up the outgoing port by calling this function (or use port number 0), a unique port number will be selected automatically. The `*_acquire` version simply takes over the given UDP socket and copies the bound address setting from it. ```c++ int srt_listen(SRTSOCKET u, int backlog); ``` This sets the backlog (maximum allowed simultaneously pending connections) and puts the socket into a listening state -- that is, incoming connections will be accepted in the call `srt_accept`. ```c++ SRTSOCKET srt_accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen); ``` This function accepts the incoming connection (the peer should do `srt_connect`) and returns a socket that is exclusively bound to an opposite socket at the peer. The peer's address is returned in the `addr` argument. ```c++ int srt_connect(SRTSOCKET u, const struct sockaddr* name, int namelen); int srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); ``` This function initiates the connection of a given socket with its peer's counterpart (the peer gets the new socket for this connection from `srt_accept`). The address for connection is passed in 'name'. The `connect_debug` version allows for enforcing the ISN (initial sequence number); this is used only for debugging or unusual experiments. ```c++ int srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen); ``` A convenience function that combines the calls to bind, setting the `SRTO_RENDEZVOUS` flag, and connecting to the rendezvous counterpart. For simplest usage, the `local_name` should be set to `INADDR_ANY` (or a specified adapter's IP) and port. Note that both `local_name` and `remote_name` must use the same port. The peer to which this is going to connect should call the same function, with appropriate local and remote addresses. A rendezvous connection means that both parties connect to one another simultaneously. **IMPORTANT**: The connection may fail, but the socket that was used for connecting is not automatically closed and it's also not in broken state (broken state can be only if a socket was first successfully connected and then broken). When using blocking mode, the connection failure will result in reporting an error from this function call. In non-blocking mode the connection failure is designated by the `SRT_EPOLL_ERR` flag set for this socket in the epoll container. After that failure you can read an extra information from the socket using `srt_getrejectreason` function, and then you should close the socket. ### Listener (Server) Example ```c++ sockaddr_in sa = { ... }; // set local listening port and possibly interface's IP int st = srt_bind(sock, (sockaddr*)&sa, sizeof sa); srt_listen(sock, 5); while ( !finish ) { int sa_len = sizeof sa; newsocket = srt_accept(sock, (sockaddr*)&sa, &sa_len); HandleNewClient(newsocket, sa); } ``` ### Caller (Client) Example ```c++ sockaddr_in sa = { ... }; // set target IP and port int st = srt_connect(sock, (sockaddr*)&sa, sizeof sa); HandleConnection(sock); ``` ### Rendezvous Example ```c++ sockaddr_in lsa = { ... }; // set local listening IP/port sockaddr_in rsa = { ... }; // set remote IP/port srt_setsockopt(m_sock, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); int stb = srt_bind(sock, (sockaddr*)&lsa, sizeof lsa); int stc = srt_connect(sock, (sockaddr*)&rsa, sizeof rsa); HandleConnection(sock); ``` or simpler ```c++ sockaddr_in lsa = { ... }; // set local listening IP/port sockaddr_in rsa = { ... }; // set remote IP/port int stc = srt_rendezvous(sock, (sockaddr*)&lsa, sizeof lsa, (sockaddr*)&rsa, sizeof rsa); HandleConnection(sock); ``` ## Sending and Receiving The SRT API for sending and receiving is split into three categories: *simple*, *rich*, and *for files only*. The **simple API** includes: `srt_send` and `srt_recv` functions. They need only the socket and the buffer to send from or receive to, just like system `read` and `write` functions. The **rich API** includes the `srt_sendmsg` and `srt_recvmsg` functions. Actually `srt_recvmsg` is provided for convenience and backward compatibility, as it is identical to `srt_recv`. The `srt_sendmsg` receives more parameters, specifically for messages. The `srt_sendmsg2` and `srt_recvmsg2` functions receive the socket, buffer, and the `SRT_MSGCTRL` object, which is an input-output object specifying extra data for the operation. Functions with the `msg2` suffix use the `SRT_MSGCTRL` object, and have the following interpretation (except `flags` and `boundary` which are reserved for future use and should be 0): - `srt_sendmsg2`: - `msgttl`: [IN] maximum time (in ms) to wait for successful delivery (-1: indefinitely) - `inorder`: [IN] if false, the later sent message is allowed to be delivered earlier - `srctime`: [IN] timestamp to be used for sending (0 if current time) - `pktseq`: unused - `msgno`: [OUT] message number assigned to the currently sent message - `srt_recvmsg2` - `msgttl`: unused - `inorder`: unused - `srctime`: [OUT] timestamp set for this dataset when sending - `pktseq`: [OUT] packet sequence number (first packet from the message, if it spans multiple UDP packets) - `msgno`: [OUT] message number assigned to the currently received message Please note that the `msgttl` and `inorder` arguments and fields in `SRT_MSGCTRL` are meaningful only when you use the message API in file mode (this will be explained later). In live mode, which is the SRT default, packets are always delivered when the time comes (always in order), where you don't want a packet to be dropped before sending (so -1 should be passed here). The `srctime` parameter is an SRT addition for applications (i.e. gateways) forwarding SRT streams. It permits pulling and pushing of the sender's original time stamp, converted to local time and drift adjusted. The `srctime` parameter is the number of usec (since epoch) in local SRT clock time. If the connection is not between SRT peers or if **Timestamp-Based Packet Delivery mode (TSBPDMODE)** is not enabled (see [SRT API Socket Options](API-socket-options.md)), the extracted `srctime` will be 0. Passing `srctime = 0` in `sendmsg` is like using the API without `srctime` and the local send time will be used (if TSBPDMODE is enabled and receiver supports it). ### Synopsis ```c++ int srt_send(SRTSOCKET s, const char* buf, int len); int srt_sendmsg(SRTSOCKET s, const char* buf, int len, int msgttl, bool inorder, uint64_t srctime); int srt_sendmsg2(SRTSOCKET s, const char* buf, int len, SRT_MSGCTRL* msgctrl); int srt_recv(SRTSOCKET s, char* buf, int len); int srt_recvmsg(SRTSOCKET s, char* buf, int len); int srt_recvmsg2(SRTSOCKET s, char* buf, int len, SRT_MSGCTRL* msgctrl); ``` ### Usage Sending a payload: ```c++ nb = srt_sendmsg(u, buf, nb, -1, true); nb = srt_send(u, buf, nb); SRT_MSGCTRL mc = srt_msgctrl_default; nb = srt_sendmsg2(u, buf, nb, &mc); ``` Receiving a payload: ```c++ nb = srt_recvmsg(u, buf, nb); nb = srt_recv(u, buf, nb); SRT_MSGCTRL mc = srt_msgctrl_default; nb = srt_recvmsg2(u, buf, nb, &mc); ``` ### Transmission Types Available in SRT Mode settings determine how the sender and receiver functions work. The main SRT [socket options](API-socket-options.md) that control it are: - `SRTO_TRANSTYPE`. Sets several parameters in accordance with the selected mode: - `SRTT_LIVE` (default) the Live mode (for live stream transmissions) - `SRTT_FILE` the File mode (for "no time controlled" fastest data transmission) - `SRTO_MESSAGEAPI` - true: (default in Live mode): use Message API - false: (default in File mode): use Buffer API See [Transmission types](#transmission-types) below. ## Blocking and Non-blocking Modes SRT functions can also work in blocking and non-blocking modes, for which there are two separate options for sending and receiving: `SRTO_SNDSYN` and `SRTO_RCVSYN`. When blocking mode is used, a function will not exit until the availability condition is satisfied. In non-blocking mode the function always exits immediately, and in case of lack of resource availability, it returns an error with an appropriate code. The use of non-blocking mode usually requires using some polling mechanism, which in SRT is **EPoll**. Note also that the blocking and non-blocking modes apply not only for sending and receiving. For example, `SNDSYN` defines blocking for `srt_connect` and `RCVSYN` defines blocking for `srt_accept`. The `SNDSYN` also makes `srt_close` exit only after the sending buffer is completely empty. ### EPoll (Non-blocking Mode Events) EPoll is a mechanism to track the events happening on the sockets, both "system sockets" (see `SYSSOCKET` type) and SRT Sockets. Note that `SYSSOCKET` is also an alias for `int`, used only for clarity. #### Synopsis ```c++ int srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events = NULL); int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events = NULL); 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); int srt_epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); int srt_epoll_clear_usocks(int eid); ``` #### Usage SRT socket being a user level concept, the system epoll (or other select) cannot be used to handle SRT non-blocking mode events. Instead, SRT provides a user-level epoll that supports both SRT and system sockets. The `srt_epoll_update_{u|s}sock()` API functions described here are SRT additions to the UDT-derived `srt_epoll_add_{u|s}sock()` and `epoll_remove_{u|s}sock()` functions to atomically change the events of interest. For example, to remove `SRT_EPOLL_OUT` but keep `SRT_EPOLL_IN` for a given socket with the existing API, the socket must be removed from epoll and re-added. This cannot be done atomically, the thread protection (against the epoll thread) being applied within each function but unprotected between the two calls. It is then possible to lose an `SRT_EPOLL_IN` event if it fires while the socket is not in the epoll list. Event flags are of various categories: `IN`, `OUT` and `ERR` are events, which are level-triggered by default and become edge-triggered if combined with `SRT_EPOLL_ET` flag. The latter is only an edge-triggered flag, not an event. There's also an `SRT_EPOLL_UPDATE` flag, which is an edge-triggered only event, and it reports an event on the listener socket that handles socket group new connections for an already connected group - this is for internal use only, and it's used in the internal code for socket groups. Once the subscriptions are made, you can call an SRT polling function (`srt_epoll_wait` or `srt_epoll_uwait`) that will block until an event is raised on any of the subscribed sockets. This function will exit as soon as at least one event is detected or a timeout occurs. The timeout is specified in `[ms]`, with two special values: - 0: check and report immediately (don't wait) - -1: wait indefinitely (not interruptible, even by a system signal) There are some differences in the synopsis between these two: #### `srt_epoll_wait` Both system and SRT sockets can be subscribed. This function reports events on both socket types according to subscriptions, in these arrays: - `readfds` and `lrfds`: subscribed for `IN` and `ERR` - `writefds` and `lwfds`: subscribed for `OUT` and `ERR` where: - `readfds` and `writefds` report SRT sockets ("user" socket) - `lrfds` and `lwfds` report system sockets **NOTE**: this function provides no straightforward possibility to report sockets with an error. If you want to distinguish a report of readiness for operation from an error report, the only way is to subscribe the socket in only one direction (either `SRT_EPOLL_IN` or `SRT_EPOLL_OUT`, but not both) and `SRT_EPOLL_ERR`, and then check the socket's presence in the array in the direction for which the socket wasn't subscribed. For example, when an SRT socket is subscribed for `SRT_EPOLL_OUT | SRT_EPOLL_ERR`, its presence in `readfds` means that an error is reported for it. This need not be a big problem, because when an error is reported on a socket, making it appear as if it were ready for an operation, then when that operation occurs it will simply result in an error. You can use this as an alternative error check method. This function also reports an error of type `SRT_ETIMEOUT` when no socket is ready as the timeout elapses (including 0). This behavior is different in `srt_epoll_uwait`. Note that in this function there's a loop that checks for socket readiness every 10ms. Thus, the minimum poll timeout the function can reliably support, when system sockets are involved, is also 10ms. The return time from a poll function can only be quicker when there is an event raised on one of the active SRT sockets. #### `srt_epoll_uwait` In this function only the SRT sockets can be subscribed (it reports error if you pass an epoll id that is subscribed to system sockets). This function waits for the first event on subscribed SRT sockets and reports all events collected at that moment in an array with the following structure: ```c++ typedef struct SRT_EPOLL_EVENT_ { SRTSOCKET fd; int events; } SRT_EPOLL_EVENT; ``` Every item reports a single socket with all events as flags. When the timeout is not -1, and no sockets are ready until the timeout time passes, this function returns 0. This behavior is different in `srt_epoll_wait`. The extra `srt_epoll_clear_usocks` function removes all subscriptions from the epoll container. The SRT EPoll system does not supports all features of Linux epoll. For example, it only supports level-triggered events for system sockets. ## Transmission Types **NOTE:** There might be a difference in terminology used in [SRT RFC](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00) and current documentation. Please consult [Data Transmission Modes](https://tools.ietf.org/html/draft-sharabayko-srt-00#section-4.2) and [Best Practices and Configuration Tips for Data Transmission via SRT](https://tools.ietf.org/html/draft-sharabayko-srt-00#page-71) sections of the RFC additionally. The current section is going to be reworked accordingly. SRT was originally intended to be used for Live Streaming and therefore its main and default transmission type is "live". However, SRT supports the modes that the original UDT library supported, that is, *file* and *message* transmission. There are two general modes: **Live** and **File** transmission. Inside File transmission mode, there are also two possibilities: **Buffer API** and **Message API**. The Live mode uses Message API. However it doesn't exactly match the description of the Message API because it uses a maximum single sending buffer up to the size that fits in one UDP packet. There are two options to set a particular type: - `SRTO_TRANSTYPE`: uses the enum value with `SRTT_LIVE` for live mode and `SRTT_FILE` for file mode. This option actually changes several parameters to their default values for that mode. After this is done, additional parameters, including those that are set here, can be further changed. - `SRTO_MESSAGEAPI`: This sets the Message API (true) or Buffer API (false) This makes possible a total of three data transmission methods: - [Live](#transmission-method-live) - [Buffer](#transmission-method-buffer) - [Message](#transmission-method-message) ### Terminology The following terms are used in the description of transmission types: **HANGUP / RESUME**: These terms have different meanings depending on the blocking state. They describe how a particular function behaves when performing an operation requires a specific readiness condition to be satisfied. In blocking mode HANGUP means that the function blocks until a condition is satisfied. RESUME means that the condition is satisfied and the function performs the required operation. In non-blocking mode the only difference is that HANGUP, instead of blocking, makes the function exit immediately with an appropriate error code (such as SRT_EASYNC*, SRT_ETIMEOUT or SRT_ECONGEST) explaining why the function is not ready to perform the operation. Refer to the error descriptions in [API-funtions.md](API-funtions.md) for details. The following types of operations are involved: 1. Reading data: `srt_recv`, `srt_recvmsg`, `srt_recvmsg2`, `srt_recvfile`. The function HANGS UP if there are no available data to read, and RESUMES when readable data become available (`SRT_EPOLL_IN` flag set in epoll). Use `SRTO_RCVSYN` to control blocking mode here. 2. Writing data: `srt_send`, `srt_sendmsg`, `srt_sendmsg2`, `srt_sendfile`. The function HANGS UP if the sender buffer becomes full and unable to store any additional data, and RESUMES if the data scheduled for sending have been removed from the sender buffer (after being sent and acknowledged) and there is enough free space in the sender buffer to store data (`SRT_EPOLL_OUT` flag set in epoll). Use `SRTO_SNDSYN` to control blocking mode here. 3. Accepting an incoming connection: `srt_accept` The function HANGS UP if there are no new connections reporting in, and RESUMES when a new connection has been processed and a new socket or group has been created to handle it. Note that this function requires the listener socket to get the connection (the flag `SRTO_RCVSYN` set on the listener socket controls the blocking mode for this operation). Note also that the blocking mode for a similar `srt_accept_bond` function is controlled exclusively by its timeout parameter because it can work with multiple listener sockets, potentially with different settings. 4. Connecting: `srt_connect` and its derivatives The function HANGS UP in the beginning, and RESUMES when the socket used for connecting is either ready to perform transmission operations or has failed to connect. It behaves a little differently in non-blocking mode -- the function should be called only once, and it simply returns a success result as a "HANGUP". Calling it again with the same socket would be an error. Calling it with a group would start a completely new connection. It is only possible to determine whether an operation has finished ("has RESUMED") from epoll flags. The socket, when successfully connected, would have `SRT_EPOLL_OUT` set, that is, becomes ready to send data, and `SRT_EPOLL_ERR` when it failed to connect. **BLIND / FAST / LATE REXMIT**: BLIND REXMIT is a situation where packets that were sent are still not acknowledged, either in the expected time frame, or when another ACK has come for the same number, but no packets have been reported as lost, or at least not for all still unacknowledged packets. The congestion control class is responsible for the algorithm for taking care of this situation, which is either `FASTREXMIT` or `LATEREXMIT`. This will be explained below. ### Transmission Method: Live Setting `SRTO_TRANSTYPE` to `SRTT_LIVE` sets the following [socket options](API-socket-options.md): - [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) = true - [`SRTO_RCVLATENCY`](API-socket-options.md#SRTO_RCVLATENCY) = 120 - [`SRTO_PEERLATENCY`](API-socket-options.md#SRTO_PEERLATENCY) = 0 - [`SRTO_TLPKTDROP`](API-socket-options.md#SRTO_TLPKTDROP) = true - [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) = true - [`SRTO_NAKREPORT`](API-socket-options.md#SRTO_NAKREPORT) = true - [`SRTO_RETRANSMITALGO`](API-socket-options.md#SRTO_RETRANSMITALGO) = 1 - [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) = 1316 - [`SRTO_CONGESTION`](API-socket-options.md#SRTO_CONGESTION) = "live" In this mode, every call to a sending function is allowed to send only so much data, as declared by `SRTO_PAYLOADSIZE`, whose value is still limited to a maximum of 1456 bytes. The application that does the sending is by itself responsible for calling the sending function in appropriate time intervals between subsequent calls. By default, this implies that the receiver uses 120 ms of latency, which is the declared time interval between the moment when the packet is scheduled for sending at the sender side, and when it is received by the receiver application (that is, the data are kept in the buffer and declared as not received, until the time comes for the packet to "play"). This mode uses the `LiveCC` congestion control class, which puts only a slight limitation on the bandwidth, if needed (i.e. by adding extra time if the interval between two consecutive packets would otherwise be too short for the defined speed limit). Note that it is not intended to work with "virtually infinite" ingest speeds (such as, for example, reading directly from a file). Therefore the application is not allowed to stream data with maximum speed -- it must take care that the speed of data being sent is in rhythm with timestamps in the live stream. Otherwise the behavior is undefined and might be surprisingly disappointing. The reading function will always return only a payload that was sent, and it will HANGUP until the time to play has come for this packet (if TSBPD mode is on) or when it is available without gaps of lost packets (if TSBPD mode is off - see [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE)). You may wish to tweak some of the parameters below: - `SRTO_TSBPDMODE`: You can turn off controlled latency if your application uses its own method of latency control. - `SRTO_RCVLATENCY`: You can increase the latency time, if this is too short. Setting a shorter latency than the default is strongly discouraged, although in some very specific and dedicated networks this may still be reasonable. Note that `SRTO_PEERLATENCY` is an option for the sending party, which is the minimum possible value for a receiver. - `SRTO_TLPKTDROP`: When true (default), this will drop the packets that haven't been retransmitted on time, that is, before the next packet that is already received becomes ready to play. You can turn this off to always ensure a clean delivery. However, a lost packet can simply pause a delivery for some longer, potentially undefined time, and cause even worse tearing for the player. Setting higher latency will help much more in the case when TLPKTDROP causes packet drops too often. - `SRTO_NAKREPORT`: Turns on repeated sending of loss reports, when the lost packet was not recovered quickly enough, which raises suspicions that the loss report itself was lost. Without it, the loss report will be always reported just once and never repeated again, and then the lost payload packet will be probably dropped by the TLPKTDROP mechanism. - `SRTO_RETRANSMITALGO`: Given the receiver sends periodic NAK reports, the sender can reduce the retransmission overhead by not retransmitting a loss more often than once per RTT (value 1). - `SRTO_PAYLOADSIZE`: Default value is for MPEG TS. If you are going to use SRT to send any different kind of payload, such as, for example, wrapping a live stream in very small frames, then you can use a bigger maximum frame size, though not greater than 1456 bytes. Parameters from the modified for transmission type list, not mentioned in the list above, are crucial for Live mode and shall not be changed. The BLIND REXMIT situation is resolved using the FASTREXMIT algorithm by LiveCC: sending non-acknowledged packets blindly on the premise that the receiver lingers too long before acknowledging them. This mechanism isn't used (i.e. the BLIND REXMIT situation isn't handled at all) when `SRTO_NAKREPORT` is set by the peer -- the NAKREPORT method is considered so effective that FASTREXMIT isn't necessary. ### Transmission Method: Buffer Setting `SRTO_TRANSTYPE` to `SRTT_FILE` sets the following [socket options](API-socket-options.md): - [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) = false - [`SRTO_RCVLATENCY`](API-socket-options.md#SRTO_RCVLATENCY) = 0 - [`SRTO_PEERLATENCY`](API-socket-options.md#SRTO_PEERLATENCY) = 0 - [`SRTO_TLPKTDROP`](API-socket-options.md#SRTO_TLPKTDROP) = false - [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) = false - [`SRTO_NAKREPORT`](API-socket-options.md#SRTO_NAKREPORT) = false - [`SRTO_RETRANSMITALGO`](API-socket-options.md#SRTO_RETRANSMITALGO) = 0 - [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) = 0 - [`SRTO_CONGESTION`](API-socket-options.md#SRTO_CONGESTION) = "file" In this mode, calling a sending function is allowed to potentially send virtually any size of data. The sending function will HANGUP only if the sending buffer is completely filled, and RESUME if the sending buffers are available for at least one smallest portion of data passed for sending. The sending function need not send everything in this call, and the caller must be aware that the sending function might return sent data of smaller size than was actually requested. From the receiving function there will be retrieved as many data as the minimum of the passed buffer size and available data; data still available and not retrieved by this call will be available for retrieval in the next call. There is also a dedicated pair of functions that can only be used in this mode: `srt_sendfile` and `srt_recvfile`. These functions can be used to transmit the whole file, or a fragment of it, based on the offset and size. This mode uses the `FileCC` congestion control class, which is a direct copy of UDT's `CUDTCC` congestion control class, adjusted to the needs of SRT's congestion control framework. This class generally sends the data with maximum speed in the beginning, until the flight window is full, and then keeps the speed at the edge of the flight window, only slowing down in the case where packet loss was detected. The bandwidth usage can be directly limited by the `SRTO_MAXBW` option. The BLIND REXMIT situation is resolved in FileCC using the LATEREXMIT algorithm: when the repeated ACK was received for the same packet, or when the loss list is empty and the flight window is full, all packets since the last ACK are sent again (that's more or less the TCP behavior, but in contrast to TCP, this is done as a very low probability fallback). Most of the parameters described above have `false` or `0` values as they usually designate features used in Live mode. None are used with File mode. The only option that makes sense to modify after the `SRTT_FILE` type was set is `SRTO_MESSAGEAPI`, which is described below. ### Transmission Method: Message Setting `SRTO_TRANSTYPE` to `SRTT_FILE` and then setting `SRTO_MESSAGEAPI` to `true` implies usage of the Message transmission method. Parameters are set as described above for the Buffer method, with the exception of `SRTO_MESSAGEAPI`. The "file" congestion controller is also used in this mode. It differs from the Buffer method, however, in terms of the rules concerning sending and receiving. **HISTORICAL INFO**: The library on which SRT was based (UDT) somewhat misleadingly used the terms `STREAM` and `DGRAM`, and used the system symbols `SOCK_STREAM` and `SOCK_DGRAM` in the socket creation function. A "datagram" in the UDT terminology has nothing to do with the "datagram" term in networking terminology, where its size is limited to as much it can fit in one MTU. In UDT it is actually a message, which may span multiple UDP packets and has clearly defined boundaries. It's rather similar to the **SCTP** protocol. Also, in UDP the API functions were strictly bound to `DGRAM` or `STREAM` mode: `UDT::send/UDT::recv` were only for `STREAM` and `UDT::sendmsg/UDT::recvmsg` only for `DGRAM`. In SRT this is changed: all functions can be used in all modes, except `srt_sendfile/srt_recvfile`, and how the functions actually work is controlled by the `SRTO_MESSAGEAPI` flag. In message mode, every sending function sends **exactly** as much data as it is passed in a single sending function call. The receiver also receives not less than **exactly** the number of bytes that was sent (although every message may have a different size). Every message may also have extra parameters: - **TTL** defines how much time (in ms) the message should wait in the sending buffer for the opportunity to be picked up by the sender thread and sent over the network; otherwise it is dropped. Note that this TTL only applies to packets that have been lost and should be retransmitted. - **INORDER**, when true, means the messages must be read by the receiver in exactly the same order in which they were sent. In the situation where a message suffers a packet loss, this prevents any subsequent messages from achieving completion status prior to recovery of the preceding message. The sending function will HANGUP when the free space in the sending buffer does not exactly fit the whole message, and it will only RESUME if the free space in the sending buffer grows up to this size. The call to the sending function also returns with an error when the size of the message exceeds the total size of the buffer (this can be modified by the `SRTO_SNDBUF` option). In other words, it is not designed to send just a part of the message -- either the whole message is sent, or nothing at all. The receiving function will HANGUP until the whole message is available for reading; if the message spans multiple UDP packets, then the function RESUMES only when every single packet from the message has been received, including recovered packets, if any. When the INORDER flag is set to false and parts of multiple messages are currently available, the first message that is complete (possibly recovered) is returned. Otherwise the function does a HANGUP until the next message is complete. The call to the receiving function is rejected if the buffer size is too small for a single message to fit in it. Note that you can use any of the sending and receiving functions for sending and receiving messages, except `sendfile/recvfile`, which are dedicated exclusively for Buffer API. For more information, see [SRT API Socket Options](API-socket-options.md). [Return to Top of Page](#srt-api) srt-1.4.4/docs/API/configuration-guidelines.md000066400000000000000000000100761412557703600212260ustar00rootroot00000000000000# Configuration Guidelines ## Receiver Buffer Size The receiver buffer can be configured with the [`SRTO_RCVBUF`](./API-socket-options.md#SRTO_RCVBUF) socket option. Buffer size in bytes is expected to be passed in the `optval` argument of the `srt_setsockopt(..)` function. However, internally the value will be converted into the number of packets stored in the receiver buffer. The allowed value of `SRTO_RCVBUF` is also limited by the value of the flow control window size [`SRTO_FC`](./API-socket-options.md#SRTO_FC) socket option. See issue [#700](https://github.com/Haivision/srt/issues/700). The default flow control window size is 25600 packets. It is approximately: - **270 Mbits** of payload in the default live streaming configuration with an SRT payload size of **1316 bytes**; - **300 Mbits** of payload in the default file transfer configuration with an SRT payload size of **1456 bytes**. The default receiver buffer size is 8192 packets. It is approximately: - **86 Mbits** of payload with the effective SRT payload size of **1316 bytes**. ### Setting Receiver Buffer Size As already mentioned, the maximum allowed size of the receiver buffer is limited by the value of `SRTO_FC`. When the `SRTO_RCVBUF` option value is set using the `srt_setsockopt(..)` function, the provided size in bytes is internally converted to the corresponding size in packets using the configured value of the `SRTO_MSS` option to estimate the maximum possible payload of a packet. The following function returns the buffer size in packets: ```c++ int getRbufSizePkts(int SRTO_RCVBUF, int SRTO_MSS, int SRTO_FC) { // UDP header size is assumed to be 28 bytes // 20 bytes IPv4 + 8 bytes of UDP const int UDPHDR_SIZE = 28; const int pkts = (rbuf_size / (SRTO_MSS - UDPHDR_SIZE)); return min(pkts, SRTO_FC); } ``` If the value of `SRTO_RCVBUF` in packets exceeds `SRTO_FC`, then it is silently set to the value in bytes corresponding to `SRTO_FC`. Therefore, to set higher values of `SRTO_RCVBUF` the value of `SRTO_FC` must be increased first. ### Calculating Target Size in Packets The minimum size of the receiver buffer in packets can be calculated as follows: `pktsRBufSize = bps / 8 × (RTTsec + latency_sec) / bytePayloadSize` where - `bps` is the payload bitrate of the stream in bits per second; - `RTTsec` is the RTT of the network connection in seconds; - `bytePayloadSize` is the expected size of the payload of the SRT data packet. If the whole remainder of the MTU is expected to be used, payload size is calculated as follows: `bytePayloadSize = MSS - 44` where - 44 is the size in bytes of an **IPv4** header: - 20 bytes **IPv4** - 8 bytes of UDP - 16 bytes of SRT packet header. - `MSS` is the Maximum Segment Size (aka MTU); see `SRTO_MSS`. ### Calculating Target Size to Set To determine the value to pass in `srt_setsockopt(..)` with `SRTO_RCVBUF` the size in packets `pktsRBufSize` must be converted to the size in bytes assuming the internal conversion of the `srt_setsockopt(..)` function. The target size of the payload stored by the receiver buffer would be: `SRTO_RCVBUF = pktsRBufSize × (SRTO_MSS - UDPHDR_SIZE)` where - `UDPHDR_SIZE` = 28 (20 bytes IPv4, 8 bytes of UDP) - `SRTO_MSS` is the corresponding socket option value at the moment of setting `SRTO_RCVBUF`. ### Summing Up ```c++ auto CalculateTargetRBufSize(int msRTT, int bpsRate, int bytesPayloadSize, int msLatency, int SRTO_MSS) { const int UDPHDR_SIZE = 28; const long long targetPayloadBytes = static_cast(msLatency + msRTT / 2) * bpsRate / 1000 / 8; const long long targetNumPackets = targetPayloadBytes / bytesPayloadSize; const long long targetSizeValue = targetNumPackets * (SRTO_MSS - UDPHDR_SIZE); return {targetNumPackets, targetSizeValue}; } // Configuring const auto [fc, rcvbuf] = CalculateTargetRBufSize(msRTT, bpsRate, bytesPayloadSize, SRTO_RCVLATENCY, SRTO_MSS); int optval = fc; int optlen = sizeof optval; srt_setsockopt(sock, 0, SRTO_FC, (void*) &optval, optlen); optval = rcvbuf; srt_setsockopt(sock, 0, SRTO_RCVBUF, (void*) &optval, optlen); ``` srt-1.4.4/docs/API/statistics.md000066400000000000000000001507711412557703600164320ustar00rootroot00000000000000 # SRT Statistics 1. [SRT Socket Statistics](#srt-socket-statistics) - [Summary Table](#summary-table) - [Accumulated Statistics](#accumulated-statistics) - [Interval-Based Statistics](#interval-based-statistics) - [Instantaneous Statistics](#instantaneous-statistics) 2. [SRT Group Statistics](#srt-group-statistics) - [Summary Table](#group-summary-table) - [Accumulated Statistics](#group-accumulated-statistics) - [Interval-Based Statistics](#group-interval-based-statistics) - [Formulas](#group-formulas) ## SRT Socket Statistics SRT provides a powerful set of statistical data on a socket. This data can be used to keep an eye on a socket's health and track faulty behavior. Statistics are calculated independently on each side (receiver and sender) and are not exchanged between peers unless explicitly stated. The following API functions can be used to retrieve statistics on an SRT socket: * `int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear)` * `int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous)` Refer to the documentation of the [SRT API Functions](API-functions.md) for usage instructions. ### Summary Table The table below provides a summary of SRT socket statistics: name, type, unit of measurement, data type, and whether it is calculated by the sender or receiver. There are three types of statistics: - **Accumulated:** the statistic is accumulated since the time an SRT socket has been created (after the successful call to `srt_connect(...)` or `srt_bind(...)` function), e.g., [pktSentTotal](#pktSentTotal), etc., - **Interval-based:** the statistic is accumulated during a specified time interval (e.g., 100 milliseconds if SRT statistics is collected each 100 milliseconds) from the time an SRT socket has been created, e.g., [pktSent](#pktSent), etc. The value of the statistic can be reset by calling the `srt_bstats(..., int clear)` function with `clear = 1`, - **Instantaneous:** the statistic is obtained at the moment the `srt_bistats()` function is called, e.g., [msRTT](#msRTT), etc. See sections [Accumulated Statistics](#accumulated-statistics), [Interval-Based Statistics](#interval-based-statistics), and [Instantaneous Statistics](#instantaneous-statistics) for a detailed description of each statistic. | Statistic | Type of Statistic | Unit of Measurement | Available for Sender | Available for Receiver | Data Type | | --------------------------------------------------- | ----------------- | ------------------- | -------------------- | ---------------------- | --------- | | [msTimeStamp](#msTimeStamp) | accumulated | ms (milliseconds) | ✓ | ✓ | int64_t | | [pktSentTotal](#pktSentTotal) | accumulated | packets | ✓ | - | int64_t | | [pktRecvTotal](#pktRecvTotal) | accumulated | packets | - | ✓ | int64_t | | [pktSentUniqueTotal](#pktSentUniqueTotal) | accumulated | packets | ✓ | - | int64_t | | [pktRecvUniqueTotal](#pktRecvUniqueTotal) | accumulated | packets | - | ✓ | int64_t | | [pktSndLossTotal](#pktSndLossTotal) | accumulated | packets | ✓ | - | int32_t | | [pktRcvLossTotal](#pktRcvLossTotal) | accumulated | packets | - | ✓ | int32_t | | [pktRetransTotal](#pktRetransTotal) | accumulated | packets | ✓ | - | int32_t | | [pktRcvRetransTotal](#pktRcvRetransTotal) | accumulated | packets | - | ✓ | int32_t | | [pktSentACKTotal](#pktSentACKTotal) | accumulated | packets | - | ✓ | int32_t | | [pktRecvACKTotal](#pktRecvACKTotal) | accumulated | packets | ✓ | - | int32_t | | [pktSentNAKTotal](#pktSentNAKTotal) | accumulated | packets | - | ✓ | int32_t | | [pktRecvNAKTotal](#pktRecvNAKTotal) | accumulated | packets | ✓ | - | int32_t | | [usSndDurationTotal](#usSndDurationTotal) | accumulated | us (microseconds) | ✓ | - | int64_t | | [pktSndDropTotal](#pktSndDropTotal) | accumulated | packets | ✓ | - | int32_t | | [pktRcvDropTotal](#pktRcvDropTotal) | accumulated | packets | - | ✓ | int32_t | | [pktRcvUndecryptTotal](#pktRcvUndecryptTotal) | accumulated | packets | - | ✓ | int32_t | | [pktSndFilterExtraTotal](#pktSndFilterExtraTotal) | accumulated | packets | ✓ | - | int32_t | | [pktRcvFilterExtraTotal](#pktRcvFilterExtraTotal) | accumulated | packets | - | ✓ | int32_t | | [pktRcvFilterSupplyTotal](#pktRcvFilterSupplyTotal) | accumulated | packets | - | ✓ | int32_t | | [pktRcvFilterLossTotal](#pktRcvFilterLossTotal) | accumulated | packets | - | ✓ | int32_t | | [byteSentTotal](#byteSentTotal) | accumulated | bytes | ✓ | - | uint64_t | | [byteRecvTotal](#byteRecvTotal) | accumulated | bytes | - | ✓ | uint64_t | | [byteSentUniqueTotal](#byteSentUniqueTotal) | accumulated | bytes | ✓ | - | uint64_t | | [byteRecvUniqueTotal](#byteRecvUniqueTotal) | accumulated | bytes | - | ✓ | uint64_t | | [byteRcvLossTotal](#byteRcvLossTotal) | accumulated | bytes | - | ✓ | uint64_t | | [byteRetransTotal](#byteRetransTotal) | accumulated | bytes | ✓ | - | uint64_t | | [byteSndDropTotal](#byteSndDropTotal) | accumulated | bytes | ✓ | - | uint64_t | | [byteRcvDropTotal](#byteRcvDropTotal) | accumulated | bytes | - | ✓ | uint64_t | | [byteRcvUndecryptTotal](#byteRcvUndecryptTotal) | accumulated | bytes | - | ✓ | uint64_t | | [pktSent](#pktSent) | interval-based | packets | ✓ | - | int64_t | | [pktRecv](#pktRecv) | interval-based | packets | - | ✓ | int64_t | | [pktSentUnique](#pktSentUnique) | interval-based | packets | ✓ | - | int64_t | | [pktRecvUnique](#pktRecvUnique) | interval-based | packets | - | ✓ | int64_t | | [pktSndLoss](#pktSndLoss) | interval-based | packets | ✓ | - | int32_t | | [pktRcvLoss](#pktRcvLoss) | interval-based | packets | - | ✓ | int32_t | | [pktRetrans](#pktRetrans) | interval-based | packets | ✓ | - | int32_t | | [pktRcvRetrans](#pktRcvRetrans) | interval-based | packets | - | ✓ | int32_t | | [pktSentACK](#pktSentACK) | interval-based | packets | - | ✓ | int32_t | | [pktRecvACK](#pktRecvACK) | interval-based | packets | ✓ | - | int32_t | | [pktSentNAK](#pktSentNAK) | interval-based | packets | - | ✓ | int32_t | | [pktRecvNAK](#pktRecvNAK) | interval-based | packets | ✓ | - | int32_t | | [pktSndFilterExtra](#pktSndFilterExtra) | interval-based | packets | ✓ | - | int32_t | | [pktRcvFilterExtra](#pktRcvFilterExtra) | interval-based | packets | - | ✓ | int32_t | | [pktRcvFilterSupply](#pktRcvFilterSupply) | interval-based | packets | - | ✓ | int32_t | | [pktRcvFilterLoss](#pktRcvFilterLoss) | interval-based | packets | - | ✓ | int32_t | | [mbpsSendRate](#mbpsSendRate) | interval-based | Mbps | ✓ | - | double | | [mbpsRecvRate](#mbpsRecvRate) | interval-based | Mbps | - | ✓ | double | | [usSndDuration](#usSndDuration) | interval-based | us (microseconds) | ✓ | - | int64_t | | [pktReorderDistance](#pktReorderDistance) | interval-based | packets | - | ✓ | int32_t | | [pktRcvBelated](#pktRcvBelated) | interval-based | packets | - | ✓ | int64_t | | [pktSndDrop](#pktSndDrop) | interval-based | packets | ✓ | - | int32_t | | [pktRcvDrop](#pktRcvDrop) | interval-based | packets | - | ✓ | int32_t | | [pktRcvUndecrypt](#pktRcvUndecrypt) | interval-based | packets | - | ✓ | int32_t | | [byteSent](#byteSent) | interval-based | bytes | ✓ | - | uint64_t | | [byteRecv](#byteRecv) | interval-based | bytes | - | ✓ | uint64_t | | [byteSentUnique](#byteSentUnique) | interval-based | bytes | ✓ | - | uint64_t | | [byteRecvUnique](#byteRecvUnique) | interval-based | bytes | - | ✓ | uint64_t | | [byteRcvLoss](#byteRcvLoss) | interval-based | bytes | - | ✓ | uint64_t | | [byteRetrans](#byteRetrans) | interval-based | bytes | ✓ | - | uint64_t | | [byteSndDrop](#byteSndDrop) | interval-based | bytes | ✓ | - | uint64_t | | [byteRcvDrop](#byteRcvDrop) | interval-based | bytes | - | ✓ | uint64_t | | [byteRcvUndecrypt](#byteRcvUndecrypt) | interval-based | bytes | - | ✓ | uint64_t | | [usPktSndPeriod](#usPktSndPeriod) | instantaneous | us (microseconds) | ✓ | - | double | | [pktFlowWindow](#pktFlowWindow) | instantaneous | packets | ✓ | - | int32_t | | [pktCongestionWindow](#pktCongestionWindow) | instantaneous | packets | ✓ | - | int32_t | | [pktFlightSize](#pktFlightSize) | instantaneous | packets | ✓ | - | int32_t | | [msRTT](#msRTT) | instantaneous | ms (milliseconds) | ✓ | ✓ | double | | [mbpsBandwidth](#mbpsBandwidth) | instantaneous | Mbps | ✓ | ✓ | double | | [byteAvailSndBuf](#byteAvailSndBuf) | instantaneous | bytes | ✓ | - | int32_t | | [byteAvailRcvBuf](#byteAvailRcvBuf) | instantaneous | bytes | - | ✓ | int32_t | | [mbpsMaxBW](#mbpsMaxBW) | instantaneous | Mbps | ✓ | - | double | | [byteMSS](#byteMSS) | instantaneous | bytes | ✓ | ✓ | int32_t | | [pktSndBuf](#pktSndBuf) | instantaneous | packets | ✓ | - | int32_t | | [byteSndBuf](#byteSndBuf) | instantaneous | bytes | ✓ | - | int32_t | | [msSndBuf](#msSndBuf) | instantaneous | ms (milliseconds) | ✓ | - | int32_t | | [msSndTsbPdDelay](#msSndTsbPdDelay) | instantaneous | ms (milliseconds) | ✓ | - | int32_t | | [pktRcvBuf](#pktRcvBuf) | instantaneous | packets | - | ✓ | int32_t | | [byteRcvBuf](#byteRcvBuf) | instantaneous | bytes | - | ✓ | int32_t | | [msRcvBuf](#msRcvBuf) | instantaneous | ms (milliseconds) | - | ✓ | int32_t | | [msRcvTsbPdDelay](#msRcvTsbPdDelay) | instantaneous | ms (milliseconds) | - | ✓ | int32_t | | [pktReorderTolerance](#pktReorderTolerance) | instantaneous | packets | - | ✓ | int32_t | | [pktRcvAvgBelatedTime](#pktRcvAvgBelatedTime) | instantaneous | ms (milliseconds) | - | ✓ | double | ### Accumulated Statistics #### msTimeStamp The time elapsed, in milliseconds, since the SRT socket has been created (after successful call to `srt_connect(...)` or `srt_bind(...)` function). Available both for sender and receiver. #### pktSentTotal The total number of sent DATA packets, including retransmitted packets ([pktRetransTotal](#pktRetransTotal)). Available for sender. If the `SRTO_PACKETFILTER` socket option is enabled (refer to [SRT API Socket Options](API-socket-options.md)), this statistic counts sent packet filter control packets ([pktSndFilterExtraTotal](#pktSndFilterExtraTotal)) as well. Introduced in SRT v1.4.0. #### pktRecvTotal The total number of received DATA packets, including retransmitted packets ([pktRcvRetransTotal](#pktRcvRetransTotal)). Available for receiver. If the `SRTO_PACKETFILTER` socket option is enabled (refer to [SRT API Socket Options](API-socket-options.md)), this statistic counts received packet filter control packets ([pktRcvFilterExtraTotal](#pktRcvFilterExtraTotal)) as well. Introduced in SRT v1.4.0. #### pktSentUniqueTotal The total number of *unique* DATA packets sent by the SRT sender. Available for sender. This value contains only *unique* *original* DATA packets. Retransmitted DATA packets ([pktRetransTotal](#pktRetransTotal)) are not taken into account. If the `SRTO_PACKETFILTER` socket option is enabled (refer to [SRT API Socket Options](API-socket-options.md)), packet filter control packets ([pktSndFilterExtraTotal](#pktSndFilterExtraTotal)) are also not taken into account. This value corresponds to the number of original DATA packets sent by the SRT sender. It counts every packet sent over the network for the first time, and can be calculated as follows: `pktSentUniqueTotal = pktSentTotal – pktRetransTotal`, or by `pktSentUniqueTotal = pktSentTotal – pktRetransTotal - pktSndFilterExtraTotal` if the `SRTO_PACKETFILTER` socket option is enabled. The original DATA packets are sent only once. #### pktRecvUniqueTotal The total number of *unique* original, retransmitted or recovered by the packet filter DATA packets *received in time*, *decrypted without errors* and, as a result, scheduled for delivery to the upstream application by the SRT receiver. Available for receiver. Unique means "first arrived" DATA packets. There is no difference whether a packet is original or, in case of loss, retransmitted or recovered by the packet filter. Whichever packet comes first is taken into account. This statistic doesn't count - duplicate packets (retransmitted or sent several times by defective hardware/software), - arrived too late packets (retransmitted or original packets arrived out of order) that were already dropped by the TLPKTDROP mechanism (see [pktRcvDropTotal](#pktRcvDropTotal) statistic), - arrived in time packets, but decrypted with errors (see [pktRcvUndecryptTotal](#pktRcvUndecryptTotal) statistic), and, as a result, dropped by the TLPKTDROP mechanism (see [pktRcvDropTotal](#pktRcvDropTotal) statistic). DATA packets recovered by the packet filter ([pktRcvFilterSupplyTotal](#pktRcvFilterSupplyTotal)) are taken into account if the `SRTO_PACKETFILTER` socket option is enabled (refer to [SRT API Socket Options](API-socket-options.md)). Do not mix up with the control packets received by the packet filter ([pktRcvFilterExtraTotal](#pktRcvFilterExtraTotal)). #### pktSndLossTotal The total number of data packets considered or reported as lost at the sender side. Does not correspond to the packets detected as lost at the receiver side. Available for sender. A packet is considered lost in two cases: 1. Sender receives a loss report from a receiver, 2. Sender initiates retransmission after not receiving an ACK packet for a certain timeout. Refer to `FASTREXMIT` and `LATEREXMIT` algorithms. #### pktRcvLossTotal The total number of SRT DATA packets detected as presently missing (either reordered or lost) at the receiver side. Available for receiver. The detection of presently missing packets is triggered by a newly received DATA packet with the sequence number `s`. If `s` is greater than the sequence number `next_exp` of the next expected packet (`s > next_exp`), the newly arrived packet `s` is considered in-order and there is a sequence discontinuity of size `s - next_exp` associated with this packet. The presence of sequence discontinuity means that some packets of the original sequence have not yet arrived (presently missing), either reordered or lost. Once the sequence discontinuity is detected, its size `s - next_exp` is added to `pktRcvLossTotal` statistic. Refer to [RFC 4737 - Packet Reordering Metrics](https://tools.ietf.org/html/rfc4737) for details. If the packet `s` is received out of order (`s < next_exp`), the statistic is not affected. Note that only original (not retransmitted) SRT DATA packets are taken into account. Refer to [pktRcvRetransTotal](#pktRcvRetransTotal) for the formula for obtaining the total number of lost retransmitted packets. In SRT v1.4.0, v1.4.1, the `pktRcvLossTotal` statistic includes packets that failed to be decrypted. To receive the number of presently missing packets, substract [pktRcvUndecryptTotal](#pktRcvUndecryptTotal) from the current one. This is going to be fixed in SRT v.1.5.0. #### pktRetransTotal The total number of retransmitted packets sent by the SRT sender. Available for sender. This statistic is not interchangeable with the receiver [pktRcvRetransTotal](#pktRcvRetransTotal) statistic. #### pktRcvRetransTotal The total number of retransmitted packets registered at the receiver side. Available for receiver. This statistic is not interchangeable with the sender [pktRetransTotal](#pktRetransTotal) statistic. Note that the total number of lost retransmitted packets can be calculated as the total number of retransmitted packets sent by receiver minus the total number of retransmitted packets registered at the receiver side: `pktRetransTotal - pktRcvRetransTotal`. This is going to be implemented in SRT v1.5.0, see issue [#1208](https://github.com/Haivision/srt/issues/1208). #### pktSentACKTotal The total number of sent ACK (Acknowledgement) control packets. Available for receiver. #### pktRecvACKTotal The total number of received ACK (Acknowledgement) control packets. Available for sender. #### pktSentNAKTotal The total number of sent NAK (Negative Acknowledgement) control packets. Available for receiver. #### pktRecvNAKTotal The total number of received NAK (Negative Acknowledgement) control packets. Available for sender. #### usSndDurationTotal The total accumulated time in microseconds, during which the SRT sender has some data to transmit, including packets that have been sent, but not yet acknowledged. In other words, the total accumulated duration in microseconds when there was something to deliver (non-empty senders' buffer). Available for sender. #### pktSndDropTotal The total number of _dropped_ by the SRT sender DATA packets that have no chance to be delivered in time (refer to [TLPKTDROP](https://github.com/Haivision/srt-rfc/blob/master/draft-sharabayko-mops-srt.md#too-late-packet-drop-too-late-packet-drop) mechanism). Available for sender. Packets may be dropped conditionally when both `SRTO_TSBPDMODE` and `SRTO_TLPKTDROP` socket options are enabled, refer to [SRT API Socket Options](API-socket-options.md). The delay before TLPKTDROP mechanism is triggered is calculated as follows `SRTO_PEERLATENCY + SRTO_SNDDROPDELAY + 2 * interval between sending ACKs`, where `SRTO_PEERLATENCY` is the configured SRT latency, `SRTO_SNDDROPDELAY` adds an extra to `SRTO_PEERLATENCY` delay, the default `interval between sending ACKs` is 10 milliseconds. The minimum delay is `1000 + 2 * interval between sending ACKs` milliseconds. Refer to `SRTO_PEERLATENCY`, `SRTO_SNDDROPDELAY` socket options in [SRT API Socket Options](API-socket-options.md). #### pktRcvDropTotal The total number of _dropped_ by the SRT receiver and, as a result, not delivered to the upstream application DATA packets (refer to [TLPKTDROP](https://github.com/Haivision/srt-rfc/blob/master/draft-sharabayko-mops-srt.md#too-late-packet-drop-too-late-packet-drop) mechanism). Available for receiver. This statistic counts - arrived too late packets (retransmitted or original packets arrived out of order), - arrived in time packets, but decrypted with errors (see also [pktRcvUndecryptTotal](#pktRcvUndecryptTotal) statistic). Packets may be dropped conditionally when both `SRTO_TSBPDMODE` and `SRTO_TLPKTDROP` socket options are enabled, refer to [SRT API Socket Options](API-socket-options.md). #### pktRcvUndecryptTotal The total number of packets that failed to be decrypted at the receiver side. Available for receiver. #### pktSndFilterExtraTotal The total number of packet filter control packets generated by the packet filter (refer to [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md)). Available for sender. Packet filter control packets contain only control information necessary for the packet filter. The type of these packets is DATA. If the `SRTO_PACKETFILTER` socket option is disabled (refer to [SRT API Socket Options](API-socket-options.md)), this statistic is equal to 0. Introduced in SRT v1.4.0. #### pktRcvFilterExtraTotal The total number of packet filter control packets received by the packet filter (refer to [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md)). Available for receiver. Packet filter control packets contain only control information necessary for the packet filter. The type of these packets is DATA. If the `SRTO_PACKETFILTER` socket option is disabled (refer to [SRT API Socket Options](API-socket-options.md)), this statistic is equal to 0. Introduced in SRT v1.4.0. #### pktRcvFilterSupplyTotal The total number of lost DATA packets recovered by the packet filter at the receiver side (e.g., FEC rebuilt packets; refer to [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md)). Available for receiver. If the `SRTO_PACKETFILTER` socket option is disabled (refer to [SRT API Socket Options](API-socket-options.md)), this statistic is equal to 0. Introduced in SRT v1.4.0. #### pktRcvFilterLossTotal The total number of lost DATA packets **not** recovered by the packet filter at the receiver side (refer to [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md)). Available for receiver. If the `SRTO_PACKETFILTER` socket option is disabled (refer to [SRT API Socket Options](API-socket-options.md)), this statistic is equal to 0. Introduced in SRT v1.4.0. #### byteSentTotal Same as [pktSentTotal](#pktSentTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for sender. #### byteRecvTotal Same as [pktRecvTotal](#pktRecvTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for receiver. #### byteSentUniqueTotal Same as [pktSentUniqueTotal](#pktSentUniqueTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for sender. #### byteRecvUniqueTotal Same as [pktRecvUniqueTotal](#pktRecvUniqueTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for receiver. #### byteRcvLossTotal Same as [pktRcvLossTotal](#pktRcvLossTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size. Available for receiver. #### byteRetransTotal Same as [pktRetransTotal](#pktRetransTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for sender. #### byteSndDropTotal Same as [pktSndDropTotal](#pktSndDropTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for sender. #### byteRcvDropTotal Same as [pktRcvDropTotal](#pktRcvDropTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Bytes for the dropped packets' payloads are estimated based on the average packet size. Available for receiver. #### byteRcvUndecryptTotal Same as [pktRcvUndecryptTotal](#pktRcvUndecryptTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for receiver. ### Interval-Based Statistics #### pktSent Same as [pktSentTotal](#pktSentTotal), but for a specified interval. #### pktRecv Same as [pktRecvTotal](#pktRecvTotal), but for a specified interval. #### pktSentUnique Same as [pktSentUniqueTotal](#pktSentUniqueTotal), but for a specified interval. #### pktRecvUnique Same as [pktRecvUniqueTotal](#pktRecvUniqueTotal), but for a specified interval. #### pktSndLoss Same as [pktSndLossTotal](#pktSndLossTotal), but for a specified interval. #### pktRcvLoss Same as [pktRcvLossTotal](#pktRcvLossTotal), but for a specified interval. #### pktRetrans Same as [pktRetransTotal](#pktRetransTotal), but for a specified interval. #### pktRcvRetrans Same as [pktRcvRetransTotal](#pktRcvRetransTotal), but for a specified interval. #### pktSentACK Same as [pktSentACKTotal](#pktSentACKTotal), but for a specified interval. #### pktRecvACK Same as [pktRecvACKTotal](#pktRecvACKTotal), but for a specified interval. #### pktSentNAK Same as [pktSentNAKTotal](#pktSentNAKTotal), but for a specified interval. #### pktRecvNAK Same as [pktRecvNAKTotal](#pktRecvNAKTotal), but for a specified interval. #### pktSndFilterExtra Same as [pktSndFilterExtraTotal](#pktSndFilterExtraTotal), but for a specified interval. Introduced in v1.4.0. Refer to [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md). #### pktRcvFilterExtra Same as [pktRcvFilterExtraTotal](#pktRcvFilterExtraTotal), but for a specified interval. Introduced in v1.4.0. Refer to [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md). #### pktRcvFilterSupply Same as [pktRcvFilterSupplyTotal](#pktRcvFilterSupplyTotal), but for a specified interval. Introduced in v1.4.0. Refer to [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md). #### pktRcvFilterLoss Same as [pktRcvFilterLossTotal](#pktRcvFilterLossTotal), but for a specified interval. Introduced in v1.4.0. Refer to [SRT Packet Filtering & FEC](../features/packet-filtering-and-fec.md). #### mbpsSendRate Sending rate in Mbps. Sender side. #### mbpsRecvRate Receiving rate in Mbps. Receiver side. #### usSndDuration Same as [usSndDurationTotal](#usSndDurationTotal), but measured on a specified interval. #### pktReorderDistance The distance in sequence numbers between the two original (not retransmitted) packets, that were received out of order. Receiver only. The traceable distance values are limited by the maximum reorder tolerance set by  `SRTO_LOSSMAXTTL`. #### pktRcvBelated The number of packets received but IGNORED due to having arrived too late. Makes sense only if TSBPD and TLPKTDROP are enabled. An offset between sequence numbers of the newly arrived DATA packet and latest acknowledged DATA packet is calculated. If the offset is negative, the packet is considered late, meaning that it was either already acknowledged or dropped by TSBPD as too late to be delivered. Retransmitted packets can also be considered late. #### pktSndDrop Same as [pktSndDropTotal](#pktSndDropTotal), but for a specified interval. #### pktRcvDrop Same as [pktRcvDropTotal](#pktRcvDropTotal), but for a specified interval. #### pktRcvUndecrypt Same as [pktRcvUndecryptTotal](#pktRcvUndecryptTotal), but for a specified interval. #### byteSent Same as [byteSentTotal](#byteSentTotal), but for a specified interval. #### byteRecv Same as [byteRecvTotal](#byteRecvTotal), but for a specified interval. #### byteSentUnique Same as [byteSentUniqueTotal](#byteSentUniqueTotal), but for a specified interval. #### byteRecvUnique Same as [byteRecvUniqueTotal](#byteRecvUniqueTotal), but for a specified interval. #### byteRcvLoss Same as [byteRcvLossTotal](#byteRcvLossTotal), but for a specified interval. #### byteRetrans Same as [byteRetransTotal](#byteRetransTotal), but for a specified interval. #### byteSndDrop Same as [byteSndDropTotal](#byteSndDropTotal), but for a specified interval. #### byteRcvDrop Same as [byteRcvDropTotal](#byteRcvDropTotal), but for a specified interval. #### byteRcvUndecrypt Same as [byteRcvUndecryptTotal](#byteRcvUndecryptTotal), but for a specified interval. ### Instantaneous Statistics #### usPktSndPeriod Current minimum time interval between which consecutive packets are sent, in microseconds. Sender only. Note that several sockets sharing one outgoing port use the same sending queue. They may have different pacing of the outgoing packets, but all the packets will be placed in the same sending queue, which may affect the send timing. `usPktSndPeriod` is the minimum time (sending period) that must be kept between two packets sent consecutively over the link used by an SRT socket. It is not the EXACT time interval between two consecutive packets. In the case where the time spent by an application between sending two consecutive packets exceeds `usPktSndPeriod`, the next packet will be sent faster, or even immediately, to preserve the average sending rate. **Note**: Does not apply to probing packets. #### pktFlowWindow The maximum number of packets that can be "in flight". Sender only. See also [pktFlightSize](#pktFlightSize). The value retrieved on the sender side represents an estimation of the amount of free space in the buffer of the peer receiver. The actual amount of available space is periodically reported back by the receiver in ACK packets. When this value drops to zero, the next packet sent will be dropped by the receiver without processing. In **file mode** this may cause a slowdown of sending in order to wait until the receiver has more space available, after it eventually extracts the packets waiting in its receiver buffer; in **live mode** the receiver buffer contents should normally occupy not more than half of the buffer size (default 8192). If `pktFlowWindow` value is less than that and becomes even less in the next reports, it means that the receiver application on the peer side cannot process the incoming stream fast enough and this may lead to a dropped connection. #### pktCongestionWindow Congestion window size, in number of packets. Sender only. Dynamically limits the maximum number of packets that can be in flight. Congestion control module dynamically changes the value. In **file mode** this value starts at 16 and is increased to the number of reported acknowledged packets. This value is also updated based on the delivery rate, reported by the receiver. It represents the maximum number of packets that can be safely sent without causing network congestion. The higher this value is, the faster the packets can be sent. In **live mode** this field is not used. #### pktFlightSize The number of packets in flight. Sender only. `pktFlightSize <= pktFlowWindow` and `pktFlightSize <= pktCongestionWindow` This is the distance between the packet sequence number that was last reported by an ACK message and the sequence number of the latest packet sent (at the moment when the statistics are being read). **NOTE:** ACKs are received periodically (at least every 10 ms). This value is most accurate just after receiving an ACK and becomes a little exaggerated over time until the next ACK arrives. This is because with a new packet sent, while the ACK number stays the same for a moment, the value of `pktFlightSize` increases. But the exact number of packets arrived since the last ACK report is unknown. A new statistic might be added which only reports the distance between the ACK sequence and the sent sequence at the moment when an ACK arrives, and isn't updated until the next ACK arrives. The difference between this value and `pktFlightSize` would then reveal the number of packets with an unknown state at that moment. #### msRTT Smoothed round-trip time (SRTT), an exponentially-weighted moving average (EWMA) of an endpoint's RTT samples, in milliseconds. Available both for sender and receiver. See [Section 4.10. Round-Trip Time Estimation](https://tools.ietf.org/html/draft-sharabayko-srt-00#section-4.10) of the [SRT RFC](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00) and [[RFC6298] Paxson, V., Allman, M., Chu, J., and M. Sargent, "Computing TCP's Retransmission Timer"](https://datatracker.ietf.org/doc/html/rfc6298) for more details. #### mbpsBandwidth Estimated bandwidth of the network link, in Mbps. Sender only. The bandwidth is estimated at the receiver. The estimation is based on the time between two probing DATA packets. Every 16th data packet is sent immediately after the previous data packet. By measuring the delay between probe packets on arrival, it is possible to estimate the maximum available transmission rate, which is interpreted as the bandwidth of the link. The receiver then sends back a running average calculation to the sender with an ACK message. #### byteAvailSndBuf The available space in the sender's buffer, in bytes. Sender only. This value decreases with data scheduled for sending by the application, and increases with every ACK received from the receiver, after the packets are sent over the UDP link. #### byteAvailRcvBuf The available space in the receiver's buffer, in bytes. Receiver only. This value increases after the application extracts the data from the socket (uses one of `srt_recv*` functions) and decreases with every packet received from the sender over the UDP link. #### mbpsMaxBW Transmission bandwidth limit, in Mbps. Sender only. Usually this is the setting from the `SRTO_MAXBW` option, which may include the value 0 (unlimited). Under certain conditions a nonzero value might be be provided by a congestion control module, although none of the built-in congestion control modules currently use it. Refer to `SRTO_MAXBW` and `SRTO_INPUTBW` in [SRT API Socket Options](API-socket-options.md). #### byteMSS Maximum Segment Size (MSS), in bytes. Same as the value from the `SRTO_MSS` socket option. Should not exceed the size of the maximum transmission unit (MTU), in bytes. Sender and Receiver. The default size of the UDP packet used for transport, including all possible headers (Ethernet, IP and UDP), is 1500 bytes. Refer to `SRTO_MSS` in [SRT API Socket Options](API-socket-options.md). #### pktSndBuf The number of packets in the sender's buffer that are already scheduled for sending or even possibly sent, but not yet acknowledged. Sender only. Once the receiver acknowledges the receipt of a packet, or the TL packet drop is triggered, the packet is removed from the sender's buffer. Until this happens, the packet is considered as unacknowledged. A moving average value is reported when the value is retrieved by calling `srt_bstats(...)` or `srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous)` with `instantaneous=false`. The current state is returned if `srt_bistats(...)` is called with `instantaneous=true`. #### byteSndBuf Instantaneous (current) value of `pktSndBuf`, but expressed in bytes, including payload and all headers (SRT+UDP+IP). \ 20 bytes IPv4 + 8 bytes of UDP + 16 bytes SRT header. Sender side. #### msSndBuf The timespan (msec) of packets in the sender's buffer (unacknowledged packets). Sender only. A moving average value is reported when the value is retrieved by calling `srt_bstats(...)` or `srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous)` with `instantaneous=false`. The current state is returned if `srt_bistats(...)` is called with `instantaneous=true`. #### msSndTsbPdDelay Timestamp-based Packet Delivery Delay value of the peer. If `SRTO_TSBPDMODE` is on (default for **live mode**), it returns the value of `SRTO_PEERLATENCY`, otherwise 0. The sender reports the TSBPD delay value of the receiver. The receiver reports the TSBPD delay of the sender. #### pktRcvBuf The number of acknowledged packets in receiver's buffer. Receiver only. This measurement does not include received but not acknowledged packets, stored in the receiver's buffer. A moving average value is reported when the value is retrieved by calling `srt_bstats(...)` or `srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous)` with `instantaneous=false`. The current state is returned if `srt_bistats(...)` is called with `instantaneous=true`. #### byteRcvBuf Instantaneous (current) value of `pktRcvBuf`, expressed in bytes, including payload and all headers (SRT+UDP+IP). \ 20 bytes IPv4 + 8 bytes of UDP + 16 bytes SRT header. Receiver side. #### msRcvBuf The timespan (msec) of acknowledged packets in the receiver's buffer. Receiver side. If TSBPD mode is enabled (defualt for **live mode**), a packet can be acknowledged, but not yet ready to play. This range includes all packets regardless of whether they are ready to play or not. A moving average value is reported when the value is retrieved by calling `srt_bstats(...)` or `srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous)` with `instantaneous=false`. The current state is returned if `srt_bistats(...)` is called with `instantaneous=true`. Instantaneous value is only reported if TSBPD mode is enabled, otherwise 0 is reported (see #900). #### msRcvTsbPdDelay Timestamp-based Packet Delivery Delay value set on the socket via `SRTO_RCVLATENCY` or `SRTO_LATENCY`. The value is used to apply TSBPD delay for reading the received data on the socket. Receiver side. If `SRTO_TSBPDMODE` is off (default for **file mode**), 0 is returned. #### pktReorderTolerance Instant value of the packet reorder tolerance. Receiver side. Refer to [pktReorderDistance](#pktReorderDistance). `SRTO_LOSSMAXTTL` sets the maximum reorder tolerance value. The value defines the maximum time-to-live for the original packet, that was received after with a gap in the sequence of incoming packets. Those missing packets are expected to come out of order, therefore no loss is reported. The actual TTL value (**pktReorderTolerance**) specifies the number of packets to receive further, before considering the preceding packets lost, and sending the loss report. The internal algorithm checks the order of incoming packets and adjusts the tolerance based on the reorder distance (**pktReorderTolerance**), but not to a value higher than the maximum (`SRTO_LOSSMAXTTL`). SRT starts from tolerance value set in `SRTO_LOSSMAXTTL` (initial tolerance is set to 0 in SRT v1.4.0 and prior versions). Once the receiver receives the first reordered packet, it increases the tolerance to the distance in the sequence discontinuity of the two packets. \ After 10 consecutive original (not retransmitted) packets come in order, the reorder distance is decreased by 1 for every such packet. For example, assume packets with the following sequence numbers are being received: \ 1, 2, 4, 3, 5, 7, 6, 10, 8, 9 SRT starts from 0 tolerance. Receiving packet with sequence number 4 has a discontinuity equal to one packet. The loss is reported to the sender. With the next packet (sequence number 3) a reordering is detected. Reorder tolerance is increased to 1. \ The next sequence discontinuity is detected when the packet with sequence number 7 is received. The current tolerance value is 1, which is equal to the gap (between 5 and 7). No loss is reported. \ Next packet with sequence number 10 has a higher sequence discontinuity equal to 2. Missing packets with sequence numbers 8 and 9 will be reported lost with the next received packet (reorder distance is still at 1). The next received packet has sequence number 8. Reorder tolerance value is increased to 2. The packet with sequence number 9 is reported lost. #### pktRcvAvgBelatedTime Accumulated difference between the current time and the time-to-play of a packet that is received late. ## SRT Group Statistics SRT group statistics are implemented for SRT Connection Bonding feature and available since SRT v1.5.0. Check the following documentation and code examples for details: - Introduction in [SRT Connection Bonding](../features/bonding-intro.md), - The concept of [SRT Socket Groups](../features/socket-groups.md). Here you will also find the information regarding the `srt-test-live` application for testing Connection Bonding, - Check also [SRT API](API.md) and [SRT API Functions](API-functions.md) documentation for Connection Bonding related updates, - Code examples: simple [client](https://github.com/Haivision/srt/blob/master/examples/test-c-client-bonding.c) and [server](https://github.com/Haivision/srt/blob/master/examples/test-c-server-bonding.c) implementation. `srt_bistats(SRTSOCKET u, ...)` function can be used with a socket group ID as a first argument to get statistics for a group. Most values of the `SRT_TRACEBSTATS` will be filled with zeros except for the fields listed in [Summary Table](#group-summary-table) below. Refer to the documentation of the [SRT API Functions](API-functions.md) for usage instructions. ### Summary Table The table below provides a summary of SRT group statistics: name, type, unit of measurement, data type, and whether it is calculated by the sender or receiver. See sections [Accumulated Statistics](#group-accumulated-statistics) and [Interval-Based Statistics](#group-interval-based-statistics) for a detailed description of each statistic. | Statistic | Type of Statistic | Unit of Measurement | Available for Sender | Available for Receiver | Data Type | | ------------------------------------------------- | ----------------- | ------------------- | -------------------- | ---------------------- | --------- | | [msTimeStamp](#group-msTimeStamp) | accumulated | ms (milliseconds) | ✓ | ✓ | int64_t | | [pktSentUniqueTotal](#group-pktSentUniqueTotal) | accumulated | packets | ✓ | - | int64_t | | [pktRecvUniqueTotal](#group-pktRecvUniqueTotal) | accumulated | packets | - | ✓ | int64_t | | [pktRcvDropTotal](#group-pktRcvDropTotal) | accumulated | packets | - | ✓ | int32_t | | [byteSentUniqueTotal](#group-byteSentUniqueTotal) | accumulated | packets | ✓ | - | int64_t | | [byteRecvUniqueTotal](#group-byteRecvUniqueTotal) | accumulated | packets | - | ✓ | int64_t | | [byteRcvDropTotal](#group-byteRcvDropTotal) | accumulated | packets | - | ✓ | int32_t | | [pktSentUnique](#group-pktSentUnique) | interval-based | packets | ✓ | - | int64_t | | [pktRecvUnique](#group-pktRecvUnique) | interval-based | packets | - | ✓ | int64_t | | [pktRcvDrop](#group-pktRcvDrop) | interval-based | packets | - | ✓ | int32_t | | [byteSentUnique](#group-byteSentUnique) | interval-based | packets | ✓ | - | int64_t | | [byteRecvUnique](#group-byteRecvUnique) | interval-based | packets | - | ✓ | int64_t | | [byteRcvDrop](#group-byteRcvDrop) | interval-based | packets | - | ✓ | int32_t | ### Accumulated Statistics #### msTimeStamp The time elapsed, in milliseconds, since the time ("connection" time) when the initial group connection has been initiated (the time when the first connection in the group has been made and therefore made the group connected). This "connection" time will be then set in this statistic in every next socket that will become a member of the group as the new connections are established. A new connection to an already connected group doesn’t change the value of "connection" time. Available both for sender and receiver. #### pktSentUniqueTotal The number of *unique original* DATA packets sent by the socket group. Available for sender. This value counts every *original* DATA packet sent over the network for the first time by the socket group. There is no difference between Connection Bonding modes (broadcast, backup and balancing). For example, sending the packet with a particular sequence number over multiple links in case of broadcast mode (it means sending this packet multiple times) does not affect the statistic and this very packet is taken into account only once. This statistic does not count retransmitted DATA packets that are individual per socket connection within the group. See the corresponding [pktRetransTotal](#pktRetransTotal) socket statistic. If the `SRTO_PACKETFILTER` socket option is enabled (refer to [SRT API Socket Options](API-socket-options.md)), this statistic does not count packet filter control packets that are individual per socket connection within the group. See the corresponding [pktSndFilterExtraTotal](#pktSndFilterExtraTotal) socket statistic. #### pktRecvUniqueTotal The number of *unique* DATA packets *received in time* by the socket group and, as a result, scheduled for delivery to the upstream application. Available for receiver. Unique means "first arrived over multiple links" DATA packets. Whichever packet comes first over whichever link is taken into account. This statistic doesn't count - discarded as duplicate by the group reader packets, see [pktRcvDiscardTotal](#group-pktRcvDiscardTotal) statistic, - dropped by the socket group packets, see [pktRcvDropTotal](#group-pktRcvDropTotal) statistic. #### pktRcvDropTotal The number of *dropped* and, as a result, *not delivered* to the upstream application by the socket group DATA packets. Available for receiver. A packet is considered dropped by the socket group if it has been dropped by the TLPKTDROP mechanism over all the links from the group. See the corresponding socket [pktRcvDropTotal](#pktRcvDropTotal) statistic. For example, if a packet with a particular sequence number has been dropped over one or several links, but has not been dropped over at least one link, it is *not* considered dropped by the socket group and can be delivered to the upstream application. Only if a packet has been dropped over all the links from the group, it is considered dropped by the socket group and can not be delivered to the upstream application. In fact, only sockets can drop the packets and the group is simply responsible for delivering received over multiple sockets packets to the application. #### byteSentUniqueTotal Same as [pktSentUniqueTotal](#group-pktSentUniqueTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for sender. #### byteRecvUniqueTotal Same as [pktRecvUniqueTotal](#group-pktRecvUniqueTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for receiver. #### byteRcvDropTotal Same as [pktRcvDropTotal](#group-pktRcvDropTotal), but expressed in bytes, including payload and all the headers (20 bytes IPv4 + 8 bytes UDP + 16 bytes SRT). Available for receiver. ### Interval-Based Statistics #### pktSentUnique Same as [pktSentUniqueTotal](#group-pktSentUniqueTotal), but for a specified interval. #### pktRecvUnique Same as [pktRecvUniqueTotal](#group-pktRecvUniqueTotal), but for a specified interval. #### pktRcvDrop Same as [pktRcvDropTotal](#group-pktRcvDropTotal), but for a specified interval. #### byteSentUnique Same as [byteSentUniqueTotal](#group-byteSentUniqueTotal), but for a specified interval. #### byteRecvUnique Same as [byteRecvUniqueTotal](#group-byteRecvUniqueTotal), but for a specified interval. #### byteRcvDrop Same as [byteRcvDropTotal](#group-byteRcvDropTotal), but for a specified interval. ### Formulas The ratio of unrecovered by the socket group packets `Dropped Packets Ratio` can be calculated as follows: ``` Dropped Packets Ratio = pktRcvDropTotal / pktSentUniqueTotal; in case both sender and receiver statistics is available Dropped Packets Ratio = pktRcvDropTotal / (pktRecvUniqueTotal + pktRcvDropTotal); in case receiver only statistics is available ```srt-1.4.4/docs/README.md000066400000000000000000000237301412557703600145560ustar00rootroot00000000000000# Documentation Overview ## SRT API Documents | Document Title | Folder | File Name | Description | | :----------------------------------------------------- | :---------------------------- | :------------------------------------------------- | :--------------------------------------------------- | | [SRT API](API/API.md) | [API](API/) | [API.md](API/API.md) | Detailed description of the SRT C API. | | [SRT API Functions](API/API-functions.md) | [API](API/) | [API-functions.md](API/API-functions.md) | Reference document for SRT API functions. | | [SRT API Socket Options](API/API-socket-options.md) | [API](API/) | [API-socket-options.md](API/API-socket-options.md) | Instructions and list of socket options for SRT API. | | [SRT Statistics](API/statistics.md) | [API](API/) | [statistics.md](API/statistics.md) | How to use SRT socket and socket group statistics. | | [Configuration Guidelines](API/configuration-guidelines.md) | [API](API/) | [configuration-guidelines.md](API/configuration-guidelines.md) | How to configure SRT buffers. | | | | | | ## Build Instructions | Document Title | Folder | File Name | Description | | :------------------------------------------------- | :---------------------------- | :----------------------------------------- | :----------------------------------------------------------- | | [Building SRT for Android](build/build-android.md) | [build](build/) | [build-android.md](build/build-android.md) | SRT build instructions for Android. | | [Building SRT for iOS](build/build-iOS.md) | [build](build/) | [build-iOS.md](build/build-iOS.md) | SRT build instructions for iOS. | | [SRT Build Options](build/build-options.md) | [build](build/) | [build-options.md](build/build-options.md) | Description of CMake build system, configure script, and
build options. | | [Building SRT for Windows](build/build-win.md) | [build](build/) | [build-win.md](build/build-win.md) | SRT build instructions for Windows. | | | | | | ## Development Documents | Document Title | Folder | File Name | Description | | :----------------------------------------------- | :---------------------------- | :----------------------------------------------- | :----------------------------------------------------------- | | [SRT Developer's Guide](dev/developers-guide.md) | [dev](dev/) | [developers-guide.md](dev/developers-guide.md) | Development setup, project structure, coding rules,
submitting issues & PRs, etc. | | [Low Level Info](dev/low-level-info.md) | [dev](dev/) | [low-level-info.md](dev/low-level-info.md) | Low level information for the SRT project (only
mutex locking). | | [Making SRT Better](dev/making-srt-better.md) | [dev](dev/) | [making-srt-better.md](dev/making-srt-better.md) | Guidelines for problem reporting, collecting debug logs
and pcaps. | | | | | | ## Features | Document Title | Folder | File Name | Description | | :----------------------------------------------------------- | :---------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | | [SRT Access Control
(Stream ID) Guidelines](features/access-control.md) | [features](features/) | [access-control.md](features/access-control.md) | Access Control (Stream ID) guidelines. | | [SRT Connection Bonding](features/bonding-intro.md) | [features](features/) | [bonding-intro.md](features/bonding-intro.md) | Introduction to Connection Bonding. Description
of group (bonded) connections. | | [SRT Socket Groups](features/socket-groups.md) | [features](features/) | [socket-groups.md](features/socket-groups.md) | Description of socket groups in SRT (Connection
Bonding). Here you will also find the information
regarding the `srt-test-live` application for testing
Connection Bonding. | | [SRT Connection Bonding: Main/Backup][main-backup] | [features](features/) | [bonding-main-backup.md][main-backup] | SRT Main/Backup Connection Bonding. | | [SRT Encryption](features/encryption.md) | [features](features/) | [encryption.md](features/encryption.md) | Description of SRT encryption mechanism. This
document might be outdated, please consult
[Section 6. Encryption][srt-rfc-sec-6] of the [SRT RFC][srt-rfc] additionally. | | [SRT Handshake](features/handshake.md) | [features](features/) | [handshake.md](features/handshake.md) | Description of SRT handshake mechanism. This
document might be outdated, please consult
[Section 3.2.1 Handshake][srt-rfc-sec-3-2-1] and
[Section 4.3 Handshake Messages](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-4.3) of the
[SRT RFC][srt-rfc] additionally. | | [Live Streaming
Guidelines](features/live-streaming.md) | [features](features/) | [live-streaming.md](features/live-streaming.md) | Guidelines for live streaming with SRT. See also
best practices and configuration tips in
[Section 7.1 Live Streaming](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-7.1) of the [SRT RFC][srt-rfc]. | | [SRT Packet
Filtering & FEC][packet-filter] | [features](features/) | [packet-filtering-and-fec.md][packet-filter] | Description of SRT packet filtering mechanism,
including FEC. | | | | | | ## Sample Applications | Document Title | Folder | File Name | Description | | :----------------------------------------------------------- | :---------------------------- | :------------------------------------------------ | :----------------------------------------------------------- | | [Using the
`srt-live-transmit` App](apps/srt-live-transmit.md) | [apps](apps/) | [srt-live-transmit.md](apps/srt-live-transmit.md) | A sample application to transmit a live stream from
source medium (UDP/SRT/`stdin`) to the target medium
(UDP/SRT/`stdout`). | | [Using the
`srt-multiplex` App](apps/srt-multiplex.md) | [apps](apps/) | [srt-multiplex.md](apps/srt-multiplex.md) | Description of sample program for sending multiple streams. | | [Using the
`srt-tunnel` App](apps/srt-tunnel.md) | [apps](apps/) | [srt-tunnel.md](apps/srt-tunnel.md) | A sample application to set up an SRT tunnel for TCP traffic. | | | | | | ## Miscellaneous | Document Title | Folder | File Name | Description | | :------------------------------------------------- | :---------------------------- | :---------------------------------------------------- | :----------------------------------------------------------- | | [Why SRT Was Created](misc/why-srt-was-created.md) | [misc](misc/) | [why-srt-was-created.md](misc/why-srt-was-created.md) | Background and history of SRT. See also
[Section 1. Introduction][srt-rfc-sec-1] of the [SRT RFC][srt-rfc]. | | | | | | [srt-rfc]: https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00 [srt-rfc-sec-1]: https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-1 [srt-rfc-sec-3-2-1]: https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-3.2.1 [srt-rfc-sec-6]: https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-6 [main-backup]: features/bonding-main-backup.md [packet-filter]: features/packet-filtering-and-fec.md srt-1.4.4/docs/apps/000077500000000000000000000000001412557703600142355ustar00rootroot00000000000000srt-1.4.4/docs/apps/srt-live-transmit.md000066400000000000000000000574171412557703600202010ustar00rootroot00000000000000# srt-live-transmit The `srt-live-transmit` tool is a universal data transport tool with a purpose to transport data between SRT and other medium. At the same time it is just a sample application to show some of the powerful features of SRT. We encourage you to use SRT library itself integrated into your products. ## Introduction The `srt-live-transmit` can be both used as a universal SRT-to-something-else flipper, as well as a testing tool for SRT. The general usage is the following: ```shell srt-live-transmit [options] ``` The following medium types are handled by `srt-live-transmit`: - SRT - use SRT for reading or writing, in listener, caller or rendezvous mode, with possibly additional parameters - UDP - read or write the given UDP address (also multicast) - Local file - read or store the stream into the file - Process's pipeline - use the process's `stdin` and `stdout` standard streams Any medium can be used with any direction, although some of them may have special direction-dependent cases. Mind that the URI has a standard syntax: ```yaml scheme://HOST:PORT/PATH?PARAM1=VALUE1&PARAM2=VALUE2&... ``` The first parameter is introduced with a `?` and all following can be appended with an `&` character. If you specify only the path (no **://** specified), then the scheme defaults to **file**. The path can be also specified as relative this way. Note also that empty host (`scheme://:PORT`) defaults to 0.0.0.0, and an empty port (when there's no `:PORT` part) defaults to port number 0. Special options for particular medium may be specified in **PARAM** items. All options are medium-specific, although there may happen some options common for multiple media types. Note also that the **HOST** part is always tried to be resolved as a name, if its form is not directly the IPv4 address. ### Example for Smoke Testing First we need to start up the `srt-live-transmit` app, listening for unicast UDP TS input on port 1234 and making SRT available on port 4201. Note, these are randomly chosen ports. We also open the app in verbose mode for debugging: ```shell srt-live-transmit udp://:1234 srt://:4201 -v ``` Now we need to generate a UDP stream. ffmpeg can be used to generate bars and tone as follows, doing a simple unicast push to our listening `srt-live-transmit` application: ```shell ffmpeg -f lavfi -re -i smptebars=duration=300:size=1280x720:rate=30 -f lavfi -re -i sine=frequency=1000:duration=60:sample_rate=44100 -pix_fmt yuv420p -c:v libx264 -b:v 1000k -g 30 -keyint_min 120 -profile:v baseline -preset veryfast -f mpegts "udp://127.0.0.1:1234?pkt_size=1316" ``` You should see the stream connect in `srt-live-transmit`. Now you can test in VLC (make sure you're using the latest version!) - just go to file -> open network stream and enter `srt://127.0.0.1:4201` and you should see bars and tone right away. If you're having trouble, make sure this works, then add complexity one step at a time (multicast, push vs listen, etc.). ## URI Syntax Transmission mediums are specified as the standard URI format: ```yaml SCHEME://HOST:PORT?PARAM1=VALUE1&PARAM2=VALUE2&... ``` The applications supports the following schemes: - `file` - for file or standard input and output - `udp` - UDP output (unicast and multicast) - `srt` - SRT connection Note that this application doesn't support file as a medium, but this can be handled by other applications from this project. ### Medium: FILE (including standard process pipes) **NB!** File mode, except `file://con`, is not supported in the `srt-file-transmit` tool! The general syntax is: `file:///global/path/to/the/file`. No parameters in the URL are extracted. There's one (non-standard!) special case, though: ```yaml file://con ``` That is, **con** is used as a *HOST* part of the URI. If you use this URI for \, then the data will be read from the standard input. If \, the data will be send to the standard output. Be careful with options being specified together with having standard output as output URI - some of them are not allowed as the extra output controlled by options might interfere with the data output. ## Medium: UDP UDP can only be used in listening mode for input, and in calling mode for output. Multicast Streaming is also possible, without any special declaration. Just use an IP address from the multicast range. The specification and meaning of the fields in the URI depend on the mode. The **PORT** part is always mandatory and it designates either the port number for the target host or the port number to be bound to read from. For sending to unicast: ```yaml udp://TARGET:PORT?parameters... ``` - The **HOST** part (here: TARGET) is mandatory and designates the target host - The **iptos** parameter designates the Type-Of-Service (TOS) field for outgoing packets via `IP_TOS` socket option. - The **ttl** parameter will set time-to-live value for outgoing packets via `IP_TTL` socket options. For receiving from unicast: ```yaml udp://LOCALADDR:PORT?parameters... ``` - The **HOST** part (here: LOCALADDR) designates the local interface to bind. It's optional (can be empty) and defaults to 0.0.0.0 (`INADDR_ANY`). For multicast the scheme is: ```yaml udp://GROUPADDR:PORT?parameters... ``` - The **HOST** part (here: GROUPADDR) is mandatory always and designates the target multicast group. The `@` character is handled in this case, but it's not necessary, as the IGMP addresses are recognized by their mask. For sending to a multicast group: - The **iptos** parameter designates the Type-Of-Service (TOS) field for outgoing packets via `IP_TOS` socket option. - The **ttl** parameter will set time-to-live value for outgoing packets via `IP_MULTICAST_TTL` socket options. - The **adapter** parameter can be used to specify the adapter to be set through `IP_MULTICAST_IF` option to override the default device used for sending For receiving from a multicast group: - The **adapter** parameter can be used to specify the adapter through which the given multicast group can be reached (it's used to bind the socket) - The **source** parameter enforces the use of `IP_ADD_SOURCE_MEMBERSHIP` instead of `IP_ADD_MEMBERSHIP` and the value is set to `imr_sourceaddr` field. Explanations for the symbols and terms used above can be found in POSIX manual pages, like `ip(7)` and on Microsoft docs pages under `IPPROTO_IP`. ### Medium: SRT Most important about SRT is that it can be either input or output and in both these cases it can work in listener, caller and rendezvous mode. SRT also handles several parameters special way, in addition to standard SRT options that can be set through the parameters. SRT can be connected using one of three connection modes: - **caller**: the "agent" (this application) sends the connection request to the peer, which must be **listener**, and this way it initiates the connection. - **listener**: the "agent" waits to be contacted by any peer **caller**. Note that a listener can accept multiple callers, but *srt-live-transmit* does not use this ability; after the first connection, it no longer accepts new connections. - **rendezvous**: A one-to-one only connection where both parties are equivalent and both connect to one another simultaneously. Whoever happened to start first (or succeeded to punch through the firewall) is meant to have initiated the connection. This mode can be specified explicitly using the **mode** parameter. When it's not specified, then it is "deduced" the following way: - `srt://:1234` - the *port* is specified (1234), but *host* is empty. This assumes **listener** mode. - `srt://remote.host.com:1234` - both *host* and *port* are specified. This assumes **caller** mode. When the `mode` parameter is specified explicitly, then the interpretation of the `host` part is the following: - For caller, it's always the destination host address. If this is empty, it is resolved to 0.0.0.0, which usually should mean connecting to the local host - For listener, it defines the IP address of the local device on which the socket should listen, e.g.: ```yaml srt://10.10.10.100:5001?mode=listener ``` An alternative method to specify this IP address is the `adapter` parameter: ```yaml srt://:5001?adapter=10.10.10.100 ``` The **rendezvous** mode is not deduced and it has to be specified explicitly. Note also special cases of the **host** and **port** parts specified in the URI: - **CALLER**: the *host* and *port* parts are mandatory and specify the remote host and port to be contacted. - The **port** parameter can be used to enforce the local outgoing port (**not to be confused** with remote port!). - The **adapter** parameter is not used. - **LISTENER**: the *port* part is mandatory and it specifies the local listening port. - The **adapter** parameter can be used to specify the adapter. - The *host* part, if specified, can be also used to set the adapter - although in this case **mode=listener** must be set explicitly. - The **port** parameter is not used. - **RENDEZVOUS**: the *host* and *port* parts are mandatory. - The *host* part specifies the remote host to contact. - The *port* part specifies **both local and remote port**. Note that the local port is this way both listening port and outgoing port. - The **adapter** parameter can be used to specify the adapter. - The **port** parameter can be used to specify the local port to bind to. **IMPORTANT** information about IPv6. This application can also use an address specified as IPv6 with the following restrictions: 1. The IPv6 address in the URI is specified in square brackets: e.g. `srt://[::1]:5000`. 2. In listener mode, if you leave the host empty, the socket is bound to `INADDR_ANY` for IPv4 only. If you want to make it listen on IPv6, you need to specify the host as `::`. NOTE: Don't use square brackets syntax in the adapter specification, as in this case only the host is expected. 3. If you want to listen for connections from both IPv4 and IPv6, mind the `ipv6only` option. The default value for this option is system default (see system manual for `IPV6_V6ONLY` socket option); if unsure, you might want to enforce `ipv6only=0` in order to be able to accept both IPv4 and IPv6 connections in the same listener. 4. In rendezvous mode you may only interconnect both parties using IPv4, or both using IPv6. Unlike listener mode, if you want to leave the socket default-bound (you don't specify `adapter`), the socket will be bound with the same IP version as the target address. If you do specify `adapter`, then both this address and the target address must be of the same family. Examples: * `srt://:5000` defines listener mode with IPv4. * `srt://[::]:5000` defines caller mode (!) with IPv6. * `srt://[::]:5000?mode=listener` defines listener mode with IPv6. If the default value for `IPV6_V6ONLY` system socket option is 0, it will accept also IPv4 connections. * `srt://192.168.0.5:5000?mode=rendezvous` will make a rendezvous connection with local address `INADDR_ANY` (IPv4) and port 5000 to a destination with port 5000. * `srt://[::1]:5000?mode=rendezvous&port=4000` will make a rendezvous connection with local address `inaddr6_any` (IPv6) and port 4000 to a destination with port 5000. * `srt://[::1]:5000?adapter=127.0.0.1&mode=rendezvous` - this URI is invalid (different IP versions for binding and target address) Some parameters handled for SRT medium are specific, all others are socket options. The following parameters are handled special way by `srt-live-transmit`: - **mode**: enforce caller, listener or rendezvous mode - **port**: enforce the **outgoing** port (the port number that will be set in the UDP packet as a source port when sent from this host). This can be used only in **caller mode**. - **blocking**: sets the `SRTO_RCVSYN` for input medium or `SRTO_SNDSYN` for output medium - **timeout**: sets `SRTO_RCVTIMEO` for input medium or `SRTO_SNDTIMEO` for output medium - **adapter**: sets the adapter for listening in *listener* or *rendezvous* mode All other parameters are SRT socket options. The following have the following value types: - `bool`. Possible values: `yes`/`no`, `on`/`off`, `true`/`false`, `1`/`0`. - `bytes` positive integer [1; INT32_MAX]. - `ms` - positive integer value of milliseconds. | URI param | Values | SRT Option | Description | | -------------------- | ---------------- | ------------------------- | ----------- | | `congestion` | {`live`, `file`} | `SRTO_CONGESTION` | Type of congestion control. | | `conntimeo` | `ms` | `SRTO_CONNTIMEO` | Connection timeout. | | `drifttracer` | `bool` | `SRTO_DRIFTTRACER` | Enable drift tracer. | | `enforcedencryption` | `bool` | `SRTO_ENFORCEDENCRYPTION` | Reject connection if parties set different passphrase. | | `fc` | `bytes` | `SRTO_FC` | Flow control window size. | | `groupconnect` | {`0`, `1`} | `SRTO_GROUPCONNECT` | Accept group connections. | | `groupstabtimeo` | `ms` | `SRTO_GROUPSTABTIMEO` | Group stability timeout. | | `inputbw` | `bytes` | `SRTO_INPUTBW` | Input bandwidth. | | `iptos` | 0..255 | `SRTO_IPTOS` | IP socket type of service | | `ipttl` | 1..255 | `SRTO_IPTTL` | Defines IP socket "time to live" option. | | `ipv6only` | -1..1 | `SRTO_IPV6ONLY` | Allow only IPv6. | | `kmpreannounce` | 0.. | `SRTO_KMPREANNOUNCE` | Duration of Stream Encryption key switchover (in packets). | | `kmrefreshrate` | 0.. | `SRTO_KMREFRESHRATE` | Stream encryption key refresh rate (in packets). | | `latency` | 0.. | `SRTO_LATENCY` | Defines the maximum accepted transmission latency. | | `linger` | 0.. | `SRTO_LINGER` | Link linger value | | `lossmaxttl` | 0.. | `SRTO_LOSSMAXTTL` | Packet reorder tolerance. | | `maxbw` | 0.. | `SRTO_MAXBW` | Bandwidth limit in bytes | | `mininputbw` | 0.. | `SRTO_MININPUTBW` | Minimum allowed estimate of `SRTO_INPUTBW` | | `messageapi` | `bool` | `SRTO_MESSAGEAPI` | Enable SRT message mode. | | `minversion` | maj.min.rev | `SRTO_MINVERSION` | Minimum SRT library version of a peer. | | `mss` | 76.. | `SRTO_MSS` | MTU size | | `nakreport` | `bool` | `SRTO_NAKREPORT` | Enables/disables periodic NAK reports | | `oheadbw` | 5..100 | `SRTO_OHEADBW` | limits bandwidth overhead, percents | | `packetfilter` | `string` | `SRTO_PACKETFILTER` | Set up the packet filter. | | `passphrase` | `string` | `SRTO_PASSPHRASE` | Password for the encrypted transmission. (must be 10 to 79 characters) | | `payloadsize` | 0.. | `SRTO_PAYLOADSIZE` | Maximum payload size. | | `pbkeylen` | {16, 24, 32} | `SRTO_PBKEYLEN` | Crypto key length in bytes. | | `peeridletimeo` | `ms` | `SRTO_PEERIDLETIMEO` | Peer idle timeout. | | `peerlatency` | `ms` | `SRTO_PEERLATENCY` | Minimum receiver latency to be requested by sender. | | `rcvbuf` | `bytes` | `SRTO_RCVBUF` | Receiver buffer size | | `rcvlatency` | `ms` | `SRTO_RCVLATENCY` | Receiver-side latency. | | `retransmitalgo` | {`0`, `1`} | `SRTO_RETRANSMITALGO` | Packet retransmission algorithm to use. | | `sndbuf` | `bytes` | `SRTO_SNDBUF` | Sender buffer size. | | `snddropdelay` | `ms` | `SRTO_SNDDROPDELAY` | Sender's delay before dropping packets. | | `streamid` | `string` | `SRTO_STREAMID` | Stream ID (settable in caller mode only, visible on the listener peer). | | `tlpktdrop` | `bool` | `SRTO_TLPKTDROP` | Drop too late packets. | | `transtype` | {`live`, `file`} | `SRTO_TRANSTYPE` | Transmission type | | `tsbpdmode` | `bool` | `SRTO_TSBPDMODE` | Timestamp-based packet delivery mode. | The list of socket options can also be found in SRT header file `srt.h` (`SRT_SOCKOPT` enum type). Please note that the set of available options may be version dependent. All options are available under the lowercase name of the option without the `SRTO_` prefix. For example, `SRTO_PASSPHRASE` can be set using a **passphrase** parameter. The mapping table `srt_options` can be found in `common/socketoptions.hpp` file. Important thing about the options (which holds true also for options for TCP and UDP, even though it's not described anywhere explicitly) is that there are two categories of options: - PRE options: these options must be set to the socket prior to connecting and they cannot be altered after the connection is made. A PRE option set to a listening socket will be also derived by the socket returned by `srt_accept()`. - POST options: these options can be set to a socket at any time. The option set to a listening socket will not be derived by an accepted socket. You don't have to worry about that actually - the application is aware of this and it sets these options at appropriate time. Note also that **blocking** option has no practical use for users. Normally the non-blocking mode is used only when you have an event-driven application that needs a common signal bar for multiple event sources, or you prefer fibers to threads, when working with multiple SRT sockets in one application. The *srt-live-transmit* application isn't defined this way. This makes that the practical result of non-blocking mode here is that it uses polling on exactly one socket with infinite timeout. Every reading and writing operation will then return always without blocking, but when they report the "again" situation the application will stall on `srt_epoll_wait()` call. This option then exists for the testing purposes, as well as educational, to serve as an example of how your application should use the non-blocking mode. ## Command-Line Options The following options are available in the application. Note that some may affect specifically only selected type of medium. Options usually have values and they are set using **colon**: for example, **-t:60**. Alternatively you can also separate them by a space, but this space must be part of the parameter and not extracted by a shell (using **"** **"** quotes or backslash). - **-timeout, -t, -to** - Sets the timeout for any activity from any medium (in seconds). Default is 0 for infinite (that is, turn this mechanism off). The mechanism is such that the SIGALRM is set up to be called after the given time and it's reset after every reading succeeded. When the alarm expires due to no reading activity in defined time, it will break the application. **Notes:** - The alarm is set up after the reading loop has started, **not when the application has started**. That is, a caller will still wait the standard timeout to connect, and a listener may wait infinitely until some peer connects; only after the connection is established is the alarm counting started. - **The timeout mechanism doesn't work on Windows at all.** It behaves as if the timeout was set to **-1** and it's not modifiable. - **-timeout-mode, -tm** - Timeout mode used. Default is 0 - timeout will happen after the specified time. Mode 1 cancels the timeout if the connection was established. - **-st, -srctime, -sourcetime** - Enable source time passthrough. Default: disabled. It is recommended to build SRT with monotonic (`-DENABLE_MONOTONIC_CLOCK=ON`) or C++ 11 steady (`-DENABLE_STDCXX_SYNC=ON`) clock to use this feature. - **-buffering** - Enable source buffering up to the specified number of packets. Default: 10. Minimum: 1 (no buffering). - **-chunk, -c** - use given size of the buffer. The default size is 1456 bytes, which is the maximum payload size for a single SRT packet. - **-verbose, -v** - Display additional information on the standard output. Note that it's not allowed to be combined with output specified as **file://con**. - **-statsout** - SRT statistics output: filename. Without this option specified, the statistics will be printed to the standard output. - **-pf**, **-statspf** - SRT statistics print format. Values: json, csv, default. After a comma, options can be specified (e.g. "json,pretty"). - **-s**, **-stats**, **-stats-report-frequency** - The frequency of SRT statistics collection, based on the number of packets. - **-loglevel** - lowest logging level for SRT, one of: *fatal, error, warn, note, debug* (default: *warn*) - **-logfa, -lfa** - selected FAs in SRT to be logged (default: all are enabled). See the list of FAs running `-help:logging`. - **-logfile:logs.txt** - Output of logs is written to file logs.txt instead of being printed to `stderr`. - **-help, -h** - Show help. - **-version** - Show version info. ## Testing Considerations Before starting any test with `srt-live-transmit` please make sure your video source works properly. For example: if you use VLC as a test player, send a UDP stream directly to it before routing it through `srt-live-transmit`. For any MPEG-TS UDP based source make sure it has packet sizes of 1316 bytes. When using `ffmpeg` like in the "Example for Smoke Testing" section above set the `pkt_size=1316` parameter in case your input is a continuous data stream like from a file, camera or data-generator. When leaving the LAN for testing, please keep an eye on statistics and make sure your round-trip-time (RTT) is not drifting. It's recommended to set the latency 3 to 4 times higher than RTT. Especially on wireless links such as WLAN, Line-of-Sight Radio (LOS) and mobile links such as LTE/4G or 5G the RTT can vary a lot. If you perform tests on the public Internet, consider checking your firewall rules. The **SRT listener** must be reachable on the chosen UDP port. Same applies to routers using NAT. Please set a port forwarding rule with protocol UDP to the local IP address of the **SRT listener**. The initiation of an SRT connection (handshake) is decoupled from the stream direction. The sender of a stream can be an **SRT listener** or an **SRT caller**, as long as the receiving end uses the opposite connection mode. Typically you use the **SRT listener** on the receiving end, since it is easier to configure in terms of firewall/router setup. It also makes sense to leave the Sender in listener mode when trying to connect from various end points with possibly unknown IP addresses. ### UDP Performance Performance issues concerning reading from UDP medium were reported in [#933](https://github.com/Haivision/srt/issues/933) and [#762](https://github.com/Haivision/srt/issues/762). The dedicated research showed that at high and bursty data rates (~60 Mbps) the `epoll_wait(udp_socket)` is not fast enough to signal about the possibility of reading from a socket. It results in losing data when the input bitrate is very high (above 20 Mbps). PR [#1152](https://github.com/Haivision/srt/pull/1152) (v1.5.0 and above) adds the possibility of setting the buffer size of the UDP socket in `srt-live-transmit`. Having a bigger buffer of UDP socket to store incoming data, `srt-live-transmit` handles higher bitrates. The following steps have to be performed to use the bigger UDP buffer size. ### Increase the system-default max rcv buffer size ```bash $ cat /proc/sys/net/core/rmem_max 212992 $ sudo sysctl -w net.core.rmem_max=26214400 net.core.rmem_max = 26214400 $ cat /proc/sys/net/core/rmem_max 26214400 ``` ### Specify the size of the UDP socket buffer via the URI Example URI: ```bash "udp://:4200?rcvbuf=67108864" ``` Example full URI: ```bash ./srt-live-transmit "udp://:4200?rcvbuf=67108864" srt://192.168.0.10:4200 -v ``` srt-1.4.4/docs/apps/srt-multiplex.md000066400000000000000000000114321412557703600174110ustar00rootroot00000000000000## srt-multiplex **srt-multiplex** (formerly called "SIPLEX") is a sample program that can send multiple streams in one direction. This tool demonstrates two SRT features: - the ability to use a single UDP link (a source and destination pair specified by IP address and port) for multiple SRT connections - the use of the `streamid` socket option to identify multiplexed resources NOTE: due to changes in the common code that can't be properly handled in the current form of srt-multiplex, this application is temporarily blocked. Instead the `srt-test-multiplex` application was added with the same functionality, although it's recommended for testing purposes only. #### Usage `srt-multiplex -i [id] [id]...` - Multiplexes data from one or more input URIs to transmit as an SRT stream. The application reads multiple streams (INPUT URIs), each using a separate SRT connection. All of the traffic from these connections is sent through the same UDP link. `srt-multiplex -o [id] [id]...` - Transmits data from a multiplexed SRT stream to the specified output URI(s). An `` can be identified as input or output using the **-i** or **-o** options. When `-i` is specified, the URIs provided are used as input, and will be output over the `` socket. The reverse is true for any output URIs specified by `-o`. Separate connections will be created for every specified URI, although all will be using the same UDP link. When SRT is in caller mode, the SRT socket created for transmitting data for a given URI will be set to the `streamid` socket option from this URI's `id` parameter. When SRT is in listener mode, the `streamid` option will already be set on the accepted socket, and will be matched with a URI that has the same value in its `id` parameter. This `streamid` is the SRT socket option (`SRTO_STREAMID` in the API). The idea is that it can be set on a socket used for connecting. When a listener is getting an accepted socket for that connection, the `streamid` socket option can be read from it, with the result that it will be the same as was set on the caller side. So, in caller mode, for every stream media URI (input or output) there will be a separate SRT socket created. This socket will have its `socketid` option set to the value that is given by user as the `id` parameter attached to a particular URI. In listener mode this happens in the opposite direction — the value of the `streamid` option is extracted from the accepted socket, and then matched against all ids specified with the stream media URIs: ``` URI1?id=a --> s1(streamid=a).connect(remhost:2000) URI2?id=b --> s2(streamid=b).connect(remhost:2000) URI3?id=c --> s3(streamid=c).connect(remhost:2000) ``` And on the listener side: ``` (remhost:2000) -> accept --> s(SRT socket) --> in URI array find such that uri.id == s.streamid ``` #### Examples - **Sender:** - `srt-multiplex srt://remhost:2000 -i udp://:5000?id=low udp://:6000?id=high` - **Receiver:** - `srt-multiplex srt://:2000 -o output-high.ts?id=high output-low.ts?id=low` In this example a Sender is created which will connect to `remhost` port 2000 using multiple SRT sockets, all of which will be using the same outgoing port. Here the outgoing port is automatically selected when connecting. Subsequent sockets will reuse that port. Alternatively you can enforce the outgoing port using the `port` parameter with the ``. - **Sender:** - `srt-multiplex srt://remhost:2000?port=5555 ...` A separate connection is made for every input resource. An appropriate resource ID will be set to each socket assigned to that resource according to the `id` parameter. ``` +-- --+ | id=1 id=1 | | ------ ------- | | \ -----> / | | id=2 \ ---------------------------- / id=2 | port=5555 -| --------- ( multiplexed UDP stream ) ---------- |- port=2000 | / ---------------------------- \ | | id=3 / \ id=3 | | ------ ------ | +-- --+ ``` When a socket is accepted on the listener side (the Receiver in this example), srt-multiplex will search for the resource ID among the registered resources (input/output URIs) and set an ID that matches the one on the caller side. If the resource is not found, the connection is closed immediately. The srt-multiplex program works the same way for connections initiated by a caller or a listener. srt-1.4.4/docs/apps/srt-tunnel.md000066400000000000000000000022401412557703600166700ustar00rootroot00000000000000SRT Tunnel ========== Purpose ------- SRT Tunnel is a typical tunnelling application, that is, it simply passes the transmission from a given endpoint to another endpoint in both directions. Tunnels can be also "chained", that is, there can be more than one tunnel on the way between the real peers. This tunnel application can use both TCP and SRT as endpoint type and the typically predicted use case is to hand over the transmission for SRT for a longer distance, leaving TCP close to the caller and listener locations: ``` --> SRT> --> ... .... (long distance) .... --> TCP> --> ``` Usage ----- The `srt-tunnel` command line accepts two argument, beside the options: * Listener: the URI at which this tunnel should await connections * Caller: where this tunnel should connect when its Listener connected Options: * -ll, -loglevel: logging level, default:error * -lf, -logfa: logging Functional Area enabled * -c, -chunk: piece of data amount read at once, default=4096 bytes * -v, -verbose: display transmission details * -s, -skipflush: exit without waiting for data to complete srt-1.4.4/docs/build/000077500000000000000000000000001412557703600143715ustar00rootroot00000000000000srt-1.4.4/docs/build/build-android.md000066400000000000000000000015741412557703600174370ustar00rootroot00000000000000# Building SRT for Android **NOTE:** The scripts have been moved to [scripts/build-android](../../scripts/build-android/) folder. ## Install the NDK and CMake The Android NDK is required to build native modules for Android. [Install and configure the NDK](https://developer.android.com/studio/projects/install-ndk) Consider installing the latest version of cmake. The higher version of cmake the better. As of writing the current version of CMake is 3.18.4 You can download Cmake from the following website: [https://cmake.org/download](https://cmake.org/download/) ## Build SRT for Android Run ```./build-android -n /path/to/ndk```. E.g. ```./build-android -n /home/username/Android/Sdk/ndk/21.4.7075529``` [Include prebuilt native libraries](https://developer.android.com/studio/projects/gradle-external-native-builds#jniLibs) from ```prebuilt``` folder into Android Studio project. srt-1.4.4/docs/build/build-iOS.md000066400000000000000000000051641412557703600165100ustar00rootroot00000000000000# Building SRT for iOS ## Prerequisites * Xcode should be installed. Check in terminal whether `xcode-select -p` points to **/Applications/Xcode.app/Contents/Developer** * Install Homebrew according to instructions on [https://brew.sh/](https://brew.sh/) * Install CMake and pkg-config with Homebrew: ``` brew install cmake brew install pkg-config ``` ## Building OpenSSL There is [OpenSSL for iPhone](https://github.com/x2on/OpenSSL-for-iPhone) project which have all necessary to build OpenSSL for our needs. It fetches OpenSSL code by itself, so you don't need to download it separately. So simply clone it and build with command: ``` ./build-libssl.sh --archs="arm64" ``` Results (both libraries and headers) will be placed in bin/<SDK_VERSION>-<ARCH>.sdk directory (for example, *bin/iPhoneOS11.2-arm64.sdk*). We assume you set **IOS_OPENSSL** variable to this path (e.g. `export IOS_OPENSSL="/Users/johndoe/sources/OpenSSL-for-iPhone/bin/iPhoneOS11.2-arm64.sdk"`). ## Building SRT code Now you can build SRT providing path to OpenSSL library and toolchain file for iOS ``` ./configure --cmake-prefix-path=$IOS_OPENSSL --use-openssl-pc=OFF --cmake-toolchain-file=scripts/iOS.cmake make ``` Optionally you may add following iOS-specifc settings to configure: * `--ios-disable-bitcode=1` - disable embedding bitcode to library. * `--ios-arch=armv7|armv7s|arm64` - specify if you want to build for specific architecture (arm64 by default) * `--ios-platform=OS|SIMULATOR|SIMULATOR64` - specify for build simulator code * `--cmake-ios-developer-root=<path>`- specify path for platform directory; {XCODE_ROOT}/Platforms/iPhoneOS.platform/Developer by default * `--cmake-ios-sdk-root=` - by default searches for latest SDK version within {CMAKE_IOS_DEVELOPER_ROOT}/SDKs, set if you want to use another SDK version Note that resulting .dylib file has install path @executable_path/Frameworks/libsrt.1.dylib, so if you need to place it in some other place with your application, you may change it with *install_name_tool* command: ``install_name_tool -id "" ``, for example ``install_name_tool -id "@executable_path/Frameworks/libsrt.1.3.0.dylib" libsrt.1.3.0.dylib`` ## Adding to Xcode project In Xcode project settings in General tab, add libsrt to **Linked Frameworks and Libraries** section - click Plus sign, then click "Add Other" and find libsrt.1.dylib Click plus sign in **Embedded binaries** section and choose Frameworks/libsrt.1.dylib In **Build settings** tab find **Header Search Paths** setting and add paths to SRT library sources (you should add srt, srt/common and srt/common directories). srt-1.4.4/docs/build/build-options.md000066400000000000000000000357061412557703600175160ustar00rootroot00000000000000# Build System The main build system for SRT is provided by the `cmake` tool, which can be used directly. There's also a wrapper script named `configure` (requires Tcl interpreter) that can make operating with the options easier. ## Portability The `cmake` build system was tested on the following platforms: - Linux (various flavors) - macOS (see [Building SRT for iOS](build-iOS.md)) - Windows with MinGW - Windows with Microsoft Visual Studio (see [Building SRT for Windows](build-win.md)) - Android (see [Building SRT for Android](build-android.md)) - Cygwin (only for testing) The `configure` script wasn't tested on Windows (other than on Cygwin). ## The `configure` Script This script is similar in design to the Autotools' `configure` script, and so usually handles `--long-options`, possibly with values. It handles two kinds of options: * special options to be resolved inside the script and that may do some advanced checks; this should later turn into a set of specific `cmake` variable declarations * options that are directly translated to `cmake` variables. The direct translate option always does a simple transformation: * all letters uppercase * dash into underscore * plus into X * when no value is supplied, it defaults to 1 For example, `--enable-c++11` turns into `-DENABLE_CXX11=1` when passed to `cmake`. Additionally, if you specify `--disable-`, the `configure` script will automatically turn it into an associated `--enable-` option, passing `0` as its value. For example, `--disable-encryption` will be translated for `cmake` into `-DENABLE_ENCRYPTION=0`. ### Build Options All options below are presented using the `configure` convention. They can all be used in `cmake` with the appropriate format changes. **`--cygwin-use-posix`** (default:OFF) When ON, compile on Cygwin using POSIX API (otherwise it will use MinGW environment). **`--enable-apps`** (default: ON) Enables compiling user applications. **`--enable-code-coverage`** (default: OFF) Enable instrumentation for code coverage. Note that this is only available on platforms with GNU-compatible compiler. **`--enable-c++-deps`** (default: OFF) The `pkg-confg` file (`srt.pc`) will be generated with the `libstdc++` library as a dependency. This may be required in some cases where you have an application written in C which therefore won't link against `libstdc++` by default. **`--enable-c++11`** (default: ON) Enable compiling in C++11 mode for those parts that may require it. Parts that don't require it will still be compiled in C++03 mode, although which parts are affected may change in future. If this option is turned OFF, it affects building a project in two ways: * an alternative C++03 implementation can be used, if available * otherwise the component that requires it will be disabled Parts that currently require C++11 and have no alternative implementation are: * unit tests * user and testing applications (such as `srt-live-transmit`) * some of the example applications It should be possible to compile the SRT library without C++11 support. However, this alternative C++03 implementation may be unsupported on certain platforms. **`--enable-debug=<0,1,2>`** This option allows control through the `CMAKE_BUILD_TYPE` variable: * 0 (default): `Release` (highly optimized, no debug info) * 1: `Debug` (not optimized, full debug info) * 2: `RelWithDebInfo` (highly optimized, but with debug info) Please note that when the value is other than 0, the `--enable-heavy-logging` option is also turned ON by default. **`--enable-encryption`** (default: ON) Encryption feature enabled, which involves dependency on an external encryption library (default: openssl). If you disable encryption, the library will be unable to set encryption options. It will be compatible with a peer that has encryption enabled, but just won't use encryption for the connection. **`--enable-getnameinfo`** (default: OFF) Enables the use of `getnameinfo` using options that allow using reverse DNS to resolve an internal IP address into a readable internet domain name, so that it can be shown nicely in the log file. This option is turned OFF by default because it may have an impact on general performance. It is recommended only for development when testing on a local network. **`--enable-haicrypt-logging`** (default: OFF) Enables logging in the *haicrypt* module, which serves as a connector to an encryption library. Logging here might be seen as unsafe, therefore this option is turned OFF by default. **`--enable-heavy-logging`** (default: OFF in release mode) This option enables the logging instructions in the code, which are considered heavy as they occur often and cover many detailed aspects of library behavior. Turning this option ON will allow you to use the `debug` level of logging and get detailed information as to what happens inside the library. Note, however, that this may influence processing by changing times, using less preferred thread switching layouts, and generally worsen the functionality and performance of the library. For these reasons this option is turned OFF by default. **`--enable-inet-pton`** (default: ON) Enables usage of `inet_pton` function by the applications, which should be used to resolve the network endpoint name into an IP address. This may be not availabe on some version of Windows, in which case you can turn this OFF. When this option is OFF, however, IP addresses cannot be resolved by name, as the `inet_pton` function gets a poor-man's simple replacement that can only resolve numeric IPv4 addresses. **`--enable-logging`** (default: ON) Enables logging. When you turn this option OFF, the library will not report any runtime information through the logging system, including errors. This option may be useful if you suspect the logging system of impairing performance. **`--enable-monotonic-clock`** (default: OFF) This option enforces the use of `clock_gettime` to get the current time, instead of `gettimeofday`. This function forces the use of a monotonic clock that is independent of the currently set time in the system. The condition variables (CV), for which the `*_timedwait()` functions are used with time specification based on the time obtained from `clock_gettime` must be appropriately configured. For now, this is only done for the GarbageCollector controlling CV, not every CV used in SRT. The consequence of enabling this option, however, may be portability issues resulting from the fact that `clock_gettime` function may be unavailable in some SDKs or that an extra `-lrt` option is sometimes required (this requirement will be autodetected). The problem is based on the fact that POSIX functions that use timeout specification (all of `*_timedwait`) expect the absolute time value. A relative timeout value can be then only specified by adding it to the current time, which can be specified as either system or monotonic clock (as configured in the resources used in the operation). However the current time of the monotonic clock can only be obtained by the `clock_gettime` function. **NOTE:** *This is a temporary fix for Issue #729* where the library could get stuck if the system clock is modified during an SRT transmission. This option will be removed when the problem is fixed globally. **`--enable-stdcxx-sync`** (default: OFF) This option enables the standard C++ `thread` and `chrono` libraries (available since C++11) to be used by SRT instead of the `pthreads`. **`--enable-profile`** (default: OFF) Enables code instrumentation for profiling. This is available only for GNU-compatible compilers. **`--enable-relative-libpath`** (default: OFF) Enables adding a relative path to a library. This allows applications to be linked against a shared SRT library by reaching out to a sibling `../lib` directory, provided that the library and applications are installed in POSIX/GNU style directories. This might be useful when installing SRT and applications in a directory, in which the library subdirectory is not explicitly defined among the global library paths. Consider, for example, this application and its required library: * `/opt/srt/bin/srt-live-transmit` * `/opt/srt/lib64/libsrt.so` By using the `--enable-relative-libpath` option, the `srt-live-transmit` application has a relative library path defined inside as `../lib64`. A dynamic linker will find the required `libsrt.so` file by this path: `../lib64/libsrt.so`. This way the dynamic linkage will work even if `/opt/srt/lib64` path isn't added to the system paths in `/etc/ld.so.conf` or in the `LD_LIBRARY_PATH` environment variable. This option is OFF by default because of reports that it may cause problems in case of default installation. **`--enable-shared`** and **`--enable-static`** (default for both: ON) Enables building SRT as a shared and/or static library, as required for your application. In practice, you would only disable one or the other (e.g. by `--disable-shared`). Note that you can't disable both at once. **`--enable-testing`** (default: OFF) Enables compiling of developer testing applications. **`--enable-thread-check`** (default: OFF) Enables `#include `, which implements `THREAD_*` macros" to support better thread debugging. Included to support an existing project. **`--enable-unittests`** (default: OFF) When ON, this option enables unit tests, possibly with the download and installation of the Google test library in the build directory. The tests will be run as part of the build process. This is intended for developers only. **`--openssl-crypto-library=`** Configure the path to an OpenSSL Crypto library. **`--openssl-include-dir=`** Configure the path to include files for an OpenSSL library. **`--openssl-ssl-library=`** Configure the path to an OpenSSL SSL library. **`--pkg-config-executable=`** Configure the path to the `pkg-config` tool. **`--prefix=`** This is an alias to the `--cmake-install-prefix` variable that establishes the root directory for installation, inside of which a GNU/POSIX compatible directory layout will be used. As on all known build systems, this defaults to `/usr/local` on GNU/POSIX compatible systems, with lower level GNU/POSIX directories created inside: `/usr/local/bin`,`/usr/local/lib`, etc. **`--pthread-include-dir=`** Configure the path to include files for a pthread library. Note that this is useful only on Windows. On Linux and macOS this path should be available in the system. **`--pthread-library=`** Configure the path to a pthread library. **`--use-busy-waiting`** (default: OFF) Enable more accurate sending times at the cost of potentially higher CPU load. This option will cause more empty loop running, which may cause more CPU usage. Keep in mind, however, that when processing high bitrate streams the share of empty loop runs will decrease as the bitrate increases. This way higher CPU usage would still be productive, while without system-supported waiting this option may increase the likelihood of switching to the right thread at the time when it is expected to be revived. **`--use-c++-std=`** Enforce using particular C++ standard when compiling. When using this option remember that: * Allowed values are: 98, 03, 11, 14, 17 and 20 * If you use 98/03 and `--enable-apps`, apps will be still using C++11 * This option is only supported on GNU and Clang compilers (will be ignored on others) **`--use-gnustl`** Use `pkg-config` with the `gnustl` package name to extract the header and library path for the C++ standard library (instead of using the compiler built-in one). **`--use-enclib=`** Encryption library to be used. Possible options for ``: * openssl (default) * gnutls (with nettle) * mbedtls **`--use-openssl-pc`** (default: ON) Use `pkg-config` to find OpenSSL libraries. You can turn this OFF to force `cmake` to find OpenSSL by its own preferred method. **`--use-static-libstdc++`** (default: OFF) Enforces linking the SRT library against the static libstdc++ library. This may be useful if you are using SRT library in an environment where it would by default link against the wrong version of the C++ standard library, or when the library in the version used by the compiler is not available as shared. **`--with-compiler-prefix=`** Sets C/C++ toolchains as `` and ``. This option will override the default compiler autodetected by `cmake`. It is handled inside `cmake`. It sets the variables `CMAKE_C_COMPILER` and `CMAKE_CXX_COMPILER`. The values for the above `` and `` are controlled by the `--with-compiler-type` option. When this option is not supplied, a system-default compiler will be used, that is: * On Mac OS (Darwin): clang * On other POSIX systems: gcc * On other systems: obtained from `CMAKE_C_COMPILER` variable Instead of `--with-compiler-prefix` you can use `--cmake-c-compiler` and `--cmake-c++-compiler` options. This can be thought of as a shortcut, useful when you have a long path to the compiler command. NOTE: The prefix is meant to simply precede the compiler type as pure prefix, so if your prefix is a full path to the compiler, it must include the terminal path separator character, as this can be also used as a prefix for a platform-specific cross compiler. For example, if the path to the C compiler is: `/opt/arm-tc/bin/arm-linux-gnu-gcc-7.4`, then you should specify `--with-compiler-prefix=/opt/arm-tc/bin/arm-linux-gnu-` and `--with-compiler-type=gcc-7.4`. **`--with-compiler-type=`** Sets the compiler type to be used as `` and `` respectively: * gcc (default): gcc and g++ * cc: cc and c++ * others: use `` as C compiler and `++` as C++ compiler This should be the exact command used as a C compiler, possibly with version suffix, e.g. `clang-1.7.0`. If this option is used together with `--with-compiler-prefix`, its prefix will be added in front. **`--with-srt-name=`** Overrides srt library name adding custom `` **`--with-extralibs=`** This is an option required for unusual situations when a platform-specific workaround is needed and some extra libraries must be passed explicitly for linkage. The argument is a space-separated list of linker options or library names. There are some known sitautions where it may be necessary: 1. Some older Linux systems do not ship `clock_gettime` functions by default in their libc, and need an extra librt. If you are using POSIX monotonic clocks (see `--enable-monotonic-clock`), this might be required to add `-lrd` through this option. Although this situation is tried to be autodetected and this option added automatically, it might sometimes fail. 2. On some systems (found so far on OpenSuSE), if you use C++11 sync (see `--enable-stdc++-sync`), the gcc compiler relies on gthreads, which relies on pthreads, and happens to define inline source functions in the header that refers to `pthread_create`, the compiler however doesn't link against pthreads by default. To work this around, add `-pthreads` using this option. srt-1.4.4/docs/build/build-win.md000066400000000000000000000261311412557703600166100ustar00rootroot00000000000000# Building SRT for Windows - [1. Prerequisites](#1-prerequisites) - [1.1. Build Tool Dependencies](#11-build-tool-dependencies) - [1.2. External Library Dependencies](#12-external-library-dependencies) - [1.2.1. Cryptographic Library](#121-Cryptographic-library) - [1.2.2. Threading Library](#122-threading-library) - [1.3. Package Managers](#13-package-managers) - [1.3.1. VCpkg Packet Manager (optional)](#131-vcpkg-packet-manager-optional) - [1.3.2. NuGet Manager (optional)](#132-nuget-manager-optional) - [2. Preparing Dependencies](#2-preparing-dependencies) - [2.1. Cryptographic Library](#21-Cryptographic-library) - [2.1.1. Install OpenSSL](#211-install-openssl) - [2.1.1.1. Using vcpkg](#2111-using-vcpkg) - [2.1.1.2. Using Installer](#2112-using-installer) - [2.1.1.3. Build from Sources](#2113-build-from-sources) - [2.1.2. Install MbedTLS](#212-install-mbedtls) - [2.1.3. Install LibreSSL](#213-install-libressl) - [2.2. Threading Library](#22-threading-library) - [2.2.1. Using C++11 Threading](#221-using-c11-threading) - [2.2.2. Building PThreads](#222-building-pthreads) - [2.2.2.1. Using vcpkg](#2221-using-vcpkg) - [2.2.2.2. Using NuGet](#2222-using-nuget) - [2.2.2.3. Build pthreads4w from Sources](#2114-build-pthreads4w-from-sources) - [2.2.2.4. Build pthread-win32 from Sources](#2114-build-pthread-win32-from-sources) - [3. Building SRT](#3-building-srt) - [3.1. Cloning the Source Code](#31-cloning-the-source-code) - [3.2. Generate Build Files](#32-generating-build-files) - [3.3. Build SRT](#33-build-srt) ## 1. Prerequisites ### 1.1. Build Tool Dependencies The following are the recommended prerequisites to build `srt` on Windows. - [CMake](http://www.cmake.org/) v2.8.12 or higher (cross-platform family of tools to build software) - [git](https://git-scm.com/about) client (source code management tool) - [Visual Studio](https://visualstudio.microsoft.com/vs/downloads/) (compiler and linker) ### 1.2. External Library Dependencies #### 1.2.1. Cryptographic Library SRT has an external dependency on **cryptographic library**. This dependency can be disabled by `-DENABLE_ENCRYPTION=OFF` CMake build option. With disabled encryption SRT will be unable to establish secure connections, only unencrypted mode can be used. With the enabled SRT encryption, one of the following Crypto libraries is required: - `OpenSSL` (**default**) - `LibreSSL` - `MbedTLS` #### 1.2.2. Threading Library SRT as of v1.4.2 supports two threading libraries: - Standard C++ thread library available in C++11 (**default on Windows**) - `pthreads` (not recommended on Windows) The `pthreads` library is provided out-of-the-box on all POSIX-based systems. On Windows it can be provided as a 3rd party library (see below). However the C++ standard thread library is recommended to be used on Windows. ### 1.3. Package Managers #### 1.3.1. VCpkg Packet Manager (optional) Can be used to: - build OpenSSL library (dependency of SRT). - build pthreads library (dependency of SRT). [vcpkg](https://github.com/microsoft/vcpkg) is a C++ library manager for Windows, Linux and MacOS. Consider its [prerequisites](https://github.com/microsoft/vcpkg/blob/master/README.md#quick-start) before proceeding. The `vcpkg` library manager has preconfigured building procedures for `OpenSSL` and `pthreads` libraries. They can be easily built and further used as dependencies of SRT library. **Note!** `vcpkg` does not support `LibreSSL` or `MbedTLS` libraries. Clone `vcpkg` using git, and build it. ```shell git clone https://github.com/microsoft/vcpkg.git cd vcpkg .\bootstrap-vcpkg.bat ``` The current working directory will further be referenced as `VCPKG_ROOT`. ```shell set VCPKG_ROOT=%cd% ``` #### 1.3.2. NuGet Manager (optional) NuGet package manager can be used to get a prebuilt version of `pthreads` library for Windows. Download [nuget CLI](https://www.nuget.org/downloads) to the desired folder. The directory with NuGet will further be referenced as `NUGET_ROOT`. ```shell set NUGET_ROOT=%cd% ``` ## 2. Preparing Dependencies ### 2.1 Cryptographic Library To build SRT with support for encryption, **one** of the following Crypto libraries is required: - `OpenSSL` (recommended) - `LibreSSL` - `MbedTLS` #### 2.1.1. Install OpenSSL ##### 2.1.1.1. Using vcpkg **Note!** The `vcpkg` working directory is referenced as `VCPKG_ROOT`. Building `openssl` library using **x64** toolset: ```shell cd VCPKG_ROOT vcpkg install openssl --triplet x64-windows ``` The next step is to integrate `vcpkg` with the build system, so that `CMake` can locate `openssl` library. ```shell vcpkg integrate install ``` CMake will be able to find openssl given the following option is provided: ```shell -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\\scripts\\buildsystems\\vcpkg.cmake ``` ##### 2.1.1.2. Using Installer (Windows) The 64-bit OpenSSL package for Windows can be downloaded using the following link: [Win64OpenSSL_Light-1_1_1c](http://slproweb.com/download/Win64OpenSSL_Light-1_1_1c.exe). **Note!** The last letter or version number may change and older versions may become no longer available. In that case find the appropriate installer here: [Win32OpenSSL](http://slproweb.com/products/Win32OpenSSL.html). Download and run the installer. The library is expected to be installed in `C:\Program Files\OpenSSL-Win64`. Add this path to the user's or system's environment variable `PATH`. It's expected to be installed in `C:\OpenSSL-Win64`. Note that this version is most likely compiled for Visual Studio 2013. For other versions please follow instructions in Section [2.1.1.3 Build from Sources](#2113-build-from-sources). ##### 2.1.1.3. Build from Sources Download and compile the sources from the [OpenSSL website](https://github.com/openssl/openssl). The instructions for compiling on Windows can be found here: [link](https://wiki.openssl.org/index.php/Compilation_and_Installation#Windows). **Note!** `ActivePerl` and `nasm` are required to build OpenSSL. #### 2.1.2. Install MbedTLS `MbedTLS` source code can be downloaded from the [website](https://tls.mbed.org/download). `MbedTLS` comes with `cmake` build system support. Use the `CMAKE_INSTALL_PREFIX` variable to specify the directory that will contain the `MbedTLS` headers and libraries. Note that building `MbedTLS` as a DLL is broken in version 2.16.3. You have to link it statically. #### 2.1.3. Install LibreSSL LibreSSL has header files that are compatible with OpenSSL, CMake can use it like OpenSSL with little configuration. The source code and binaries can be downloaded from here: [link](https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/). Since there are no recent Windows builds, the only option is to build a new version from sources. LibreSSL comes with CMake build system support. Use the `CMAKE_INSTALL_PREFIX` variable to specify the directory that will contain the LibreSSL headers and libraries. ### 2.2. Threading Library SRT can use one of these two threading libraries: - C++11 threads (SRT v1.4.2 and above) - recommended, default since SRT v1.4.4; - `pthreads` (not recommended on Windows). #### 2.2.1. Using C++11 Threading To be able to use the standard C++ threading library (available since C++11) specify the CMake option `-DENABLE_STDCXX_SYNC=ON`. This way there will be also no external dependency on the threading library. Otherwise the external PThreads for Windows wrapper library is required. #### 2.2.2. Building PThreads It is not recommended to use `pthreads` port on Windows. Consider using [C++11 instead](#221-using-c11-threading), ##### 2.2.2.1. Using vcpkg **Note!** The `vcpkg` working directory is referenced as `VCPKG_ROOT`. Build the `pthreads` library using the **x64** toolset: ```shell vcpkg install pthreads --triplet x64-windows ``` The next step is to integrate `vcpkg` with the build system, so that `CMake` can locate `pthreads` library. ```shell vcpkg integrate install ``` Now go to the `srt-xtransmit` cloned folder `XTRANSMIT_ROOT` and run `cmake` to generate build configuration files. CMake will be able to find openssl given the following option is provided: ```shell -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\\scripts\\buildsystems\\vcpkg.cmake ``` ##### 2.2.2.2. Using NuGet This step assumes the NuGet is available from the `NUGET_ROOT` folder (refer to [1.3.2. NuGet Manager (optional)](#132-nuget-manager-optional)). Run `nuget` to install `pthreads` to the specified path. In the example below the library will be installed in `C:\pthread-win32`. ```shell nuget install cinegy.pthreads-win64 -version 2.9.1.17 -OutputDirectory C:\pthread-win32 ``` Two CMake options have to be provided on the step [3.2. Generate Build Files](#32-generating-build-files). ```shell -DPTHREAD_INCLUDE_DIR="C:\pthread-win32\cinegy.pthreads-win64.2.9.1.17\sources" -DPTHREAD_LIBRARY="C:\pthread-win32\cinegy.pthreads-win64.2.9.1.17\runtimes\win-x64\native\release\pthread_lib.lib" ``` ##### 2.2.2.3. Build pthreads4w from Sources Download the source code from SourceForge ([link](https://sourceforge.net/projects/pthreads4w/)) and follow the build instructions. ##### 2.2.2.4. Build pthread-win32 from Sources Compile and install `pthread-win32` for Windows from GitHub: [link](https://github.com/GerHobbelt/pthread-win32). 1. Using Visual Studio 2013, open the project file `pthread_lib.2013.vcxproj` 2. Select configuration: `Release` and `x64`. 3. Make sure that the `pthread_lib` project will be built. 4. After building, find the `pthread_lib.lib` file (directory is usually `bin\x64_MSVC2013.Release`). Copy this file to `C:\pthread-win32\lib` (or whatever other location you configured in variables). 5. Copy include files to `C:\pthread-win32\include` (`pthread.h`, `sched.h`, and `semaphore.h` are in the toplevel directory. There are no meaningful subdirs here). Note that `win##` is part of the project name. It will become `win32` or `win64` depending on the selection. ## 3. Building SRT ### 3.1. Cloning the Source Code Retrieve the SRT source code from GitHub using a git client. ```shell git clone --branch https://github.com/haivision/srt.git srt cd srt set SRT_ROOT=%cd% ``` where `--branch ` can be used to define a specific release version of SRT, e.g. `--branch v1.4.1`. **Note!** The current working directory will further be referenced as `SRT_ROOT`. If `--branch ` is omitted, the latest master is cloned. To get a specific release version run `git checkout ` from the `SRT_ROOT` folder. ```shell git checkout v1.4.1 ``` ### 3.2. Generate Build Files ```shell cmake ../ -G"Visual Studio 16 2019" -A x64 -DPTHREAD_INCLUDE_DIR="C:\pthread-win32\cinegy.pthreads-win64.2.9.1.17\sources" -DPTHREAD_LIBRARY="C:\pthread-win32\cinegy.pthreads-win64.2.9.1.17\runtimes\win-x64\native\release\pthread_lib.lib" ``` **Note!** Additional options can be provided at this point. In case vcpkg was used to build pthreads or OpenSSL, provide: - `-DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\\scripts\\buildsystems\\vcpkg.cmake` In case NuGet was used to get pre-built pthreads libray, provide: - `-DPTHREAD_INCLUDE_DIR` - `-DPTHREAD_LIBRARY` ### 3.3. Build SRT ```shell cmake --build . ``` srt-1.4.4/docs/dev/000077500000000000000000000000001412557703600140505ustar00rootroot00000000000000srt-1.4.4/docs/dev/developers-guide.md000066400000000000000000000274621412557703600176500ustar00rootroot00000000000000# SRT Developer's Guide * [Development Setup](#development-setup) * [Installing Dependencies](#installing-dependencies) * [Forking SRT on GitHub](#forking-srt-on-github) * [Building SRT](#building-srt) * [Project Structure](#project-structure) * [Coding Rules](#coding-rules) * [Submitting an Issue](#submitting-an-issue) * [Submitting a Pull Request](#submitting-a-pull-request) * [Commit Message Format](#commit-message-format) * [Generated files](#generated-files) ## Development Setup This section describes how to set up your development environment to build and test SRT, and explains the basic mechanics of using `git` and `cmake`. ### Installing Dependencies Install the following dependencies on your machine: * [Git](http://git-scm.com/): The [GitHub Guide to Set Up Git][git-setup] is a good source of information. * [CMake](http://cmake.org): v2.8.12 or higher is recommended. * [OpenSSL](http://www.openssl.org): v1.1.0 or higher. Alternatively Nettle and mbedTLS can be used. ### Forking SRT on GitHub To contribute code to SRT, you must have a GitHub account so you can push code to your own fork of SRT and open Pull Requests in the [GitHub Repository][github]. To create a GitHub account, follow the instructions [here](https://github.com/signup/free). Afterwards, go ahead and [fork](http://help.github.com/forking) the [main SRT repository][github]. ### Building SRT To build SRT, clone the source code repository and use CMake to generate system-dependent build files: ```shell # Clone your Github repository to 'srt' folder. git clone https://github.com//srt.git srt # Go to the SRT directory. cd srt # Add the main SRT repository as an upstream remote to your repository. git remote add upstream "https://github.com/Haivision/srt.git" # For macOS also export OpenSSL paths with the following commands: # export OPENSSL_ROOT_DIR=$(brew --prefix openssl) # export OPENSSL_LIB_DIR=$(brew --prefix openssl)"/lib" # export OPENSSL_INCLUDE_DIR=$(brew --prefix openssl)"/include" # Create a directory for build artifacts. # Note: To create a directory on windows use `md` command instead of `mkdir`. mkdir _build && cd _build # Generate build files, including unit tests. cmake .. -DENABLE_UNITTESTS=ON # Build SRT. cmake --build ./ ``` **Note.** If you are using Windows, please refer to [Building SRT for Windows](../build/build-win.md) instructions. **Note.** Please see the following document for the build options: [SRT Build Options](../build/build-options.md). To see the full list of make options run `cmake .. -LAH` from the `_build` folder. **Note.** There is an alternative `configure` script provided. It is **NOT** an alternative Autotools build, but a convenience script. It processes the usual format of `--long-options` and calls `cmake` with appropriate options in the end. This script is dependent on "tcl" package. Please see the following document for `configure` usage: [SRT Build Options](../build/build-options.md). The build output is in the `_build` directory. The following applications can be found there. * `srt-live-transmit` - A sample application to transmit a live stream from source medium (UDP/SRT/`stdin`) to the target medium (UDP/SRT/`stdout`). See [Using the `srt-live-transmit` App](../apps/srt-live-transmit.md) for more info. * `srt-file-transmit` - A sample application to transmit files with SRT. * `srt-tunnel` - A sample application to set up an SRT tunnel for TCP traffic. See [Using the `srt-tunnel` App](../apps/srt-tunnel.md) for more info. * `tests-srt` - unit testing application. ## Language standard requirements The following conventions for the language standard are used in this project: 1. The SRT library requires C++03 (also known as C++98) standard. 2. The examples (to be enabled in cmake by `-DENABLE_EXAMPLES=1`) require either C++03 or C89 standard. 3. The following require C++11 standard: * demo applications * testing applications (to be enabled in cmake by `-DENABLE_TESTING=1`) * unit tests (to be enabled in cmake by `-DENABLE_UNITTESTS=1`) Note that C++11 standard will be enforced if you have enabled applications and haven't specified the C++ standard explicitly. When you have an old compiler that does not support C++11 and you want to compile as many parts as possible, the simplest way is to use the following options (in cmake): ``` -DENABLE_APPS=0 -DUSE_CXX_STD=03 -DENABLE_EXAMPLES=1 ``` Note also that there are several other options that, when enabled, may require that the SRT library be compiled using C++11 standard (`-DENABLE_STDCXX_SYNC=1` for example). ## Project Structure The SRT installation has the following folders: * apps - the folder contains [srt-live-transmit](../apps/srt-live-transmit.md), `srt-file-transmit` and [srt-tunnel](../apps/srt-tunnel.md) sample applications. * *common - holds some platform-dependent code. * *docs - contains all the documentation in the GitHub Markdown format. * *examples - example applications (use `-DENABLE_EXAMPLES=ON` CMake build option to include in the build) * *haicrypt - encryption-related code. * *scripts - some scripts including CMake and TCL scripts. * *srtcore - the main source code of the SRT library. * *test - unit tests for the library. * *testing - the folder contains applications used during development: `srt-test-live`, `srt-test-file`, etc. Use `-DENABLE_TESTING=ON` to include in the build. ## Coding Rules TBD. ## Submitting an Issue If you found an issue or have a question, please submit a (GitHub Issue)[github-issues]. Note that questions can also be asked in the SRT Alliance slack channel: [start the conversation](https://slackin-srtalliance.azurewebsites.net/) in the `#general` or `#development` channel on [Slack](https://srtalliance.slack.com). ## Submitting a Pull Request Create a pull request from your fork following the [GitHub Guide][github-guide-prs]. ### Commit Message Format We use a certain format for commit messages to automate the preparation of release notes. Each commit must start with one of the following tags, identifying the main scope of the commit. If your PR contains several distinguishable changes, it is recommended to split them into several commits, using the described commit message format. Please note that it is preferred to merge PRs by rebasing onto the master branch. If a PR contains several commits, they should be in the defined format. If your PR has several commits, and you need to update or change them, please use `git rebase` to save the commits structure. An alternative merging strategy is squash-merging, when all commits of the PR are squashed into a single one. In this case you can update your PR by making additional commits and merging with master. All those secondary commits will be squashed into a single one after merging to master. The format of the commit message is `[] `, where possible commits tags are: * `[core]` - commit changes the core SRT library code, * `[tests]` - commit changes or adds unit tests, * `[build]` - commit is related tp build system, * `[apps]`- commit mainly changes sample applications or application utilities, including testing and example applications, * `[docs]` - commit changes or adds documentation. ## Generated files Please note *before modifying any files* that some of them are generated. This is indicated after the file header, or in any section of a file that needs to be replaced by generated code if related changes are added. The following sections require attention: ### Logging functional areas In addition to levels (Debug, Note, Warn, Error, Fatal) the logging system has functional areas (FA) that allow a developer to selectively turn on only specific types of logs. For example, in this logging instruction: ``` LOGC(cclog.Note, log << "This is a note"); ``` * `LOGC` is the macro, which allows for file and line information pass-through * `cclog` is the logger variable named after the FA, here "cc" means Congestion Control * `Note` is the log level * The expression after the comma is the log text composition expression The FA system allows a developer to enable or disable printing all logs assigned to particular functional area. This allows the developer to selectively turn on only specific areas. This is useful during testing to help minimize the impact of logging on performance or behavior. To add a name designating a new functional area to be used in the logs, modify the `generate-logging-defs.tcl` script. A list of loggers is contained in the `loggers` list at the top of the TCL file. You can insert an addition anywhere in this list, as long as it has these three unique elements: a long name, a short name, and an ID (e.g.` GENERAL g 0`). The TCL file contains a`hidden_loggers` list with additional definitions that do not always need to be added to particular generated files. Alternative declarations for items in this list are provided in a different way. To add or rename one of the logger definitions: * Modify appropriately the `loggers` list * Run the script to generate the files * Note that you need to press Enter to confirm overwrite Note that the script can have arguments, which is the list of files that have to be generated (must be identical with the keys used in `generation` dictionary). By default it regenerates all required files. Note also that `srt.h` is exceptionally a file that is being only modified, that is, the current contents of the file is preserved, and only the part replaced. The script contains also all definitions for file generation in the following variables: * `special`: contains a code that should be executed for particular target * `generation`: the definition of the file contents to be generated Both these are dictionaries, whose keys are "targets". Target names are names of the generated files. If the name is an explicit path (contains at least one path separator), it's the relative path towards the repository top directory, otherwise the file will be generated in the current directory. The `generation` dictionary should contain complete definition of all files to be generated. Every entry is an array containing 3 elements: * format model (can be empty, if the generated file consists only of the list of entries) * entry format for `loggers` * entry format for `hidden_loggers` (optional) The 'format model' uses two variables: `globalheader`, which is the obligatory header for all source files, and `entries` in a place where the list of functional area (FA) entries is expected to be placed. The entry formats should utilize the variable names as defined in the `model` variable in the beginning, as it sees fit. Currently generated files are: * `srtcore/logger_default.cpp`: contains setting of all FA as enabled * `srtcore/logger_defs.h` and `srtcore/logger_defs.cpp`: declares/defiones logger objects * `apps/logsupport_appdefs.cpp`: Provides string-to-symbol bindings for the applications ### Build options If you modify the `CMakeLists.txt` file and add some build options to it, remember to generate this list of options and update appropriately the `configure-data.tcl` file. This file should be run in the build directory and passed the `CMakeCache.txt` file as argument. The list of options will be printed on the standard output, and it should be the content of the `cmake_options` variable defined in `configure-data.tcl` file. Note that this does not mean that the contents should be blindly pasted into the options list. Apply only the new options that you have added. The script does its best to make sure that no option is missing. Note that some options might be provided by an external dependent script (like `build-gmock`) and therefore mistakenly added to the list. [git-setup]: https://help.github.com/articles/set-up-git [github-issues]: https://github.com/Haivision/srt/issues [github-guide-prs]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork [github]: https://github.com/Haivision/srt srt-1.4.4/docs/dev/low-level-info.md000066400000000000000000000174401412557703600172370ustar00rootroot00000000000000# Low Level Information for the SRT project ## Introduction This document contains information on various topics related to the SRT source code, including descriptions of some cross-source analysis that would not be obvious for a source code reviewer. It's not a complete documentation of anything, rather a collection of various kind of information retrieved during development and even reverse engineering. ## Mutex locking This analysis is a result of detected lots of cascade mutex locking in the SRT code. A more detailed analysis would be required as to which mutex is going to protect what kind of data later. Here is the info collected so far: ### Data structures The overall structure of the object database, involving sockets and groups is as follows (pseudo-language): ``` CUDTUnited (singleton) { CONTAINER m_Sockets; CONTAINER m_ClosedSockets; CONTAINER m_Groups; CONTAINER m_ClosedGroups; } CUDTGroup { type SocketData { CUDTSocket* ps; SRTSOCKET id; int state; ... } CONTAINER m_Group; } ``` Dead sockets (either closed manually or broken after connection) are moved first from `m_Sockets` to `m_ClosedSockets`. The GC thread will take care to delete them physically after making sure all inside facilities do not contain any remaining data of interest. Groups may only be manually closed, however a closed group is moved to `m_ClosedGroups`. The GC thread will take care to delete them, as long as their usage counter is 0. Every call to an API function (as well as TSBPD thread) increases the usage counter in the beginning and decreases upon exit. A group may be closed in one thread and still being used in another. The group will persist up to the time when the current API function using it exits and decreases the usage counter back to 0. Containers and contents guarded by mutex: `CUDTUnited::m_GlobControlLock` - guards all containers in CUDTUnited. `CUDTSocket::m_ControlLock` - guards internal operation performed on particular socket, with its existence assumed (this is because a socket will always exist until it's deleted while being in `m_ClosedSockets`, and when the socket is in `m_ClosedSockets` it will not be deleted until it's free from any operation, while the socket is assumed nonexistent for any newly called API function even if it exists physically, but is moved to `m_ClosedSockets`). `CUDTGroup::m_GroupLock` - guards the `m_Group` container inside a group that collects member sockets. There are unfortunately many situations when multiple locks have to be applied at a time. This is then the hierarchy of the mutexes that must be preserved everywhere in the code. As mutexes cannot be really ordered unanimously, below are two trees, with also some possible branches inside. The mutex marked with (T) is terminal, that is, no other locks shall be allowed in the section where this mutex is locked. ### Mutex ordering information Note that the list isn't exactly complete, but it should contain all mutexes for which the locking order must be preserved. ``` - CUDTSocket::m_ControlLock - CUDT::m_ConnectionLock - CRendezvousQueue::m_RIDVectorLock - CUDTUnited::m_GlobControlLock - CUDTGroup::m_GroupLock - CUDT::m_RecvAckLock || CEPoll::m_EPollLock(T) ---------------- - CUDTUnited::m_GlobControlLock - CUDTGroup::m_GroupLock || CSndUList::m_ListLock(T) - CUDT::m_ConnectionLock - CRendezvousQueue::m_RIDVectorLock - CUDT::m_SendLock - CUDT::m_RecvLock - CUDT::m_RecvBufferLock - CUDT::m_RecvAckLock || CUDT::m_SendBlockLock ------------------ ANALYSIS ON: m_ConnectionLock -- CUDT::startConnect flow CUDTUnited::connectIn -- > [LOCKED s->m_ControlLock] CUDT::open -- > [MAYBE_LOCKED m_ConnectionLock, if bind() not called] CUDT::clearData --> [LOCKED m_StatsLock] CUDTUnited::updateMux -- > [LOCKED m_GlobControlLock] { [SCOPE UNLOCK s->m_ControlLock, if blocking mode] CUDT::startConnect -- > [LOCKED m_ConnectionLock] CRcvQueue::registerConnector CRendezvousQueue::insert --> [LOCKED CRendezvousQueue::m_RIDVectorLock] } END. -- CUDT::groupConnect flow CUDT::groupConnect (no locks) CUDT::setOpt [LOCKS m_ConnectionLock, m_SendLock, m_RecvLock] { [LOCKS m_GlobControlLock] CUDTGroup::add [LOCKS m_GroupLock] } CUDT::connectIn --> continue with startConnect flow -- CUDTUnited::listen (API function) CUDTUnited::listen CUDTUnited::locateSocket [LOCKS m_GlobControlLock] { [SCOPE LOCK s->m_ControlLock] CUDT::setListenState -- > [LOCKED m_ConnectionLock] CRcvQueue::setListener -- > [LOCKED m_LSLock] } -- CUDT::processAsyncConnectRequest CRcvQueue::worker -> ... CRcvQueue::worker_TryAsyncRend_OrStore CUDT::processAsyncConnectResponse -- > [LOCKED m_ConnectionLock] CUDT::processConnectResponse CUDT::postConnect CUDT::interpretSrtHandshake -> [IF group extension found] CUDT::interpretGroup { [SCOPE LOCK m_GlobControlLock] [IF Responder] { CUDT::makeMePeerOf [LOCKS m_GroupLock] CUDTGroup::syncWithSocket CUDTGroup::find --> [LOCKED m_GroupLock] } debugGroup -- > [LOCKED m_GroupLock] } -- CUDT::acceptAndRespond CRcvQueue::worker_ProcessConnectionRequest { [SCOPE LOCK m_LSLock] CUDT::processConnectRequest CUDTUnited::newConnection locateSocket -- > [LOCKED m_GlobControlLock] locatePeer -- > [LOCKED m_GlobControlLock] [IF failure, LOCK m_AcceptLock] generateSocketID --> [LOCKED m_IDLock] CUDT::open -- > [LOCKED m_ConnectionLock] CUDT::updateListenerMux -- > [LOCKED m_GlobControlLock] CUDT::acceptAndRespond --> [LOCKED m_ConnectionLock] CUDT::interpretSrtHandshake -> [IF group extension found] CUDT::interpretGroup { [SCOPE LOCK m_GlobControlLock] [IF Responder] { CUDT::makeMePeerOf [LOCKS m_GroupLock] CUDTGroup::syncWithSocket CUDTGroup::find --> [LOCKED m_GroupLock] } debugGroup -- > [LOCKED m_GroupLock] } { [SCOPE LOCK m_GlobControlLock] CUDT::synchronizeWithGroup -- > [LOCKED m_GroupLock] } CRcvQueue::setNewEntry -- > [LOCKED CRcvQueue::m_IDLock] { [SCOPE LOCK m_GlobControlLock] { [SCOPE LOCK m_GroupLock] } } { [SCOPE LOCK m_AcceptLock] CEPoll::update_events } [IF Rollback] CUDT::closeInternal [LOCKING m_EPollLock] { [SCOPE LOCK m_ConnectionLock] [SCOPE LOCK m_SendLock] [SCOPE LOCK m_RecvLock] [LOCKING m_RcvBufferLock] } { [SCOPE LOCK m_GlobControlLock] CUDT::removeFromGroup --> [LOCKED m_GroupLock] } CEPoll::update_events } -- CUDT::bstats: TRT-LOCKED m_ConnectionLock -- CUDT::packData CSndQueue::worker CSndUList::pop -- > [LOCKED m_ListLock] CUDT::packData ``` srt-1.4.4/docs/dev/making-srt-better.md000066400000000000000000000136031412557703600177340ustar00rootroot00000000000000Making SRT Better ================= SRT is a library that deals with networks, which often behave in unpredictable ways. SRT tries to do its best to deal with the resulting problems, but like any other software of this kind, it isn't perfect. In many cases, "best effort" is all you can count on. That being said, it can always be made better. And so we warmly welcome everyone who can contribute improvements to SRT. We encourage you to read the following guidelines, which are based on the experiences of previous contributors and are intended to make it easier for you to debug and report problems in a way that benefits the entire SRT community. Problem Reporting Guidelines ============================ 1. We treat every problem report very seriously and will be doing our best to resolve them, but we need something to start the research with. When you report a problem, providing a description of the behavior and maybe error logs is a good start. But sometimes this isn't enough. If you can, try to replicate the behavior, and attach the debug log(s) and any pcap file(s) to your report. 2. Sometimes problems result from a network that doesn't satisfy the minimum requirements for SRT. For example, the available bandwidth might not be enough to bear the traffic you are trying to send through it, or the latency might not be high enough to compensate for the network's maximum non-spiked RTT. We need to sort this kind of problem out first. 3. A thorough description of your environment is very important. We will be trying to recreate it in our lab in order to be able to test your case ourselves. Note that in many situations this may not be possible. There may be some peculiarities in your environment or network configuration that you may not even be aware of. If you are using tc and netem for traffic shaping, there may be some distinct settings in the network that will make it impossible for us to see what you are seeing. This is another case where you can help us by providing debug logs and the pcap files. 4. If you ever see the `IPE` (Internal Program Error) keyword in the error logs, please try to report that as a top priority (just check if it wasn't reported already). This reports the execution path that shall never be taken. 5. *Do not hesitate to report any unexpected behavior*, even if you feel the information is incomplete. We have some tricks up our sleeves, as do other project members, that may help us fill in the blanks. And sometimes, Lady Luck is also on our side! Debug Logs ========== The debug logs that can be generated with SRT provide very detailed descriptions of its internal behaviour. In fact, they can sometimes approach the equivalent of "record and replay" for a testing session. Having the debug logs collected is in most cases essential to start researching a potential problem. This is because, as SRT is very highly time-based software, the usability of a debugger is very limited. Additionally the debug logs allow the developers to research a problem that they cannot reproduce. Keep in mind, though, that debug logs put a great burden on the performance, and for this reason have been shifted to the "heavy logging" category, which is not enabled by default, neither in the library itself, nor at compile time. You can only manually enable them at compile time: ./configure [...] --enable-heavy-logging or directly in `cmake`: cmake [...] -DENABLE_HEAVY_LOGGING=1 Note that in the *Debug mode* (`--enable-debug`) heavy logging is enabled by default. Keep in mind that enabling *Debug mode* creates a less optimized version of SRT, more suitable for the debugger. Enabling heavy logging at compile time is required, but the debug logging level must be also set at runtime. For the `srt-live-transmit` application use the following option: -loglevel:debug If you are using any other application that uses SRT as a library, follow the description in that application; in the worst case, if no description is available, remember that the SRT API call to set the debug log level is: srt_setloglevel(LOG_DEBUG); (The `LOG_DEBUG` symbol is defined in the `` include file on POSIX-based systems, and there is a drop-in replacement for it for Windows in `common/win/syslog_defs.h`.) Some applications may use an extended C++ API (this is not really recommended): UDT::setloglevel(logging::Loglevel::debug); When running an application with debug logs, please remember that they will put a burden on the program's performance. Always stream the log into a file; it may be necessary in some cases to send it over the network to another machine for collection, if the filesystem is so slow that the performance burden changes the rules. It has been observed on several platform types that the burden may make the application unusable. Turning on the logs may prevent the problem you are trying to debug from occurring ("heisenbug") or decrease its probability ("schroedingbug"). `pcap` Files ============ Recording a pcap file may be very useful in researching an issue with SRT. For tracing a pcap, you need to have administrator privileges on the machine where you are running it, and you need to record it on the machine on which the SRT application instance is using a predictable port number, that is: - With a **Rendezvous** connection, on any of the machines - On the **Listener** machine, where you use the listening port - On the **Caller** machine, if you explicitly set the Caller's outgoing port - To set the Caller's outgoing port explicitly, use the `port=` parameter in the SRT URI. To record the PCAP file on POSIX-based systems, use the following command (replacing `eth0` with your device name and `9000` with the connection port): [sudo] tcpdump -i eth0 port 9000 -w test.pcap On Windows there's a similar solution, the Windump application. --- *Thanks for helping us make SRT the best it can possibly be!* :sunglasses:   **The SRT Project Moderators** srt-1.4.4/docs/features/000077500000000000000000000000001412557703600151105ustar00rootroot00000000000000srt-1.4.4/docs/features/access-control.md000066400000000000000000000451131412557703600203550ustar00rootroot00000000000000# SRT Access Control (Stream ID) Guidelines ## Motivation One type of information that can be interchanged when a connection is being established in SRT is "Stream ID", which can be used in a caller-listener connection layout. This is a string of maximum 512 characters set on the caller side. It can be retrieved at the listener side on the newly accepted socket through a socket option (see `SRTO_STREAMID` in [SRT API Socket Options](../API/API-socket-options.md)). As of SRT version 1.3.3 a callback can be registered on the listener socket for an application to make decisions on incoming caller connections. This callback, among others, is provided with the value of Stream ID from the incoming connection. Based on this information, the application can accept or reject the connection, select the desired data stream, or set an appropriate passphrase for the connection. ## Purpose The Stream ID value can be used as free-form, but there is a recommended convention so that all SRT users speak the same language. The intent of the convention is to: - promote readability and consistency among free-form names - interpret some typical data in the key-value style In short, 1. `SRTO_STREAMID` is designed for a caller (client) to be able to identify itself, and state what it wants. 2. `srt_listen_callback(...)` function is used by a listener (server) to check what a caller (client) has provided in `SRTO_STREAMID` **before** the connection is established. For example, the listener (server) can check if it knows the user and set the corresponding passphrase for a connection to be accepted. 3. Even if `srt_listen_callback(...)` accepts the connection, SRT will still have one more step to check the PASSPHRASE, and reject on mismatch. If a correct passphrase is not provided by the client (caller), the request from caller will be rejected by SRT library (not application or programmer). **Note!** `srt_listen_callback(...)` can't check the passphrase directly for security reasons. The only way to make the app check the passphrase is to set the passphrase on the socket by using the `SRTO_PASSPHRASE` option. This lets SRT to reject connection on mismatch. ## Character Encoding The Stream ID uses UTF-8 encoding. ## General Syntax The recommended syntax starts with the characters known as an executable specification in POSIX: `#!`. The next character defines the format used for the following key-value pair syntax. At the moment, there is only one supported syntax identified by `:` and described below. Everything that comes after a syntax identifier is further referenced as the content of the Stream ID. The content starts with a `:` or `{` character identifying its format: - `:` : comma-separated key-value pairs with no nesting, - `{` : a nested block with one or several key-value pairs that must end with a `}` character. Nesting means that multiple level brace-enclosed parts are allowed. The form of the key-value pair is ~~~ key1=value1,key2=value2,... ~~~ ## Standard Keys Beside the general syntax, there are several top-level keys treated as standard keys. All single letter key definitions, including those not listed in this section, are reserved for future use. Users can additionally use custom key definitions with `user_*` or `companyname_*` prefixes, where `user` and `companyname` are to be replaced with an actual user or company name. The existing key values must not be extended, and must not differ from those described in this section. The following keys are standard: - `u`: **User Name**, or authorization name, that is expected to control which password should be used for the connection. The application should interpret it to distinguish which user should be used by the listener party to set up the password. - `r`: **Resource Name** identifies the name of the resource and facilitates selection should the listener party be able to serve multiple resources. - `h`: **Host Name** identifies the hostname of the resource. For example, to request a stream with the URI `somehost.com/videos/querry.php?vid=366` the `hostname` field should have `somehost.com`, and the resource name can have `videos/querry.php?vid=366` or simply `366`. Note that this is still a key to be specified explicitly. Support tools that apply simplifications and URI extraction are expected to insert only the host portion of the URI here. - `s`: **Session ID** is a temporary resource identifier negotiated with the server, used just for verification. This is a one-shot identifier, invalidated after the first use. The expected usage is when details for the resource and authorization are negotiated over a separate connection first, and then the session ID is used here alone. - `t`: **Type** specifies the purpose of the connection. Several standard types are defined, but users may extend the use: - `stream` (default, if not specified): for exchanging the user-specified payload for an application-defined purpose - `file`: for transmitting a file, where `r` is the filename - `auth`: for exchanging sensible data. The `r` value states its purpose. No specific possible values for that are known so far (FUTURE USE] - `m`: **Mode** expected for this connection: - `request` (default): the caller wants to receive the stream - `publish`: the caller wants to send the stream data - `bidirectional`: bidirectional data exchange is expected Note that `m` is not required in the case where you don't use `streamid` to distinguish authorization or resources, and your caller is expected to send the data. This is only for cases where the listener can handle various purposes of the connection and is therefore required to know what the caller is attempting to do. Examples: ```js #!::u=admin,r=bluesbrothers1_hi ``` This specifies the username and the resource name of the stream to be served to the caller. ```js #!::u=johnny,t=file,m=publish,r=results.csv ``` This specifies that the file is expected to be transmitted from the caller to the listener and its name is `results.csv`. ### Rejection Codes The listener callback handler is also able to decide about rejecting the incoming connection. In a normal situation, the rejection code is predefined as `SRT_REJ_RESOURCE`. The handler can, however, set its own rejection code. There are two number spaces intended for this purpose (as the range below `SRT_REJC_PREDEFINED` is reserved for internal codes): - `SRT_REJC_PREDEFINED` and above: predefined errors. Errors from this range (that is, below `SRT_REJC_USERDEFINED`) have their definitions provided in the `access_control.h` public header file. The intention is that applications using these codes understand the situation described by these codes standard way. - `SRT_REJC_USERDEFINED` and above: to be freely defined by the application. Codes from this range can be only understood if each application knows the code definitions of the other. These codes should be used only after making sure that both applications understood them. The intention for the predefined codes is to be consistent with the HTTP standard codes. Therefore the following sub-ranges are used: - 0 - 99: Reserved for unique SRT-specific codes (unused by HTTP) - 100 - 399: Info, Success and Redirection in HTTP, unused in SRT - 400 - 599: Client and server errors in HTTP, adopted by SRT - 600 - 999: unused in SRT Such a code can be set by using the `srt_setrejectreason` function. The SRT-specific codes are: #### SRT_REJX_FALLBACK This code should be set by the callback handler in the beginning in case the application needs to be informed that the callback handler actually has interpreted the incoming connection, but hasn't set a more appropriate code describing the situation. #### SRT_REJX_KEY_NOTSUP Indicates there was a key specified in the StreamID string that this application doesn't support. Note that it's not obligatory for the application to react this way - it may chose to ignore unknown keys completely, or to have some keys in the ignore list (which it won't interpret, but tolerate) while rejecting any others. It is also up to the application to decide to return this specific error, or more generally report the syntax error with `SRT_REJX_BAD_REQUEST`. #### SRT_REJX_FILEPATH The resource type designates a file, and the path either has the wrong syntax or is not found. In the case where `t=file`, the path should be specified under the `r` key, and the file specified there must be able to be saved this way. It's up to the application to decide how to treat this path, how to parse it, and what this path specifically means. For the `r` key, the application should at least handle the single filename, and have storage space available to save it (provided a file of the same name does not already exist there). The application should decide whether and how to handle all other situations (like directory path, special markers in the path to be interpreted by the application, etc.), or to report this error. #### SRT_REJX_HOSTNOTFOUND The host specified in the `h` key cannot be identified. The `h` key is generally for a situation when you have multiple DNS names for a host, so an application may want to extract the name from the URI and set it to `h` key so that the application can distinguish the request also by the target host name. The application may however limit the number of recognized services by host name to some predefined names and not handle the others, even if this is properly resolved by DNS. In this case it should report this error. The other error codes are HTTP codes adopted for SRT: #### SRT_REJX_BAD_REQUEST General syntax error. This can be reported in any case when parsing the StreamID contents failed, or it cannot be properly interpreted. #### SRT_REJX_UNAUTHORIZED Authentication failed, which makes the client unauthorized to access the resource. This error, however, confirms that the syntax is correct and the resource has been properly identified. Note that this cannot be reported when you use a simple user-password authentication method because in this case the password is verified only after the listener callback handler accepts the connection. This error is rather intended to be reported in case of `t=auth` when the authentication process has generated some valid session ID, but then the session connection has specified a resource that is not within the frames of that authentication. #### SRT_REJX_OVERLOAD The server is too heavily loaded to process your request, or you have exceeded credits for accessing the service and the resource. In HTTP the description mentions payment for a service, but it is also used by some services for general "credit" management for a client. In SRT it should be used when your service is doing any kind of credit management to limit access to selected clients that "have" enough credit, even if the credit is something the client can recharge itself, or that can be granted depending on available service resources. #### SRT_REJX_FORBIDDEN Access denied to the resource for any reason. This error is independent of an authorization or authentication error (as reported by `SRT_REJX_UNAUTHORIZED`). The application can decide which is more appropriate. This error is usually intended for a resource that should only be accessed after a successful authorization over a separate auth-only connection, where the query in StreamID has correctly specified the resource identity and mode, but the session ID (in the `s` key) is either (a) not specified, or (b) does specify a valid session, but the authorization region for this session does not embrace the specified resource. #### SRT_REJX_NOTFOUND The resource specified in the `r` key (in combination with the `h` key) is not found at this time. This error should be only reported if the information about resource accessibility is allowed to be publicly visible. Otherwise the application might report authorization errors. #### SRT_REJX_BAD_MODE The mode specified in the `m` key in StreamID is not supported for this request. This may apply to read-only or write-only resources, as well for when interactive (bidirectional) access is not valid for a resource. #### SRT_REJX_UNACCEPTABLE Applies when the parameters specified in SocketID cannot be satisfied for the requested resource, or when `m=publish` but the data format is not acceptable. This is a general error reporting an unsupported format for data that appears to be wrong when sending, or a restriction on the data format (as specified in the details of the resource specification) such that it cannot be provided when receiving. #### SRT_REJX_CONFLICT The resource being accessed (as specified by `r` and `h` keys) is locked for modification. This error should only be reported for `m=publish` when the resource being accessed is read-only because another client (not necessarily connected through SRT): - is currently publishing into this resource - has reserved this resource ID for publishing Note that this error should be reported when there is no other reason for having a problem accessing the resource. #### SRT_REJX_NOTSUP_MEDIA The media type is not supported by the application. The media type is specified in the `t` key. The currently standard types are `stream`, `file` and `auth`. An application may extend this list, and is not obliged to support all of the standard types. #### SRT_REJX_LOCKED The resource being accessed is locked against any access. This is similar to `SRT_REJX_CONFLICT`, but in this case the resource is locked for reading and writing. This is for when the resource should be shown as existing and available to the client, but access is temporarily blocked. #### SRT_REJX_FAILED_DEPEND The dependent entity for the request is not present. In this case the dependent entity is the session, which should be specified in the `s` key. This means that the specified session ID is nonexistent or it has already expired. #### SRT_REJX_ISE Internal server error. This is for a general case when a request has been correctly verified, with no related problems found, but an unexpected error occurs after the processing of the request has started. #### SRT_REJX_UNIMPLEMENTED The request was correctly recognized, but the current software version of the service (be it SRT or any other software component) doesn't support it. This should be reported for a case, when some features to be specified in the StreamID request are supposed to be supported in a predictable future, but the current version of the server does not support it, or the support for this feature in this version has been temporarily blocked. This shouldn't be reported for existing features that are being deprecated, or older features that are no longer supported (for this case the general `SRT_REJX_BAD_REQUEST` is more appropriate). #### SRT_REJX_GW The server acts as a gateway and the target endpoint rejected the connection. The reason the connection was rejected is unspecified. The gateway cannot forward the original rejection code from the target endpoint because this would suggest the error was on the gateway itself. Use this error with some other mechanism to report the original target error, if possible. #### SRT_REJX_DOWN The service is down for maintenance. This can only be reported when the service has been temporarily replaced by a stub that is only reporting this error, while the real service is down for maintenance. #### SRT_REJX_VERSION Application version not supported. This can refer to an application feature that is unsupported (possibly from an older SRT version), or to a feature that is no longer supported because of backward compatibility requirements. #### SRT_REJX_NOROOM The data stream cannot be archived due to a lack of storage space. This is reported when a request to send a file or a live stream to be archived is unsuccessful. Note that the length of a file transmission is usually pre-declared, so this error can be reported early. It can also be reported when the stream is of undefined length, and there is no more storage space available. ## Example An example of Stream ID functionality and the listener callback can be found under `tests/test_listen_callback.cpp`. A listener can register a callback to be called in the middle of accepting a new socket connection: ```c++ srt_listen(server_sock, 5); srt_listen_callback(server_sock, &SrtTestListenCallback, NULL); ``` A callback function has to be implemented by the upstream application. In the example below, the function tries to interpret the Stream ID value first according to the Access Control guidelines and to extract the username from the `u` key. Otherwise it falls back to a free-form specified username. Depending on the user, it sets the appropriate password for the expected connection so that it can be rejected if the password isn't correct. If the user isn't found in the database (`passwd` map) the function itself rejects the connection. Note that this can be done by both returning -1 and by throwing an exception. ```c++ int SrtTestListenCallback(void* opaq, SRTSOCKET ns, int hsversion, const struct sockaddr* peeraddr, const char* streamid) { using namespace std; // opaq is used to pass some further chained callbacks // To reject a connection attempt, return -1. static const map passwd { {"admin", "thelocalmanager"}, {"user", "verylongpassword"} }; // Try the "standard interpretation" with username at key u string username; static const char stdhdr [] = "#!::"; uint32_t* pattern = (uint32_t*)stdhdr; bool found = -1; // Extract a username from the StreamID: if (strlen(streamid) > 4 && *(uint32_t*)streamid == *pattern) { vector items; Split(streamid+4, ',', back_inserter(items)); for (auto& i: items) { vector kv; Split(i, '=', back_inserter(kv)); if (kv.size() == 2 && kv[0] == "u") { username = kv[1]; found = true; } } if (!found) { cerr << "TEST: USER NOT FOUND, returning false.\n"; return -1; } } else { // By default the whole streamid is username username = streamid; } // When the username of the client is known, the passphrase can be set // on the socket being accepted (SRTSOCKET ns). // The remaining part of the SRT handshaking process will check the // passphrase of the client and accept or reject the connection. // When not found, it will throw an exception cerr << "TEST: Accessing user '" << username << "', might throw if not found\n"; string exp_pw = passwd.at(username); cerr << "TEST: Setting password '" << exp_pw << "' as per user '" << username << "'\n"; srt_setsockflag(ns, SRTO_PASSPHRASE, exp_pw.c_str(), exp_pw.size()); return 0; } ``` srt-1.4.4/docs/features/bonding-intro.md000066400000000000000000000227501412557703600202110ustar00rootroot00000000000000# What are groups ? A Group is an entity that binds multiple sockets, and is required to establish a "bonded connection". Groups can be used in the same way as sockets for performing a transmission. A group is connected as long as at least one member-socket connection is alive. As long as a group is in the connected state, some member connections may get broken and new member connections can be established. Groups are fully flexible. There's no limitation how many single connections they can use as well as when you want to establish a new connection. On the other hand, broken connections are not automatically reestablished. The application should track the existing connections and reestablish broken ones if needed. But then, the application is also free to keep as many links as it wants, including adding new links to the group while it is being used for transmission, or removing links from the list if they are not to be further used. How the links are utilized within a group depends on the group type. The simplest type, broadcast, utilizes all links at once to send the same data. To learn more about socket groups and their abilities, please read the [SRT Socket Groups](socket-groups.md) document. # Reminder: Using sockets for establishing a connection Before we begin, let's review first how to establish a connection for a single socket. ## Important changes Keep in mind these important changes to SRT: 1. Specifying family (`AF_INET/AF_INET6`) when creating a socket is no longer required. The existing `srt_socket` function redirects to a new `srt_create_socket` function that takes no arguments. The family is decided at the first call to `srt_bind` or `srt_connect`, and is extracted from the value of the `sa_family` field of the `sockaddr` structure passed to this call. 2. There's no distinction between transmission functions bound to message or file mode. E.g. all 3 functions: `srt_send`, `srt_sendmsg` and `srt_sendmsg2` can be used for sending data in any mode - all depends on what your application needs. ## Socket connection Let's review quickly how to establish a socket connection in the caller-listener arrangement. On the listener side, you create a listening endpoint. Starting with creating a socket: ```c SRTSOCKET sock = srt_create_socket(); ``` The listener needs to bind it first (note: simplified code): ```c sockaddr_any sa = CreateAddr("0.0.0.0", 5000); srt_bind(sock, sa.get(), sa.len); srt_listen(sock, 5); sockaddr_in target; SRTSOCKET connsock = srt_accept(sock, &target, sizeof target); ``` The caller side can use default system selected address and simply connect to the target: ```c SRTSOCKET connsock = srt_create_socket(); sockaddr_any sa = CreateAddr("target.address", 5000); srt_connect(connsock, sa.get(), sa.len); ``` After the connection is established, you use the send/recv functions to transmit the data. In this case we'll utilize the most advanced versions, `srt_sendmsg2` and `srt_recvmsg2`. Sender side does: ```c SRT_MSGCTRL mc = srt_msgctrl_default; packetdata = GetPacketData(); srt_sendmsg2(connsock, packetdata.data(), packetdata.size(), &mc); ``` Receiver side does: ```c SRT_MSGCTRL mc = srt_msgctrl_default; vector packetdata(SRT_LIVE_DEF_PLSIZE); int size = srt_recvmsg2(connsock, packetdata.data(), packetdata.size(), &mc); packetdata.resize(size); ``` # Group (bonded) connection Except for several details, most of the API used for sockets can be used for groups. Groups also have numeric identifiers, just like sockets, which are in the same domain as sockets, except that there is one bit reserved to indicate that the identifier is for a group, bound to a `SRTGROUP_MASK` symbol. IMPORTANT: Socket groups are designed to utilize specific features. The broadcast or backup group are designed to provide link redundancy (to keep transmission running in case when one of the links gets broken). The balancing groups allow to share the bandwidth load between links. In order to be able to utilize any of these features, every member link in the group must be routed through a different network path. Some terminal parts of these links can be common for them all - but if so, for these parts these features will not be used: a broken network path in this part would break all links at once, and the "balanced" traffic will go through one route path as a whole anyway. SRT has no possibility to check if you configured your links right. This means that on the caller side you need to use different target address for every link, while on the listener side you should use a different network device for every link. For the listener side, note that groups only replace the communication socket. Listener sockets still have to be used: ```c SRTSOCKET sock = srt_create_socket(); ``` To handle group connections, you need to set `SRTO_GROUPCONNECT` option: ```c int gcon = 1; srt_setsockflag(sock, SRTO_GROUPCONNECT, &gcon, sizeof gcon); sockaddr_any sa = CreateAddr("0.0.0.0", 5000); srt_bind(sock, sa.get(), sa.len); srt_listen(sock, 5); sockaddr_in target; SRTSOCKET conngrp = srt_accept(sock, &target, sizeof target); ``` Here the (mirror) group will be created automatically upon the first connection and `srt_accept` will return its ID (not Socket ID). Further connections in the same group will be then handled in the background. This `conngrp` returned here is however the exact ID you will use for transmission. On the caller side, you start from creating a group first. We'll use the broadcast group type here: ```c SRTSOCKET conngrp = srt_create_group(SRT_GTYPE_BROADCAST); ``` This will need to make the first connection this way: ```c sockaddr_any sa = CreateAddr("target.address.link1", 5000); srt_connect(conngrp, sa.get(), sizeof sa); ``` Then further connections can be done by calling `srt_connect` again: ```c sockaddr_any sa2 = CreateAddr("target.address.link2", 5000); srt_connect(conngrp, sa.get(), sa2.len); ``` IMPORTANT: This method can be easily used in non-blocking mode, as you don't have to wait for the connection to be established. If you do this in the blocking mode, the first `srt_connect` call will block until the connection is established. While it can be done this way, it's usually unwanted. So for blocking mode we use a different solution. Let's say, you have 3 addresses: ```c sockaddr_any sa1 = CreateAddr("target.address.link1", 5000); sockaddr_any sa2 = CreateAddr("target.address.link2", 5000); sockaddr_any sa3 = CreateAddr("target.address.link3", 5000); ``` You have to prepare the array for them and then use one group-connect function: ```c SRT_SOCKGROUPDATA gdata [3] = { srt_prepare_endpoint(NULL, &sa1, sizeof sa1), srt_prepare_endpoint(NULL, &sa2, sizeof sa2), srt_prepare_endpoint(NULL, &sa3, sizeof sa3) }; srt_connect_group(conngrp, gdata, 3); ``` This does the same as `srt_connect`, but blocking rules are different: it blocks until at least one connection from the given list is established. Then it returns and allows the group to be used for transmission, while continuing with the other connections in the background. Note that some group types may require certain conditions to be satisfied, like a minimum number of connections. If you use non-blocking mode, then `srt_connect_group` behaves the same as running `srt_connect` in a loop for all required endpoints. Once the connection is ready, you use the `conngrp` id for transmission, exactly the same way as above for the sockets. There's one additional thing to be covered here: just how much should the application be involved with socket groups? # Controlling the member connections The object of type `SRT_MSGCTRL` is used to exchange some extra information with `srt_sendmsg2` and `srt_recvmsg2`. Of particular interest in this case are two fields: * `grpdata` * `grpdata_size` These fields have to be set to the pointer and size of an existing `SRT_SOCKGROUPDATA` type array, which will be filled by this call (you can also obtain it separately by the `srt_group_data` function). The array must have a maximum possible size to get information about every single member link. Otherwise it will not fill and return the proper size in `grpdata_size`. The application should be interested here in two types of information: * the size of the filled array * the `sockstate` field in every element From the `sockstate` field you can track every member connection as to whether its state is still `SRTS_CONNECTED`. If a connection is detected as broken after the call to a transmission function (`srt_sendmsg2/srt_recvmsg2`) then the connection will appear in these data only once, and with `sockstate` equal to `SRTS_BROKEN`. It will not appear anymore in later calls, and it won't appear at all if you check the data through `srt_group_data`. Example: ```c SRT_SOCKGROUPDATA gdata[3]; SRT_MSGCTRL mc = srt_msgctrl_default; mc.grpdata = gdata; mc.grpdata_size = 3; //... srt_sendmsg2(conngrp, packetdata.data(), packetdata.size(), &mc); for (int i = 0; i < 3; ++i) if (mc.grpdata[i].sockstate == SRTS_BROKEN) ReestablishConnection(mc.grpdata[i].id); ``` In the above example the socket ID is used to identify the item in the application's link table, at which point a decision is made. If the connection is to be revived, this function should call `srt_connect` on it. There might be only an attempt to establish the link, in which case you'll get first the `SRTS_CONNECTING` state here, and then a failed socket will simply disappear. Therefore the function should also check how many items were returned in this array, match them with existing connections, and filter out connections that are unexpectedly not established. srt-1.4.4/docs/features/bonding-main-backup.md000066400000000000000000000440601412557703600212430ustar00rootroot00000000000000# SRT Connection Bonding: Main/Backup I. [Introduction](#i-introduction) II. [Mode Overview](#ii-mode-overview) III. [Sender Logic](#iii-sender-logic) IV. [Sending Algorithm](#iV-sending-algorithm) ## I. Introduction *SRT Main/Backup Switching* mode saves contribution bandwidth by using only one (main) link at a time, while keeping a stream alive if the main link gets broken. This feature is useful for broadcasting where the traffic costs for a main link are relatively low, and the main link is reasonably reliable. To make sure a transmission doesn’t fail, one or several backup connections are on stand-by. These backup connections ensure reliability, but the traffic costs may be high. To save costs, the backup links are only activated when the main link fails or doesn’t achieve the required throughput. The goal of SRT's main/backup bonding mode is to identify a potential link break before it happens, thus providing a time window within which to seamlessly switch to one of the backup links. ## II. Mode Overview ### Mode Constraints The **main constraints** of the main/backup switching mode are: - **only one active link at a time**, except when switching to one of the backup links or to a link with a higher weight; - **seamless switching** to one of the backup links ideally happens without packet drops, but dropping packets for a certain period of time is acceptable during the switch under certain circumstances (e.g. severe conditions). Transmission happens over the main link until it is considered broken or is presumably about to become broken. ### Sensitivity Levels The only sensitivity level implemented at the moment is a **pre-emptive switch** to a backup path from an unstable main path before it breaks. The goal is to predict an upcoming link breakage before it happens, and to be ready to switch to an activated backup link while losing as few packets as possible. An additional sensitivity level (**handover switch**) may be added in the future for cases where low latency and packet loss is not critical. The switch would take place once the link is actually broken, without trying to predict it thereby reducing processing overhead. Since the main path in this case is already broken, there would be a delay associated with activating the backup path resulting in a discontinuity in streaming (data loss during the switch). ### Mode Limitations Detecting a potential link break and switching to a backup link requires time. If the activation of the backup link happens within the `SRTO_PEERLATENCY` interval, there is a good chance that not a single packet will be lost. Therefore, one of the limitations (or usage guidelines) is to set `SRTO_PEERLATENCY ≥ 3×RTT`. The logic of the main/backup sending algorithm is triggered each time an application submits new payload (calls `srt_sendmsg2(..)`). This imposes two limitations: - it is better to submit new packets no later than every 50 or 60 ms to trigger the logic with enough frequency (the bitrate should be > 180 kbps); - file transmission logic does not fit well with this algorithm, and is not supported. ## III. Sender Logic ### Member Link State In addition to an individual SRT socket state (e.g. `SRTS_CONNECTED`, `SRTS_BROKEN`, etc.; see [srt_getsockstate(..)](./../API/API-functions.md#srt_getsockstate)), a socket member of a group has a member status (see [SRT_MEMBERSTATUS](./../API/API-functions.md#SRT_MEMBERSTATUS)). The top level member status [SRT_MEMBERSTATUS](./../API/API-functions.md#SRT_MEMBERSTATUS) is visible from the SRT API. However, `SRT_GST_RUNNING` member status can have sub-statuses used by the group sending algorithm. Member link status can be: 1. **SRT_GST_PENDING**. The link is visible, but the connection hasn't been established yet. The link cannot be used for data transmission yet. 2. **SRT_GST_IDLE** (**stand by**). The link is connected and ready for data transmission, but it is not activated for actual payload transmission. KEEPALIVE control packets are being exchanged once per second. 3. **SRT_GST_RUNNING** (**active**). The link is being used for payload transmission. 1. **Fresh-Activated**. A link was freshly (newly) activated, and it is too early to qualify it as either stable or unstable. 2. **Stable**. Active link is considered stable. 3. **Unstable**. Active link is considered unstable, e.g. response time has exceeded some threshold. 4. **Unstable-Wary**. A link was identified as unstable (e.g. no response longer than some threshold) until a new response makes it potentially stable again. 4. **SRT_GST_BROKEN**. The link has just been detected to be broken. It is about to be closed and removed from the group. ### Member State Transition The initial state for group sender logic to operate on a member is once the member socket is connected and transitions to **SRT_GST_IDLE**. | Event \ State | => q0 (GST_IDLE) | q1 (Fresh-Activated) | q2 (Stable) | q3 (Unstable) | q4 (Wary) | q5 =>(Broken) | | ---------------------------------------------------------------- | ---------------------------------- | -------------------------------- | -------------------------------- | ------------------------------ | ---------- | ------------- | | Last response < LST* | x | q1 | q2 | **q4** (`m_tsWarySince` = now) | x | x | | Last response >= LST* | x | **q3** (`tsUnstableSince` = now) | **q3** (`tsUnstableSince` = now) | x | **q3** | x | | `tsUnstableSince`!= 0 and (now - `tsUnstableSince`> PEERIDLETMO) | x | x | x | **q5** | **q5** | x | | Probing period is over | x | q2 / `tsFreshActivation` = 0 | x | x | **q2** | x | | Activate (group decision) | **q1** (`tsFreshActivation` = now) | x | x | x | x | x | | Silence (group decision) | x | :question: | **q0** | x | :question: | x | | Close/break (external event) | **q5** | **q5** | **q5** | **q5** | **q5** | x | Member activation happens according to conditions described in the [Member Activation](#member-activation) section. Upon activation a member transitions to the **q1** (**SRT_GST_RUNNING**: **Fresh-Activated**) state. A member in the **q1** (**SRT_GST_RUNNING**: **Fresh-Activated**) state can either become **q2** (**SRT_GST_RUNNING**: **Stable**) or **q3** (**SRT_GST_RUNNING**: **Unstable**). Conditions to qualify a member as unstable are described in the [Qualifying Member Unstable](#qualifying-member-unstable) section. If the conditions are met, the member is transitioned into the **q3** (**SRT_GST_RUNNING**: **Unstable**) state. An **Unstable** member (**q3** (**SRT_GST_RUNNING**: **Unstable**) or **q4** (**SRT_GST_RUNNING**: **Unstable-Wary**)) **either**: - transition back to the **q2** (**SRT_GST_RUNNING**: **Stable**) state through the **q4** (**SRT_GST_RUNNING**: **Unstable-Wary**) state; - transition to the **q0** (**SRT_GST_IDLE**) state (be silenced); or - transition to the **q5** (**SRT_GST_BROKEN**) state (be closed and removed from the group). A member in the **q5** (**SRT_GST_BROKEN**) state will eventually be closed and removed from the group. ### Member Ordering by Priority When comparing two members, one is ordered before the other depending on which of the following ordering conditions applies (in the order of priority). 1. Higher weight (highest priority) 2. By backup state (if equal weight): 1. **SRT_GST_RUNNING**: **Stable** 2. **SRT_GST_RUNNING**: **Fresh-Activated** 3. **SRT_GST_RUNNING**: **Unstable-Wary** 4. **SRT_GST_RUNNING**: **Unstable** 5. **SRT_GST_BROKEN** 6. **SRT_GST_IDLE** (**stand by**) 7. **SRT_GST_PENDING** 3. By the Socket ID (lower value first). For example, an unstable member with a higher weight is ordered before a stable member with lower weight. **Potential Improvement**: Order by connection start time (older connection comes first) before comparing socket IDs. ### Member Activation *Member activation* means transitioning an idle (stand by) member with status **SRT_GST_IDLE** to a **SRT_GST_RUNNING**: **Fresh-Activated** state. The time it takes to activate a member is saved as `tsFreshActivation = CurrentTime`. Activation is needed if one of the following is true: 1. There are no **SRT_GST_RUNNING**: **Stable** or **SRT_GST_RUNNING**: **Fresh-Activated** members. 2. The weight of one of the idle members is higher than the maximum weight of **SRT_GST_RUNNING** links. An idle link to be activated is taken from the top of the list of idle links, sorted according to [member ordering priority](#send-member-ordering). A member remains in the **SRT_GST_RUNNING**: **Fresh-Activated** state while `CurrentTime - tsFreshActivation > ProbingPeriod`, i.e. for the whole probing period: `ProbingPeriod = ILST + 50ms` Here **Initial Link Stability Timeout** `ILST = max(LSTmin; SRTO_PEERLATENCY)`, - `LSTmin = 60ms` ; - `SRTO_PEERLATENCY` is the corresponding socket option value on a connected socket. ### Qualifying a Member as Unstable A member in the active (**SRT_GST_RUNNING**) state can be transitioned to the **q3** (**SRT_GST_RUNNING**: **Unstable**) state (become unstable) for any of the reasons described below. #### Unstable due to response timeout A member link is considered unstable if the time elapsed since the last response from a peer (`LastRspTime`) exceeds the link stability timeout: `CurrentTime - LastRspTime > LST` where - `CurrentTime` is the time when the next data packet **is submitted to a group** for sending; - `LastRspTime` is the time when the latest response (*ACK, loss report (NAK), periodic NAK report, KEEP_ALIVE message, or DATA packet in case of bidirectional transmission*) was received from the SRT receiver by the SRT sender for a member in the **SRT_GST_RUNNING**: **Fresh-Activated** state `LastRspTime ≥ tsFreshActivation`; - `LST` (Link Stability Timeout) is a dynamic value for stability timeout calculated based on the group `SRT Latency` and RTT estimate on a link. This value is calculated individually for each active (**SRT_GST_RUNNING**) link. The link stability timeout for an active (**SRT_GST_RUNNING**) member (**except** for **SRT_GST_RUNNING**: **Fresh-Activated**) is calculated with each data packet submission (on `srt_sendmsg2(..)`). For a member in **SRT_GST_RUNNING**: **Fresh-Activated** state `LST = ILST` (see [Member Activation](#member-activation)). For active (**SRT_GST_RUNNING**) members in states **different** from **SRT_GST_RUNNING**: **Fresh-Activated** `LST` is calculated as follows: `LST = 2 * SRTT + 4 * RTTVar,` and `LSTmin ≤ LST ≤ SRTO_PEERLATENCY`, where - `LSTmin = 60ms`; - `SRTO_PEERLATENCY` is the corresponding socket option value on a connected socket; - `SRTT` and `RTTVar` are smoothed RTT and RTT variances calculated on an individual socket member in runtime as described in the SRT RFT [Round Trip Time Estimation](https://tools.ietf.org/html/draft-sharabayko-srt-00#section-4.10) section. #### Unstable due to sending failure If sending a packet (`srt_sendmsg2(..)`) over a member SRT socket has failed with error `SRT_EASYNCSND`, the member is qualified as **q3** (**SRT_GST_RUNNING**: **Unstable**). This error indicates that there was not enough free space in the sender's buffer to accept this data packet for sending. It should not happen under normal conditions and if buffers are configured correctly this kind of error indicates some possible congestion on a path. #### Unstable due to sender-side packet drops If a member has dropped a packet (see SRT RFC [Too-Late Packet Drop](https://tools.ietf.org/html/draft-sharabayko-srt-00#section-4.6) section) **since the previous submission of a data packet** for sending (previous call to `srt_sendmsg2(..)`), it transitions to the **q3** (**SRT_GST_RUNNING**: **Unstable**) state. #### Unstable due to receiver-side packet drops (TBD) **IMPORTANT: For the time being, the main backup algorithm does not react to lost packets or packets dropped by the receiver.** Note that an SRT sender does not know the drop rate on the receiver's side. A receiver acknowledges packets it drops. PR [#1889](https://github.com/Haivision/srt/pull/1889) extends ACK packets to include the total number of packets dropped by the receiver. ### Qualifying a Member as Broken #### Broken due to peer idle timeout Similar to a single SRT connection, a member SRT socket is considered broken if there has been no response from a peer for a certain time, defined by the [SRTO_PEERIDLETIMEO](./../APISocketOptions.md#SRTO_PEERIDLETIMEO) socket option (5 seconds by default). A broken socket will be closed by the SRT library. It is also removed from a group. #### Broken due to remaining unstable for too long The only additional condition to break an SRT connection from a group is if a member remains **unstable** for too long. A group can request a socket member to break its connection if the time elapsed since the socket has become unstable (`tsUnstableSince`) exceeds the timeout defined by the [SRTO_PEERIDLETIMEO](./../APISocketOptions.md#SRTO_PEERIDLETIMEO) socket option: `CurrentTime - tsUnstableSince > SRTO_PEERIDLETIMEO`. ### Qualifying a Member as Stable Only a member SRT socket in the active (**SRT_GST_RUNNING**) state can be qualified as stable. #### Freshly-activated becomes stable A freshly activated member SRT socket (**SRT_GST_RUNNING**: **Fresh-Activated**) is qualified as stable (**SRT_GST_RUNNING**: **Stable**) once the probing period `ProbingPeriod` is over (unless it has already been qualified as unstable). The probing period is defined in the [Member Activation](#member-activation) section. #### Unstable becomes stable If there is no longer a reason to qualify a member as unstable (see [Qualifying a Member as Unstable](#qualifying-a-member-as-unstable)) it can transition to the stable state. A member in the **q3** (**SRT_GST_RUNNING**: **Unstable**) state transitions immediately to the **q4** (**SRT_GST_RUNNING**: **Unstable-Wary**) state. The time of the transition event is saved as `tsWarySince = CurrentTime`. If a member remains in the **q4** (**SRT_GST_RUNNING**: **Unstable-Wary**) state for `4 × SRTO_PEERLATENCY`, it can transition to the **SRT_GST_RUNNING**: **Stable** state. *Note that if a member becomes unstable **q3** (**SRT_GST_RUNNING**: **Unstable**) again, the `tsWarySince` time will be reset on the next transition to the **Unstable-Wary** state.* ### Silencing an Active Member There must be only one stable (**SRT_GST_RUNNING**: **Stable**) member SRT socket active in a group. There may be several active unstable or fresh activated sockets in a group. However, if more than one member is qualified as stable, only one must remain active. In order to select a stable member to remain active the [Member Ordering by Priority](#send-member-ordering) is applied. All active members ordered after the first stable member are silenced. All active members ordered before the first stable member in the list, including the stable member, remain active. ## IV. Sending Algorithm The group sending workflow is triggered by a submission of the following data packet via the `srt_sendmsg(..)` SRT API function. The following steps apply in order. ### 1. Qualify Member States Before sending a packet, all member links are qualified according to the states described above. ### 2. Sending the Same Packet over Active Links The same data packet is sent (duplicated) over all members qualified as active (**SRT_GST_RUNNING**). **If sending fails, the link is re-qualified as unstable** as described in the [Unstable by Sending Failure](#unstable-by-sending-failure) section. ### 3. Save the Packet Being Sent to the Group SND Buffer The group has a separate buffer for packets being sent and to be acknowledged. If a member socket gets broken, those packets can be resent over a freshly-activated backup member. ### 4. Check if Backup Link Activation is Needed See the [Member Activation](#member-activation) section. ### 5. [IF] Activate Idle Link If a member is activated, all buffered packets (see step 3) are submitted to this SRT member socket. If sending fails (see the [Unstable by Sending Failure](#unstable-by-sending-failure) section), another member is activated by following the logic described in the [Member Activation](#member-activation) section. ### 6. Check Pending and Broken Sockets Check if there are pending sockets that failed to connect, and should be removed from the group. Check if there are broken sockets to be removed from the group. Check if there are member socket unstable for too long that should be requested to transition to the `broken` state. ### 7. Wait for Sending Success There may be a situation where no sending has succeeded, but there are active members. If the group is in non-blocking operation mode, the group returns `SRT_EASYNCSND` error. If the group is in blocking operation mode, sending over active members is retried until the sending timeout [`SRTO_SNDTIMEO`](./../API/API-socket-options.md#SRTO_SNDTIMEO) is reached (`SRT_EASYNCSND` error is returned), or at least one member successfully accepts a data packet for sending. ### 8. Silence Redundant Members The rules described in the [Silencing an Active Member](#silencing-an-active-member) section are applied in this step. srt-1.4.4/docs/features/encryption.md000066400000000000000000000327011412557703600176270ustar00rootroot00000000000000# SRT Encryption **NOTE:** This document might be outdated, please consult [Section 6. Encryption](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-6) of the [SRT RFC](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00) additionally. This document describes an encryption mechanism that protects the payload of SRT streams. Despite using standard cryptographic algorithms, the mechanism is unique and does not interoperate with any known third party stream encryption method. ## Terminology | Term | Description | |------|-------------| | AEAD | Authenticated Encryption with Associated Data | | AES | Advanced Encryption Standard | | AESkw | AES key wrap not specified ([RFC3394] or [ANSX9.102]) | | AESKW | AES Key Wrap with associated data authentication [ANSX9.102] | | ARM | Advanced RISC Machine (Texas Instrument processor) | | CCM | Counter with CBC-MAC | | CTR | Counter | | DSP | Digital Signal Processor | | DVB | Digital Video Broadcast | | DVB-CA | DVB - Conditional Access | | ECB | Electronic Code Book | | ECM | Entitlement Control Message (DVB/MPEG) | | EKT | Encrypted Key Transport (SRTP) | | eSEK | Even SEK | | FIPS | Federal Information Processing Standard | | GCM | Galois/Counter mode | | GDOI | Group Domain Of Interpretation | | GOP | Group Of Pictures | | HDCP | High-bandwidth Digital Content Protection | | HMAC | Hash-based Message Authentication Code | | HTTP | Hypertext Transfer Protocol | | HTTPS | HTTP Secure | | IV | Initialisation Vector | | KEK | Key Encrypting Key | | LSB | Least Significant Bits | | MAC | Message Authentication Code | | MD5 | Message Digest 5 | | MIKEY | Multimedia Internet KEYing | | MPEG | Motion Picture Expert Group | | MSB | Most Significant Bits | | oSEK | Odd SEK | | PBKDF2 | Password-Based Key Derivation Function version 2 | | PES | Packetized Elementary Stream (MPEG) | | PKCS | Public-Key Cryptography Standards | | PRNG | Pseudo Random Number Generator | | RISC | Reduced Instruction Set Computer | | RTP | Read-time Transport Protocol | | SEK | Stream Encrypting Key | | SHA | Secure Hash Algorithm | | SIV | Synthetic Initialisation Vector | | SO | Security Officer | | SRT | Secure Reliable Transport | | SRTP | Secure Real-time Transport Protocol | | SSL | Secure Socket Layer | | TP | Transmission Payload | | TS | Transport Stream (MPEG) | | TU | Transmission Unit | | UDP | User Datagram Protocol | ## References * [ANSX9.102] Accredited Standards Committee, Wrapping of Keys and Associated Data, ANS X9.102, not for free document. * [FIPS 140-2] Security Requirements for Cryptographic Modules, NIST, [FIPS PUB 140-2](https://csrc.nist.gov/csrc/media/publications/fips/140/2/final/documents/fips1402.pdf), May 2001. * [FIPS 140-3] Security Requirements for Cryptographic Modules, NIST, [FIPS PUB 140-3](https://csrc.nist.gov/publications/detail/fips/140/3/archive/2009-12-11), December 2009. * [SP800-38A] Recommendation for Block Cipher Modes of Operation, M. Dworkin, NIST, [FP800-38A](https://csrc.nist.gov/publications/detail/sp/800-38a/final), December 2001. * [HDCP2] High-bandwidth Digital Content Protection System, Interface Independent Adaptation, Revision 2.0, [HDCP IIA 2.0](https://www.digital-cp.com/files/static_page_files/2C1C0F30-0E09-E813-BFAB6BAAE8A76080/HDCP%20Interface%20Independent%20Adaptation%20Specification%20Rev2_0.pdf), Digital Content Protection, LLC, October 2008. * [PKCS5] [PKCS #5 v2.0 Password-Based Cryptography Standard](http://www.rsa.com/rsalabs/node.asp?id=2127), RSA Laboratories, March 1999. * [RFC2998] PKCS #5: Password-Based Cryptography Specification Version 2.0, B. Kaliski, [RFC2898](https://tools.ietf.org/html/rfc2898), September 2000. * [RFC3394] Advanced Encryption Standard (AES) Key Wrap Algorithm, J. Schaad, R. Housley, [RFC3394](https://tools.ietf.org/html/rfc3394), September 2002. * [RFC3547] The Group Domain of Interpretation, M. Baugher, B. Weis, T. Hardjono, H. Harney, [RFC3547](https://tools.ietf.org/html/rfc3547), July 2003. * [RFC3610] Counter with CBC-MAC (CCM), D. Whiting, R. Housley, N. Ferguson, [RFC3610](https://tools.ietf.org/html/rfc3610), September 2003. * [RFC3711] The Secure Real-time Transport Protocol (SRTP), M. Baugher, D. McGrew, M. Naslund, E. Carrara, K. Norrman, [RFC3711](https://tools.ietf.org/html/rfc3711), March 2004. * [RFC3830] MIKEY: Multimedia Internet KEYing, J, Arkko, E. Carrara, F. Lindholm, M. Naslund, K. Norrman, [RFC3830](https://tools.ietf.org/html/rfc3830), August 2004. * [RFC5297] Synthetic Initialization Vector (SIV) Authenticated Encryption Using the Advanced Encryption Standard (AES), D. Harkins, [RFC5297](https://tools.ietf.org/html/rfc5297), October 2008. * [RFC5649] Advanced Encryption Standard (AES) Key Wrap with Padding Algorithm, R. Housley, M. Dworkin, [RFC5649](https://tools.ietf.org/html/rfc5649), August 2009. * [RFC6070] PBKDF2 Test Vectors * [SRTP-EKT] Encrypted Key Transport for Secure RTP, D. McGrew, F. Andreasen, D. Wing, K. Fisher, [draft-ietf-avt-srtp-ekt-02](https://tools.ietf.org/html/draft-ietf-avt-srtp-ekt-02), March 2011. ## Operators | Operator | Setting | |----------|---------| | a \|\| b | Concatenation | | a XOR b | Bit-wise exclusive or | | a ^ b | a exponent b | | an | a is n bits long | | AESkw(kek,k) | AES key wrap k with kek (Key Encrypting Key) | | LSB(n,v) | Least significant n bits of v | | MSB(n,v) | Most significant n bits of v | | PRNG(n) | Pseudo Random Number Generator (n bits) | | PBKDF2(p,s,i,l) | Password-based Key Derivation Function (PKCS #5)
p: password, s: salt, i: iterations, l: key length | ## Overview AES in counter mode (AES-CTR) is used with a short lived key to encrypt the media stream. This cipher is suitable for random access of a continuous stream, content protection (used by HDCP 2.0), and strong confidentiality when the counter is managed properly. The short lived key is randomly generated by the sender and transmitted within the stream (KM Tx Period), wrapped with another longer-term key, the Key Encrypting Key (KEK). For connection-oriented transport such as SRT, there is no need to periodically transmit the short lived key since no party can join the stream at any time. The KEK is derived from a secret shared between the sender and the receiver. The shared secret provides access to the stream key which provides access to the protected media stream. The distribution and management of the secret is more flexible than the stream encrypting key. A pre-shared password used with a password-based key derivation mechanism is proposed in this document as the default shared secret but other automated key distribution methods that scale better could be proposed in a separate document. The short lived key, hereafter called the Stream Encrypting Key (SEK), is regenerated for cryptographic reasons when enough packets have been encrypted with it (KM Refresh Rate). To ensure seamless rekeying, the next key to use is transmitted in advance to receivers (KM Pre-Announce) so they can switch keys without disruption when rekeying occurs. KM Refresh Rate and KM Pre-Announce are system parameters that can be configurable options if shorter time than the cryptographic limit is required (for example to limit the material obtained from a compromised SEK). ### Definitions This section defines the elements of the SRT encryption mechanism. Figure 1 shows the encryption of arbitrary SRT payload. ![Figure 1][figure1] Figure 1 #### Ciphers (AES-CTR) The payload is encrypted with a cipher in counter mode (AES-CTR). The counter mode is one of the only cipher mode suitable for continuous stream encryption that permits decryption from any point, without access to start of the stream (random access), and for the same reason tolerates packet lost. The Electronic Code Book (ECB) mode also has these characteristics but does not provide serious confidentiality and is not recommended in cryptography. #### Media Stream message (MSmsg) The Media Stream message is formed from the SRT media stream (data) packets with some elements of the SRT header used for the cryptography. SRT header already carries a 32-bit packet sequence that is used for the cipher’s counter (ctr) and 2 bits are stolen from the header’s message number (then reduced to 27-bits) for the encryption key (odd/even) indicator. #### Keying Material For each stream, the sender generates a Stream Encrypting Key (SEK) and a Salt (not shown in Figure 1). For the initial implementation and for most envisioned scenarios where no separate authentication algorithm is used for message integrity, the SEK is used directly to encrypt the media stream. The Initial Vector (IV) for the counter is derived from the Salt only. In other scenarios, the SEK can be used along with the Salt as a key generating material to produce distinct encryption, authentication, and salt keys. #### Stream Encrypting Key (SEK) The Stream Encrypting Key (SEK) is pseudo-random and different for each stream. It must be 128, 192, or 256 bits long for the AES-CTR ciphers. It is non-persistent and relatively short lived. In a typical scenario the SEK is expected to last, cryptographically, around 37 days for a 31-bit counter (231 packets / 667 packets/second). The SEK is regenerated every time a stream starts. It must be discarded before 231 packets are encrypted (31-bit packet index) and replaced seamlessly using an odd/even key mechanism described further. SRT is conservative and regenerates the SEK key every 225 packets (~6 hours in the above scenario of a 667 packets per second stream). Reusing an IV (often called nonce) with the same key on different clear text is a known catastrophic issue of counter mode ciphers. By regenerating the SEK each time a stream starts we remove the need for fancier management of the IV to ensure uniqueness. #### Initialization Vector (IV) The IV (also named nonce in the AES-CTR context) is a 112 bit random number. For the initial implementation and for most envisioned scenarios where no separate authentication algorithm is used for message integrity (Auth=0), the IV is derived from the salt only. IV = MSB(112, Salt) ; Most significant 112 bits of the salt. #### Counter (ctr) The counter for AES-CTR is the size of the cipher’s block, i.e. 128 bits. It is made of a block counter in the least significant 16 bits, counting blocks of a packet, and a 32 bits packet index in the next 32 bits. The upper 112 bits are XORed with the IV to produce a unique counter for each crypto block. ![Figure 2][figure2] Figure 2 The block counter (bctr) is incremented for each cipher block while producing the key stream. The packet index is incremented for each packet submitted to the cipher. The IV is derived from the Salt provided with the Keying Material. #### Keying Material message (KMmsg) The SEK and a Salt are transported in-stream, in a Keying Material message (KMmsg), implemented as a SRT custom control packet, wrapped with a longer term Key Encrypting Key (KEK) using AES key wrap [RFC3394]. There are possibilities for an eventual key wrapper with integrity such as AESKW [ANSX9.102] or AES-SIV [RFC5297]. Transmitting a key in-band is not original to this specification. It is used in DVB MPEG-TS where the stream encrypting key is transmitted in an Entitlement Control Message (ECM). It is also proposed in an IETF draft for SRTP for Encrypted Key Transport [SRTP-EKT]. The connection-oriented SRT KM ctrl packet is transmitted at the start of the connection, before any data packet. In most case, if the control packet is not lost, the receiver is able to decrypt from the first packet. Otherwise, the initial packets are dropped (or stored for later decryption) until the KM control packet is received. The SRT ctrl packet is retransmitted until acknowledged by the receiver. #### Odd/Even Stream Encrypting Key (oSEK/eSEK) To ensure seamless rekeying for cryptographic (counter exhausted) or access control reasons, a two-key mechanism, similar to the one used with DVB systems is used. The two keys are identified as the odd key and the even key (oSEK/eSEK). Basically, an odd/even flag in the SRT data header tells which key is in use. The next key to use is transmitted in advance (KM Pre-Announce) to the receivers in a SRT ctrl packet. When rekeying occurs, the SRT data header odd/even flag flips and the receiver already have the new key in hand to continue decrypting the stream without missing a packet. #### Key Encrypting Key (KEK) The KEK is used by the sender to wrap the SEK and by the receiver to unwrap it and then decrypt the stream. The KEK must be at least the size of the key it protects, the SEK. The KEK is derived from a shared secret, a pre-shared password by default. The KEK is derived with the PBKDF2 [PCKS5] derivation function with the stream Salt and the shared secret for input. Each stream then uses a unique KEK to encrypt its Keying Material. A compromised KEK does not compromise other streams protected with the same shared secret (but a compromised shared secret compromises all streams protected with KEK derived from it). Late derivation of the KEK using stream Salt also permits to generate a KEK of the proper size, based on the size of the key it protects. The shared secret can be pre-shared; password derived [PKCS5]; distributed using a proprietary mechanism; or using a standard key distribution mechanism such as GDOI [RFC3547] or MIKEY [RFC3830]. The cryptographic usage limit of the KEK is 248 wraps (AESKW) which means virtual infinity at the expected SEK rekeying rate (90000 years to rekey 100 keys every second). [figure1]: images/srt-encryption-1.png [figure2]: images/srt-encryption-2.png srt-1.4.4/docs/features/handshake.md000066400000000000000000002241221412557703600173630ustar00rootroot00000000000000# SRT Handshake Published: 2018-06-28 Last updated: 2018-06-28 **NOTE:** This document might be outdated, please consult [Section 3.2.1 Handshake](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-3.2.1) and [Section 4.3 Handshake Messages](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-4.3) of the [SRT RFC](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00) additionally. **Contents** - [Overview](#overview) - [Short Introduction to SRT Packet Structure](#short-introduction-to-srt-packet-structure) - [Handshake Structure](#handshake-structure) - [The "UDT Legacy" and "SRT Extended" Handshakes](#the-udt-legacy-and-srt-extended-handshakes) - [UDT Legacy Handshake](#udt-legacy-handshake) - [Initiator and Responder](#initiator-and-responder) - [The Request Type Field](#the-request-type-field) - [The Type Field](#the-type-field) - [The Caller-Listener Handshake](#the-caller-listener-handshake) - [The Induction Phase](#the-induction-phase) - [The Conclusion Phase](#the-conclusion-phase) - [The Rendezvous Handshake](#the-rendezvous-handshake) - [HSv4 Rendezvous Process](#hsv4-rendezvous-process) - [HSv5 Rendezvous Process](#hsv5-rendezvous-process) - [Serial Handshake Flow](#serial-handshake-flow) - [Parallel Handshake Flow](#parallel-handshake-flow) - [Rendezvous Between Different Versions](#rendezvous-between-different-versions) - [The SRT Extended Handshake](#the-srt-extended-handshake) - [HSv4 Extended Handshake Process](#hsv4-extended-handshake-process) - [HSv5 Extended Handshake Process](#hsv5-extended-handshake-process) - [SRT Extension Commands](#srt-extension-commands) - [HSREQ and HSRSP](#hsreq-and-hsrsp) - [KMREQ and KMRSP](#kmreq-and-kmrsp) - [Congestion controller](#congestion-controller) - [Stream ID (SID)](#stream-id-sid) ## Overview SRT is a connection protocol, and as such it embraces the concepts of "connection" and "session". The UDP system protocol is used by SRT for sending data as well as special control packets, also referred to as "commands". An SRT connection is characterized by the fact that it is: - first engaged by a *handshake* process - maintained as long as any packets are being exchanged in a timely manner - considered closed when a party receives the appropriate close command from its peer (connection closed by the foreign host), or when it receives no packets at all for some predefined time (connection broken on timeout). Just like its predecessor UDT, SRT supports two connection configurations: 1. **Caller-Listener**, where one side waits for the other to initiate a connection 2. **Rendezvous**, where both sides attempt to initiate a connection As SRT development has evolved, two handshaking mechanisms have emerged: 1. the **legacy UDT handshake**, with the "SRT" part of the handshake implemented as extended control messages; this is the only mechanism in SRT versions 1.2 and lower, and is known as **HSv4** (where the number 4 refers to the last UDT version) 2. the new **integrated handshake**, known as **HSv5**, where all the required information concerning the connection is interchanged completely in the handshake process The version compatibility requirements are such that if one side of the connection only understands *HSv4*, the connection is made according to *HSv4* rules. Otherwise, if both sides are at SRT version 1.3.0 or greater, *HSv5* is used. As the new handshake supports several features that might be mandatory for a particular application, it is also possible to reject an HSv4-to-HSv5 connection by setting the `SRTO_MINVERSION` socket option. The value for this option is an integer with the version encoded in hex. For example: int req_version = 0x00010300; // 1.3.0 srt_setsockflag(s, SRTO_MINVERSION, &req_version, sizeof(int)); **IMPORTANT:** Your SRT application must do either of these two things: - Be *HSv4* compatible. In this case it must: - **NOT** use any new features in 1.3.0 or higher (such as bidirectional transmission or Stream ID) - **ALWAYS** set `SRTO_SENDER` to true on the sender side - Require *HSv5*. If so, it must prevent connections to any older versions of SRT by setting the minimum version 1.3.0 as shown above. ## Short Introduction to SRT Packet Structure Every UDP packet carrying SRT traffic contains an SRT header (immediately after the UDP header). In all versions, the SRT header contains four major 32-bit fields: - `PH_SEQNO` - `PH_MSGNO` - `PH_TIMESTAMP` - `PH_ID` Their interpretation depends on the type of packet, of which there are two: *control packets* and *data packets*, defined by the first bit in the `PH_SEQNO` field. Here, for example, is a representation of an SRT 1.3.0 **data packet header** (where the "packet type" bit = 0): ``` 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| Packet Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |FF |O|KK |R| Message Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time Stamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Socket ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` **NOTE:** Packet diagrams in this document are in network bit order. While a complete description of a data packet is out of scope for this document, here is a description of some other header fields unique to SRT: - **FF** = (2 bits) Position of packet in message, where: - 10b = 1st - 00b = middle - 01b = last - 11b = single - **O** = (1 bit) Indicates whether the message should be delivered in order (1) or not (0). In File/Message mode (original UDT with UDT_DGRAM) when this bit is clear then a message that is sent later (but reassembled before an earlier message which may be incomplete due to packet loss) is allowed to be delivered immediately, without waiting for the earlier message to be completed. This is not used in Live mode because there's a completely different function used for data extraction when TSBPD mode is on. - **KK** = (2 bits) Indicates whether or not data is encrypted: - 00b: not encrypted - 01b: encrypted with even key - 10b: encrypted with odd key - **R** = (1 bit) Retransmitted packet. This flag is clear (0) when a packet is transmitted the very first time, and is set (1) if the packet is retransmitted. In **Data** packets, the third and fourth fields are interpreted as follows: - `PH_TIMESTAMP`: Usually the time when a packet was sent, although the real interpretation may vary depending on the type, and it's not important for the handshake - `PH_ID`: The **Destination Socket ID** to which a packet should be dispatched, although it may have the special value 0 when the packet is a connection request Additional details for Data packets will be discussed in the sections below covering **extension flags**. An SRT control packet header ("packet type" bit = 1) has the following structure: ``` 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| Message Type | Message Extended Type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Additional Data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time Stamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Socket ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` For **Control** packets the first two fields are interpreted respectively (using network bit order) as: - `PH_SEQNO`: - Bit 0: packet type (set to 1 for control packet) - Bits 1-15: Message Type (see enum `UDTMessageType`) - Bits 16-31: Message Extended type - `PH_MSGNO`: Additional data The type subfields (in the `PH_SEQNO` field) are used in two ways: 1. The **Message Type** (`SEQNO_MSGTYPE`) is one of the values enumerated as `UDTMessageType`, except `UMSG_EXT`. In this case, the type is determined by this value only, and the **Message Extended Type** (`SEQNO_EXTTYPE`) value should always be 0. 2. The **Message Type** is `UMSG_EXT`. In this case the actual message type is contained in the **Message Extended Type**. The **Extended Message** mechanism is theoretically open for further extensions. SRT uses some of them for its own purposes. This will be referred to later in the section on the **[SRT Extended Handshake](#the-srt-extended-handshake)**. The `Additional Data` field (`PH_MSGNO`) is used in some control messages as extra space for data. Its interpretation depends on the particular message type. Handshake messages don't use it. [Return to top of page](#srt-handshake) ## Handshake Structure The handshake portion of a control packet, which comes immediately after the UDT header and SRT header, consists of the following 32-bit fields in order: | Field | Description | |:-----------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------| | `Version` | Contains number 4 in this version. | | `Type` | In SRT versions up to 1.2.0 (HSv4) must be the value of `UDT_DGRAM`, which is 2. For usage in later versions of SRT see the "Type field" section below. | | `ISN` | Initial Sequence Number; the sequence number for the first data packet | | `MSS` | Maximum Segment Size, which is typically 1500, but can be less | | `FlightFlagSize` | Maximum number of buffers allowed to be "in flight" (sent and not ACK-ed) | | `ReqType` | Request type (see below) | | `ID` | The SOURCE socket ID from which the message is issued (target is in SRT header) | | `Cookie` | Cookie used for various processing (see below) | | `PeerIP` | Placeholder for the sender's IPv4 or IPv6 IP address, consisting of four 32-bit fields | Here is a representation of the HSv4 handshake structure (which follows immediately after the SRT control packet header): ``` 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | UDT Version {4} | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Socket Type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Initial Packet Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Maximum Packet Size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Maximum Flow Window Size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Connection Type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Socket ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SYN Cookie | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Peer IP Address | | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` And here is the equivalent portion of the HSv5 handshake structure (to simplify the comparison here, the extended portion of the HSv5 handshake structure is not shown. See the [**"UDT Legacy" and "SRT Extended" Handshakes**](#the-udt-legacy-and-srt-extended-handshakes) section for details): ``` 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | UDT Version {5} | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Encryption Flags | Extension Flags | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Initial Packet Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Maximum Packet Size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Maximum Flow Window Size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Connection Type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Socket ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SYN Cookie | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Peer IP Address | | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` The HSv4 (UDT-legacy based) handshake is based on two rules: 1. The complete handshake process, which establishes the connection, is the same as the UDT handshake. 2. The required SRT data interchange is done **after the connection is established** using **SRT Extended Message** with the following Extended Types: - `SRT_CMD_HSREQ`/`SRT_CMD_HSRSP`, which exchange special SRT flags as well as a latency value - `SRT_CMD_KMREQ`/`SRT_CMD_KMRSP` (optional), which exchange the wrapped stream encryption key used with encryption (`KMRSP` is used only for confirmation or error reporting) **IMPORTANT:** There are two rules in the UDT code that continue to apply to SRT version 1.2.0 and earlier, and therefore affect the prerequisites for any future versions of the protocol: 1. The initial handshake response message coming from the Listener side **DOES NOT REWRITE** the `Version` field (it's simply blindly copied from the handshake request message received). 2. The size of the handshake message must be **exactly** equal to the legacy UDT handshake structure, otherwise the message is silently rejected. As of SRT version 1.3.0 with HSv5 the handshake must only satisfy the minimum size. However, the code cannot rely on this until each peer is certain about the SRT version of the other. Even in HSv5, the **Caller** must first set two fields in the initial handshake message: - `Version` = 4 - `Type` = `UDT_DGRAM` The version recognition relies on the fact that the **Listener** returns a version of 5 (or potentially higher) if it is capable, but the **Caller** must set the `Version` to 4 to make sure that the Listener copies this value, which is how an HSv4 client is recognized. This allows SRT to handle the following combinations: 1. **HSv5 Caller vs. HSv4 Listener:** The Listener returns version 4 to the Caller, so the Caller knows it should use HSv4, and then continues the handshake the old way. 2. **HSv4 Caller vs. HSv5 Listener:** The Caller sends version 4 and the Listener returns version 5. The Caller ignores this value, however, and sends the second phase of the handshake still using version 4. This is how the Listener recognizes the HSv4 client. 3. **Both HSv5:** The Listener responds with version 5 (or potentially higher in future) and the HSv5 Caller recognizes this value as HSv5 (or higher). The Caller then initiates the second phase of the handshake according to HSv5 rules. With **Rendezvous** there's no problem because both sides try to connect to one another, so there's no copying of the handshake data. Each side crafts its own handshake individually. If the value of the `Version` field is 5 from the very beginning, and if there are any extension flags set in the `Type` field (see note below), the rules of HSv5 apply. But if one party is using version 4, the handshake continues as HSv4. **NOTE**: Previously, the `Type` field contained only the extension flags, but now it also contains the encryption flag. So for HSv5 rules to apply the extension flag needs to be expressly set. [Return to top of page](#srt-handshake) ## The "UDT Legacy" and "SRT Extended" Handshakes ### UDT Legacy Handshake The first versions of SRT did not change anything in the UDT handshake mechanisms, which are identified as *HSv4*. Here the connection process is the same as it was in UDT, and any extended SRT handshake operations are done after the HSv4 handshake is established. The HSv5 handshake was first introduced in SRT version 1.3.0. It includes all the extended SRT handshake operations in the overall handshake process (known as "integrated handshake"), which means that these data are considered exchanged and agreed upon at the moment when the connection is established. ### Initiator and Responder The addition of a new handshake mechanism necessitates the introduction of two new roles: "Initiator" and "Responder": - **Initiator:** Starts the extended SRT handshake process and sends appropriate SRT extended handshake requests - **Responder:** Expects the SRT extended handshake requests to be sent by the Initiator and sends SRT extended handshake responses back There are two basic types of SRT handshake extensions that are exchanged in both handshake versions (HSv5 introduces some more extensions): - `SRT_CMD_HSREQ`: Exchanges the basic SRT information - `SRT_CMD_KMREQ`: Exchanges the wrapped stream encryption key (used only if encryption is requested) The **Initiator** and **Responder** roles are assigned differently in *HSv4* and *HSv5*. For an *HSv4* handshake the assignments are simple: - **Initiator** is the sender, which is the party that has set the `SRTO_SENDER` socket option to *true*. - **Responder** is the receiver, which is the party that has set `SRTO_SENDER` to *false* (default). Note that these roles are independent of the connection mode (Caller/Listener/Rendezvous), and that the behavior is undefined if `SRTO_SENDER` has the same value on both parties. For an **HSv5** handshake, the roles are dependent of the connection mode: - For Caller-Listener connections: - the Caller is the **Initiator** - the Listener is the **Responder** - For Rendezvous connections: - The **Initiator** and **Responder** roles are assigned based on the initial data interchange during the handshake (see [**The Rendezvous Handshake**](#the-rendezvous-handshake) below) Note that if the handshake can be done as HSv5, the connection is always considered bidirectional and the `SRTO_SENDER` flag is unused. [Return to top of page](#srt-handshake) ### The Request Type Field The `ReqType` field in the **Handshake Structure** (see [above](#handshake-structure)) indicates the handshake message type. **Caller-Listener Request Types:** 1. Caller to Listener: `URQ_INDUCTION` 2. Listener to Caller: `URQ_INDUCTION` (reports cookie) 3. Caller to Listener: `URQ_CONCLUSION` (uses previously returned cookie) 4. Listener to Caller: `URQ_CONCLUSION` (confirms connection established) **Rendezvous Request Types:** 1. After starting the connection: `URQ_WAVEAHAND` 2. After receiving the above message from the peer: `URQ_CONCLUSION` 3. After receiving the above message from the peer: `URQ_AGREEMENT`. Note that the **Rendezvous** process is different in HSv4 and HSv5, as the latter is based on a state machine. In case when the connection process has failed when the party was about to send the `URQ_CONCLUSION` handshake, this field will contain appropriate error value. This value starts from 1000 (see `UDTRequestType` in `handshake.h`, since `URQ_FAILURE_TYPES` symbol) added with the value of the rejection reason (see `SRT_REJECT_REASON` in `srt.h`). [Return to top of page](#srt-handshake) ### The Type Field There are two possible interpretations of the `Type` field. The first is the legacy UDT "socket type", of which there are two: `UDT_STREAM` and `UDT_DGRAM` (in SRT only `UDT_DGRAM` is allowed). This legacy interpretation is applied in the following circumstances: - in an `URQ_INDUCTION` message sent initially by the Caller - in an `URQ_INDUCTION` message sent back by the HSv4 Listener - in an `URQ_CONCLUSION` message, if the other party was detected as HSv4 For more information on Induction and Conclusion see the [Caller-Listener Handshake](#the-caller-listener-handshake) section below. UDT interpreted the `Type` field as either a **Stream** or **Message** type, and rejected the connection if the parties each used a different type. Since SRT only uses the **Message** type, HSv5 uses only the `UDT_DGRAM` value for this field in cases where the message is going to be sent to an HSv4 party (which follows the UDT interpretation). In all other cases `Type` follows the HSv5 interpretation and consists of the following: - an upper 16-bit field (0 - 15) reserved for **encryption flags** - a lower 16-bit field (16 - 31) reserved for **extension flags** The **extension flags** field should have the following value: - in a `URQ_CONCLUSION` message, it should contain a combination of extension flags (with the `HS_EXT_` prefix) - in a `URQ_INDUCTION` message sent back by the Listener it should contain `SrtHSRequest::SRT_MAGIC_CODE` (0x4A17) - in all other cases it should be 0. The **encryption flags** currently occupy only 3 out of 16 bits, which are used to advertise a value for `PBKEYLEN` (packet based key length). This value is taken from the `SRTO_PBKEYLEN` option, divided by 8, giving possible values of: - 2 (AES-128) - 3 (AES-192) - 4 (AES-256) - 0 (PBKEYLEN not advertised) The `PBKEYLEN` advertisement is required due to the fact that while the Sender should decide the `PBKEYLEN`, in HSv5 the Sender might be the Responder. Therefore `PBKEYLEN` is advertised to the Initiator so that it gets this value before it starts creating the SEK on its side, to be then sent to the Responder. **REMINDER:** Initiator and Responder roles are assigned differently in HSv4 and HSv5. See the **[Initiator and Responder](#initiator-and-responder)** section above. The specification of `PBKEYLEN` is decided by the Sender. When the transmission is bidirectional, this value must be agreed upon at the outset because when both are set, the Responder wins. For Caller-Listener connections it is reasonable to set this value on the Listener only. In the case of Rendezvous the only reasonable approach is to decide upon the correct value from the different sources and to set it on both parties (note that **AES-128** is the default). [Return to top of page](#srt-handshake) ## The Caller-Listener Handshake This section describes the handshaking process where a Listener is waiting for an incoming packet on a bound UDP port, which should be an SRT handshake command (`UMSG_HANDSHAKE`) from a Caller. The process has two phases: *induction* and *conclusion*. ### The Induction Phase The Caller begins by sending an "induction" message, which contains the following (significant) fields: - **Version:** must always be 4 - **Type:** `UDT_DGRAM` (2) - **ReqType:** `URQ_INDUCTION` - **ID:** Socket ID of the Caller - **Cookie:** 0 The **Destination Socket ID** (in the SRT header) in this message is 0, which is interpreted as a connection request. **NOTE:** This phase serves only to set a cookie on the Listener so that it doesn't allocate resources, thus mitigating a potential DOS attack that might be perpetrated by flooding the Listener with handshake commands. An **HSv4** Listener responds with **exactly the same values**, except: - **ID:** Socket ID of the HSv4 Listener - **SYN Cookie:** a cookie that is crafted based on host, port and current time with 1 minute accuracy An **HSv5** Listener responds with the following: - **Version:** 5 - **Type:** - Extension Field (lower 16 bits): `SrtHSRequest::SRT_MAGIC_CODE` - Encryption Field (upper 16 bits): Advertised `PBKEYLEN` - **ReqType:** (UDT Connection Type) `URQ_INDUCTION` - **ID:** Socket ID of the HSv5 Listener - **SYN Cookie:** a cookie that is crafted based on host, port and current time with 1 minute accuracy **NOTE:** The HSv5 Listener still doesn't know the version of the Caller, and it responds with the same set of values regardless of whether the Caller is version 4 or 5. The important differences between HSv4 and HSv5 in this respect are: 1. The **HSv4** party completely ignores the values reported in `Version` and `Type`. It is, however, interested in the `Cookie` value, as this must be passed to the next phase. It does interpret these fields, but only in the "conclusion" message. 2. The **HSv5** party does interpret the values in `Version` and `Type`. If it receives the value 5 in `Version`, it understands that it comes from an HSv5 party, so it knows that it should prepare the proper HSv5 messages in the next phase. It also checks the following in the `Type` field: - whether the lower 16-bit field (extension flags) contains the magic value (see the **[Type Field](#the-type-field)** section above); otherwise the connection is rejected. This is a contingency for the case where someone who, in attempting to extend UDT independently, increases the `Version` value to 5 and tries to test it against SRT. - whether the upper 16-bit field (encryption flags) contain a non-zero value, which is interpreted as an advertised `PBKEYLEN` (in which case it is written into the value of the `SRTO_PBKEYLEN` option). [Return to top of page](#srt-handshake) ### The Conclusion Phase Once the Caller gets its cookie, it sends a `URQ_CONCLUSION` handshake message to the Listener. The following values are set by an HSv4 Caller. Note that the same values must be used by an HSv5 Caller when the Listener has returned Version 4 in its `URQ_INDUCTION` response: - **Version:** 4 - **Type:** `UDT_DGRAM` (SRT must have this legacy UDT socket type only) - **ReqType:** `URQ_CONCLUSION` - **ID:** Socket ID of the Caller - **Cookie:** the cookie previously received in the induction phase If an HSv5 Caller receives a confirmation from a Listener that it can use the version 5 handshake, it fills in the following values: - **Version:** 5 - **Type:** appropriate Extension Flags and Encryption Flags (see below) - **ReqType:** `URQ_CONCLUSION` - **ID:** Socket ID of the Caller - **Cookie:** the cookie previously received in the induction phase The Destination Socket ID (in the SRT header, `PH_ID` field) in this message is the socket ID that was previously received in the induction phase in the `ID` field in the handshake structure. The **Type** field contains: - **Encryption Flags:** advertised `PBKEYLEN` (see above) - **Extension Flags:** The `HS_EXT_` prefixed flags defined in `CHandShake` - see the **[SRT Extended Handshake](#the-srt-extended-handshake)** section below. The Listener responds with the same values shown above, without the cookie (which isn't needed here), as well as the extensions for HSv5 (which will probably be exactly the same). **IMPORTANT:** There isn't any "negotiation" here. If the values passed in the handshake are in any way not acceptable by the other side, the connection will be rejected. The only case when the Listener can have precedence over the Caller is the advertised `PBKEYLEN` in the `Encryption Flags` field in `Type` field. The value for latency is always agreed to be the greater of those reported by each party. [Return to top of page](#srt-handshake) ## The Rendezvous Handshake When two parties attempt to connect in **Rendezvous** mode, they are considered to be equivalent: Both are connecting, but neither is listening, and they expect to be contacted (over the same port number for both parties) specifically by the same party with which they are trying to connect. Therefore, it's perfectly safe to assume that, at some point, each party will have agreed upon the connection, and that no induction-conclusion phase split is required. Even so, the Rendezvous handshake process is more complicated. The basics of a Rendezvous handshake are the same in HSv4 and HSv5 - the description of the HSv4 process is a good introduction for HSv5. However, HSv5 has more data to exchange and more conditions to be taken into account. [Return to top of page](#srt-handshake) ### HSv4 Rendezvous Process Initially, each party sends an SRT control message of type `UMSG_HANDSHAKE` to the other, with the following fields: - **Version:** 4 (HSv4 only) - **Type:** `UDT_DGRAM` (HSv4 only) - **ReqType:** `URQ_WAVEAHAND` - **ID:** Socket ID of the party sending this message - **Cookie:** 0 When the `srt_connect()` function is first called by an application, each party sends this message to its peer, and then tries to read a packet from its underlying UDP socket to see if the other party is alive. Upon reception of an `UMSG_HANDSHAKE` message, each party initiates the second (conclusion) phase by sending this message: - **Version:** 4 - **Type:** `UDT_DGRAM` - **ReqType:** `URQ_CONCLUSION` - **ID:** Socket ID of the party sending this message - **Cookie:** 0 At this point, they are considered to be connected. When either party receives this message from its peer again, it sends another message with the `ReqType` field set as `URQ_AGREEMENT`. This is a formal conclusion to the handshake process, required to inform the peer that it can stop sending conclusion messages (note that this is UDP, so neither party can assume that the message has reached its peer). With HSv4 there's no debate about who is the Initiator and who is the Responder because this transaction is unidirectional, so the party that has set the `SRTO_SENDER` flag is the Initiator and the other is Responder (as is usual with HSv4). [Return to top of page](#srt-handshake) ### HSv5 Rendezvous Process The HSv5 Rendezvous process introduces a state machine, and therefore is slightly different from HSv4, although it is still based on the same message request types. Both parties start with `URQ_WAVEAHAND` and use a `Version` value of 5. The version recognition is easy - the HSv4 client does not look at the `Version` value, whereas HSv5 clients can quickly recognize the version from the `Version` field. The parties only continue with the HSv5 Rendezvous process when `Version` = 5 for both. Otherwise the process continues exclusively according to *HSv4* rules. With HSv5 Rendezvous, both parties create a cookie for a process called a "cookie contest". This is necessary for the assignment of Initiator and Responder roles. Each party generates a cookie value (a 32-bit number) based on the host, port, and current time with 1 minute accuracy. This value is scrambled using an MD5 sum calculation. The cookie values are then compared with one another. Since you can't have two sockets on the same machine bound to the same device and port and operating independently, it's virtually impossible that the parties will generate identical cookies. However, this situation may occur if an application tries to "connect to itself" - that is, either connects to a local IP address, when the socket is bound to INADDR_ANY, or to the same IP address to which the socket was bound. If the cookies are identical (for any reason), the connection will not be made until new, unique cookies are generated (after a delay of up to one minute). In the case of an application "connecting to itself", the cookies will always be identical, and so the connection will never be made. ```c++ // Here m_ConnReq.m_iCookie is a local cookie value sent in connection request to the peer. // m_ConnRes.m_iCookie is a cookie value sent by the peer in its connection request. const int64_t contest = int64_t(m_ConnReq.m_iCookie) - int64_t(m_ConnRes.m_iCookie); if ((contest & 0xFFFFFFFF) == 0) { return HSD_DRAW; } if (contest & 0x80000000) { return HSD_RESPONDER; } return HSD_INITIATOR; ``` When one party's cookie value is greater than its peer's (based on 32-bit subtraction of both with potential overflow), it wins the cookie contest and becomes Initiator (the other party becomes the Responder). At this point there are two "handshake flows" possible (at least theoretically): *serial* and *parallel*. #### Serial Handshake Flow In the **serial** handshake flow, one party is always first, and the other follows. That is, while both parties are repeatedly sending `URQ_WAVEAHAND` messages, at some point one party - let's say Alice - will find she has received a `URQ_WAVEAHAND` message before she can send her next one, so she sends a `URQ_CONCLUSION` message in response. Meantime, Bob (Alice's peer) has missed her `URQ_WAVEAHAND` messages, and so Alice's `URQ_CONCLUSION` is the first message Bob has received from her. This process can be described easily as a series of exchanges between the first and following parties (Alice and Bob, respectively): 1. Initially, both parties are in the *waving* state. Alice sends a handshake message to Bob: - **Version:** 5 - **Type:** Extension field: 0, Encryption field: advertised `PBKEYLEN`. - **ReqType:** `URQ_WAVEAHAND` - **ID:** Alice's socket ID - **Cookie:** Created based on host/port and current time Keep in mind that while Alice doesn't yet know if she is sending this message to an HSv4 or HSv5 peer, the values from these fields would not be interpreted by an HSv4 peer when the **ReqType** is `URQ_WAVEAHAND`. 2. Bob receives Alice's `URQ_WAVEAHAND` message, switches to the *attention* state. Since Bob now knows Alice's cookie, he performs a "cookie contest" (compares both cookie values). If Bob's cookie is greater than Alice's, he will become the **Initiator**. Otherwise, he will become the **Responder**. **IMPORTANT**: The resolution of the [Handshake Role](#initiator-and-responder) (Initiator or Responder) is essential to further processing. Then Bob responds: - **Version:** 5 - **Type:** - *Extension field:* appropriate flags if Initiator, otherwise 0 - *Encryption field:* advertised `PBKEYLEN` - **ReqType:** `URQ_CONCLUSION` **NOTE:** If Bob is the Initiator and encryption is on, he will use either his own `PBKEYLEN` or the one received from Alice (if she has advertised `PBKEYLEN`). 3. Alice receives Bob's `URQ_CONCLUSION` message. While at this point she also performs the "cookie contest", the outcome will be the same. She switches to the *fine* state, and sends: - **Version:** 5 - **Type:** Appropriate extension flags and encryption flags - **ReqType:** `URQ_CONCLUSION` **NOTE:** Both parties always send extension flags at this point, which will contain `SRT_CMD_HSREQ` if the message comes from an Initiator, or `SRT_CMD_HSRSP` if it comes from a Responder. If the Initiator has received a previous message from the Responder containing an advertised `PBKEYLEN` in the encryption flags field (in the `Type` field), it will be used as the key length for key generation sent next in the `SRT_CMD_KMREQ` block. 4. Bob receives Alice's `URQ_CONCLUSION` message, and then does one of the following (depending on Bob's role): - If Bob is the Initiator (Alice's message contains `SRT_CMD_HSRSP`), he: - switches to the *connected* state - sends Alice a message with `ReqType` = `URQ_AGREEMENT`, but containing no SRT extensions (*Extension flags* in `Type` should be 0) - If Bob is the Responder (Alice's message contains `SRT_CMD_HSREQ`), he: - switches to *initiated* state - sends Alice a message with ReqType = `URQ_CONCLUSION` that also contains extensions with `SRT_CMD_HSRSP` - awaits a confirmation from Alice that she is also connected (preferably by `URQ_AGREEMENT` message) 5. Alice receives the above message, enters into the *connected* state, and then does one of the following (depending on Alice's role): - If Alice is the Initiator (received `URQ_CONCLUSION` with `SRT_CMD_HSRSP`), she sends Bob a message with `ReqType` = `URQ_AGREEMENT`. - If Alice is the Responder, the received message has `ReqType` = `URQ_AGREEMENT` and in response she does nothing. 6. At this point, if Bob was Initiator, he is connected already. If he was a Responder, he should receive the above `URQ_AGREEMENT` message, after which he switches to the *connected* state. In the case where the UDP packet with the agreement message gets lost, Bob will still enter the *connected* state once he receives anything else from Alice. If Bob is going to send, however, he has to continue sending the same `URQ_CONCLUSION` until he gets the confirmation from Alice. [Return to top of page](#srt-handshake) #### Parallel Handshake Flow The serial handshake flow described above happens in almost every case. There is, however, a very rare (but still possible) **parallel** flow that only occurs if the messages with `URQ_WAVEAHAND` are sent and received by both peers at precisely the same time. This *might* happen in one of these situations: - if both Alice and Bob start sending `URQ_WAVEAHAND` messages perfectly simultaneously, or - if Bob starts later but sends his `URQ_WAVEAHAND` message during the gap between the moment when Alice had earlier sent her message, and the moment when that message is received (that is, if each party receives the message from its peer immediately after having sent its own), or - if, at the beginning of `srt_connect`, Alice receives the first message from Bob exactly during the very short gap between the time Alice is adding a socket to the connector list and when she sends her first `URQ_WAVEAHAND` message The resulting flow is very much like Bob's behaviour in the serial handshake flow, but for both parties. Alice and Bob will go through the same state transitions: Waving -> Attention -> Initiated -> Connected In the *Attention* state they know each other's cookies, so they can assign roles. It is important to understand that, in contrast to serial flows, which are mostly based on request-response cycles, here everything happens completely asynchronously: the state switches upon reception of a particular handshake message with appropriate contents (the Initiator must attach the `HSREQ` extension, and Responder must attach the `HSRSP` extension). Here's how the parallel handshake flow works, based on roles: **Initiator:** 1. `Waving` - Receives `URQ_WAVEAHAND` message - Switches to `Attention` - Sends `URQ_CONCLUSION` + `HSREQ` 2. `Attention` - Receives `URQ_CONCLUSION` message, which: - contains no extensions: - switches to `Initiated`, still sends `URQ_CONCLUSION` + `HSREQ` - contains `HSRSP` extension: - switches to `Connected`, sends `URQ_AGREEMENT` 3. `Initiated` - Receives `URQ_CONCLUSION` message, which: - Contains no extensions: - REMAINS IN THIS STATE, still sends `URQ_CONCLUSION` + `HSREQ` - contains `HSRSP` extension: - switches to `Connected`, sends `URQ_AGREEMENT` 4. `Connected` - May receive `URQ_CONCLUSION` and respond with `URQ_AGREEMENT`, but normally by now it should already have received payload packets. **Responder:** 1. `Waving` - Receives `URQ_WAVEAHAND` message - Switches to `Attention` - Sends `URQ_CONCLUSION` message (with no extensions) 2. `Attention` - Receives `URQ_CONCLUSION` message with `HSREQ` **NOTE:** This message might contain no extensions, in which case the party shall simply send the empty `URQ_CONCLUSION` message, as before, and remain in this state. - Switches to `Initiated` and sends `URQ_CONCLUSION` message with `HSRSP` 3. `Initiated` - Receives: - `URQ_CONCLUSION` message with `HSREQ` - responds with `URQ_CONCLUSION` with `HSRSP` and remains in this state - `URQ_AGREEMENT` message - responds with `URQ_AGREEMENT` and switches to `Connected` - Payload packet - responds with `URQ_AGREEMENT` and switches to `Connected` 4. `Connected` - Is not expecting to receive any handshake messages anymore. The `URQ_AGREEMENT` message is always sent only once or per every final `URQ_CONCLUSION`message. Note that any of these packets may be missing, and the sending party will never become aware. The missing packet problem is resolved this way: 1. If the Responder misses the `URQ_CONCLUSION` + `HSREQ` message, it simply continues sending empty `URQ_CONCLUSION` messages. Only upon reception of `URQ_CONCLUSION` + `HSREQ` does it respond with `URQ_CONCLUSION` + `HSRSP`. 2. If the Initiator misses the `URQ_CONCLUSION` + `HSRSP` response from the Responder, it continues sending `URQ_CONCLUSION` + `HSREQ`. The Responder must always respond with `URQ_CONCLUSION` + `HSRSP` when the Initiator sends `URQ_CONCLUSION` + `HSREQ`, even if it has already received and interpreted it. 3. When the Initiator switches to the `Connected` state it responds with a `URQ_AGREEMENT` message, which may be missed by the Responder. Nonetheless, the Initiator may start sending data packets because it considers itself connected - it doesn't know that the Responder has not yet switched to the `Connected` state. Therefore it is exceptionally allowed that when the Responder is in the `Initiated` state and receives a data packet (or any control packet that is normally sent only between connected parties) over this connection, it may switch to the `Connected` state just as if it had received a `URQ_AGREEMENT` message. 4. If the the Initiator is already switched to the `Connected` state it will not bother the Responder with any more handshake messages. But the Responder may be completely unaware of that (having missed the `URQ_AGREEMENT` message from the Initiator). Therefore it doesn't exit the connecting state (still blocks on `srt_connect` or doesn't signal connection readiness), which means that it continues sending `URQ_CONCLUSION` + `HSRSP` messages until it receives any packet that will make it switch to the `Connected` state (normally `URQ_AGREEMENT`). Only then does it exit the connecting state and the application can start transmission. [Return to top of page](#srt-handshake) ### Rendezvous Between Different Versions When one of the parties in a handshake supports HSv5 and the other only HSv4, the handshake is conducted according to the rules described in the **[HSv4 Rendezvous Process](#hsv4-rendezvous-process)** section above. Note, though, that in the first phase the `URQ_WAVEAHAND` request type sent by the HSv5 party contains the `m_iVersion` and `m_iType` fields filled in as required for version 5. This happens only for the "waving" phase, and fortunately HSv4 clients ignore these fields. When switching to the conclusion phase, the HSv5 client is already aware that the peer is HSv4 and fills the fields of the conclusion handshake message according to the rules of HSv4. [Return to top of page](#srt-handshake) ## The SRT Extended Handshake ### HSv4 Extended Handshake Process The HSv4 extended handshake process starts **after the connection is considered established**. Whatever problems may occur after this point *will only affect data transmission*. Here is a representation of the HSv4 extended handshake packet structure (including the first four 32-bit segments of the SRT header): ``` 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=0x7fff | Ext {HSREQ(1),HSRSP(2)} | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Additional Info = undefined | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time Stamp (µsec) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Socket ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SRT Version {<10300h} | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SRT Flags | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TsbPd Resv = 0 | TsbPdDelay {20..8000} | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved = 0 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` The HSv4 extended handshake is performed with the use of the [aforementioned](#overview) "SRT Extended Messages", using control messages with major type `UMSG_EXT`. Note that these command messages, although sent over an established connection, are still simply UDP packets. As such they are subject to all the problematic UDP protocol phenomena, such as packet loss (packet recovery applies exclusively to the payload packets). Therefore messages are sent "stubbornly" (with a slight delay between subsequent retries) until the peer responds, with some maximum number of retries before giving up. It's very important to understand that the first message from an Initiator is sent at the same moment when the application requests transmission of the first data packet. This data packet is **not** held back until the extended SRT handshake is finished. The first command message is sent, followed by the first data packet, and the rest of the transmission continues without having the extended SRT handshake yet agreed upon. This means that the initial few data packets might be sent without having the appropriate SRT settings already working, which may raise two concerns: - *There is a delay in the application of latency to received packets* - At first, packets are being delivered immediately. It is only when the `SRT_CMD_HSREQ` message is processed that latency is applied to the received packets. The time stamp based packet delivery mechanism (TSBPD) isn't working until then. - *There is a delay in the application of encryption (if used) to received packets* - Packets can't be decrypted until the `SRT_CMD_KMREQ` is processed and the keys installed. The data packets are still encrypted, but the receiver can't decrypt them and will drop them. The codes for commands used are the same in HSv4 and HSv5 processes. In HSv4 these are minor message type codes used with the `UMSG_EXT` command, whereas in HSv5 they are in the "command" part of the extension block. The messages that are sent as "REQ" parts will be repeatedly sent until they get a corresponding "RSP" part, up to some timeout, after which they give up and stay with a pure UDT connection. [Return to top of page](#srt-handshake) ### HSv5 Extended Handshake Process Here is a representation of the HSv5 **integrated** handshake packet structure (without SRT header): ``` 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ --- | UDT Version {5} | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | Encryption Flags | Extension Flags | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | Initial Packet Sequence Number | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | Maximum Packet Size | H +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ A | Maximum Flow Window Size | N +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ D | Connection Type | S +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ H | Socket ID | A +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ K | SYN Cookie | E +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | Peer IP Address | | | | | | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ --- | Ext Type=SRT_CMD_HSREQ(1) | Ext Size {3} | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ H | SRT Version {>=10300h} | S +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ R | SRT Flags | E +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Q | RcvTsbPdDelay {20..8000} | SndTsbPdDelay {20..8000} | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ --- | Ext Type=SRT_CMD_KMREQ(3) | Ext Size (bytes/4) | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |0| V{1} PT{2}| Sign {2029h} | Resv {0} |KK| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | KEKI {0} | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | Cipher {2} | Auth {0} | SE {2} | Resv1 {0} | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | Recv2 {0} | Slen(bytes)/4 | klen(bytes)/4 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | Salt[Slen] | | | | | | | K | | M +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ R | Wrap[((KK+1/2)*Klen) + 8] | E | | Q | | | | | | | | | | | | | | | | | | | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ --- ``` The **Extension Flags** subfield in the `Type` field in a conclusion handshake message contains one of these flags: - `HS_EXT_HSREQ`: defines SRT characteristic data; always present - `HS_EXT_KMREQ`: if using encryption, defines encryption block - `HS_EXT_CONFIG`: informs about having extra configuration data attached The above schema shows the HSv5 packet structure, which can be split into three parts: 1. The Handshake data part (up to "Peer IP Address" field) 2. The HSREQ extension 3. The KMREQ extension Note that extensions are added only in certain situations (as described above), so sometimes there are no extensions at all. When extensions are added, the HSREQ extension is always present. The KMREQ extension is added only if encryption is requested (the passphrase is set by the `SRTO_PASSPHRASE` socket option). There might be also other extensions placed after HSREQ and KMREQ. Every extension block has the following structure: (1) a 16-bit command symbol (2) 16-bit block size (number of 32-bit words following this field) (3) a number of 32-bit fields, as specified in (2) above What is contained in a block depends on the extension command code. The data being received in the extension blocks in the conclusion message undergo further verification. If the values are not acceptable, the connection will be rejected. This may happen in the following situations: 1. The `Version` field contains 0. This means that the peer rejected the handshake. 2. The `Version` field was higher than 4, but no extensions were added (no extension flags set), while the rules state that they should be present. This is considered an error in the case of a `URQ_CONCLUSION` message sent by the Initiator to the Responder (there can be an initial conclusion message without extensions sent by the Responder to the Initiator in Rendezvous connections). 3. Processing of any of the extension data has failed (also due to an internal error). 4. Each side declares a transmission type that is not compatible with the other. This will be described further, along with other new HSv5 features; the HSv4 client supports only and exclusively one transmission type, which is *Live*. This is indicated in the `Type` field in the HSv4 handshake, which must be equal to `UDT_DGRAM` (2), and in the HSv5 by the extra *Smoother* block declaration (see below). In any case, when there's no *Smoother* declared, *Live* is assumed. Otherwise the Smoother type must be exactly the same on both sides. **NOTE:** The `TsbPd Resv` and `TsbPdDelay` fields both refer to latency, but the use is different in HSv4 and HSv5. In HSv4, only the lower 16 bits (`TsbPdDelay`) are used. The upper 16 bits (`TsbPd Resv`) are simply unused. There's only one direction, so `HSREQ` is sent by the Sender, `HSRSP` by the Receiver. `HSREQ` contains only the Sender latency, and `HSRSP` contains only the Receiver latency. This is different from HSv5, in which the latency value for the sending direction in the lower 16 bits (`SndTsbPdDelay`, 16 - 31 in network order) and for receiving direction is placed in the upper 16 bits (`RcvTsbpdDelay`, 0 - 15). The communication is bidirectional, so there are two latency values, one per direction. Therefore both HSREQ and HSREQ messages contain both the Sender and Receiver latency values. [Return to top of page](#srt-handshake) ### SRT Extension Commands #### HSREQ and HSRSP The `SRT_CMD_HSREQ` message contains three 32-bit fields designated as: - `SRT_HS_VERSION`: string (0x00XXYYZZ) representing SRT version XX.YY.ZZ - `SRT_HS_FLAGS`: the SRT flags (see below) - `SRT_HS_LATENCY`: the latency specification ``` +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SRT Version {>=10300h} | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SRT Flags | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |(HSv4) TsbPd Resv = 0 | TsbPdDelay {20..8000} | |(HSv5) RcvTsbPdDelay {20..8000}| SndTsbPdDelay {20..8000} | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` The flags (`SRT Flags` field) are the following bits, in order: (0) `SRT_OPT_TSBPDSND`: The party will be sending in TSBPD (Time Stamp Based Packet Delivery) mode. This is used by the Sender party to specify that it will use TSBPD mode. The Responder should respond with its setting for TSBPD reception; if it isn't using TSBPD for reception, it responds with its reception TSBPD flag not set. In HSv4, this is only used by the Initiator. (1) `SRT_OPT_TSBPDRCV`: The party expects to receive in TSBPD mode. This is used by a party to specify that it expects to receive in TSBPD mode. The Responder should respond to this setting with TSBPD sending mode (HSv5 only) and set the sending TSBPD flag appropriately. In HSv4 this is only used by the Responder party. (2) `SRT_OPT_HAICRYPT`: The party includes `haicrypt` (legacy flag). This **special legacy compatibility flag** should be always set. See below for more details. (3) `SRT_OPT_TLPKTDROP`: The party will do TLPKTDROP. Declares the `SRTO_TLPKTDROP` flag of the party. This is important because both parties must cooperate in this process. In HSv5, if both directions are TSBPD, both use this setting. While it is not always necessary to set this flag in live mode, it is the default and most recommended setting. (4) `SRT_OPT_NAKREPORT`: The party will do periodic NAK reporting. Declares the `SRTO_NAKREPORT` flag of the party. This flag means that periodic NAK reports will be sent (repeated `UMSG_LOSSREPORT` message when the sender seems to linger with retransmission). (5) `SRT_OPT_REXMITFLG`: The party uses the REXMIT flag. This **special legacy compatibility flag** should be always set. See below for more details. (6) `SRT_OPT_STREAM`: The party uses stream type transmission. This is introduced in HSv5 only. When set, the party is using a stream type transmission (file transmission with no boundaries). In HSv4 this flag does not exist, and therefore it's always clear, which corresponds to the fact that HSv4 supports Live mode only. **Special Legacy Compatibility Flags** The `SRT_OPT_HAICRYPT` and `SRT_OPT_REXMITFLG` fields define special cases for the interpretation of the contents in the SRT header for payload packets. The SRT header contains an unusual field designated as `PH_MSGNO`, which contains first some extra flags that occupy the most significant bits in this field (the rest are assigned to the Message Number). Some of these extra flags were already in UDT, but SRT added some more by stealing bits from the Message Number subfield: 1. **Encryption Key** flags (2 bits). Controlled by `SRT_OPT_HAICRYPT`, this field contains a value that declares whether the payload is encrypted and with which key. 2. **Retransmission** flag (1 bit). Controlled by `SRT_OPT_REXMITFLG`, this flag is 0 when a packet is sent the first time, and 1 when it is retransmitted (i.e. requested in a loss report). When the incoming packet is late (one with a sequence number older than the newest received so far), this flag allows the Receiver to distinguish between a retransmitted packet and a reordered packet. This is used by the "reorder tolerance" feature described in the API documentation under `SRTO_LOSSMAXTTL` socket option. As of version 1.2.0 both these fields are in use, and therefore both these flags must always be set. In theory, there might still exist some SRT versions older than 1.2.0 where these flags are not used, and these extra bits remain part of the "Message Number" subfield. In practice there are no versions around that do not use encryption bits, although there might be some old SRT versions still in use that do not include the Retransmission field, which was introduced in version 1.2.0. In practice both these flags must be set in the version that has them defined. They might be reused in future for something else, once all versions below 1.2.0 are decommissioned, but the default is for them to be set. The `SRT_HS_LATENCY` field defines Sender/Receiver latency. It is split into two 16-bit parts. The usage differs in HSv4 and HSv5. In **HSv4** only the lower part (bits 16 - 31) is used. The upper part (bits 0 - 15) is always 0. The interpretation of this field is as follows: - Receiver party: Receiver latency - Sender party: Sender latency In **HSv5** both 16-bit parts of the field are used, and interpreted as follows:: - Upper 16 bits (0 - 15): Receiver latency - Lower 16 bits (16 - 31): Sender latency The characteristics of Sender and Receiver latency are the following: 1. **Sender latency** is the minimum latency that the Sender wants the Receiver to use. 2. **Receiver latency** is the (minimum) value that the Receiver wishes to apply to the stream that it will be receiving. Once these values are exchanged via the extended handshake, an **effective latency** is established, which is always the maximum of the two. Note that latency is defined in a specified direction. In HSv5, a connection is bidirectional, and a separate latency is defined for each direction. The Initiator sends an `HSREQ` message, which declares the values on its side. The Responder calculates the maximum values between what it receives in the `HSREQ`and its own values, then sends an `HSRSP` with the effective latencies. Here is an example of an **HSv5 bidirectional transmission** between Alice and Bob, where Alice is Initiator: 1. Alice and Bob set the following latency values: - Alice: `SRTO_PEERLATENCY` = 250 ms, `SRTO_RCVLATENCY` = 550 ms - Bob: `SRTO_PEERLATENCY` = 500 ms, `SRTO_RCVLATENCY` = 300 ms 2. Alice defines the latency field in the HSREQ message: ``` hs[SRT_HS_LATENCY] = { 250, 550 }; // { Lower, Upper } ``` 3. Bob receives it, sets his options, and responds with `HSRSP`: ``` SRTO_RCVLATENCY = max(300, 250); //<-- 250:Alice's PEERLATENCY SRTO_PEERLATENCY = max(500, 550); //<-- 550:Alice's RCVLATENCY hs[SRT_HS_LATENCY] = { 550, 300 }; ``` 4. Alice receives this `HSRSP` and sets: ``` SRTO_RCVLATENCY = 550; SRTO_PEERLATENCY = 300; ``` We now have the **effective latency** values: - For transmissions from Alice to Bob: 300ms - For transmissions from Bob to Alice: 550ms Here is an example of an *HSv4* exchange, which is simpler because there's only one direction. We'll refer to Alice to Bob again to be consistent with the Initiator/Responder roles in the HSv5 example: 1. Alice sets `SRTO_LATENCY` to 250 ms 2. Bob sets `SRTO_LATENCY` to 300 ms 3. Alice sends `hs[SRT_HS_LATENCY] = { 250, 0 };` to Bob 4. Bob does `SRTO_LATENCY = max(300, 250);` 5. Bob sends `hs[SRT_HS_LATENCY] = {300, 0};` to Alice 6. Alice sets `SRTO_LATENCY` to 300 Note that the `SRTO_LATENCY` option in HSv5 sets both `SRTO_RCVLATENCY` and `SRTO_PEERLATENCY` to the same value, although when reading, `SRTO_LATENCY` is an alias to `SRTO_RCVLATENCY`. Why is the Sender latency updated to the effective latency for that direction? Because the `TLPKTDROP` mechanism, which is used by default in Live mode, may cause the Sender to decide to stop retransmitting packets that are known to be too late to retransmit. This latency value is one of the factors taken into account to calculate the time threshold for `TLPKTDROP`. [Return to top of page](#srt-handshake) #### KMREQ and KMRSP `KMREQ` and `KMRSP` contain the KMX (key material exchange) message used for encryption. The most important part of this message is the AES-wrapped key (see [SRT Encryption](encryption.md) for details). If the encryption process on the Responder side was successful, the response contains the same message for confirmation. Otherwise it's one single 32-bit value that contains the value of `SRT_KMSTATE` type, as an error status. Note that when the encryption settings are different at each end, then the connection is still allowed, but with the following restrictions: - If the Initiator declares encryption, but the Responder does not, then the Responder responds with `SRT_KM_S_NOSECRET` status. This means that the Responder will not be able to decrypt data sent by the Initiator, but the Responder can still send unencrypted data to the Initiator. - If the Initiator did not declare encryption, but the Responder did, then the Responder will attach `SRT_CMD_KMRSP` (despite the fact that the Initiator did not send `SRT_CMD_KMREQ`) with `SRT_KM_S_UNSECURED` status. The Responder won't be able to send data to the Initiator (more precisely, it will send scrambled data, not able to be decrypted), but the Initiator will still be able to send unencrypted data to the Responder. - If both have declared encryption, but have set different passwords, the Responder will send a `KMRSP` block with an `SRT_KM_S_BADSECRET` value. The transmission in both directions will be "scrambled" (encrypted and not decryptable). The value of the encryption status can be retrieved from the `SRTO_SNDKMSTATE` and `SRTO_RCVKMSTATE` options. The legacy (or unidirectional) option `SRTO_KMSTATE` resolves to `SRTO_RCVKMSTATE` by default, unless the `SRTO_SENDER` option is set to *true*, in which case it resolves to `SRTO_SNDKMSTATE`. The values retrieved from these options depend on the result of the KMX process: 1. If only one party declares encryption, the KM state will be one of the following: - For the party that declares no encryption: - `RCVKMSTATE: NOSECRET` - `SNDKMSTATE: UNSECURED` - Result: This party can send payloads unencrypted, but it can't decrypt packets received from its peer. - For the party that declares encryption: - `RCVKMSTATE: UNSECURED` - `SNDKMSTATE: NOSECRET` - Result: This party can receive unencrypted payloads from its peer, and will be able to send encrypted payloads to the peer, but the peer won't decrypt them. 2. If both declare encryption, but they have different passwords, then both states are `SRT_KM_S_BADSECRET`. In such a situation both sides may send payloads, but the other party won't decrypt them. 3. If both declare encryption and the password is the same on both sides, then both states are `SRT_KM_S_SECURED`. The transmission will be correctly performed with encryption in both directions. Note that due to the introduction of the bidirectional feature in HSv5 (and therefore the Initiator and Responder roles), the old HSv4 method of initializing the crypto objects used for security is used only in one of the directions. This is now called **"forward KMX"**: 1. The Initiator initializes its Sender Crypto (TXC) with preconfigured values. The SEK and SALT values are random-generated. 2. The Initiator sends a KMX message to the Receiver. 3. The Receiver deploys the KMX message into its Receiver Crypto (RXC) This is the general process of Security Association done for the "forward direction", that is, when done by the Sender. However, as there's only one KMX process in the handshake, in HSv5 this must also initialize the crypto in the opposite direction. This is accomplished by **"reverse KMX"**: 1. The Initiator initializes its Sender Crypto (TXC), like above, and then **clones it** to the Receiver Crypto. 2. The Initiator sends a KMX message to the Responder. 3. The Responder deploys the KMX message into its Receiver Crypto (RXC) 4. The Responder initializes its Sender Crypto by **cloning** the Receiver Crypto, that is, by extracting the SEK and SALT from the Receiver Crypto and using them to initialize the Sender Crypto (clone the keys). This way the Sender (being a Responder) has the Sender Crypto initialized in a manner very similar to that of the Initiator. The only difference is that the SEK and SALT parameters in the crypto: - are random-generated on the Initiator side - are extracted (on the Responder side) from the Receiver Crypto, which was configured by the incoming KMX message The extra operations defined as "reverse KMX" happen exclusively in the HSv5 handshake. The encryption key (SEK) is normally configured to be refreshed after a predefined number of packets has been sent. To ensure the "soft handoff" to the new key, this process consists of three activities performed in order: 1. Pre-announcing of the key (SEK is sent by Sender to Receiver) 2. Switching the key (at some point packets are encrypted with the new key) 3. Decommissioning the key (removing the old, unused key) Pre-announcing is done using an SRT Extended Message with the `SRT_CMD_KMREQ` extended type, where only the "forward KMX" part is done. When the transmission is bidirectional, the key refreshing process happens completely independently for each direction, and it's always initiated by the sending side, independently of Initiator and Responder roles (actually, these roles are significant only up to the moment when the connection is considered established). The decision as to when exactly to perform particular activities belonging to the key refreshing process is made when the **number of sent packets** exceeds a certain value (up to the moment of the connection or previous refresh), which is controlled by the `SRTO_KMREFRESHRATE` and `SRTO_KMPREANNOUNCE` options: 1. Pre-announce: when # of sent packets > `SRTO_KMREFRESHRATE - SRTO_KMPREANNOUNCE` 2. Key switch: when # of sent packets > `SRTO_KMREFRESHRATE` 3. Decommission: when # of sent packets > `SRTO_KMREFRESHRATE + SRTO_KMPREANNOUNCE` In other words, `SRTO_KMREFRESHRATE` is the exact number of transmitted packets for which a key switch happens. The Pre-announce happens `SRTO_KMPREANNOUNCE` packets earlier, and Decommission happens `SRTO_KMPREANNOUNCE` packets later. The `SRTO_KMPREANNOUNCE` value serves as an intermediate delay to make sure that from the moment of switching the keys the new key is deployed on the Receiver, and that the old key is not decommissioned until the last packet encrypted with that key is received. The following activities occur when keys are refreshed: 1. **Pre-announce:** The new key is generated and sent to the Receiver using the SRT Extended Message `SRT_CMD_KMREQ`. The received key is deployed into the Receiver Crypto. The Receiver sends back the same message through `SRT_CMD_KMRSP` as a confirmation that the refresh was successful (if it wasn't, the message contains an error code). 2. **Key Switch:** The Encryption Flags in the `PH_MSGNO` field get toggled between `EK_EVEN` and `EK_ODD`. From this moment on, the opposite (newly generated) key is used. 3. **Decommission:** The old key (the key that was used with the previous flag state) is decommissioned on both the Sender and Receiver sides. The place for the key remains open for future key refreshing. **NOTE** The handlers for `KMREQ` and `KMRSP` are the same for handling the request coming through an SRT Extended Message and through the handshake extension blocks, except that in case of the SRT Extended Message only one direction (forward KMX) is updated. HSv4 relies only on these messages, so there's no difference between initial and refreshed KM exchange. In HSv5 the initial KM exchange is done within the handshake in both directions, and then the key refresh process is started by the Sender and it updates the key for one direction only. [Return to top of page](#srt-handshake) #### Congestion controller This is a feature supported by HSv5 only. This adds functionality that has existed in UDT as "Congestion control class", but implemented with SRT workflows and requirements in mind. In SRT, the congestion control mechanism must be set the same on both sides and is identified by a character string. The extension type is set to `SRT_CMD_CONGESTION`. The extension block contains the length of the content in 4-byte words. The content is encoded as a string extended to full 4-byte chunks with padding NUL characters if needed, and then inverted on each 4-byte mark. For example, a "STREAM" string would be extended to `STREAM@@` and then inverted into `ERTS@@MA` (where `@` marks the NUL character). The value is a string with the name of the SRT Congestion Controller type. The default one is called "live". The SRT 1.3.0 version contains an additional optional Congestion Controller type called "file". Within the "file" Congestion Controller it is possible to designate a stream mode and a message mode (the "live" one may only use the message mode, with one message per packet). This extension is optional and when not present the "live" Congestion Controller is assumed. For an HSv4 party, which doesn't support this feature, it is always the case. The "file" type reintroduces the old UDT features for stream transmission (together with the `SRT_OPT_STREAM` flag) and messages that can span multiple UDP packets. The Congestion Controller controls the way the transmission is handled, how various transmission settings are applied, and how to handle any special phenomena that happen during transmission. The "file" Congestion Controller is based completely on the original `CUDTCC` class from UDT, and the rules for congestion control are completely copied from there. However, it contains many changes and allows the selection of the original UDT code in places that have been modified in SRT to support live transmission. [Return to top of page](#srt-handshake) #### Stream ID (SID) This feature is supported by HSv5 only. Its value is a string of the user's choice that can be passed from the Caller to the Listener. The symbol for this extension is `SRT_CMD_SID`. The extension block for this extension is encoded the same way as described for Congestion Controler above. The Stream ID is a string of up to 512 characters that a Caller can pass to a Listener (it's actually passed from an Initiator to a Responder in general, but in Rendezvous mode this feature doesn't make sense). To use this feature, an application should set it on a Caller socket using the `SRTO_STREAMID` option. Upon connection, the accepted socket on the Listener side will have exactly the same value set, and it can be retrieved using the same option. For more details about the prospective use of this option, please refer to the [SRT API Socket Options](../API/API-socket-options.md) and [SRT Access Control (Stream ID) Guidlines](access-control.md). srt-1.4.4/docs/features/images/000077500000000000000000000000001412557703600163555ustar00rootroot00000000000000srt-1.4.4/docs/features/images/block-aligned-5rx10c.png000066400000000000000000002102471412557703600226040ustar00rootroot00000000000000‰PNG  IHDRJÇ@åw iCCPICC ProfileH‰•–PTWÇï{Ûm—¥ÃÒ;Ò«ÀÂÒ‹)‚e—+,UÄŒ@D%”PŒ†"± ¢ °g‘  |  ¢ò=$‰ùf¾Ì7ß™9ïýæÜ{Ï=÷ž7óþ"Ùññ±°q¼$¾¯³=cSP0÷àq€bsãí¼½=À?Úâ€VßwtWsýó¼ÿj¢Ü°D7Â;¸‰œ8„»vàÄó“€Ñ+§&ů²Â4>R ÂëW9bW×ÒBטûeŽŸ/ á4ðd6›1‰3R8Hb ÂúÏÃgá‹ñøËøü4~™ BP%X¼\ÂNB¡†ÐI¸M˜",E‰êDk¢1š˜I,!6¯ßH$%’ɇEÚG*!%õ‘&HïÉbd-2‹¼…œL>D®#w‘ï“ßP(5 “LI¢¢4P®QžPÞ Q…ô„\…¸B{…ʄڄF„^ „U…í„· §  Ÿ¾-<'BQa‰°Eöˆ”‰\Y¥Šˆz‰Æ‰æ‹6ŠÞÉ©‰9ŠqŲŪىMRQTe*‹Ê¡î§ÖP¯S§hXš:Í•MË£¡ ÒæÅÅÄÅÄÓÄËÄ/‰ è(ºÝ•K/ Ÿ£Ñ?HÈIØI„I”h–‘X’”‘dJ†IæJ¶HŽJ~bH9JÅH‘j—z,–Ö’ö‘N•>%}]zN†&c%Ñɕ9'ó@–Õ’õ•Ý%[-; » '/ç,/wBîšÜœ<]ž)-_$Y~Vª`£¥P¤pEá9CœaLje”0z󊲊.ŠÉŠ•ŠƒŠËJêJþJYJ-J•‰ÊæÊáÊEÊÝÊó* *ž**M*T ªæª‘ªÇU{U—ÔÔÕÕ¨µ«Í¨Kª»ª§«7©?Ò hØj$hTiÜÕÄjškÆhžÔÒ‚µL´"µÊ´nkÃÚ¦ÚQÚ'µ‡u0::<*q]²®nŠn“î„]ÏC/K¯]ïå:•uÁ뎬ë]÷YßD?V¿Fÿ¡˜›A–A§ÁkC-CŽa™á]#Š‘“Ñ^££WÆÚÆaƧŒï™PMÚ º·¡Ý x¹zõzì­îàý³ÖÇÛ§Ì癯o†oïFêÆí7.úÙûø=ô×ðOöïØÐ°èX(Ø´nÓîMýAÒAQAÁ¸à€àÚà…ÍŽ›mžÚb²%gËØVõ­i[on“Þ»íÒváíìíçC0!!!Ù^ì*öB¨khyè<‡Å9ÎyÁer‹¸³aÖa…aÓáÖá…á3ÖG#f#m#‹#ç¢XQ¥Q¯¢]¢+¢—b¼bêbVbc[âðq!qxb¼^Ïùi;†ãµãsâ – ÇæùîüÚD(qkbG ùù$k$“<‘b“R–ò.5 õ|šh/m`§Ö΃;§ÓÒØ…ÞÅÙÕ¡˜‘™1±ÛnwåhOèžî½Ê{³÷NísÞWŸIÌŒÉü%K?«0ëíþÀýÙrÙû²'¿qþ¦)G(‡Ÿ3~Àê@Å·èo£¾ 586 455 å¥òd@IDATxì]˜EÓ.9àÈ 9ˆä(ˆ€ ð‘‘tpJ$üxDAP@@ÂE<²ŒH ’sP$ç$9þýöÝ,»{»{;»S³»Øõ<»3ÓÝÓSoõ„êîªê—þDŠ””””””””âH Aœ• $ $ $ $ $ $ $ $ % %u#( ( ( ( ( ( ( 8‘€R”œF%+ ( ( ( ( ( ( (EIÝJJJJJJJN$ÐIºL^¼t±ÜfÈ”ÞU±€Î;}ü4½ôRÊ‘çÕ€ÆáŠy…Ñ•t'Oµcà´•+NU;º’Nàä©v œ¶rÅéµ+×(a‚Dê´ØK®¼Þ (¡EJJJJJJJ/ªÚ·oïšË%Œ$AQªÒ œÓ =ãü‘ËtìÀI…1ÀRµc€7`,ûªU;ŠÔ½(-åšÏ+ÇoÐÁ}‡]R6J.Å£2•”””””þËPŠÒ¹õv%%%%%%—PŠ’Kñ¨L%%%%%%ÿ²”¢ô_n}…]I@I@I@I@I@IÀ¥\*J/¹Ýë}Œ·nÞ¢;Qw]ò–1s vYƨLŒoÑÑÏèÜ™s„gñ™ØÏ‘ûUÊ‘Ëüø€œŸ={&¾‹—èä±S”4YRÊ[ 7½œöeMl[—OÞ¥m·S«m2»õÐzù"Õ Ü¿wŸº¶ëAëVmÐ’ä¶^“P 3Ðæ!ÕSÖ¦2ƒ81‚Õè§Ñ4kÚ\ ï;Br>xtjÒ²‘Á(\WÇ… Ò˜ð±ôý¤™6 @11j…Ö°Iç<`Ãxÿô Íž:φ}`ôu?ªÛ Ä&ó€ £=ÏÖl¢¶M?”ÉP”ðœ›E\q¯–)XÑ)Œñ‘c¨zjNóÌà¨ñˆÎ¾ÃiãÚÍZ’ÜæÊ““Vn[f“ÆuÀ…±c‹Î´gÇ^—l÷Ö›š·i沌™\ÁÛžRßOÐñ#'lX-[á-6v0eÉ–Ù&ë€㟻öQ÷޽„2xÞ†ý®_|Buë`“fôKEIïˆÒ}ñ‘áeY¼T1^'NL)S¥°¤=yü„št”7qö¯PHýZ” ˆf|7›~Z°ŒD L9a¨,¯§¬åL;\Á.F zwíOïÝÏĽ{Õra\ûÛRI‚ÒP£îÛrié¿È^{ç°îôˆŔ¿P>÷˜ô²Æu«7J%I»§S¬‹ç-¥S'NS·½¨pÑB”;_./¹wït.ŒÖWôè}Õkˆu’©û\?|dÁñví*–}mǬ6Äõ¸0¢nŒ>4¬ÑL>ƒÄ°â£zX¼‹â‹-ƒó".ŒUjT¤4/§vÈæÚ•ëdzÂD‰æÈ…#mÄ·£Øo–)!xÌÎLI[ÅàEÏ{Ó¬Ÿ¦ Ça}\O‰4šÔn.¯YµfeªP¥=|œæE. 1Clj™‰ÌÔ é»y2"Ñ¥¢¤wDIcÚù'ŸuÔn7¯ß*•$(U?®œKiÓÅ ŸU©Q‰×zŸðíÙ¿›Æ×SÖáÅÆC+5”œ¶îÐ’N;§‡ÇÃe•FcÄ00´ÿ:¶’æ¸xûÎm¨rñ˜‘¤ß—¯1MQÒ€1wÞœ4ä›â¡­g™ši!ž‡·K…Ðõk7ä‹ËÌ,pQ“¶3&Ï‘=¼ÊÕ+ƶ.ǽυž 3#¸Ùw«~Œ_vÿJ~`ëÔ¯M#Æ ¡D‰ÍQœ6c‡.Žg8Ð)Õ¥jµ*;c‡%ÝhŒ[Öo“m3†YK¦[Þ;«–§šeCiûæ2ß•I‹Ñ@Æ8nÔDÉâ»ëÒˆñábÙ±íæ<_þ–ÆšDõß{Ç’n4—ÆÜF_̺¾Ÿ~øY¶ëfQ’P¬dQ*Qº¸Ì[ýër«§¬<ÁOþôòÝ<¬)-[¿ˆzîi3Eé'p²¡c‘7^“C¤˜[Ö7:z ‹ç.Ê­¿ýéÁˆ^yã -/+`Á ª °½݉º#·þö§£Æ;lF‰iFàƒìïä FÇdÏŸŒ˜rÛ¼n«¬bà¨~>W’ì±8;ÖƒÑY3¿›#³4«'í±œ•óUºŒ×ň¨D©7lÞ;ÁI‚-ìc†ÆßHFL»š}ðž2„w-Óq°]â"Ÿ)JÐrAè±ÙSÉ·b¥3'ÏÈ,=eíëòå±^¾ûïCøÐéÅè[Ôí(™œ6=¿Qž£ëÇ—æ-F”jz¾y㻜Oò=Á8¼ÿ(ÉkßðÏÅôFŸð­ç¢ž`ÔS¿?”ÕƒqçÖÝ’åÆÍؘEøW<èÁè¨8Êü8'fÁ÷–mßwTÄçiz0~½äwÅÒ•ÒV 0zž6!R¦¿õ¿R”"ås³™èz0jvI©S§²á3Q0sÁ¼‹\N½yzÑe —K¯&Ø%åÊ“ƒJŧ|åçëÅA»Å4Ã…ö”5Öð ^'zÊÚ×Ãyl$FN>½©Û ŒxiíÚ¶G²Yº\)oØõè\ŒxIáÞ}$ì\މyôo‡“Cßo¼ù:aŠÊlâÀˆ—^ÌE‹¡zMÞ¡+—®˜ ËæzqCûP§°n”@ õg£Ÿ…ФwkúdôÅhŒG•2L)¼j#'Ï¢½;þ”÷-l”J‹k}Ñ®fzö£1J€vóg.”)˜¹x-Vɰ+Âzh4F(B¡ ChÙ¢Ô4¤%uìÚŽ8"§ÂaÚ2X˜˜MFc„2e ÞÒð ¶&xÙ"/>ïFësôîª(%Iãâ ÍÎ^»«Põ4qVAyŠºý|ú!i²¸.ÓIb§fàn¬§¬^ðž”çÀè œç˜‰†x xØ”«X†–MÝœáÅÙ±E'›ë¡C0fÊ(S?<\Ÿ>yj1à†ÇPPϦ‰ câàIJý` »rÙ*›¶Œ>ž¦ÿ8Ù4×k.Œ7¯ß’¸¦O˜aƒp¨Y¾äWšöÃ$¶%Nƒ¸0Ú³ oF o؇­ì³Y91ŽNY³g¥Éc¦Ò¤1S,8þ>²‰t³ˆ cµZU¤2?¤Ïpf¡+®þs~_¾ÚâÕø,:𠦡o¸2JÓ†¿VÓî[鯳;iÍ®_©×€î’y¸žNú&¦5C,dDGǹdVOYëó¸ö90rñêi½fa\#¼à´!ð¡cÙ„ƒð”wwÏãĈO‹¶ÍÄHK(¡ƒº|ñ 5¬Þ”öÿyÀ]½.Ç…qÞŒÒ a+^/QÔk>½©€ #ìè¶øƒvÝDûÎí¢M¯ŠîHi;ˆÞk¾ð†m]çra|ôð¡ä£‚‘ §Ðþ »éàŽ41ÖxöKk[§‹WO sa´çç—EË-†ÏøøšIœ1»pvÌt":5«ÛJzSkÇÜ[.Œí»´‘f)˜‰B'´r‰šÒékÊØçÞ|„á"C%¸$¢çŒ0 ˆá²6·¶z®Z±VâH‘òy@IGÃeµ0Ò‰¹U÷Ër ɺ^ŒÖõûþþ}ˆ>lÙYÂýbPiˆh&vNŒðîë7´·ôÎ@YØžá!ïÒö39lVŒ˜R=$BpÚÛvÔÌ Lö×àÀ¨]AS ×ò$Â(6cæŒR¯EL-³asÛ33ˆ cpì ¼…ÊU*#Gû`²š#gÐ>“B•pa´nL‰O)“àYlö´"Æm›vPëFíäû1“~Ûú3­ÝýaJ´¦uZÉÆÖ²àÚç˜^è‹WÍ—•BijÕ®9õÒ‹ yÍ®7]Æt\°ÈPEÉ—o”Œ‰©„˜ s§ بØÓÙÓçeRñ¢ÒSÖ¾·ŽcÝ Ý*ë¢7]TkL–Ÿa¢ [vÅaSëÔb©_’·­yÿ~âLyˆ0,)ôò£æQ[¨hAH˜Š*þfÌ …Y£Ÿ6 Xx‹Ñª*›Ý%±a†œÓ³Ï¥¢¤7òxa©kBl ÕƒÊU,kÉ mXGîÏ™6Ï&þÁ¢y?I­#NeË¿%Ëè)k¹€»;:ãKpat—]Êù FŒ6´nØN× ­NÃ"gì'çLŸ/{®æ×èñãÇô]ì\:¦¦µÑT-ßí­`„MÒ"a jÿ›<'Æ(؇¥w•C²Ó'Æï¾fú>zð5ªÙ,ŠQ‡ÅqÿŒSÈËNŒxÂq÷?l0z´ED¡‡­hðèrËýlj‘›wwëçÄ8ràéMƒ6D$utñbÊ%ß âĈi ¬W‡w4–‡€óÁñA‚·ñ•¬§¹ðrbÔxž9e®Ü…s…¦Xhyfl91"TÿƒháÜ%Ré-/–÷€-¡æ­ [áê!üërb!•÷J"¼Jòɤb¯-õ… Õ«•gmÆ ‚œ]áÌù3tMÌÃç*ÿ0zBñA€ 8JG£¿…w"¿"j/Ü0±È­µa6†ëˆõÝN=%Ë#¤`V5sa´º=zôˆ¾ê5Ä:ÉÔ}.Œ>²àx»v˾¶cVâz\Q7FÖh&ŸÁb XñQ=,ÞÍ÷FSˆ c•)ÍË©bX»rLO˜(‘Ã|£¹0b$°ø>bôóÍ2%¤—iã#i«¼èùqošõÓt£á8¬ ã©ã§©IíæòšUkV¦ UÊÑÑÃÇi^ä3tœ˜™ÈL š¾ë'#]*JžÚ(A;ÿ䳎ñòZ©¡,ÓºCK:qìdœVÁæõ[¥’ìÇ•s)mº˜¡¶*5*QãZï>¶=ûw3uÈßhŒÀê®<4¹poƈa`hÿtl%‡MÁûÎm¨rñ˜‘¤ß—¯1MQÒdg4ÆÜysÒoˆ‡¶žej¦…xÞ.BׯÝ/.3?²Ài4FMvØÎ˜F|x&ÌŒàfß­ú90~Ùý+ù­S¿67„%6GipØhŒº8žá@§TS”ªÕªìŒ–t£1nY¿M¶!Ìf-™nyïT¬Zžj– ¥í›wÊ|3ÍTŒÆ8nÔDÙï6®K#ƇÓK±Ë†ÀœçëÁßÒøQ“¨þ{ïXÒn8—ÆÜF_̾¾æaMiÙúEÔ{pÏ8SrÖeúágyØ®S˜EIBB±’E©Déâ2oõ¯È­¿ý¹‹|ë)ëO8Ýå»È¯É!RÌ-k„=ÐÅsåÖÿÜň^yã -/+`Á ª °§݉º#·þøç.FwØ"ŒÓŒÀ8H/Æ@ÀdÏ£»1å¶yÝVyúÀQý|®$Ùãpuì.FguÌünŽÌjЬž´ÇrVΗéîb¼.F”@%J½aóÞ Nla³.þHîbÄ´¨ÙïÙ(Cxׂ0Û%.r9¢Ä-ÚþÃû¸… 1½;{*ùVq9Útæäû,¿8v#˜ÕSÖ/ÀÅ2á-ßQ·£dMiÓóåy*7o0 T{ÐóÈë) ìçéÅ8¼ÿ(ÉSßðÏÅôFvþŒ¸€^ŒF\Óì:ÜŸsënÉZãæ â˜E˜Í³Þ빋ÑQ½p”ùqÎb™Õ²íûŽŠøEš» ¿^Hò»béJjÕ¡…T˜`ôleŸÍz̉qĸpÊ*FS&™J“ÆL±àXøû<¶QËE¬v¸0V«UE*óCú —aVºâê?×é÷å«-^Ï¢£­81v×Ð7\™ ¥iÃ_«i÷‰­ô×Ù´fׯÔk@wÉ1\O'}ó¼Ý…¡m¡|ttÜøLîÖcT9ŒFñfT=fa\#¼à´!ð¡c™ârâĈO‹¶ÍÄHK(¡ƒº|ñ 5¬Þ”öÿy@›ñÇ…qÞŒÒ a<^/QÔ (N¯Á…vt[üA;n¢}çvѦ¿×Ew¤´¥DïµÇG_8åÉè .Œ>”¬bT0ráÚa7¼¸—&ƯÃ~iío댆ã°>.ŒöûeÑr‹á3>¾f'FŒð.œ3ˆN§FÍê¶’ÞÔÚ1÷– cû.möŸ˜]B'´r‰šÒ‘kÊØçÞ|zaôÈÂPE .‰è9# b¸¬ÍÇ­-†ž«V¬ÕÛ,›"åó€’Ž†Öj! 2¤Ó]·''p`ô„ÎsÌÀxðïCôaËÎÆƒzÈyuNLöusb„w_¿¡½¥wzäøÈjy—¶ŸÉpöüps`Ä”âè!Ò€ûÓÞ¶£fâ«“£vM]L-\Ë“£ØŒ™3RH½Z1}´Ì†ÍlÏÌ .ŒÁ±3ð*W©ŒíG€Éj"DŒœAûâ Ýb~.ŒÖüaJ|ê¸H™Ok³§¹0nÛ´ƒZ7j'•ÄLúmëÏ´v÷o„)9tКÖi%[Ë‚kŸ czñ}_¼j¾ì¬tJS«vÍ©Ï^´PxÁãÝ J—‘O0têÍ™ðß(S 1;ôyVh’°gòeMgOŸ—‡ÄKÍ—ä F_ò­çÚFaðDò#Gè tVðÓè(A¨Pþ‚yµd÷¦(JÎÅ(Hù æñÀkÅ nò-¶[Âhi‘ºa0îKò£/yw÷ÚF`„KyËzaRñELŒž±S³îòÀ]ÎŒŽxLnµ|IPBS;GlÈ4o0b¹LÕØÓãG-/,ägϙݾˆ©ÇÞ`tÅè%+dM™pUž3Ï[Œù ļwlÙ‡M­S‹¥Z|IÞb´æýû‰3å!\Ò1Rè/ä-FÍ£¶PÑ‚60UüÍbRQ2kôÓ†«o1ZUe³»$6t:fœž}†N½!€–±&ÄÆÁP=¨\ŲÖYnï‡6¬#ËΙ6Ï&V¢y?ÉFœÊ–Ëíú¼)È…ÑžŒ>— #FZ7l'‡ƒk†V§aƒ}fÌ…qÎôùR©Ç0¿F?¦ïbçÒ15ûÕ âÀ›¤EÂ@Ôþ7yNŒQ>°!K™AÁ÷îí{J½5¡÷þeÌRKð`4kí>.ŒðlÁ¾EûØâÓâKæ/Å.•)_Zn¹ÿ¸0j|¢Tƒ (ù‚¸0æUxÎYb ¦*ðLåΗÛ&ë€ #HìcA!Ðæ€žƒ%˜øp’a][,e yú € >Wÿ¹F«D´e³C×¶6XúˆåKP¤Åôˆœ<›´à‘=ú}JùÄpÂêã|¬cS§B}yŒÅF1Êê'â1™1ß̉8Ü•Êr'Æï¾fú>zð5ªÙ,ŒQ‡ÅqÿŒSÈËNŒxQÀq÷?l0z´ED–‡­hðèrËýlj‘›wwëçÄ8ràéMƒ6D$utñ^ÒÉ…’oqbÄ»ëÕáå!à|ðD|àíB|%ë©F.¼œ5žgN™+wá\¡)Zž[NŒˆÕ¿Ç Z8w‰TzË‹å=`K¨ykÂ\¥zÿº„œG‡GHå½’¯’Ôˉ1Qâç·›f+`î»Üĉ±K¯eäX(ñÚ} <˜ŽêñeWámgÎÈ''FGí“0aL6s NŒxgÁv£-øiTûÔ­oÓBpb¦áÂø7MšÔÒû!@„=Kk“l¹1bDw±˜}µù¨µÜšýlj±Yë&„çoìȉÒëŸiå·SÏL (ʉßú©ã¾·´#ðÁ»¯ËçS-ñLrÓKb8ë_gÙ¸m #ViPÎY‘8é¨ƪ·oEQZñÀ=Íý4šÎ=OE)#†¾Ï¹LÇœô+Œq„êe‚ÂèX€œ÷êClA‰%xcà%â-ù{;BÉMäU0FÃxûæm¢£ðâÍ$Þ4"f”¿aÄ}‰ûöW©„·ïlÃ| x0Eþ†¸ðݽ&fi`¯O1ogZü #¾ý/\"ØCbé+륰¼iÓ+ÇoÈ Û·oï´šç]|§Eôe îNŒÖ?öݽs—Ò¥OK^3‰ ãë7 í舲fËBA ƒe±¤qa´föÚÕëtöô9ŠºEé3¦§‚…óSÂD.?Ö§{½ÏñÖÍ[t'ê®KÞ2fÎ@ÁÁÁ.Ë•ÉQã-:ú;sŽð,>û9r¿J9r½ªe›¶åÄøìÙ3ñ]¼D'¢¤É’RÞ¹éå´/³c3ô)ضq;µjÐÖ)Ó[­—/RÀÇrLøXú~ÒL›òP|†F ¤Z¡5lÒïß»O]Ûõ u«6ؤ×kJácšö@sb°è§Ñ4kÚ\ ï;Bâ<º?5iÙÈ3÷F½mΉ“ ãý4rÐ74{ê<öq_úºÕmb“ÎyÀ…Ñžç k6QÛ¦Êd(JxÎÍ".Œ¸WË¬èÆøÈ1T½N5§ùFfpaÔxÄGgHßá´qíf-InsåÉI+·-³Iã:àÂØ±EgÚ³c¯K¶ûëMÍÛ4sYƈL.ŒàmÏÎ?©ï§èø‘6¬–­ð ;˜²dËl“ÎuÀ‰ñÏ]û¨{Ç^B¡ºu°I3úÀ¥¢¤7<À}ñ‘áeY¼T1^'NL)S¥°¤­ýí©$áR£îÛrié¿È\ç°îôˆŔ¿P>YþÉã'Ö¤£¼á³çx…Bê×¢ A4ã»ÙôÓ‚eôï¿ÿÒÈ C-usîpaÏèݵ?ý½w?'„xëæÂ¨§ÍãeÒË\×­Þ(•$í>M!îïÅó–Ò©§©[‡^T¸h!Ê/——Ü»w:Fë«?zôˆ¾ê5Ä:ÉÔ}.Œ>²àx»v˾¶cVâz\Q7FÖh&߻İâ£zX¼‹î;Œ"¦Æ*5*Rš—S;İvå:™ž0Q"‡ùF'raÄH`ñ}Äèç›eJH3.ÓÆGÒV1xÑóãÞ4ë§éFÃqXÆSb•&µ›ËkV­Y™*T)GG§y‘ hÌÐqb6*35hú®CžŒHt©(yzhçŸ|ÖÑåé„&øAÇVr …ÛwnC•‹ÇŒ$ý¾|EQÚ¼~«T’ €ý¸r.¥M3ÔV¥F%j\ë}‚‚Õ³7S‡üÆü¡•bC­;´¤ÇNÆéáÉLÿŒÆ¨§ÍÍ‚i4ÆÜysÒoˆ‡¶žej¦…xÞ.BׯÝ/.3?²£Ñ­ÛfÆä9²‡W¹zÅ8£½Öå¸÷¹0âÃ3af7ûnÕÏñËî_Élúµiĸ!”(±9Jƒ3ÀFcìÐÅñ :¥š¢T­Vegì°¤qËúm² aº2kÉtË{§bÕòT³l(mß¼Sæ›i¦b4Æq£&ʶx·q]1>œ^z)Æ0æ<_þ–ÆšDõß{Ç’ntù4æŽaÅèKÆÔWä×äpæ5hh‹ ‹çž¯¿òÓ?Ë´vÂ,JŠ•,J%J—y«ýCnýéOFðÝ<¬)-[¿ˆzîi™¢ô'<ŽxуQOYG×òUš¾Ñ+oÜ¢¡åežñ‚*(ìé@w¢îÈ­¿ýéÁ¨ñ[„QbšøÐéñwò£¿c²çOFL¹m^·UV1pT?Ÿ+IöXœëÁ謎™ßÍ‘Y šÕ“öXÎÊù*]ÆëbD T¢Ô6ïà$Áö1ëâo¤#¦Ý@Í>xÏF»„é8Ø.q‘KE‰ë¢®êº%³Ó¦n „Þ=•|+FQ:sòŒ}–ß;ÂfûïCøÐ¾ä £#lzÊ::ßWiîò ƒRíAÏW ¯¯Øõ躮0ï?JÖÙ7üs1½‘Æ£úýá$Wý?#xp„qçÖݲêÆÍؘEq=_Ôá£#>à(óãœÅ2«eÛ÷ñÛ4G ¿^Hò»béJi«„=O›)Óßú_)J‘ò¹Ù‹Lôã?G5»¤Ô©SÙpŽÙ%˜9€`ÞÀE,SoË.—žl°KÊ•'•ÊLùÊåâÅ€x×¶=²\ér¥äš0¦,@Z´§¬±FjðP1“ŒÄh&ßz®eFGm®‡GoËr`ÄK ÷ã#açrLÌ£;lœú~ãÍ× STfFt^ðb.Z¼Õkò]¹tÅlX6×ãÀˆ Ú„:…u£b¨?‹ñ.T¤ …¼[Ó'£/FcD¶ì,ó¾ÔC¥iS¤|PÒÑÐÚC-$A†tÚ)¬[Œ¬ {P¹]µ¹,ë>…#¼ûú í-½3Ð#ÇG¶gxÈ»´ýL†³ÐͰ'p`Ä”âè!Ò€ûÓÞ¶£f°èõ)5¦t1µp-O"Œb3fÎH!õjQÄôÑ26g°=3ƒ¸0ÇÎÀ[¨\¥2r´&«‰0rí3)T FëöÁ”øÔq‘2 žÅfO+raܶiµnÔN¾_3é·­?ÓÚÝ¿¦äÐAkZ§• `l- ®}.ŒéÅ÷}ñªù²³ÒA(M­Ú5§>CzÑBá¯Ùõ¦Ëȧ:õæLøo”Œ‰©„˜ötæÔY速5Að˜gÅGö,šá–Væìéór7ƒx©ù’¼ÁèK¾õ\Û(Œñµ¹žŒ.kFk¾ð‘ÅCݪ~9—Žûi¾"o0îÙ¾WöÈÁ{ÙB•â@Àsš?CQB »‹§ÆÉ7+ÁŒ®x„§ŽFW¯\ó©»·S§I%¡Üt`ß©M×ܽkŽIƒ&Sû­·­ëC`TÍ$¤a¬"hï«}o1Žò­dÓàZ,¡W^ÍFÓL¢zU›ÈÑ_„Ðiûɾ‚HÞbã]Î ~=%õÊ_0¯–løÖEé¹)_Á<6à^ܲ^˜T‚¡gì4M!qðZ±Â„›|ˆí–pZ-R7 Æ}IÞbô%ïî^ی»<]ÎŒŽxJnµ|IPBS;GlÈ4o0b¹LÕØÓãG-/,ägϙݾˆ©ÇÞ`tÅè%+dM™pUž3Ï[Œù ļwlÙ‡M­S‹¥Z|IÞb´æýû‰3å!°`¤Ð_È[ŒšGm¡¢m a*ªø›Å¤¢dÖè§ VÞb´ªÊfwIlè tÌ8=û zC/,5bMˆ‡„¡zP¹Še-Yèy¶nØN Ö ­NÃ";5 mXGž7gÚ<›X ‹æý${q*[þ-KÝœ;\9yÖ[7F=m®—g½å¹0Ι>_*õæ×èñãÇô]ì\:¦¦q¿šAa“´HˆÚÿ&ωqÄ6äaé3ˆ#øÞ½}A©·&ô^ÿŒYZŒf­ÝÇ…žm Ø·h[cZ|Éü¥Ø¥2åKË-÷Fo`B”j%_Ƽ± ïÂ9Kl`ÁTž© ÜùrÛäqpa„‰},(ÚÐs°„N2¬k‹¥ 4OÄáê?×h•ˆ° BcvèÚÖ‚å»o§Y†Aïô¥¬ó­ ÄÀøä³¥ñ¡L`þãÄÖñ‘Ũ‹5A9Ô:ìh¥hZ—1rŸ£ž67“}]œ»ôúXFŽ…¯Ý§¸>¦£z|ٕʈ!b3ˆ£#þ& ’Éf.Á‰ï,Øî`´?j¿SƒºõíbZhNŒÀ4\ÿ¦I“Zz#$£‚°gimg/*3þ¸1bDw±˜}µù¨µÜšýlj±Yë&„çoìȉÒëŸiå·SÏL (ʉßú©ã¾·´#ðÁ»¯ËçS-ñLrÓKb8ë_gÙ¸m #ViPÎY‘8é¨ƪ·oEQZñÀ=Íý4šÎ=OE)#†¾Ï¹LÇœô+Œq„êe‚ÂèX€œ÷êClA‰%xcà%â-ù{;¢c“0(È«`Œþ†ñöÍÛ„EGáÅ›I,¼iDÌ(Èû÷+ì¯R o#ÞÙþ†ø@ð`4Šü #pá»{MÌJÀ^žbÞδøF|û/^¸D°‡ÄrgÖËŸyÓ¦WŽß @·oßÞi5χuœÑ—¸GðèáòêAÃçÌCS—æÆh0»U§0z$6ËIx!#L€¯ÉÌv„2á âÄ£_0üåĈ6Ãýš+oN_4ŸåšœT, {°Ã‰ìÀ“Qófô€=CNáˆo¿½×»! »Q‰¡ÆÜn\OQPPPPPP (E)`šJ1ª$ $ $ $ $ $`¶”¢d¶ÄÕõ””””””FJQ ˜¦RŒ* ( ( ( ( ( ˜-¥(™-qu=%%%%%%€‘€R”¦©£JJJJJJfK@)JfK\]OI@I@I@I@I@I `$à2àäì9³ã¬Ý0È£JJJJJJJnHÀ〓"v¤¤\…|»¸=.rþø%¹¾‘Âè±ýâDÕŽ~Ñ ^3¡ÚÑkúEªý¢¼fâ¿ÐŽÿœ».Öp¼ïRV.#sçÈýª íóV”&H$—0Q]Þ'~Ÿ©ÚÑï›È-U;º%&¿/¤ÚÑï›È-ÿ í˜4Qr©ç¸ˆK%§‹À¹ªQå) ( ( ( ( ( ( ¼ p©(Åμ½ P %%%%%%%}p©(é«J•VPPPPPPx±$àRQRSo/Vc+4JJJJJJú$àRQÒW•*­$ $ $ $ $ $ $ðbIÀ¥×›'6J—.\¦§OŸÆ‘RPPe}%Kœô{wïÑ‘ƒGéæ[”1SÊ“?7%Kž,N9-åì;D $ BE Pòɵ,Ó¶Üäê•«tñüeÊ•7'¥JÒ4lÚ…81>xðí?L·D›gΚI`ÌEI“&Ñ.mÚ–£Þûš 4'FkžÏŸ½@wïÜ¥téÓRñ›I\o\¿)܆ï9„’5[ Jä0#‘ £5¯×®^§³§ÏQÔ­(JŸ1=,œŸ&rù‰°>Ýë}Œ·nÞ¢;Qw]ò–1s vYƨLŒoÑÑÏèÜ™s„gñ™Ø‡×zŽ\¯jÙ¦m91>{öL|/ÑÉc§(i²¤”·@nz9íËìØ } ¶mÜN­´uÊôÖCëå‹Ü@#}C³§Î³)ÅgÐ×ý¨nƒ›ôû÷îS×v=hݪ 6éõš„Rø˜¦=М,úi4Íš6—ÂûŽ8îOMZ6²ÁÌ}À… Ò˜ð±ôý¤™6ÐæC#R­Ð6éœluÞ׈ўç k6QÛ¦Êd(JxÎÍ"¶v÷j™‚Â9†ª×©æ4ßÈ .Œøè é;œ6®Ý¬%Ém®<9iå¶e6i\\;¶èL{vìuÉvÿa½©y›f.ˑɅ¼íÙù'õýt?r†ղÞ¢acS–l™mÒ¹81þ¹kuïØK(ƒçmØïúÅ'ôQ·6iF¸T”ôÚ(Ý ^–ÅK³á5qâÄ”2U KÚºÕ¥’”=Ç+R¿¥ËÅó–Ò©§©[‡^T¸h!Ê/—,ÿäñ kÒQÞðZù A4ã»ÙôÓ‚eôï¿ÿÒÈ C-usîpaÏYëݵ?ý½w?'„xëæÂ¸ö·?¤’ŨFÝ·å(ÒÒ‘½öÎaÝé— ‹)¡|ñògD.Œzîk#p¸ªƒ £õ5=zD_õbdê>ÆÇYp¼]»Še_ÛÑÞMÚ1ç– #xÆèCÃÍä3X@Œ +>ª‡Å»èà¾Ãœ°lêæÂX¥FEJórj›kikW®“» %Ò’X·\1ØF|1úùf™Rnj˴ñ‘´U ^ôü¸7Íúi:+6­r.Œ§ŽŸ¦&µ›ËËT­Y™*T)GG§y‘ hÌÐqbf"35hú®Æ†á[—Š’§WƒvþÉg]žž;oNòÍ®že»…8ïíR!týÚ ÙÀÚËhóú­RI‚öãʹ”6]ÌP[••¨q­÷ Ûžý»™:äo4F+´RC)³ÖZÒ‰c'ãôð\ ”!ÓhŒ†öÿAÇVrØ,·ï܆*Iú}ùÓ%M\FcÔs_k®õE»šéÙfŒÆ(ÚýÍŸ¹P¦`6âµX%îë¡S3@IDATÑ¡…6 ¡e‹VPÓ–Ô±k;:|àˆœ ‡¹Ê`aâb6Ê”%xKÃ3Þšàe‹¼ø¼­ÏÑ»ïRQÒ;¢”$IŒ‹74;{í®BÕÿÑÄYåI#x°ulÑI;”[(Cc¦Œ²< Q·ŸOÁ%M7l@’Øi¸&›AÍà[Ï5ÌÄC<¾f'FŒð.œ3ˆN§FÍê¶’ÞÔÚ1÷– cû.m¤Y f—0¸R¹DMéÈ5eìso>ëA£q¾$¦¶œmܶ Ñ* âN›éadÂèÉÒ…ö70VvFÿ\þ‡Ú¼÷¡lX µ­Þ¹BN½ÌjŠ—6©Õ1gÚ<úêópz¯U#©¿–ìööü‘ËtìÀIŸb´V5ÆáÒ‰^q”üãÁ¿Q½ªM$Ü/õžpvO¶þˆQÃáè¾vÔæZyg[ÀˆÀ°ÕÞ¬-YÄó‰¡} )Ὀcoâ(ùFWm»žæï´–˜wÝè‘m–¿`ü´}O9jÔOŒ ÂãØš>ïü¥×ò“œÆéÖ§³u–[ûþ‚ÑšYL‰×.÷®œíøü«Ï(ì£ÿ³ÎÖ½ï/·mÚA­ê·‘ü#f¼¿.ǘ/DB‚³Åï"–'`ý#À!LЪkäôÛƒû){ÎW¨ø›Å¨ˆ/Æù+fIƒv½ yåø  £}ûöNO5tDÉÙUÞ(£è f‡+ʘ9#õÒKA¯ ö+ˆ¡½ŒqlOgOŸ—ID$Y_’7}É·žk…ñÌ©³2î®Pøù …Ñ£ûÚ:ßì}o0îÙ¾WöÈ1%U¶P%ÊŸ¡¨üi!ÐãCÚÿ¹_*?ÕèñãÇô]ìœ#Œºµ‘¤Ð†udL³YÇJX$††ÑC@¹²åßÒªaÝradeZgå\1Úкa;i‡V3´: ‹ì3C`.ŒzîkÍ¢»8FØ$-¢ö¿ÉsbŒòñ,"SÆfFð½{û‚RoMXv)ü˘¥…à™ëjMJëó¼ÝçÂÏ6¦g´-Ž1-¾dþRìR™ò¥å–û £Æ70!J5Š’/ˆ #F@ ç,±/0x¦‚rçËm“ÇuÀ…$öVB´9 ç` ¥ÍÇ­¹ Éz QÂRšBѹúÏ5Z%¢-ƒÐ˜º¶•ûøƒ@aàrpoÄèÑÆÜ Á£È-þVçC3®S¡¾<†­F™@ýDü!3ÜX91G±| dÒbœDNžMZ0Íý>•C²Ó'Æï¾fú>zð5ªikH£&‹ãþi4TNŒzîk£qY×ljÑú:¾ÜçÄ8ràéMƒwV@Ï¡¶H.”|3ˆ#¦-°^ÞÑXN5OÄ Þn ÄW²žjäÂˉQãy改rNCšb¡å™±åĈXPý{ ¢…s—H¥·¼XÞár4oMØõV©Æ“ãèð©¼Waƒ’§H&{m©/¢®X­<+¾ ‚œ]áìù3tMÌÃç*ÿ0zB¡è@P‡¥£‡ŽÑßÂk ‘_aH7L,\›"erË¥_鲈½r`ß!9døwïÜ“Ãú£&„Óÿ¬â.a±ŽXîÄÑS²nnÁÎ10Žì'× ³T¬s'êú]ºqõ¦Ï1‚m,j9œÁ3a¸ cüÞiTG¬Ã¤(Ü_0nß¼CôÔ÷J\ †‡ý¯áûõ Ó;zÉ_0깯£#¾1Ú2]LmÀîå/Ü®ý¥“$ ïšã´ÿ¯ƒÒï<“µß©!ÜX{9’‡«4Á+‰Ìk7å(Þ݈g‡QÁÎ=?¢Oût6¢žEÓó'Œ˜©èôA7Ù$ø^ÀØòŒE…Ý0j†xŒŸ8zRB„ò;4b0äö̆×_0â{i6|÷íù›à ƒgpÀˆ>Ôª]s›hÝzÛöÞò;T²dI§§îõ†á1]ß¾EiŧMŸ9ãà¡6‰`u‰„¢•.c: ~vÝÑ9ÑO£éÜÙó”Q™2bèÛ«~nŒŽp{“¦0:–g;꽯sh›êïíˆ5 ƒ‚¼ Æèooß¼MXt4iÒ$”I,¼iDÌ(È» ÷+ì¯R E7¾w¶í]éøÈß0(‰Õ²Ž9w?Õß0‚s|w¯‰Y Øë¤ÏÎ뙈oÿÅ —öXúÊz),÷[-nIw¼ÞÆ=Í»XÜÃË?w7.Ku—0Å–3ww‹³”ãÆÈ´ÎJƸÓs¯ê)÷JÆ¥˜ÙŽP&|Aœaô놿œÑf¸_såÍ鋿³\“#ðùqb>ŒèjÞŒ¾ÂË…ß~ûð@fa4Ô˜Û,¦Õu”””””””Ì€R”̲º†’€’€’€’€’€’@@J@)JÙlŠi%%%%%%3$ %3¤¬®¡$ $ $ $ $ $PŠR@6›bZI@I@I@I@I@IÀ (EÉ )«k( ( ( ( ( ( ¤\†8}ü´…X /*:zFa|Wµã Ј‚jGÕŽ"u¯JK¹æóü©‹® ˆ\—'gÏ™gí¶xkT”””””””HíÛ·wÊ­Ë¥y^¥CûS•åœVèžD 4Ì c µ˜c~U;:–K ¥ªv ´s̯jGÇr ´T-2·+¾•’+é¨<%%%%%%ÿ´”¢ôŸn~^I@I@I@I@I@IÀ•\*Jž­íêr*OI@I@I@I@I@I@I p$àRQ ŠS%%%%%%%ã%àRQú×øë©•””””””F.½ÞàÂiLøXú~ÒLPv‡F ¤Z¡5lÒ9Ø0Þ@#}C³§Î³a}Ýê6±Iç<àÂhÏs|Ï®}y#¹0â^-S°¢SVÇGŽ¡êuª9Í72ƒ £Æ#>:Cú§k7kIr›+ONZ¹m™MׯŽ-:Óž{]²ÝXojÞ¦™Ë2Fdrao{vþI}?@Çœ°aµl…·hØØÁ”%[f›t®NŒîÚGÝ;öÊàyö»~ñ }Ô­ƒMšÑ.%½»/> (:ÅK³9=qâÄ”2U ›4íàÑ£GôU¯!Úaœí“ÇO(¬IGyÃgÏñ …Ô¯EA ‚hÆw³é§Ëèßÿ¥‘†Æ9# #xÅDï®ýéï½û9Xw»N.ŒkûC*IPjÔ}[Ž"-ýñÙkïÖ~Ù°˜òÊç6ŸÞä¸nõF©$i÷i uñ¼¥têÄiêÖ¡.ZˆrçËå ënŸË…Ñšøž]ë²û\?|da÷íÚU,ûÚŽYmˆëqaDÝ lX£™| ˆ$`ÅGõ°x1ôÌ".ŒUjT¤4/§vcíÊu2=a¢DóN䈑À6âûˆÑÏ7Ë” .Œ§Ä*!Mj7—׬Z³2U¨RŽŽ>Nó"И¡ãÄÌDfjÐô]‡<‘h¨¢¤1íü“Ï:j‡ñngLž#µÄÊÕ+Æ1ÂÉ›×o•J°WÎ¥´éb†ÚªÔ¨Dk½OøØöìßÍét]¼ xPÀhŒ`!´RCÉIë-éı“qzx°éÕ)FcÄ00´ÿ:¶’æ`®}ç6T¹xÌHÒïËט¦(i‚1cî¼9iÈ7ÄC[Ï25ÓB<o— ¡ë×nÈ—™Yà4£&;lã{v­ËrîsaćgÂÌNÖÝ®›ã—Ý¿’Ø:õkÓˆqC(Qbs”g ÆØ¡‹ãtJ5E©Z­ÊÎØaI7ã–õÛdÂŒaÖ’é–÷NŪå©fÙPÚ¾y§Ì7ÓLÅhŒãFM”mñnãº4b|8½ôRÌ|Ìy¾ü-5‰ê¿÷Ž%Ýè†siÌmôÅÕ‡ùÌQbªˆ¨#ú釟er»Na% ÅJ¥¥‹Ë¼Õ¿þ!·þøçFðÝ<¬)-[¿ˆzîétŠÒñ'w0yã59Dйep££‡ºx.þ5wdAý¹ƒ½òÆ-Z^V`÷vAaOºuGnýõÏŒïzÊjçøÃ6PùÖ#;w0bÊmóº­²Ú£úù\IÒƒeÝÁè¬Î™ßÍ‘Y šÕ“öXÎÊù:ÝŒ×ň¨D©7lÞ;ÁI‚-ìcÖÅ_ÉŒ˜v5ûà=eïZ¦ã`»ÄE>W”†÷%±õ ÿ\ ‘¦qˆ1½;{*ùVŒ¢tæäû,¿9v#˜í?¼áCˆä.FGØ¢nGÉä´éùò]ßÝ4O1 T{ÐóÈëîå|RNF=e}ÆÉE•o'p&»ƒqçÖÝòÜÆÍ85‹pX¹Ÿ$ºƒÑ«p”ùqÎb™Õ²íûŽŠøMš; ¿^Hò»béJi«„=O›)Óßú_)J‘Ò±Ù‹,àã?w0jvI©S§²á³K0sÁ¼‹\N½yªƒ.[¸\z5Á.)WžTB(3å+Ç]/ ·hñ"Â(ûºréJœÐ„1eÂТ=e5Rƒ‡Š™d$F3ùÖs-30⥵kÛÉVér¥ô°gHYŒxIá~|$ì\މyôo‡“Cßo¼ù:azÙlâÀèγk&NŒàÿÐþ#Ô)¬%CýYÄèg¡")äÝš>}1ã‘CGe¥^µ‘“gÑÞÊû6J¥Åǵ¾x'›éÙfŒÆ(ÚýÍŸ¹P¦`6âµX%îë¡Ñ¡…6 ¡e‹VPÓ–Ô±k;:|àˆ4c¹Ê`a `6Ê”%xKÃؚà!¼ø¼­ÏÑ»ïRQŠ™t¿Ê$Ib\¼¡ÙÙkwªþ&Ί (O §OžZ ¸áuäxp+êö󩊤ÉâºW'‰ÆAX3ˆ£|빆™aˆ‚‡M¹Šeô°éUYNŒðÌìØ¢“ PòÇLeꇇ £»Ï®˜¸0&ŽyOÁ@vå²U6ÜG OÓœlšë5Æ›×oI\Ó'̰Á‡8É,_ò+Mûa’ ŧ€Á \íÙ„7£†7ìÃVö٬ǜGŒ §¬Ù³Òä1SiÒ˜) ŸGÙDºYÄ…±Z­*R™Òg¸ ³‚ÐWÿ¹N¿/_mñj|ÍÓ±v{9½#Je*”¦ ­¦Ý'¶Ò_gwÒš]¿R¯Ýemp=ôÍóœ7cte„ëûë%Š:¨m¡@ttÜøLNOdÊàÀÈĪÇÕš…qð‚Ó†À‡ŽdZˆ†#z<-Ú6“¡+ÐA]¾x…VoJûÿ< ÍøãÂèî³ÈaG·åÀ´óè&Úwnmú{PtGJÛAô^{|ô…ðä5¸ÚñÑDz~ŒèG.œBû/즃÷ÒÄXãuØ/­ým)8¹0Ú3ÿË¢åÃg||Í$NŒá]8;f:NšÕm%½©µcî-Æö]ÚH³Ì.¡Z¹DMéÈ5eìso>m†£KEIïá’ˆž3 € †ËÚ|ÜÚb¤½jÅZY%¦%F‰F®Ÿö¶íyÛ_3EÊç% ­=ÔBdHg*Ë1FF½¨Ô Œÿ>D¶ì,¹übPiˆè˺OåÄï¾~C{Kï ôÈñ‘…íò.m?“á,t3ìÁ õ<»°¬ûŒº˜Z¸–'F±3g¤zµ(búh™ ›3Øž™A\ƒcgà-T®R9Ú“ÕDˆ9ƒö™ª„ £uû`J|ê¸H™Ïb³§¹0nÛ´ƒZ7j'ß/ˆ™ôÛÖŸiíîßSrè 5­ÓJ0¶–×>Æôâû¾xÕ|ÙYé ”¦VíšSŸ!½h¡ð‚×ìzÓeäÓ\N½%Ì7JÆÄTBÌОí{¥Vý²…*acCø äÏP”,kÆâ©²‡4سh†[Ú gOŸ—»ÄKÍ—ä-F_òîîµÂxæÔY÷×E¨üü…ŒÂhY<Ô­ê·‘s鸑æ+ò#Œ_µhÕî<»ˆïgÏM®^¹æÔE+ùõ¦1ušT’½›ì;µéš»wÍ1ip&'o1Z׋À¨šIHÃXEÐ:ßWûÞb=ä[É:LX´XB¯¼š¦-˜Dõª6‘37¡Óö“|‘¼Åƺü4z Jꔿ`^-Ùð­)ŠÒ…s1 R¾‚y$„ŒÇp¯==~ôØùÙsf—E^+V˜p“oÙ°Ý@;W‹Ô ƒq_’·}É»»×6#\A[Ö “½ÄÄè;5ë.ÜåŒÀèˆÇäVË—%4å±sĆLó£Þg×)ÌÞ`tÅÚ%+dM™pUž3Ï[Œù ļwlÙ‡M­S‹¥Z|IÞb´æýû‰3å!°`¤Ð_È[ŒšGm¡¢m a*ªø›Å¤¢dÖè§ VÞb´ªÊfwIlè ªpzöúÆF¯2åKÛ¬i…Ø8˜f•«XVna“´H™Ùâ ø ,õ­óCÖ‘ŠÒœi󤯬­·hÞO²‡€òeË¿e_Ë1Ff=¬” #F[7l'‡ƒk†V§aƒñ{Ⱥۧqaœ3}¾õ,/"Çbôøñcú.v.SÓ¸_Í .ŒÖϦ†ÃÙ³«åsm¹0îÞ¾G¬M™Õféô^ÿŒYZŒf­ÝÇ…žm Ø·àc L L‹/™¿Tîã}nqaÔx&D©AQòqaÌ+^,]²pΛ˜ªÀ«”;_nS sa„ ¦J­m–hs@ÏÁL|8É0E Khž> ˆÂÕ®Ñ*m„Æìе­GXV_»êT¨/Ãìc±QŒ2ú‰øCfÌ7sbŽ>bùÈ ¤Å8‰œ<›´`š=ú}Jù‡q]NŒß};Í2ô}ôà1jT³.iC£&‹ãþiSÀ€NŒxQÀq÷?l0z´ED–‡­hðèrËýlj‘›wwëçÄ8ràéMƒ6D$u¬5‰çP›v„’oqbÄ»ëÕáå!°næñA‚·ñ•¬§¹ðrbÔxž9e®Ü…s¾%f'FL‡÷ï1ˆÎ]"•^tÒ`K¨ykÂ\¥zÿº„œG‡GHå½’¯’91:jgÏ®£²F¥qbÄ; ¶;mÁO£ÚïÔ n}»˜€#0 Æ¿iÒ¤–Þ§ ˆ'ìYZ›d;È#º‹Åì¨ÍG­åÖì?NŒÍZ7!<cGN”6XÿL#(¿z~dJ@QNŒøÖO÷½¥Þ}]>ÿ˜j‰g’›^ÿuv‘Û6Ð!±0b•圉“Žê`¬zûV¥œÞi|(9 èý4šÎ=OE)#†¾Ï¹LÇœô+Œq„êe‚ÂèX€œ÷êClT‰%xcà%â-ù{;Æ÷캃ßß0Þ¾y›°è(¼x3‰…7Å{s›VÆß0‚/ܯ°¿J% ¼õ¾³5\Ö[Ã| x0Eþ†¸ðݽ&f%`¯O1ogZü #¾ý/\"Ø2ÃüÆz),oÚôÊñrèöíÛ;­æyßi}˜C„G§^=x!¹"4|ÎÜ9\aÏãÆÈÀ (Œñ ÉÕ½Š2ÂøšÌlGWòà”'Fýúƒá/'F´ î×\ysr6S¼usb4RAŠˆ‹œqYx2jÞŒ.Ø`Íâˆo¿½×;+«Êc¬M­Ô®’€’€’€’€’€’€’€’@Œ”¢¤î%%%%%%%'PŠ’Á¨d%%%%%%%¥(©{@I@I@I@I@I@I@IÀ‰”¢äD0*YI@I@I@I@I@I@I@)JêPPPPPPPp"¥(9ŒJVPPPPPPpprάYtïÁ%%%%%%%%%VœDà(P‘,Y_Xá½ú=~úTa ðVíà ˾jGÕŽ"u¯JK¹æóôí[t÷þ}—…\FæÎóÊ+ô×Ñ£B‰Èâ²’@ÎL81í;sZa äF¼«v ðŒe_µ£jÇ@‘€ºW¥¥\ó$–{žãŠ”’+é¼ yÿ’Óåü^„ÿ ª_ŒvVíøb´£Bñß‘€R”þ;m­* ( ( ( ( ( 蔀R”t ,‹¿D1¶fÈ»âù¹T;>—E ï©v äÖS¼ÿ% ¥ÿ@««¡þ£‘U;ªv|1$ P( –”¢Xíå·ªë‘Øüî$ÕŽ~×$1¤ÚÑ#±©“”|&—^ožpuáÊz"Üíí)aP½’9³Mòµ›7ºå¡,α'¸ñýyø0%¡ ^/P€R$Kf_„ý˜#\¾vÎ_¾Lùrä Ô)S²c²¿'ÆÒ>áepC¸ef˘IbLš$‰= ìÇœqŸî?vL`¼M™Ó§§9sRòô^EC¹x‘îÜ»Ké_N+ñ²7žÕ¸ÚÑ“÷“[†îra´fòŸ7èÔùót+*Š2¥KG¯åËG‰þ‰°¾¤Í>F<QwïÚ\Çþ s† ”Dx?›A5¾£Ÿ=£ÓÎÓ™  ûy²g§Üâg6qb|&pßÅ£§OS²¤I¨`®Ü”.Mvˆ†>vî¤Ð>tÊôñß§ âE ÂÇ2OêNËÎ9’B+W±äßžúô¦•›6YÒ°Ó4$„Æ}ÙÏ´š#ð<ަïü@_ŒCŠèÓ‡þ¯^}¹oÖF´ù ‰hüܹ6P ìNèןޭVÍ&ó€ ã} µÿ¸±¢ ذŒßöîCjÖ´Iç<àÂhÏóª-[¨Q—Î29CÚ´t|åïöEØŽ¹0ê}?±saÔxÆGçóÑ_Óš­[µ$¹Íûê«´{Ñb›4®.ŒÍºw£mýå’íQ={R»ÆM\–1"“ #xÛ¾ou2˜ŸW;êy?éìO툑À*ÿ×J>ƒEÄRˆx¿">ª Ýwäˆ^h—çjÇšåËÓË©R;äë×dºY£f\1Ø Ó'² Ë/NuÅC‚/Q„XYŠK‡þýè—I“ÊÀèD.ŒÇΜ¡·Ã>ìÖ®P‘ª•-COœ é‹ÑàI)k¦ŒÔ¼n¨Ñp,õª(iµ¶kܘzµm§ºÜ¢açŽúÚe™?¶o—J°5ßGŠáý—eùZå+PµZÓ¿® ;›:äo4F*÷~3‰ë£fïÓ‘Ó§âôðd¦žÚD1wöWÚÿÇï¿/†M“J$Ýþ¯5½ZWîÿ²îÓ%MŒFcÄTéØ>}éýÐPËÔqû&ïÑõëÑUñBÛ°k'yª(ùK;j²Ãv’謠‡‡’ýh¯u9î}£ÛQã×÷“VÖÝ­?µc—ð!òÛ°F š4à+Jœ(‘»0XÊÝŽÝZÇ|\í™E§TS”jWŒÛQ·/oä±Ñ×íØ.Û0kÆŒ´lâ$Ë{çí²åèÍF iãîÝ2ßL3£1Ÿ:E6Á{µChòW_‘¶jHva¢óÕøñ4bêTz¿N]Kº‘í…º˜{Þòåw—–­,JÞ,R„Ê+&óV¬_/·ü×¶QcÚ2w íÖMLQÆ(ƒŒÇž÷â… Ë!RMIB>lÑÐC»tInùv­êÕ³¼¬€/(ôÖA·ïÜ‘Ûᶘf>(ÀŠK˜rC'4æ‹Þ>W’̔ޤæËË5Øc2ýsý†dÿ­×‹Ù¼w’[`ý‹i—&L»Ú4jd£ µz·žLGgíÜe¾ïG@(J›öì–Â@ïΞ4EéĹ³öYwüu¯^Ò€ÒhÆõõ}ýøê»}7FyH÷*‡ÀƒRíA/˜;O|âpšïoíØgÌÉëðÏ>£´©Oo8óÎð—vܲwl…Vï¾K©R<7‹xÑ›Ž23—.•0;ˆÑÞ@§7 ”–¬^%m•p£çˆÙ³dz…’%)eòär?Pÿ4»¤4vŽM˜]‚™èø>€eêmÁ¯¿Òß« øy_Í!G}ª–)ã°0Þ²WO1§š€0ŒV4~ªÿvuKïš0¦,@Z´'Í“*f’‘Íä[ϵÌÀˆ—Ö–½{%[åK”ÔÞ!e90â%uíÖ-zøè!:q’†Lž$‡¾K »=LQ™M7‰á|¼˜K.LÍÄ÷¥þ1–Íõ80âñ½Ÿl˜`>0ããÇ%Çðª0o®üÈ^ïQþ–/ù¦h×:6#ÌðdõFctÄsä’ut²‹Å*ŽÊq¥±¼P„תE?þöÕhFÝ?6fGåT8ÌU"„)€Ùd4F(CP– WÈ•Ë¼Š‘Ÿw£ÍI: U”’ǸxÃp?kªV¶,ͶHÁ±n˜Á±sá0äýyíZë¢>y2-;Nº6ZOU$µk±.¬¹•ß¹wÏ:™mŸ#³Vl&Æ!›ʥK{ȱþÓ81®Ü¼‰šŠéSk‚’>ÔÔF„ÿèkÀ=ªg/ _FwßOfàæÂx](ó ±³gÇ'™E¿¯¤Åce'6Nƒ¸0Ú³ oF ï'Í›Ûg³sb„}YöÌYhtä÷ôõ÷Ó-8ÖFFÒ«Y²Z޹w¸0†;2(óðÎLž,)É›®\¿NËþXkñjDH.2ô WñÍ7éÐòtîutyã&úë§¥4¨KÉ;\O¿þþ{ Ø©ým%^³–.oÚL‡WüJ߇‡4`h‡í…¥>H3ÚÂ>\ç}M}Éþúfa„œ6>A¸°šå}¼œ äÌEí›4‘¡+ÐA]#.•…wÑÞCÆz4ÊÊüqa„§ Ü[‹°%_{ÍÉÕÍIæÂèîûÉ ”\>z$ÙǨàÒñèŸÍ[èÚÖmçØ/á5ƒ¸0ÚóŽQtÎÑq1Ûˆ›#Fxgý3ˆN§F5Û¶•ÞÔÚ1÷– ã§­[K;OÌ.¡ZäPéÈ5fæL ¤Ä‰ù U”0}†óÝéÁpYç--†žËׯ³€ÂŒè^N•Š`t–Eý‚ÛÿŒaÃdØtÀ¶ÃznÕÑÐz  iÍ1ÈãÀ(øÑŸ1¥' ðO»Ñ[¯¿nª81"ÈÛÈ=…wÆ@Ù#G'ÆÜxÈ[ñ…gaŽa%FLqœ0>Æ€ÛEÌ4³“£Æ»;ï'­,ç– c’Xc_Lb4£ýè¬ \ ŒœAF‡*q&'.ŒÖ×Ôø·³b>¬ð,vÐØº¼Ñû\7îÚEï~ü‘|¿Lèߟv-\Dû–þL°MB­FÛ62€±ÑxÕÇ…1£@ùcÆL9˜¥©cÓ¦4¬{wZ9Ãâ(“‘Q0TQr$8¤Á6¤dÉ'%„g”FZƒà1Ê‚=‹=!’,Èמ Þ`´Çä¯ÇFa.ðèMš;WÎ&7ÄßÌ… d'ÊâÂhï^ƒ ã¥kW©Ž®9™¹a&—#0ÆÉ?Ï•J=ÌüŠˆìÕ#§M•›šVÓø¨ý\KŒðI‚ Üó7gÄH Ï"ö}+¦Þ±ƒ80‚o3ï'nœ\ÙBBBõ±Å6†Åg-Z„UéË'W˜ÿ¸0*¶ YªAï7l Šm]ra„Õ43ÊGI‚«Ê/+VÈÍ`“ܪsºäˆO—…Õ[¶P·a‘®:pñá$Ë,JHù¯"}@)Å/J¯ôU’4fwC–Ô"QÌøGÍŸ+—P°î|˜`Òaî/E5_« MˆÐŒ_~§¡C¯ 4äëòƒ„:Ã…?ˆãÍœ£³˜¾à Û·Ëe„È|¼8*™æÀÎ]¤©Qî`úãÄ8RD`(Ó÷>š\©e‹'PL4ø‰ðÏ'*…XÀ‰ί\€BTN|ˆ`=‚S,”C]¡ºœC¿e‡sb4ó~² —qb„E©vÅŠ"rh•œófâƒ4OÌÉ B~%$‰å&NŒŠw•`Á˜HÕnâĈ\P ýR8s/J/RñÀ—PE“ÃWØ8o*vNŒƒ#"hÖâETíÕW¥${å?÷aóæTµL.Xò¼–)J˜}ùÃ-h²°ú@«T„¬½ï‹ EVmÊ}˜„‰Þ ~ŠêU©Bý;vr›õçXúÝ$ê(R—ãÜj¼900UŠ·¹âÔù¬\rbŸK×­såŒR|C9Ää+¿ªkÅ’c‚¨”àS)Lž<+ç|Ïr+·91öiמâ  «û¼c8jàm³|rbôÖñâE'ڹȉÑÌûÉ›<¬*ãÄÇ‹hS C!ú)@° viÖLø6•ÛÜÜaÑÅ|‹ n˃/Yqb|ïí·¥¡àËIßI?`D¥*‚òÛ»m;[ŠrbÄ·ŽøªÑ}}Ú·§z•«(¸lË8œå3góêÕ´K$xjd" NgÕëbª†t©S=u˜áÚÍ›Òò”TDÉe‰Îž6ƒ'Ï¥LiÓ¹†æB‘Î>aÁÙ}ò„£0†‚ÇÛ±{¯\¦=bRA'µ£7>C)sZ;Þ¡×HÂÅ!½ˆîÄK$Trz;BÉ…ÂÊ|aNkG³ï§@ÚØ‰íˆûõŒðM•2ÅSßÙ`tZ;È8­G 8üÕqFðz]|O1*‘"y2BX¨#-Nˆo?îQ(¾HNmœ Ë_;=mßþÛ·¤žÓ¶m[ŸU-³(©+ÀãQ>øBè͘q¬CÃçÍþ¿<\Ãê:Ü­æ7˜óiŒOJÍ̽Š2Ò„›ìlG£ÅØNܜʹ9'fNŒà÷+&t'qb´RA EFœÁ"U4c(|†r,F|ûÕt%¡ð̱–:sÀ>FK@K@K@K@K@K@KÀ©Њ’S[Fó¥% % % % % %v hE)ìM ÐÐÐÐÐÐpª´¢äÔ–Ñ|i h h h h h „]ZQ {h´´´´´´œ*­(9µe4_ZZZZZZa—€ßôGNž’ "—‚]ä3©ΜŽÏÄŒº9ýT¿zu·rÎ .Œž<¯Ø¸‘êwí"‹Ó§ICG–-÷¬Â¶Í…÷jÞjU}ò=ó믩v…Š>÷[¹ƒ £â1Ðw°ªÏ±äÂØ¸{7úï®]~YÞ³'½ß ¡ß:VìäÂÞ6ïÞM]¾øœ;æÆjù_¤ ýPÖŒÝʹ681nݳ‡Ú|ÖW(ƒgÝØïÛ¾}ܺµ[™Õ–*JwîG&§ÄËò¥bÅÜxM˜ !¥L–Ü­ ½ÐŠ-šË^[QaAªñÚk”M4(zâ»tÕ}ø×_ôV—Îò†Ï•5+½UµÅ‹—ÆÏžM?.YBÿˆè÷ï rÕç\áž÷ Ü|>˜¶ïÛg)³áÈ\—¬]+•$( u*U"X‘æˆöC¯½ù'½hãì©H¾|–b÷u2.ŒË7lJ’ºOS$KJ?,\HGN¢Ö}ûH+ha] †œÒŽFÞï?|H=¾f,²u«KQMñ^ò¤9sy¼í¤v ô0¸ +rµcõ²e)uÊg¼rµtÝZYîmäÂë!ra„%ð­ÎÈ÷h™çŸ§ZB7 £¡¸´ëßM˜"÷Î…ñðÉ“Tå½V’‰7Ê•§Ê¥_¡}GÒ÷óæÑçÆS–Œ¨i­Ú1D-K%uý÷4 ^mÞW›>—]‡|!÷íjÕh€”0A¯uWmÞ,•$(`¿O™*Ìû©e½×Ë–£Ê­ZÒœ¥KhP—.¶šü­Æ@eš4–¸:6nBOÂÊ&wÚøg5Æ<Ù³´ÿNMš³i‰¤[‹–T¤v-¹¾hõ*Û%%F«1b¨tLŸ¾Ô¤vm×ÐLÛ†ïÐsoÖ£Kâ…¶ö­¬¢¤x6»´£ñúDg=<|<­½ÆzÜë\ñá™5ünö:?Æ@ßÁ1hA%«1vkùqõd R¥(½QþIEس¾•ÛVc\½e³üŽfÉŽŸàzïT)]†JÕ›ÖmÛ&÷Ûé¦b5Æa“'É&xç4qà@Š#\o@ÙÅøÀqãè«É“©IÍZ®r¹Ó¿°9sÃÜ 4ª÷§>•$쟽x1ÔµYs—’„íRE‹Ò+%J`•–¬Y#—Nú3ƒ|·©ß€6ΚM_vë&†(#•A+ðãèuÍ`|¾ð³ÒDª”$\¾è!€Nÿù§\:íÏ FøÖ5¯WÏõ²¼ `1ݸuK.ƒùsJ;*Þá‹€aFàƒìt2ÓŽœXœÒŽN‘‡YY[Á÷„9?ÊË6øÄ:Ì`¼xåªdÿåâ%ÜÞ;I%rÁúÃ.#31ìj]¿¾›2Ô¼n=YŽÎÚéó|ß°)Jwl—›×­K)“»ÉɆ¿õÛ·É-ôîYÒ$T4_~ºpå -\µÒÕˆ”\déº|©R´ñ:½j5_·žvý{> îÚUòŽðÿo¦Lqá¸ÿà\Gtþ¸º¸a#]Þô_—ã$ü—%¥œ¶P¡óá&ŒÜ˜ÌFÙØ…í«Là"„Õ®èÈ›cÁ\¹©mÆ2u: sÂâRADxîØ|D£SÚ‘&Cn)ÒV”,RDâ ×W;ÂîÐoËèÄï+éüú t`ÉRš2d¡‡ŽÞk[I,9¥}‹ÓÌq\íèɬ.P~Ñq±Û‰›#,¼3D'¢Ó©¨z›62šZms/¹0~Ô²¥ôóÄè:¡EëÔ–\£¦OwAJ˜Ð{0˜«B+–*Jèyà„Ï,=0—uy·™ËÑsñšÕ.VG9šÁlK,MøP"v „ÉÇV½™ÖÐC¥OcCF ÀAv`Ärœ€†|Ô^.^ÜV pbD’·¯?î)¢3I«(>²pæÆCÞ²wo‘ÎÂÇJŒâ1.ÒÛOÎ4»“£âN¾©S¦$8ÅfI ‘–dÚСr7|Îà{fqa ô1yÇø·3"?¬ˆ,6a¨ñœfÖ¹ÚqÝPÝNåû%¢úãçy´{þ‚o:hÕÚ´– ŒÍðl].ŒDeÕ´é²³¥©}£F4´{wZ9uš+P&£`éЛ/áÂ7¤²°žJ¼€@Êü+7¢þrdÎ,×é‚G/ø³(Ç-UÙ¼AáŽ\£Ââô¥U>-ób/Ràç² £>²C»u§ZÚËg÷1ÊÂE¡`Dò>•­:ŸHëáIxNŸy±!Ñ݈ÈaUÏ:vl‡‚Ñ/ˆÈME0ý‡Ó‰=TŒ¾ƒÞp,CÅhä‰Q•KÈ»uêw…u=TŒEDŽę*—PÎ,Yhžp])ßì]iýE D‡‹BžáóŒÎ ~ŠàyAÏæå ”±EQ:%K‚ €ΓG–mˆŠh“QH‚RŽÚðêÇM¾fëW:€¨ª®Ü-*úM•Û½ £Ýüs=+0"¤¼fûvRñENŒÁ]? †¶c¬Àè9ãô%ñãÇóVŶ²P0BÁÃp¹'!)¬zaaž¬Ù<«Øº FŒbúE9Dj‹pR¨ͼƒÃ…3TŒF¾ÇÎúAn" ,…N¡P1ªˆÚb ºAÂ( ?c˜Ü.맆P1N嶪R¡cf}r«dÁ†¥CoHà…©FŒ„Ü8ƒÄ´ £³."Û@H†¥Û’™µhV¥ –ðèMš;WÎ&7ÄßÌ… d'ÊâÂhï^ƒ ã¥kW©Ž®9™¹a&—#0ÆÉ?Ï•J=ÌüŠˆ,Ï#§M•›šöœÆGÕ³zÉ>I0{þæŒ)Ùdzˆ}ߊ©wì Œà{ÓÎ¥ÞHè½~:r„,BÙ¨üëY½Î…ÑÌ;ØjLžç㨮ƒï ²TƒÞoØ@ÛºäÂX(Êè03ÊGI‚«Ê/+VÈM»’ÜraD‰§ËÂê-[¨Û°È¡p¸øp’e%¤üW‘>H ˆ”â¥Wú*É?³»!K*z3µ+V^ë«djrÌÙaÌóÁ_ A5_«@8šñËï4”Û˜PV&ÐpábÇx3'Fàè,¦/9´aûv¹Œ™G%Óع ©^ ÜÉðljq¤ˆÀP¦ï}"=D¥–-ž@0iÐà'Â?Ÿ¨b'F8¨#p Q9Ñ€õ PAv…êrb Qü–Ήq€H¤‰aFøyäÏ•Ktï‰çpµkØsÚAœͼƒ9±rbT|«“®ÀDªv'Fä‚úhè—™{4< | U´&ÜU옗ãàˆšµxU{õUé ㊚êëÃæÍ©j™2¬Mo€ _W8{â„üp ¼ŠÊý‡¤ù}ßÑ#´C8b#ó&²öva˜ãú~ö„i¬j™WeƒîÚ>ÌöûÅÜ-è‘önÛ–úuìDq£Ò”clÓœ:~BÌïr„ö9LðsAŒ‘Ÿô–Ö _žV~Iô/¥+ÜÁg—/¾µ£"µ†2@¸á±,kj8Rî ðïÒ½»tQ8ž†ãÚm¸B9a ¾ |u<ïÖ®C˜ÍÛ,9¥‘7 JÑ®ä}мH]á¨ï"Ï|bfp:¥½ñ|G/ï׃ÇKD0@ŒJ}F‘k(r F|/f E ïÓm{÷ÒŸ—.É”2HÒÜþFnÑñfq^÷ %E§ÈÅæ¬|íܼz5íŒ52‘§ƒ³êu1UCºÔ©f¸'Rœ œ*eЧÖGŠ€“çÎR¦´é,1}ïÚ}ò„£0új`Ë÷^¹L{Ĥ‚NjÇ`±ø:Îiíˆ{IÉ™^DO%ŽÊæ‹ÿ@RèB,@IDATÊÞŽˆ@':L¾æl £ÓÚñÚÍ›Ò2žTDñfÉp­*vb;šyGÇv>qZ@pø«ã´{¼^÷+>ú)’'#D…:Òâ$ŒøöCO€â‹9ÞŒSaùk§§íÛû–ÔsÚ /²lèM]yàði&ª7/& „Ððy³ÿ/OD ÇX]‡£Õüs>ѻԽWQiÂMv¶£Jþj7fNŒpúu‚ã/'F´W ÷5gÛrb>''FàC$£Šf ^.Œøö[e 4+K¹Í^\××ÐÐÐÐÐÐp²´¢ääÖѼi h h h h h „UZQ «øõŵ´´´´´œ,­(9¹u4oZZZZZZa•€V”Â*~}q------'K@+JNnÍ›–€–€–€–€–€–@X% ¥°Š__\K@K@K@K@K@KÀÉð›pò‡3w5i h h h h h h ÄT p‰£@L}]…wèÒEz(æ˜Ó£k Fò­Û1z·Ÿâ^·£’Dô^êvŒÞí§¸ íxBLavûî]ÙëÒofî¼Ù²ÉÔÞE3gözpL(Œ+¦–À&cônMÝŽÑ»ý÷º•$¢÷R·côn?Å}lhÇx)’K=Gaö¶Ô>JÞ¤ÃÊþ!ŸÓùÅ0¤1ŽnǘѾºcF;j±GZQŠ=m­‘j h h h h h ˜”€V”L LW×ÐÐÐÐЈ=ЊRìikTK@K@K@K@K@KÀ¤´¢dR`ºº–€–€–€–€–€–@쑀ߨ·`ÄpöÂúK„Û{Rüxñ([¦L®â«7nÐÍÛ·]ÛÞV2¥OO‰ET𑯷óÀŠ+R/X’'MjÜmË:7F€8ù29žòçÌIϤHa .ãE81Þ»Ÿv:DWEXfÖ %Æ$‰/oË:'Fܧÿwø°Àxƒ2¥KGså¢d1ô^EcëbÇðž=éý e;âÃӪϧ´lýz·cÕ¨Ac?ëgÛ͉Àþ~ôˆ¾ûiõ1Bâݧµ¨÷¦fî .ŒP q³f¹A€²ѯ?Õ­\Ù­œsƒ ã]‘ µÿØ1¢ rc¿ý´Õ¯^Ý­œsƒ £'Ï+6n¤ú]»ÈâôiÒБeË=«°msaĽš·ZUŸ|Ïüúkª]¡¢ÏýVîà¨xÄGç“ßÐï›6©"¹Ì—#m›÷‹[×F³ß.|8/Fœ{óîÝÔå‹ÏéÀ±cØtQù_¤ ýPÖŒ]eœ+œ·îÙCm>ë+”Á³nú¶ï@·níVfõ†¥ŠÒû‘Y¼ñ²|©X17^&HH)“%w•U/[–R§|Ƶm\Yºn­ÜT½™‡ýEoué,«\Y³Ò[U«Q¼xqiüìÙôã’%ôˆ~ÿnÐ ã)ØÖ¹0‚á½ÂñÁçƒiû¾}lürb.ŒKÖ®•J”†:•*¬HsDûÁúÒü“^´qöT$_¾@X ¹Æå6H%Iݧ)’%¥.¤#§NQë¾}¤´€°.ÙA\¼ßøz|5ÌXdë:FàRTóµ×ÔªkY g.×:÷ Fð K`ÅÍå3XTXj¬ÙÄGÖÐÝrCsŸ £™ïŒ‹¦.Œ°¾ÕùÙ†ežžj >nÜ84Z̬Å¥]ÿ~´hÂD&Tî§åÂxøäIªò^+y±7Ê•§Ê¥_¡}GÒ÷óæÑçÆS–Œ¨i­ÚîÌX¸e©¢¤øz¿AêÕæ}µéuÙ­e$hÏP”¢ôFùÈԪ͛¥’ì÷)S…y?µ<ìõ²å¨r«–4géÔ¥‹­&«1P™&%®Ž›ÐÁÇŸèáÉ6þY1Oölí¿S“&ÂlšD"éÖ¢%©]K®/Z½Ê6EI‰ÑjŒ*Ó§/5©]Û54Ó¶á;ôÜ›õè’x¡­ýc+Ù¥(qaTçÅr‚謠‡‡’§µ×X{ÝêvTüâÃ3kø7j3¬KŒ]‡|!?°oW«F ¤„ Ä(Œf¾3v·ºWoÙ,Û0K† ´pü×{§Jé2TªþÛ´nÛ6¹ßN7«1›í?zŒ¾˜8Aš¾_~{¢²›80®æ|¼˜_xöYj,LÞ^¼h7,·ëq`Äà§Ó¬WOáó—`æ/V ½Y¥jX¬/VcÜ{䈔!¢j#fÏ’Ù+â= Çß²%K‰v­éf¡p8Ó†Õ½±éí;ã­W™ÕË E¨Áë¯ÓÜß~£j­ß£î­Þ>f‡äP8ÜUF W»ÉjŒP† ,A¯(˜;·DcßÓ¢èÝ2¹a©¢”$Qdˆ7Wñ3RåÒ¥i¶ëOäî¯ê ÊdÌÌ™róƒ¦MU±ÛPE²(¿×N±¢ÂÊoݹc,f[çÀÈÆl'¶ã„› /½$ÇæãĸlÃzj$†O%ê/mýðpaDúîQÜÃ{ö¢xB‰qaL嫃@ƒ+WºÁ2q"ý:f¬m¡×\¯e¤Þ»F’™·|ý2zŒTû8Ö¹0zòêë;ãYc›#ü˲gÊL#¦N¡o¦|ïbåÔ©”#s×6÷ ÆÂ_Ê<¢3“%MBEóå§ W®ÐÂU+]ÑóH‰ÀE–¾áÊ—*Eû/¡Ó«VÓùuëi׿çÓà®]%ï=ýfÊŸ8  㥄ŠrâFeå´…u„·›80†“çõíˆ(85Ô!BXU”£'?Ûœ æÊMm6$¤®@tNX\*ˆè¢ûí‹häˆH„!·i+J)ÂÑ<Ÿ“ #üèý¶ŒNü¾’ίß@–,¥)C†zèè½¶‘DvÆûH° ÎA7l¤Ë›þër^‡ÿžQ;ˆ £'ï¾¾3žõ8¶91ÂÂ;cA¤Û :Šª·i#£©Õ6÷’ ãG-[J?OŒ.¡Z´NmÈ5jút¤„ ù,U”`ž†¢ƒñnXz`.ëòn3—£çâ5«] Œ+ªøvF$`D|¹ÇV½™ÖÐC¥O“ÖxJ¶uŒlÌyb;0bH9N@C>êF//$·ÁƉIÞ¾þ¸§ˆÎ${äøÈ™yËÞ½E: {+90bˆ{PĸHn?9Ó‚kóGq`T\ ˜$uÊ”§ØÌ"ù-Ò’L:Tî†Ï|Ïì .Œ‰£œ}1t k.¬ýè¬ ‚i@v¥*áÂhlßc=®u.Œëþøƒêvê(ß/ýûÓ?Ï£Ýó|“ÐA«Ö¦µL`Ì…Ëx^.ŒDeÕ´é²³¥©}£F4´{wZ9uš+P&£`éЛQ`Æuøf€”C–qÖ‘°N Õ½[§ŽÛn½8|dàÏ¢·T%d’©9Un÷2Œvóìõ¬Âxìôi™ | U~N!«0ñà#;´[wªÕ¡½|p£,\ F$‰…å”O„”{žÓg^,EHt·0"rXÕ³ŽÛ¡`ôÇß "rSLÿátbc*¡‚Ԝ…eŽÌ™å&²­‡“BÅhäÝßwÆXÏîõP14«\B9³d¡ybØ´|³w¥õ)t5. #ø†Ï3:+ø)B‚_äü=›—/PÆEé”Hj*ìÈØY?ÈýGΓàÕ›|ÍÖ-®tªŽÊÝ¢¢ßT¹ÝËP1ÚÍo0׳#BÊk¶o'_äÄÜõÃ`Xa;Æ ŒÞ˜3N_?~;×CÅhäõißc];×CŨ"j‹(èÆ6,„HüŒar»¬Ÿn 6BÅh8•ÛªJ„Ž™qôÉ­’–½!Q$¦1rã ÓV€¼9ëbÙCAï7l —žðèMš;WΦöÏ\¸@Z¢`q‚ ì .Œvðè5¸0^ºv•êˆá˜ƒ‘™fâp9saœüó\©ÔÃ̯èÈòH ˆ”â¥Wú*É?³»—lÜ*¹œ^1Á7ªùZÂñÐŒ_~§¡C¯ 'T…• 4\øƒýš¼ÃŠ2NŒà¯³˜¾æ|ІíÛå2Bd>^•Ls`ç.¤zr'Ã'Æ‘"C ±î¡É•Z¶xÁ¤AƒŸÿ|¢RˆœáüŠÀ(DåćÖ#8ÅB9٪ˉ1Dñ[v8'Æ"‘&†áç‘?W.Ѽ'žÃÕ®aGÌMhqbÄ»¤vÅŠ"rh•œÁø Ísr‚_ Ib¹‰£â=˱äĈœƒ ýR8s/J/RñÀ—PEkÂ]ÅŽy 91Žˆ Y‹QµW_•þ‘Pì•ÿ܇͛SÕ2e8šÍuNË¥ÄÂÌ÷a‹4YX} U*BÖÞ÷EЇb|T…ò«}èic,?çXúÝ$ê(R—ãÜo!צJñ6“¬`ñ'F°ºtÝ:9$edÊ!~ _éøõC]çĘ *ä<*…É“_åœïYnå6'Æ>íÚSÜ8q¤UIݧàÃQ?èl›å“£·¶ˆ/r8ÑÎÈENŒ˜ŒX[ðST¯Jêß±“m©81Óxm wDŸ"%VÁ.Íš ßÁ¦r›ûc ßNœœß{ûmi(ørÒwÒQ©Š üönÛΖ„¢œñ­GÀ—Ò€Ñ}}Ú·§z•«(¸lË8œå3góêÕ´K$xjd" NgÕëbª†t©S=u˜á^Tˆª1ݺ?´HpòÜYÊ”6%¦ï}‚³ûä Gaô‡?˜}{¯\¦=bRA'µc08üã´vÄ}$ŒPÒ‹è)¼DB%§·#”\(L¡Ìæ´v¼vó¦´Œ'Q¼YD"F+†ŠØŽ¸_Ïÿ«T)S<õÈ}ì´v4û‰ŽÁóuq¿bT"Eòd„(°PGZœÔŽøöã…â‹ä¯Æ©°i/_uöß¾%õœ¶mÛúªB–Y”Ô÷ŸFõª ©ó£áófÿ_žUnç’£X|]Kc|R2fîUÔEš€p“íèi1¶ ;'FX[¼˜Ø…M]‡#®û:‡“81šyv9eÀ‰|#’QE3râðwn.Œøö{F½ûãÃÊ}–:s[ɘ>—–€–€–€–€–€–€–@¸% ¥p·€¾¾–€–€–€–€–€–€c% %Ç6fLK@K@K@K@K@K ÜЊR¸[@__K@K@K@K@K@KÀ±Њ’c›F3¦% % % % % %n hE)Ü- ¯¯% % % % % %àX øMpää)É8r)ÄTÚætŒÇx jâ`ÝŽÑû.Öí½ÛOq¯ÛQI"z/c÷#6`1F4£¸Scþ½3ZÊ?ŠØÐŽ±£ÿVÖ{c’b½¢“Ó–è_»ty4’€nÇhÔX~XÕíèG8z—–€%ë%ýÒrà]Kºƒš>DK€Iúyd¬>mX$à7R0½pþúûï'/eË”é‰òGÓ‰³gèäÙs„õ¼"Ò.øù¢ÛwïÒÎ(nœ8T¼`AJž4©¯ª•c"æÆÆÏ_¾LgD~‡ü9sÒ3)R„ÅÊJœïÝ¿O»¢«7®SÖ %Æ$‰‡Ä¾ÓÚ÷éÿ>,0Þ LéÒQÁ\¹(Yˆ÷j0âlG#?'Ï£[wnSºÔi$^ã>îu.Œ—¯]#´£7» ï4»ˆ £‘ÿ‹W¯Òq‘síúÍ›”1mZ*’??%ˆÜ'Â)Ï#ž¿›·oa>±ž)}zJœ0áåœíhö[ÊçäÄøXè§ÅwñЉ”4Ib*”;¥M•Š Šë¼Á=®ÃÝWÖnÝJµ;vp/4lY¾œÒ‹©¢Í»wS—/>§ÇŽ©"¹,ÿâ‹4¡ÿÊš1£«üŽxaµêó)-[¿ÞU†•F5jÐØÏúý@›íùpbž¿=¢ï~šC½GŒÀ&îÓ‡ZÔ{S®ÛõÇ… Òàñ4nÖ,7(Pv#úõ§º•+»•›ÙpJ;ÞyÇú#Úð'7öñÛOûPýêÕÝÊ97¸ÚÑ“ç7Rý®]dqú4ièȲåžUض¹0â^Í[­ªO¾g~ý5Õ®PÑç~+wpaT<â£óɈoè÷M›T‘\æË‘ƒ¶ÍûÅ­,Ð §<»w£ÿîÚå—íá={Òû ú­cÅNÎv4ó-µ‹¯spbܺgµù¬¯0¬œu»|ßöèãÖ­ÝʬްTQºs?29%^–/+æÆk )e²ä®2ô^Þêü챕yþyª%^:qãÆ¡Ñ"É%„Ý®?Z4a¢¬ÿð¯¿è­.å Ÿ+kVz«j5Š/.Ÿ=›~\²„þ.8ß ä:7ç Fð¼WX >ø|0mß·ÂSÏÍ…qÉÚµRI‚ÒP§R%‚iŽh?ôÚ›Ò‹6Îþ‘ŠäË÷Tþ¬¨À…qù† RIR÷iŠdI釅 éÈ©SÔºoi- ¬KvF#ï÷>¤_ 3ٺ΅¸Õ|í5µêZș˵ν…|ÃX±Esù ¤k6ÑA…5t÷ÁƒÜÐ\ççÂX½lYJò×uŒ+K×­•›ÁZÍŒç d £™oi |†R‡ ãá“'©Ê{­$ko”+O•K¿BûŽ¥ïçÍ£Ï'Œ§,3PÓZµCaÝï±–*JêJï7h@½Ú¼¯6½.WoÙ,Î,2ÐÂñ\fì*¥ËP©úoÓºmÛä~|TWmÞ,•$(`¿O™*Ìû©å9_/[Ž*·jIs–.¡A]ºØjò·#•iÒXâêØ¸ HžÖ^c=îu«ÛQñ‹NܬáߨͰ.90vò…|Ͼ]­M0&H£0vkùqõ…N©R”Þ(ÿ¤"ìYßÊm«ÛÑÌ·ÔJþÎe5Æa“'É˽óF š8p Å®7 ìb|à¸qôÕäÉÔ¤f-W¹Üiá_Øœ¹/^¹*a¼\¼„냂‚$‰¹àýS‘ Ù‹Ëe×fÍ]J J-J¯”(!÷-Y³F.ôg#ønS¿mœ5›¾ìÖM QF*ƒNÂã3Ÿ/ü¬4‘*% 烯z Óþ)—Nû3ƒ~ÍëÕs»§¡ì£·ºqë–ÓàI~Ì`Tà‹€aFàƒìt £Ó1yòg#†ÜÐ êýiØ•$O,¾¶Í`ôuŽ s~”»šŠ ü±œFf0š©ë$œføÆ°¨uýúnÊPóºõd9:k§Ïó}?¦(=W¨øëVÆWApÔ=s†\/W²$¥H–L®¯ß¾M.Ñ»ó$¥(=}ÊsW@ÛÁ8tbQÉ Fœó›^½¤e çwB=³½ñ|ãv¤ò6åÐIí艥êA/”'¯çnGlÓŽ}F’¼ëуÒ<ã}xÃ࢘£“ø„37îØ.OÙ¼n]J™ün\çiuœü<"PfúüùB;aíu"™iG3u„Õ ßÊ/)•G`F—àæ:r28 ™° ½ý´t)íQM0ãæË‘SZ}*½òŠ?e…"Ôàõ×iîo¿QµÖïQ÷Vï‰qñCÒ|!¶Ñbø«†,@¦ó$I‡;ÉJŒvòmæZv`ÄKk㎒­²/”4Þ%u90Bá¿|ý:ÝpŸö=F_Lœ ‡7^~{¢²›80®Cãèä¼ðì³ÔX˜¼ÿ¼xÑnXn×ãÀˆ ÀO§Y¯žÂ2®4ó+P€Þ¬R5,Ö«1î=rDÊQµ³gÉëñEMÙ’¥D»Öt³Œº œiÃjŒÞØœúk¤ƒ::Ù%¢:ìÞêq•Y1Ðo)oçµ#”!(KÐ+ æÎívIDcßÓ¢Ý2¹a©¢”$Qdˆ7Wñ3RåÒ¥i¶ëOdÃĘxöL™iÄÔ)ôÍ”ï]ÕWNJ92g‘ÛÆ¡ŠdQ~-®ŠbE…•ߺsÇXðºÙè Œ3dE³½;;1~!ñ@ˆ°©ðÒKA" µÉ¤šœ—mXOÄð©‘ äOòeH§´#ÒtràÞ³ÅJD¸ˆ«Eùê Ð`ÁÊ•nð†LœH¿Žë7‰ÛNiÇ+B™™9ÓƒC’A2ó–/£_F‘JâžRà¤çÑÈ*¢Þš65îb_çºWÁx ßRv€â\k?2(óˆÎL–4 Í—Ÿ.\¹B W­tE5"=Yú†+_ªí_¼„N¯ZMç×­§]ÿžOƒ»v•¼#ôô›)SÜp W:cA¤ JEÕÛ´‘`ØVN[XGè¼Õdö¥ÅÑjLžç3ûÒ² #¢à” ²pæÆCÞ²wo9œlXŒâ1.ÒÛOÎ4;ðáïpòM2¥ 0É,’"-É´¡CånøœÁ÷Ìâ˜8*pC§°æÂÚÎ Ò!ÀÉdWª.ŒÆöÁø·3"?¬ˆ,¶3a(øàÂè·Ô( ®u.ŒDeÕ´é²³¥©}£F4´{wZ9uš+P&£`©¢äKøðÍ)‡,¬/[’}!ÿ,G9³d¡yÂÔ[(O©õ#ì‚G/OB&YP¸#BÁè‰É©ÛVaDÇ&Í+ç?“âoæÂ²‡‹eqa´ƒ÷@¯Á…ñÒµ«TG ×`h™¹1ä.G`.Œ“žKè¹Â̯èÈòªÇ’#|’`÷üÍ1RBÀ³ˆ}ߊ©wì Œà{ÓÎrÎ*#LOóéȲ=d£òk¬gõ:FD¶ÜW}l±añY‹a•àsbqaT¼f|½ß°*¶uÉ…ÑÌ·”0F¨¼Š Ãê-[¨Û°È¡p¸øp’eQoHù¯"}@)Å/J¯ôU’4fwC–T䝸hè—™{|P‘>þ*ÂþMj.¥š¯UÃqp }ù†b ½‚œP$ÐpábÇx3'Fàè,¦/Qæü Û·£HxúϦÅQÉ4vî"MrÓ'Æ‘"šQ™¾÷‰ÐäJ-[Q)ÄNŒp~Eà¢râCëœb¡‚TÚ‹!<õpNŒO½¸M81‰41ÌŸÉü¹r‰à=ñ®–CŽ€‡¹ í NŒ°(Õ®XQD­’ÓC`ÞL|æ‰99Aȯ„$±ÜĉQñ®L"¸©ÚMœÍ|K9qsbA³/¢j¯¾*ý#¡Ø+ÿ¹›7§ªeÊpB£xùºÂÙ'䇻hT¨¾¯z(‡¢rÿá9^¸ïèÚ!æ+CæWdíí$Â0ÇõýÌÍ4ö¼0Ïg“]ÈÙ€wìßO——À;N¼ˆ2Šü ŒM"Åþ¡ã'Äü.Ghï‘Ã?äÀùIoiƒø»$z‰Ä,öáÆÖ»|ñ…ìÍ©0¿Ê#¶ñƒe-Sø¥{wé¢p< 7ƵÛþp…rbXõ‚ð9óü½[»a6o³ä”vDÞ0(E»÷)ò~ u†£¾8ˆ<ó‰™Áé”vôÆóñýáBB¸P®Ҏs†2¿S´#ÞMxçà™¬W¥ŠLs’OLU,9©«–yU¾cv ‹ |=ö‹ù³`ìݶ-õëØ‰âFMa«SÚ|âÛ\$ó!¹JP( Bøs F3ßR³p‚ß‹ÙBQÂûtÛÞ½ôç¥K2¥ ’4·§‘[t¼YŒ—ÅýEIÑ)òEq„9ë_;7¯^M»cL$Äéà¬z]LÕ.uª€†®ß¼)M‘<ÁsÝŸu)Nž;K™Ò¦³Äô½Oh÷ÉŽÂè«=‚-ß{å2í“ :©ƒÅâë8§µã=z$Œˆ"J/¢§ò‡ùÂð´r§·#"Pã‰S(ó…9­¯‰w,ãIEo‘ˆÑŠ¡b'¶#î×3Âÿ*Uʽ³Ÿv¯:­dœ"ëiž¶ßiÁ¯™oéÓða¿“0âÛ{Š/æx3N…_uöß¾%õœ¶¢sà‹,zS@ô>ñ ”}¡"0žv ”¨¼Ùÿ—séiõ9öscäàÙì95Fï ô^Å iÂMv¶£Jþj7fNŒpúu‚ã/'F´îWLèNâÄh¥‚ŠŒ81‚¯@ßO¡`xÚ±\ñí·Êø4 žû-uæö<¹ÞÖÐÐÐÐÐÐˆÎЊRtn=Í»–€–€–€–€–€–«´¢Ä*^}r------è,­(EçÖÓ¼k h h h h h °J@+J¬âÕ'×ÐÐÐÐÐˆÎЊRtn=Í»–€–€–€–€–€–«´¢Ä*^}r------è,¿ '˜1ƒqW“–€–€–€–€–€–€–@L•@Ð '‘8 ÈÔÑUx‡.]¤‡b~#1º¶`$ߺ£wû)îu;*IDï¥nÇèÝ~ŠûØÐŽ'Äf·ïÞU½.ýfæÎ›-›Lí]4sf¯Dޏbj La¢1FïÖÔí½ÛOq¯ÛQI"z/u;FïöSÜdžvŒ—"¹ÔsfoKí£äM*1¬ìò9_ C³áèvŒí«Û1f´£F{$ ¥ØÓÖ©–€–€–€–€–€–€I hEɤÀ¢cõ8éky×<ÿOºÿ'‹è¼¦Û1:·žæ=6J@+J± Õµ©?f4²nGÝŽ1C…–@ô’€V”¢W{Å­îÁ%6ǤÛÑqMCºƒ›>HK lðõ Wg/\ ¿D¸½'ŲeÊäYL?¦ÓçÏÓ¡'(i’ÄT(wJ›*ÕõTÂøv8@qEê‚â Rò¤IÕ.Û–ÜäüåËtFÈ%ΜôLжaSâÄxïþ}Ú}è]a™Y3d”“$N¬.mÛ’#îÓÿ;|X`¼A™Ò¥£‚¹rQ²z¯¢ÁNž;G·îܦt©ÓH¼¶5¢¸W;^¾vÍgØ0Þex§ÙE\ü_¼z•ŽŸ9C×oÞ¤ŒiÓR‘üù)A|Ë?ÆKº­s`Äówóöm·ëxndJŸž‹èg;ˆ£âû‘ø–ž8{†Nž=GXÏ›=;å?»‰£Y}Á*ì–>k·n¥Ú;øäíÈòå”^¼Hmݳ‡Ú|ÖW4îYU$—}Ûw [·v+»#><­ú|JËÖ¯w+oT£ý¬Ÿm4'FûûÑ#úî§9Ô{ĉstŸ>Ô¢Þ›n˜¹7¸0BA<>‚ÆÍšåÊnD¿þT·re·rÎ .ŒwE‚ÖþcLj6üÉ}`üöÓ>T¿zu·rÎ .Œž<¯Ø¸‘êwí"‹Ó§ICG–-÷¬Â¶Í…÷jÞjU}ò=ó믩v…Š>÷[¹ƒ £âÔOF|C¿oÚ¤Šä2_Ž´mÞ/ne\\wïFÿݵË/ÛÃ{ö¤÷4ô[ÇŠ\ÁÛæÝ»©ËŸÓcÇÜX-ÿâ‹4¡ÿÊš1£[9×'F3ú‚Õø,U”îÜÌâ—åKÅŠ¹ñš0ABJ™,¹«ìðÉ“Tå½Vrûrå©réWhßÑ£ôý¼yôù„ñ”%cjZ«¶Üÿð¯¿è­.å Ÿ+kVz«j5Š/.Ÿ=›~\²„þÑïß ä:7ç Fð¼WX >ø|0mß·ÏRf}[¸0.Y»V*IPêTªD°"ÍíëKóOzÑÆÙ?R‘|ù,Åîëd\—oØ •$uŸ¦H–”~X¸Žœ:E­ûö‘Vк 9¥¼ßøz|5ÌXdë:W;—¢š¯½¦V]Ë9s¹ÖÍ®8©a ¬Ø¢¹|‹ R 5›ø¨ÂºûàA³Ð‚®ÏÕŽÕË–¥Ô)ŸñÊ×Òuke¹]V3.Œ°¾ÕùÙ†ežžj >nÜ84Z̬Å¥]ÿ~´hÂD¯2°º £}ÁjL8Ÿ¥Š’bðý ¨W›÷Õ¦×å°É“dù;oÔ ‰’Êž]˜´ŽG_MžLMjÖ’å«6o–J°ß§LæýÔòØ×Ë–£Ê­ZÒœ¥KhP—.¶šü­Æ@eš4–¸:6nBO¢‡'wñ¬O„ÕódÏF°vjÒD ³&‘HºµhIEj×’ë‹V¯²MQRb´#†JÇôéKMj×v Í´mø=÷f=º$^hkÿØJÁ*JNiG%;,'ˆÎ ,Âø yZ{õ¸×­nGÅ/><³†£6-Y:©»ùB~`ß®V& H $°c°'±º»µŒìŒ{òƒN©R”Þ(ÿ¤"ìYßÊm«1®Þ²Y¶a– háø ®÷N•Òe¨Tý·iݶmr¿n*Vc4£/XÙVê\asæ† Ôº~}—’„íæuëa!_¾§Ïÿ)×g/^,—]›5w)I((U´(½R¢„Ü·d͹tÒŸŒà»Mý´qÖlú²[71D© : 7^Ì`|¾ð³rHU)I8|=`Qþ3²½å†ƒþÌ`„_Gózõ\/+ÀÀ ½uÐ[·äÒif0*Þá‹€aFàƒìt £Ó1yòg#†ÜÐ êýiØ•$O,¾¶Í`ôuŽ s~”»šŠ ü±œFf0^¼rU²ÿrñnï$‰¹`ýƒa‡‘ŒfêrÀ ›¢¤ü’Ry8*ÃZ„a Б“§ärýömr‰Þ')EéèéȺžûùm#øü¦W/é@i5ÏfMýf®o£·s߸©<¤u¨r*F8”ª½Pž¼ÞDP™ÓڱϨQ’ïa=zPšg¼o̦J¡¶£Ul:¥7îØ.!5¯[—R&ÿŸ[„U8¹Îj;"Pfúüù’½vÂÚëD2ƒñ¹B…$„_ÿ³Bú*aNÏ£gÎååJ–¤É’Éu'ý™Áh¦.F–¡·Ÿ–.¥="ª fÜ|9rJ«O¥W^qãÊÀ£^ÁܹÝö!Jû­MC ˜=IEÒ!BÅN²£|›¹–ñÒÚ¸c‡d«ì %ͰgI]ŒxI]¾~î?¸Oû£/&N¦ï…߆¨ì&Œë…9/æž}–‹!ò?/^´–Ûõ80âðÓiÖ«§ðùˆKp (V ½Y¥jX¬/VcÜ{䈔!¢j#fÏ’Ù+â= Çß²%K‰v­éf¡p8Ó†Õ½±9õ×Hut²KD)Þêq•Y±¬P„¼þ:Íýí7ªÖú=êÞê=ácvH…Ã]e´p°›¬Æ¨¾À…ÓRE)I¢Èo8®âg¤Ê¥KÓl1ÖŸ(* ³†ÆÃ‰h‹dI“PÑ|ùé•+´pÕJW”BCÉ¢üZŒçUaå·îÜ1³­s`dc6ÈÛ‰ñ á¸B„M…—^ ’có‡qb\¶a=5çF‚’?uÈ—¶~x¸0"ýG÷(îá={Q<¡D„‹¸0&ŠòÕA Á‚•+Ýà ™8‘~3Ö¶Ðk.ŒW„23s¦>l HfÞòeôËè1RI|¢‚Å\=ÙD4£ÂûAÓ¦ž»Y·91¿,{¦Ì4bêúfÊ÷.+§N¥™³¸¶¹W¸0ª/pá³TQ*_ªí_¼Dú, ’ŠÏ¡ø|öí·Ò1ù›)SèÓví$–Z¶”Ž­ˆ®ðü¨(° &pó_Bè|¸‰c¸1y^ß.Œˆ‚S&ðÂjWô ðrb,˜+7µmØPZD/‰:Â®Ï ‹K]4W WÁWËâˆÈT„!·i+J)bŸ×àÂ?ºC¿-“–£ÄÂ×¹…6íÜA=‡—Öî¶"’è?ßOñÉ—•;¸0Þð@² «`ÿNPéçž“J"7›ôè.ý—ðŒÖªPÁJ8^ÏÅ…Ñób°º@ùEÇÅn'nNŒ°ðÎX9œˆN§2TToÓ†~õ-‹K‡§l±Í…1P}ÁOV”Yª(Á(PÒ¤u[ÅPœ§/Ž¥uí Œvðmæv`Ärœ€†|Ô^.^Ü ‹!×åĈ$o_ÜÓÅ㟗.É,¸´pÆ@õ.|–*J¾˜„oH9d©zðaÂË?EH؇ èÙ¼yåÍ…qVø)ÁŸÊ”‘Id|©÷Ûµ F»x õ:Va¼úq“¯ÙºÅ•@GånQÑoªÜîe¨íæ7˜ëY!å5Û·“Š/rh îúa0¬°cFo̧/‰?ž·*¶•…‚ †j< IaUûódÍæYÅÖíP0úcÓ-)Ê!œ»ÃI¡b,œ'dCTT± Q‚T°ŒqŸë¡b4ò:vÖriXRG)‰ÆýáZ£Š¨-V  ø#ñ3†Éaý '…ŠÑïÞô_uC)·Ô  ¼0Õˆ‘g˜¶d4ïÂ!Ô3·Ãê-[¨Û°¡².†ìÁ£4iî\9ÿ™*Ÿ¹pì!ÀâÅÊâÂhï^ƒ ã¥kW©Ž®¿2sÃL.G`.Œ“ž+•z˜ù=YžGN›*714mœÆGÕáXr`„O†Ì=sFŒ”ð,bß·bê;ˆ#øÞ´s§œCΈÖîOGŽEè!•_c=«×¹0"² „„„êc‹m ‹ÏZ´«ÒçD®0ÿqaTl²TƒÞoØ@ÛºäÂX(Já壤@ÁUå—+äf°InÕ¹]ra4£/Ê«™z–Y”ò_9e# ¦ ¹(£ØVI~Ð˜Ý YRGDЬŋ¨Ú«¯J<¨jꎛ7§ªeʸpÔ|­áxhÆ/¿Ó° Uae þ vŒ7sbŽÎbú˜óA¶o—Ë‘ùxqT2Í»êÊ œGŠ eúÞ'B“+µlñ‚Iƒ?‘.â‰J!pb„ó+œ·¡•"XÔÊ!È®P]NŒ!Šß²Ã91‰41GrÐäSÎܹ{O<‡«]ÃŽ˜›ÐâĈwIíŠE¤ñ*9æÍÄiž˜“„üJvpbTm¤L"ú¯ÛMœ‘ ꣡_ gîRéE*øªhM¸«Ø1/!'F3úGÛZ¦(aöå[´ ÉÂê­R²ö¾/"€>ã£*”û¿ŽupToý>íÛS½ÊUT‘\âK¿›DÅT'87Æ[A8¦Jñ6“¬`ñ'F°ºtÝ:WÎ(Å:”Cü@¾Òñ«ºV,91&ˆ ¹ŸJaòäY9ç{–[¹Í‰±O»ö7N©Ä«û¼c8jàm³|rbôÖñâE'ڹȉ“¤âck ~ŠêU©Bý;v²-5'F`߀†Bô)R€`ìÒ¬™ðl*·¹ÿ¸1¢«¾3Æ‘ n\Æósb|ïí·¥¡àËIßIHD¥*‚òÛ»m;[Šrb4£/(ìV.ãˆá¯|póêÕ´KD¤52‘§ƒ³êu1UCºÔ©ü3 ÜÿŒóÇŒdnÆ©-|ñ„cNž;K™Ò¦³Äô½OXpvŸ<á(Œ¾°[¾÷ÊeÚ#&!vR;‹Å×qNkÇ{"ôI¡8¤S$à%*9½¡äBa e¾0§µã5‘–ñ¤bç,"£CÅNlGܯx§J™Âï;;Ð{Øií| ã´bñUÏiÁ'ÒX`T"Eòd2j<Ô‘'a F_ðÕvÆòýbfè9mE4«/²Ì¢¤.€ÉmáðHTÑ3ŠMÇ×ÇäÍžÃ×n[ʹ1Úâ)ÑÝdö^Å iÂMv¶£Ñbl'nNŒpúu‚ã/'F´îWLèNâÄh¥‚ŠŒ81‚/D2ªhÆPø åX.ŒfßÁ¡`ð<ÖRgnÏ“ëm-------è,­(EçÖÓ¼k h h h h h °J@+J¬âÕ'×ÐÐÐÐÐˆÎЊRtn=Í»–€–€–€–€–€–«´¢Ä*^}r------è,­(EçÖÓ¼k h h h h h °JÀoz€C§ÎÈ‹ÿqã+á<ùñ³gcÆs±£nÇp>GV][?VI2¼ç‰íÞ91ãÙ³‘zŽ¿'ÆoÂÉ3gѽ»á=ÚózŸ–€–€–€–€–€–€–@¨:ádöœùéÐþôB…æ¡òàØã¯œÝM'ïÔÛB1¦Û1099½–nG§·P`üév LNN¯Úñö…=RÏñ×ÚGÉŸtbÊ>Ÿ“ÔÄ€±‡nǘÑкcF;j±FZQŠ5M­j h h h h h ˜•€V”ÌJL××ÐÐÐÐЈ5ЊR¬ij TK@K@K@K@K@KÀ¬´¢dVbº¾–€–€–€–€–€–@¬‘€ß3åÊ[’&M§kG#˜sgNÐÛ7)uÚ ”.}&ã.ÛÖ­nÇkW/Ë÷7™²d§xñ‚|}:´¯^¾HgN£›7®QZцù ¥øñxƒÏZfe;Þ¸~•nߺá—ßô2SÂD‰ýÖ±z§•o8çÙ3Çéìéôøñ#Êž3ŸøåU»m_²`|üX|Oщc)I’d”;_aJ•:-;¶ Ÿtÿ|mZ·œ†ÞƒN8ìV±á»èãÏF¸ÊîŠM}>jNëW/u•a¥FÝ&ôÙ n©™ºn'cÚàÀV=ú›~š9žF é)9ï3xÕkø ÿ§µ#¤ñ#Ь©cÜ. Ÿߗ©rõ7ÝÊíØ°ã½{whìðÏdùÆO¥êµ‹mY·£'Ó×.£®ïדÅiÒ¦§eOyVaß¶#îÕj¥³ûäûë±s¨BÕ:>÷sì°£âC>¦MëV¨"¹Ì‘+?Í[¶Û­Œ{ÃjŒÝ;Ô§]Û7ùe»g¿‘Ô i{¿u¬Üi5Fð¶{Ç鋾騑ýn¬¾øJê?te̜ͭœ{ƒãž[è³-…"xÜýöö§Ö>q+³zÃrEiíÊEÔ½CÉg™òÕ •(qbÙ ü¯w+S—ÖuäM kSÕõE-Íž6Ž–ÌŸEÿüó úú{y3u­·óq`ÄuÜCŸ÷é@ûölóvÙàË‚GæÀ¸ö÷ÅRI‚ÒP©Z]iEZ²`¶ìµÒ¥ Í^¸•ò(¸O_ úÈXdû:G;>|ðÀ…ãµÊµ]ëj%gïÖqµßïÒAíK`‹úåä3˜¿`1z­r-ùQ…5ôà¾~aX½“£ËVxƒR¦Jã•Õu+Ër;­fa ì,¾¥°b?_êU¡À×¥¸btfÆ¿FÒÖÿ®¦þ½ZÓ„é˼ʀ£ãÉã‡è½w^“ì–«T“J—­BGï£y³'Ñ„Q)cƬTë­fpä9ýfæ^½n«©„“h¨7Êå• Ë,H¾V¤Ú½Eèþ¸h;¥N“NVý¿][©UÃòr}éúãÒŒo¦®¯ëù*7›P‹ #ø{±`Éfã…iñ€ìáYaQºrF$Õ<xRM.Œûÿo;mÖÆ&-;K³)Àbx±vÅÈN».ŸQ›NŸJ˜ýsJ;BÙÝ»ûª-Z54y¾Yµ]½r‰zõEõ›´3 OÖwJ;™Ÿ6éaAëKø á9 Õ¢ä”vÄðSå—²ÈÏw?üÇ9äu'µãïÕ¢Í~§j5Ѐa“)A‚„!ãà œÒ޾Àà9mRç%¹û· '(mºŒ¾ªú,w Æßþ(,-­(C¦¬´`å×{ÊEý×KHþ×l¿è×¥ÅH§`„%é·…sè:iàWÿ¢8q"ǯ§NüšÆè']{~]±×Uî ·r•pÒ_fnK¹—/ùÙ¥ÕúS’Àìâÿ ynÖ¦»KIBAÑ/R‰JË}k~_(—fêÊÿ¸0‚e|@g-ØBÝ>ýJÈ$½u(LúDpa,\ôi"ÅØ²"øz ‡úSŒ=ÛE\Ñ+¯× •ëe<° å/T\B»uÓ¿¿„_üiGÅã…?ÏH% øÚwí¯Šm]rµ#+‡´#†Ü $zc™’ŒììnÇ9Ó#$›èУ$9 ãaQþ·÷N¢D‘oìÃÄÕŽvÕoÒÖMª+Þµ ÇÁw‰‹,U”¶nZ%ù|óÖOåwû–µ²L…ž¤¥Ó'ŽÈ]fêzžËêm.ŒàÖ|hÃMœ½a»}óº,N:Ҫ譎Õevb„C©zÐóäÖj(>ÏÇqÔ°H¿€}†Ó3>†7|2gÑnŒ±Òi¸0îØº^òU·~KJžâ™x õ`.ŒÞøB ÌüŸ§Ê] ßíè­ KÆBEž“üþgé<éâ‚ÇÂéyæ÷£dyÉ—ËS²ä)å:÷Få—䨄‘(¸9€N‰ÇNˆ@IDATw÷‰¶«¥>Jˆ\=|ø€&ŽL÷ï¢ûw¦‚TQ8>¾T¦’Üíà ˜ = V¢NÌÔõ<Ç6F>C9§ñÒÚñÇÉî /• …mSÇrbÄK꺸w<¸'ÇÑ'Ž$-­Åž{I Q½nŠÏP*sbÜ&::x1?[¬$լה.^8 «AˉLÑ™½º4¦¸qâÞK — *o¼m«õ… ã‘C{¥ÜS¤|Fø†Ž¡ÝÛÿK×®]¦Œ™²>®hW5|tx Fo—ÿuοd1:äJÉðVÏê2.Œ%_*O¯×~GMµnT‘ZµïI‡ìq …÷i=³·óqa„2e ÑÒ¹ór»4¢l±ï¶ˆºå"K%|ô@pH6Ê~ž5‘:|8€ÞëЋnÂ5“$Ij¬*×U¨øÛ·LÕ}âD Ø é”vb„#6J‘‰ùæÄ¸aÍRêÖ¾¾'è 9Ó¶.Î…ñï¿ÿ¢¯~(ñõì7ŠâŠ ŒpÆ #}uà_¶rÙ¿Ýà¡8æ_ m ½æÂx](E ™ß놨Y¾x.ž¼@:?QÁâ.Œžl"šQámÚª«çnÖmNŒð/Ë”%ÁggÊ„¯\8¦Î]G™³ætms¯pa,_©–TæG é!\’Q>1òråÒZµüß®¨F¤#à"K‡ÞðR!¼ÖüÍ´yÿZ³ãµíÜW–5€.]ü“ŒCôžÎL]yæ?ŒÌ,‹jsW° #¢#” ¼ÿÐïÜÒA˜ãØ|mNŒ¹ò’ xJ—«*™»xþ¬ˆ.z•àÐ49¤i‚0d¤­(R¼TÐp¬8«áG'ßß·œ£õ»®Ò’uÇ„¢;C:«£÷Ú¿ç{Á³ïv|pÿ¾Ä«à¸)‹iÞë´iïM1W–à ϨÄÕŽž¼Ã!×BÇ¥|åHßHÏ:\Ûœ·m^K ¢†ÑéTÔ¦IeM­¶¹—\[¶í!ÝR0…NhŠeÐ×ôÉ#\&LäZ·zÅRE N &-»HçU„("É^뎽]ClG…¹×8^ê-´~PštLÕ•1ÿq`dfÙôéíÀxpß.W‰z%M3œ‘ä QŸˆÎ@Yøžá!ïýá»¶9Vr`ÄpxÄÈÒA½ã‡BhkåÀ¨8ƒ“oÊgR‹ô&II ‘Âdè·³änøœÁ÷Ì∴- ±Áš‹ Bå‘"NÎ ËS•ȳ>ùÇ…Ñx% ‰Ï˜_Ë"X#¼ÝóÆúV­s`ܵ}£ì‘£çX­L™Òi-TŠ(ƒØîØâ «`ø=F,\ì×î+Q®®¦.Œ)S¦–_¿vå ÎÕp Üì .ŒFÞ‘U%A®óvsã.[Ö¹0Fˆðxg"—Bç³dË%;iyDæj<«KçÏŽÖÁrp/µlPî ÁúzÊáŸOT² € ãZ1-îw(DøÅŸ6‹Ê!È.×âˆs;…¸0"Û8æC˜<>2wïÜ–Ï¡rVí'|Aì".Œx—TÓ]¬Z1_Nà|íB~%$‰µƒ¸0*Þç̈TÜ\‰TÃA\6ë@CûwÎÜÓ¤Ò eåÚÕK®hM„ÖÛ5/!ƈ‘ýi±PÞ¡SÀà²gçf—ÿ\ó6ÝÓ¥q’¥S˜€Qd{: ‹l0Å8¼ð»  O0PŒönKjÎÔG^‹6úHÅI¥™ºÆãž¶n6E;ÎÇ…±ºðùPù¥¼ñ=yöJWÖroû}•™2çáÀˆ¦¼ðGÓÙH…‹<﯊×}NiGL_òݘÏ] ¿bÃQE&â`É)íèD³Ö(—G*ˆ‹×D³{«÷´2§´ã²E?ÉvT>-Šï*¯¿E» )5€“ÚñŽð…•LEŸ'¬‚ÍZw“¾ƒÁ¦~pJ;,º¯K…UÝgU'aü÷Ü)4iì®™+þ ü"ê\%eTå.‚Ê{ŸnÍÝØ†^Ѿk?ª,žIø2KLab¹¢¤˜½%²-Ãi³?ÍDýèÑß„1ñ´"q”•u/þ–ÁÜê|\Õù­ZóbV׎6ÏŠùì>ŸÂ‡%Æ÷ïÉ$ŒñÅDÐiÓf „‰"#ŒŒ×5»îôvD´*¬À¡Ìæ´çs¾ÁÊ›Xä{ÃÄ›Á*ƶvb;â~=ÿçi‚ƒ·CÃNkGà!‚Ñ*rFàº)¾»2H.2q# LÊ‹ÙI¡'ÀGŠ/’¿ªÑ©`±©ãQ”,zSÆ©Æ=Ó÷×јÙsæ3ù\7S×çI,ÚÁ…Ñ"ö,9Æè.Æ@ï?¼‘&À)dG;ªD±áÂÌN¿ápüõ%CŒ¸îל¹ øº¬­å­T¬Fð•R|wñsYïÞ`-c¡Ê#x{U¨WÖÇk h h h h h h 8\ZQrxiö´´´´´´Â'­(…OöúÊZZZZZZ—€V”Þ@š=------ðI@+Jᓽ¾²–€–€–€–€–€–€Ã% %‡7fOK@K@K@K@K@K |ЊRød¯¯¬% % % % % %àp øM89cæ,ºw÷¶Ã!hö´´´´´´´‚—@Û¶m}ì7ádœ8qä™s¹ÏJìólÑpÇ¥3ûÅüFIcŒ†g`Y·£AÑxõÒYñ<þ¥ŸÇhÜ„’õXñ<Ɔ{5`¼q阘Ëñ–ßGί¢”-G>:´GŒV"ÆC'ìŒÙŒbz˜¬ êvôûœG› cý0ê÷j´yæü1žÇIâJ=ÇŸ´’?éÄ”}ÿÄ ±‡nǘqèvŒí¨QÄ hE)rt1f7¸Æ3Ú76´cÌh)ÿ(bC;ÆŒþ[YïAЊRlèÝiŒ1ã‘ í3ZÊ?ŠØÐŽ±£ÿVÖ{c´¢z>cÌxdcC;ÆŒ–ò"6´clÀè¿•õÞ$­(Å Æô E÷î|Š&ZíÐí­šË'³º}ŠFïÐp¢üF½…ÂðãGèÂù3tæÔ1úK„ûfÍž›ræ.àó”—/§óçN‹:ù)EÊT>ë!ŒïÀÞ'n\*X¸8%M–ÂgÝ€v„ðÒ⾕G@C¨Äñþý»"Ê`7ݸ~…2dÌF9óä§Ä‰“†À¥8ÔaíˆûôðÁÿ“Ó¥ÏL¹ò¤¤I“‡†1„£9ÚÑÈι3'èÎí›”:mJ—>“q—mëVc¼võ²Ï°áLY²S¼xl¯OŸ2³£ñBW/_¤3§ÑÍ×(­hÃü‹Rüø ŒU_wÈóxãúUº}ë†_¾ÓgÈL %ö[Çêíˆsž=sœÎž>A?¢ì9ó‰_^«Yø|,?zÂ):qì %I’Œrç+L©R§ ˜§`+²<é›Ö-§áŸ÷ S'»ñÕðÝôñg#ÜÊ=ú›~š9žF é)Ëû Gõ¾çVwEâË>5§õ«—ºí«Q· }6dBð´ÛÙßàÀˆ«*À9 ¾¦Õ¡ 9€fMãÆ”Ý~_N¤ÊÕßt+·cÃjŒ÷îÝ¡±Ã?“÷´‘`ütÐXª^«¡±Ø–u«1z2½qí2êú~=Yœ&mzZ¶ñ”göm«1â^­V:»O¾¿;‡*T­ãs?Ç«1*ñÑ1äcÚ´n…*’˹òÓ¼e»Ýʸ7¬ÆØ½C}Úµ}“_¶{öI š¶÷[ÇÊVco»wü—¾èÛ‘ŽÙïÆê‹¯T þC'QÆÌÙÜʹ780îÙ¹…>ëÑR(‚ÇÝØoÿajÝá·2«7,W”Ö®\DÝ;4|–)_ÐP‰'– ™ {ïäðÁ=ôyŸ´oÏ6¿¸`‘êÒºŽ¼áa™ªZ£¾èÍÅ£ÙÓÆÑ’ù³èŸþ¡A_ï÷VîäÀþ•‡•X|‹ãÚßK% JC¥ju¥iɂٲ×þI—&4{áVÊW ¨/–,/çÀ¸aÍ2©$©û4™Àºð—²Óз{ *øl Ê•§ åX|£ñZܧ¯}d,²}ãÃ\8^«\Ûµ®VræñmWu¬\r`°¶¨_N>ƒù £×*×’UXCîÛi%„§ž‹cÙ oPÊTi¼^{ÝÊŲ[DuÜ9ÒZöÙŠôrzAñíÏ91î<˜K—­@õ·¤ çÏøÃš÷º~v|91‚ÉÃ":³_ø»”:Uj³¦D©rôÊko›;úâ]ò Æ£¿íg y\ø{ާ}»~¢ØØK”+w~ÂËzUÓÇ`1èÓ\=1öõ‚²ledxª÷À2›Ü«*V£z ›Óªå ¨Ã;5©]ç¾tä×ýr*î*>vŒž=§ 6Á¨Œ!DK)ZÒ…SDÙÂPŠQ·\äÛPòsø/=´‡²Åó¦R—ž‘Ô¾K?ã©î_3„vfÊ”9Q}V~=Þ÷꿉~˜Pà'D¶Ÿš‰Ñof•Û#ñ@ˆ° ÄV2²ÆÍëWR¯ÎMkr #XÔÜ ^D}":=r¼dÕŸ¼ÏV2…WÆL<ÁSÜ“¢"¥wמ‘&rXS'pò y<›Ho’‰”iIF|6Ož†Ï|Ϭ .ŒHÛÂFsñ¢A¨V7F#¼d{ %‹0áé>6Ö7kŸãÞ][d=Ç:/¤Â2ÉJñ€ʺ¶yÍ,>ÛáÀè낥ʖwžŽIp5p0ípa É&9¾“ˆså¨KC¢PÀ…ÑxY$FUI½ÝÚxÊ’}.Œ“Dx<‰3‘K¡óyó–´P‘¹ÿÕ•Kç'kŒ`©+ÐYéÖûci$ÁX -^Ê9bV”1PÆTC©DB4²„ s‡'O8¢Òà(è/•,ãx8)zãï7­[%Ušã9Ž}.Œ¼Ú&'F„”wn]Wö~£Gßá²Ôï81zb̸|IZ‹–¾àÀ˜#g^éÀéãGõê€åù †zƒée}1‰e–åÎçèü©c®-F¼d@»vlLÄ:Q‚§Æ âÂhä}ÞÌqòiX0Rh5qaTµ40FË>û¢,ÂrQVFo¼«ÔAHlmœ}òV?ÐrS ¥J"[&è+U›©&Î/F Ë +#ëøó~Т/§ÈõàÔo—/™-{qz¡r U̺åÂÈÊ´ŸsaÄ=ѵíëÒ_§VÝÆ"µþ4SýÉ…A è¹b˜_¢@£?-á‘-a„TçÚr`„ORôâM‰>c¦,‘0ð_Äy,Edq`ß{vn!õFÂò4QÃÁ(ˆ`4¿ÆzfïsaD´þ-êe‹cL‹ûõ\ìÒó•ªË-÷FÅ70!¨Ô¬åûrkõFŒ–-‰vW•Dd*Ȫ$·\@âž jû–µôi¤Ã!¿UGp‰‹L<ðõæç…ÝuÆäòEØê­ÊT¥z=™J­!„CÅÐZi9gŽ›¤ÍTÇðÀ ô¡ˆ¼±j¾™ #p$U¨ËI\gMåú>zøµmZ5Œ!£f& ÿLTÉ„.ŒIJ¸ßaáE”6mZÚ&þÐðYe@àZ\Ѷ]ˆ #i"?Âäñ’¹q=^>—”³ê á bqaijµ¦XîâÇ–Êå!|€¢Ý@ȯ„$±VFÅû‚9ÃÁXHõaÆfïu¡á™;Z½0Vb/_tFk"´Þªu ¹0NŠŠ Âx‡M—ý{¶9ýçZwìEX.“R +Ík¦„u›vÐowSù­“̲½Žˆ — S?‚~oá£á¦®ðqP9’T]ãvúüµÎÌÛ0Œ÷ïäL;zÈѱÛi8çÏþåÓûèø‘=¶Àè<’#F¼|¢?ÿ?Ÿ¬Ïþj •*óœÏ:žNÚEöýLÓÆuñŠWLGý[þ"”ì‚Ñÿˆf}½j¨4W¬<ù«]0®þv¡Ô£òiQ˜_©÷uí5$àÔhÇ.ÁËu‘{£d*úe|¯C/é;hê;aĈn•²OšŒî & ‰l$áËN¿Y4“>Ÿð‰³C¦ø„ñ‹¨sK]0ÂxÐËÕ]Ñ¹Ç ª-þ“ðe”â/ì—vN§N¼6áÛPÚ( ¥CþJêJ×D¶e8­bÕb3‡¨ïÝ»+rÌ.’L™ÑnÌ©}t⨆7FÕ¾Y[Ñ·$9îÕÛ·nÊ$ŒiÅBÐÙ³ç¤ôF¾9ñ}ÖîzD*FvƒY/Ìnã®ÆÊQïŒ"‡Þ Ôp0jÖnÁî×sgO¼Í˜¶Fà!‚Ñ,²FàŠï]<&2q# ,Ø™;aÄ»>‚0|‘üÕ8;ŒNãÏ CIØ9¾ %S§ÞŒÌ"Õ¸{ºqãù@÷¡ø…ŠúóÄ¿3&jJ|Ög FŸ 䤯èSjzÄiL%›ëQ% ³Í0Âé×tÇ_›a„¾p¿*R"(Õ¹üØfÍ4œ8m†|…ˆ÷.>¦‘0âÝèÈX°ò|¼*Ø+Ûå÷^'í  |hŒ&ÑMh=Ú@ &° õh‚mЄ֣ ”` ÚP Âb¶FEú*I’€Öc’ÄdûJZ¶WQ’|ô¨1&éVH •´¡”´¨1h h h h h h °H@J,bÕj h h h h h ¤ hC)%hQcÐÐÐÐÐÐ`‘€6”XĪÕÐÐÐÐÐH ð™àÄñ##r)¤T:uü€Æ˜”«õ˜”( 8õ(ÁZFG/91êçªe*æ¸Sú^å¯emž;ýà¹>NΙ;nÞˆ·Œa}!-------«%pÂÉ…Šœ™Ûj^/˜Ì£^ÓêßiŒVKœçz„Eïü„ŸK ñH›¯ÕGBA¬xÀ'ys[ŽyîÕG£ÊÌíëîÐ>JB. _w@J9§õ˜R4©q¤ èÿcJÐ¢Æ m(é[AK@K@K@K@K@K@KÀ‹´¡äE0)ªX÷îR†:µµS†4 -d%m(%+uifµ´´´´´¬”€6”¬”öú–ÅáÏ fŠ¿®ÖcÊP±ÖcÊУFñÈHÀg¥`¤pÿÞ=:îúóýõ×ÊW *R"Q“·nÝ‘uûèê•Ê™+? -N3fNTOܸ~~=°‡R¥NMa¥ž¡ÌY²ªS–o¹0È¥‹çèÜ™“BfÅ)kÈ–cSäÀè¯Î/\[Œ¸OþEÞ×OåÈC…‹†QæÌqAx`»=sê8]£lÙsÒS9rOY¶o6ÆØË—zôD¹ó 4iØŸž.)ËÌÆh¼ÐåKèÔÉcw5–² {šÒ¦Mg¬bɾ™¯^¹Lñ×®úä;GÎ<”>CFŸuÌ>i&FÅÚ<}ê:}ò8Ý¿ *&>EÕiË·,ïßïÅ?éø±Ã”)S*R¬=‘-;;6–úÖßÓè¡}èÏ„„• E³V]èÃcä!^–“£"iÞ¬ñê´ÜÂð4|*Õ®û¦Kù ‘ÏiÀ­iÓº•.寿т›bùš#€Ý»w—ÎLc†õ•8|<‘7kï‚Ùïƒ}[ÌÆè¯ÎýÆÀÌÆxóæuš0z Ô¡‘Ü×ÿ2ê6hf,öoß&ztgzˆÕÔã_eñ“ÙsÐê-ºWa?6[¸WëT.à•ïQPWy=ïó„Íôˆ—ΘaÒÖ?¸°]°pqZ²ÚÂÄŸâêfë±w—&´w×V\î}EQÓ–Ý‹ÙŽÍÆF÷íþ‰>ù¨+;zÈ…ï*Õ ˆŸS®<ù]ʹ80îß³öi+ Á?\ØïÜ3‚:tùK™Ù¦JÖ~K½»4•|¾T­.AQ2f”ŠL—îŸÞɆ5+¤‘„H­:oÈQ¤ï–Í—=¸ÿ„· ùËwP±OËv0"Þ¡‘¼á12õêëMDo. ÍžHß-Gÿý7 õ…Ù²ñÚF\ìÈáý4t@:¸§×k[u‚£?:·'ÆÍëWK#IݧYÄý½ü«9²ÓðQï6Vº ³ž¼F#ównߢ‘C>0Y¾ÏñÎíÛNÕk7tî«B¡‰GÇÕ9Ž-Fð‰‘À6MªÊçnñ°²T½vùRÅhèáƒ{8 xm“ãË5^£'žôxÍkWÈr+GÍ80b$°»x?bôó¹ç«þ J-f\æÌˆ¢?­£ˆ~hÊìÕeÀQÈñÄ¿QûæÕ%»UkÕ§Ê/¿B¿9HKæNSƦ\¹òQƒ·Þã€#Û4ÕP‚¢öqŒ~`ä#HŠÜ-v ÂlѶ»BC½6úPÚŽк–9 ¥m›×H# ½Õ™ 7P¶'Ÿ’;\ãuj׬­Vxßa– ùsa *J\ï¶é.†MÔÓ'-øâÂèιaraÄT逡“¨¡øÓª©™f­:Ó›¯–¡Ë1égñà²ÊPâÂhÔÍüÙe/$÷Ñ^c=®}nŒxñŒž´‹ý$µË‰qØ ËlúM)òÓé”.]ú$ñdv%.ŒmßÿÐ#«è”*C©š0­ .ŒÛ·®•:Ì™;Mž½ÊùÜ©\õUjR¯íܶAž·ÂM… ãô‰Ã¤Š^kô. 9ƒR¥r ËæÎS€&ŽDÓ' §úo¶r–›­OS¹¿ÿn±Óª5Iž˜.õty9\†yFE˜÷‡µ:+æ!­øæK¹û^ÇÞN# O—{Ê•¯,Ï­_³\n¹¿¸0‚ï&-Þ§y˶S¯ÿŽ8spCñÚ>Ftî•9“NpaD¯¼qÓv·ØÅªxÉg$ç×â|ûK˜O6Ã…Qñxþì)1Íø‘Ä×¹G„*¶tËÑR0^.Æ…Snè„‚úÿÐŒ$\Ÿ #ÚöD fO’ÅèÐd*—§*¦—qaŒ#J gž«äòÜÉ!“f]¬ .Œ˜v5iÑÉÅzCîŠüm¶lŽQ#ìÚ¾A–¡wçNÊP:yüÁ Û¹ÿ6c.Œà¥_ÄXá@Y6¶Lý 'FOŒzÒ¹§zf–Y‰¥êZ¼´™0|¶Åqì§¿€>FÓã^¦7|2hÂInŒ&°t\wïØ$y{£I[z,ëãAóL\=ñ„@™¥‹gÉSÍZuõT…¥Œ cÉ2ÏJ~ÿ·r‰tqÁÁ}áô<÷‹±²¼Â‹Õ(Ëc!rŸû‹ £òKrlÂìÜ@'þ8ÂÏÔ©7D£îܹMSÇ}L‡í¥[7®‹H¶0ª)+¾TË'ÜÀ»Þ,딯XUna cÊ„¡EwÂ(*VFv¾ýìLX‰Ñ“ÎÙå!.À‰©+â~¼}û¦œGŸ:nˆi-ûlEz¹F½ÀáÙH;EçæÒe+PýÆ-éÂù3ã â—œz[‡EDn¿ðw)uªÔ„gM‰Råè•×ÞnôÅ&z<úÛ)ù¬! Ïñ´o×O{‰råÎOx¹B¯jú8%é§Üz42ñõ‚òledÏsísa¬P±ÕkØœV-_@Þ©Ií:÷¥#¿î—SápWð±côŒ —±].Œ0†`,!B¾HÑ’ÆKJ—œ‹Q·\dª¡„—ÉF‚CÙâyS©KÏHjߥŸñ”Ë>œ²@ˆ¶PFÕ5Chg¦L™]êã@¥¸ï9Œ7Ñ‚,àÀ$Kþ¹c:÷ÁõjX‰Ñ“ΓÌh91n^¿’zunâÂŒüaQsƒ{ñØDwïþE#÷”øúK©E`ÅÃ".=¦KïðÕÏÅÚÕ߸ÀC'püŒå‡^ÛDW„QšûÅg.øp€ ™ïW,¢qÓ—IÇàDL.àÒ£;›ˆfTx[¶ëá~šõ˜#üËrç-H³¦Ž¢™SF:qÌZ´‘òä+ä<æÞáÂX­ViÌÖGLõg¡bbæ%æâyúñûoœQHGÀE¦N½á¡BÈþ¼¥ÛhÛ¡ë´~÷EêÔý#Y>yl$]¼pVî»ÁS^ ‡FŒ˜æ ÷7>Sî1 Âoǽ]Ë´r?{°Vaô¦sÓpûhˆcáÐ’2ÿ8T‚.œ;-¢‹ªÐ¡_vùàê§l¢GDš i+Ê<óü˜æ=Í¥GøN®Ú|œÖl?C›ö^¦ï6†îB½×ˆ¾íf=Þ¾uKbÀ¨àÄ™+hóþ+´õ@œp^_$Ëá¿„ÿ¨Ä¥GwÞ1ê‚k¡ãR­¶ÃÖ½×1'F8l/K˜NÄ@ƒ¢Ž-jËhju̽åÂØVzÁ-³Kè„6ª&¹fOã„”>}ç¾Ù;¦JÊ«¾EÛp鼊E$Ùëе¿sÚì÷„á^#Ã÷:S |ФtJSçs«ž‡¡‡zò©œê'¬[Œ¬ и}é<–ýþ 'FD÷!êÑè‘ã%«þäý{¶’é,üf8€p`Ä÷¤¨HéÀݵgd\™ûŒŠC8ù†<žM¤7ÉDHJˆ´$#>›'OÃç ¾gVF¤maŠ #øxÑ T)àä ²*U F "á Sâs¦GÉ#D[5­¨xàÂøó¶õÔ­]}iDD ŸF‹Wí¥¥kÉéStÐ:¾[[&0V|pn¹0âý½d“ì¬Àhz§u7ê-|#1b¦üz9mS %¶wÕõƒ)¹Ï#ž<ñ;…wtä)Aª|Œ„ߢRÃzÆóÈü ²*r£ö¹1>HçVÈ€£^²½Œ’Eðt뛵Ïqï®-²GŽžc— Ò a™äG¥õ@e]Û¼f Ÿíp`ôuÁReË;OÇ$¸8 ˜v¸0†„d“_‰IĹš®±Ê¥ ££ª$ÈÞnmÆI"<„Ä™È%„Ðù¼ù ËNZ¨È\ÿêÊ¥ó“5F0Ôè¬tëý±4’`,…/å1+Ê(cª¡T"!YB„¹Ã“'QipT„ðâέëJKùzô®N¹lK–q<œ”G½ñä¦u«ä¡Š~3žãØçÂÈÁk mrbLªÎå=©¿ãÄè‰ãò%i-Zú‚cŽœy¥7¦kŒÕ«v”ç+êI ¦—q`ôÅ$–R”;Ÿ£ó§Ž¹¶\ñ’íÚ±1ëHD Bž+ˆ £‘÷y3ÇÉC¤aÁH¡ÕÄ…QEÔ"ÐÀH!,û싲K„YA\½ñ®R!±µqöÉ[ý@ËM5”*‰l™ ¯DTAlB¤š<^8CC–+VFÖÁù®m_—¾µê6iÖ§yu …G?hÑ—Säúgò@|-_2[ö0âôB媘uË…‘•i?çÂèÎýdÙïê\´€ž+†ù! 4úóÑò¾ÙFHÕy®-Fø$E/Þ”è3fÊ ÿEœÇÒ;VFð½gç‚Qo$,O5ÜŒ‚F£ñk¬gö>FDKàߢ^¶8Æ´ø·_ÏÅ.=_©ºÜrqaT|‚Š@ÍZ¾/·VqaĨhÙ’hHpUùAD¦‚¬JrË…$î¹ ¶oYKŸF:ò[up—¸ÀÄS£ÞÝuÆäÒøiõVeªR½žL¥ÖBž •`rÖ´QÎaУ‡PÛ¦UÁ2j¦ Äœ9n86oPAΡÃ?/$Ї"òƪùf.ŒÀhÁ˜Kç±+zy›äv~ôZ¿ÆáPÙ½ÏP1ÔÈŸ‡‡ £?:—࿸0nË"à~‡A„QÚ´ii›øCÃWd•kqaDÛv!.ŒH¤‰5Â&—Ìëñò¨œU _«ˆ #ž%5År?þ°T.à¼íB~%$‰µ‚¸0*ÞÌqî®ÀBªƒ¸06{¯ ˆÎÜÑÒè…±{ù¢3Z¡õ¯Kè§ ¸0NŠŠ Âx‡M—ý{¶9ýçZwìEX.“L5”´ Ñ#"Ã¥ÂT¾ xá÷>F0ÆTùjÞØè­[7e3íËÿÑàþdÚy,YBŒŽÝHÃIXðÅ…¬oüq…y3€q¨:ô–ŽßXߌ}.ŒþèÜ ¾ÚàÂø~ø á#Zñê>˜Žú·0t1DlqaôÄa‚ÒZ¼F,¹ÇuŒ¶à£è•zoQ×^CO  òcË…,`$ÓPˆ8FJFßëÐ+‘¿¨<Éôʼn#ºXoÔª=ïȃ/ñpa|ûÉ‚Ï'|"#2•ªÆ/¢Îqm+ˆ cÉÒÏIG|¥G`]Ñ¹Ç ª-þ“Ü”J gy V]·q‡Hð´›Ê×hí7×D†m8­bÕb3‡¨ïÝ»+rÌž#·)íÆœÞG'Žì±F¿…ý€Äœ¦pŒ6Óãmaä# cZ±töì9)}G„ÑTåó´ÝõˆTŒì bŸ€<œ´Ûÿ1îj¬åÍ(r¸aáM3rFÙQ¸_Ï=Ipð6cjØnz>"Í"»a®8ñÞEÁc"7¢À‚i±F¼ûá#ÃÉ_ÕìT°úŒ?¿_Ú9:uòÚ”©#JÆ« Õ¸{ºqãù@÷¡ø…ŠúsSÇ…ÑT&ƒlLc L€x #M€]È =ªä¯ 3FŒ¶< Ç_o2äÀˆká~-T¤„·ËZZÎÑLÉ ap`_!⽋ÈlŒx÷«åJ¬Ægª3·ÕÌëëi h h h h h h pJ@JœÒÕmk h h h h h $k hC)Y«O3¯% % % % % %À)m(qJW·­% % % % % %¬%  ¥d­>ͼ–€–€–€–€–€–§´¡Ä)]ݶ–€–€–€–€–€–@²–€6”’µú4óZZZZZZœð™prÎÜytóF<çõuÛZZZZZZZU'œL•*•d zÌš)µ´s| Dû(ù’NJ9çu5¿”ðÁ¡õ˜2­õ˜2ô¨Q<2ІÒ#£j TK@K@K@K@K@KÀ_ hCÉ_‰%ÇúW³äȹæÙ(­G£4’ï¾ÖcòÕæü‘”€6”µë¡þ”¡e­G­Ç”!BK YI@JÉJ]2«{° Îf?Óz´™BdGë1@ÁéŸi < øŒz †¥û÷îÑùs§èÔŸÇè/~Ÿ¯@*T¤D¢&–wäð/tõJ =•#.F™3?–¨ž*@ý_ì¡T©SSX©g(s–¬ê”å[.Œréâ9:wæ¤YqÊò„åØÔ90ÞºuCDì“:Ï™+? -N3fV—´|ËÑßûš4F#ÏgN§ëñq”-{Nñ?Îmõ>yœîß¿G Ÿ¢ê´å[Œ÷ï‹÷âŸtüØaÊ”) )VŠžÈ–Ë?}ëÆïiôÐ>ôçñ#.šµêB#ËnÞ¼NF¤…s'»Ôáóß!¨nƒf.å7DâË´¦MëVº”¿þF 8lŠåhŒvïÞ])“1ÃúJœ>žH›µwÁlÕÙa MŽŠ¤y³Æ»@€Î ŸJµë¾éRnÅÙý½¯“#Fwž·lXM=þÕX?™=­Þò§{öc³õˆ{µNå^ù5aÕxµ‘×ó'ÌÆ¨xÄKg̰iëÆT‘Ü,\œ–¬ÞçRÆ}`6ÆÞ]šÐÞ][}²ÝwP5mÙÙg3Oš¼íÛý}òQW:vô «/TªA#>§\yò»”sp`Ü¿g; ìÓV‚¸°ß¹guèò—2³L7”6¬ý–zwi*ù|©Z]‚¢2dÌ(™.Ý?½“ÍëWKƒ#M¯¾Þ„²ˆ—åò¯æHãê£Þm(¬t9*&ÛÁˆTx‡Fò†WõÓ¤ICó£'ÒwKçÑßÿMCF}a¶l¼¶Ç;rx? Ð…îßéõÚÀ·…ã†5+¤‘èV7ä(ÒwËæË^ûÂ[Ðüå;¨X‰§‚È80ús_ûͳMôhäûÎí[4rÈÆ"Ë÷9ôxçöm'Žêµ:÷ÕN¡ÐÄ£ãêÜ·6Ò#FÛ4©*ÿƒÅÃÊRõÚ äK£ü‡îy 3+pèñå¯QÈOzdsãÚ²ÜÊQ3Œ ì.ÞÅ~îù*€ƒR‹—93¢hÇOë(¢_š2{µGpr`<ñÇoÔ¾yuÉnÕZõ©ò˯ÐïGÒ’ùŸÓ”±ƒ)W®|Ôà­÷8àÈ6M5” ¨}£9Â’"w‹SJ†N¢†œÂnÖª3½ùjºs‘~ V†Ò¶Ík¤‘„ÞêÌ…(Û“OÉf_®ñ:µkVVŠ—mxßa– ùsa *J\ï¶é.†MÔÓ'ùòÓ'‚ #†aý·hÛ]›J›N}¨aMÇKgÝË,3”¸0ús_û­J›èÑÈ÷üÙe/$÷Ñ^c=®}.=*~ñâ=i¡:4gk#=ôoù‚­S¿)E~:Ò¥KoF?[áÒcÛ÷?ôÈ :¥ÊPª&ŒC+ˆ ãö­k¥sæÎG“g¯r¾O+W}•šÔ+G;·mç­pSáÂ8}â0©¢×½KƒGÎ µjHî<hâ˜A4}Òpªÿf+g¹Ùú4Õ™ûûï;­Z£‘ä‰iô^7mçT*ê@‘ÅK>#«_‹ûg^yÅ7_ʲ÷:övI(xºÜ T®|eynýšårËýÅ…|7iñ>Í[¶zýw¤À™ƒŠ×ö¹0–zº¼"Åܲ"øz ‡:+æž­".ŒþÜ×ÜX¹0*¾ÏŸ=%¦Ï?’ÿÛÎ="T±¥[nŒ–‚ñr1.Œ˜rC'Ôðø‡f$áú\Ѷ'Z0{’,FG=ûS¹Ñfb5uê Ñh ;wnÓÔqÓáC{éÖë"ª)Œj ÇÇŠ/Õráʼ""Knß¾)ç§Ž"G¤Ê>[‘^®QOÖ…%Œ©8†Ý # D¨XA­àÛŸkX‰­Ý?o–앯XÕ6ƒªË‰1)÷uPÌ'ñÇœwŠÎ Ì¥ËV ú[Ò…óg’È•¹Õ81‚ÓÃ":³_ø»”:Uj³¦D©rôÊko[:úÂ…ñèo¤2²†<.ü=ÇÓ¾]?Qlì%Ê•;?áå ½*·sµ–¸5.Œ‰¯Dôõ‚²ledxªgvÆ «Q½†ÍiÕòÔášÔ®s_:òë~9w•;FÏÌÆã©=.Œ0†`,!ZºHÑ’.—F”-ÎÅ‹¨[.2ÕPÂK‡d#Á¡lñ¼©Ô¥g$µïÒÏyjóú•Ô«sç1v` ‹šëüƒ^3„vfʔ٥.TXùõxß«ÿ&úa€d…ígVb„#6î†4@Ñ0'ƤÜלØTÛ\ïÞý‹Fî)/ÓwÐXJ-+qaL—Þ᫟‹µ«¿q‡NàøË- ½æÂxEE ¹_|æ‚’ù~Å"7}™t NTÁä.Œîl"šQámÙ®‡ûiÖcNŒð/Ë· Íš:ŠfNéÄ1kÑFÊ“¯ó˜{‡ cµZ ¤1?fX1ÕŸ…Љ™—˜‹çéÇï¿qF5"™:õ†‡ !ûó–n£m‡®ÓúÝ©S÷dù䱑tñÂY¹¯Â¡%¥Ã7êÃñ táÜi…Q…ý²Kýï1 B^, _“pYK«X…Ñj}ç¾Ù;¦JÊ«¾EÛpé”E$ìе¿sÚì÷„á^A¢ãàÅŽž FJý{¶’aÿƹUO‰ÃÐC=ùTN¹åþâÀÈͳ¿í[ñðÁ½Î4ô)ýå3˜úœ“r_Ã{RËSÜ“¢"¥wמ‘Ie…­FÅ,œ|CÏ&Ò›d"$%D“ŸÍ“§áK ß3+ˆ #Ò¶€0ņÑ\¼h*pr™žªD¶šø‹ £ñJ˜Ÿ3=J!²ØªiEÅÆŸ·­§níêK#"bø4Z¼j/-]sHNŸbà¡ã»µ#芮-F¼ß£—l’Mï´îF½…o$FÌ”_/§ `ª¡¤Âö㮺>@`0å _óˆxõ0JÖC¯ Ãxø-zq 5¬'¾ùdUäÆ(¶Ùpcû¢,ÂaVFo¼«ÔAHlmœ}òV?ÐrS ¥J"[&è+U›©&Î/F Ë +#ëÀ¹>†C!Z.úóÑòsÈÙF’àÑZôå—Õò%³e#N/T®!ëpqaäæÛŸö¹0âžèÚöué‡V«nc‘ZÚCsæÂèÏ}íN©Ë>IÑ‹7%úŒ™²D²ˆÿ"Îcé+ˆ#øÞ³s Á¨7–§‰îFAd®Ñø5Ö3{Ÿ #¢¥@ðoQ/[cZüÛ¯çb—ž¯T]n¹¿¸0*¾ AE f-ß—[«¿¸0bÔ´lI´ $¸ªü "SA*y³K†.Œ qϵ}ËZú4Òáߪƒ#¸„’lÒÔ¨7dw1y„|¶z«2U©^O&Rk!OJ6¸A¤G9 "üaÓ¦MKÛpÌ©‚ŒZÌ™ãf€ióä:ü`h>‘7VÍ7saD Æ\:]ÑËÛ$·ó£'Ðú5‡Êî}†Š¡ÆòKÉ _\gMåú>zøµmZ5÷CFÍLþ™¨’ \ý¹¯M€á³ .Œ>/jñI.ŒH¤‰5Â&—Ìëñò¨œU _«ˆ #ž%5År?þ°T. ¼íBž@yIlIDAT~%$‰µ‚¸0*ÞÌqîÂBªƒ¸06{¯ ˆÎÜÑÒè…±{ù¢3Z¡õV­KÈ…qRT­Æ;l ¸ìß³Íé?׺c/Âriœ”&R· ÿóŒxqŸ£<…]‡ô¼ÕÇ ÌUª×ÍA:*Ö úõÀn‚¼ð±[-â§(CîÔA]äGÀ|8†í‹º•ª¼â¼L:1„ü!2ÉþþÛ/²>ü\ã?"£,Ö ”n^;OW/?|Œàÿ“]eî©?EòL¬o‚ó,Žñ©×ð€†ÂoÆÙãNát¨¨„¡ Ÿ3÷O÷ÛHÇY Þ/»èÑŸûÚx²ª]ôè‰ï›7oЗ3?£¬bJ£e»pOU’Tf=Ây†ð|B.žQøO¾Rï-†¥j%;éñ¥juä3ëºaY¬Ÿ…QÁNÝR×S*á#ÙEà3ýÄZ’ d W eA_vÁXúé rÀ:Dvjܯüþ«Dãwа©/ïeŒx_`š vÂ};dô<žµý">£æÂWI-iˆ:ï\¿ íœ *xýy*1œå5Oìº;c»©|Ö^ðvâšÈ¶Œ— V-ö5D}ûÖMi0¥ æfÏž“`lù¢{÷îæÏ³‹$S¾ÚõÕ†ñ\Ìé}tâÈ[a4ògÆ~Ì)ñh Çh3=ú{_'EÏv×#"P1²Ìzavû?Æ]•£¼E7,¼iFÎ(;ê÷ë¹³' ÞÊå!)÷¤·:vÓ#ð`›EvÃ\q⽋ ƒÇD&nD;Ób'Œx÷ÃG†/’¿ªÙ©`õ~¿´s:uêäµ)S§ÞŒWAªq÷tãÆój7.©“JP|BÅ’ZµFV¦ýl\cüG`þÜ«þÔýç |{VèQ%åCá»eŒpú}Ž¿Þr`ĵp¿*RÂÛe--çÀ|v"ŒÀ"Þ»øØÌƈw¿Y£þÊ'°qU¯¢ëk h h h h h h $C hC)*M³¬% % % % % %`´¡dœõU´´´´´´’¡´¡” •¦YÖÐÐÐÐаFÚP²FÎú*ZZZZZZÉPÚPJ†JÓ,k h h h h h X#Ÿéb.ž•\ Ï@J¥s§kÐ¥hŒ§ŒZ)â/ªÿ)BôHèñQxæ<•ãëŸç3áäòåËéìY‡±ä«}NK@K@K@K@K@K@K ¹JÀWÂIŸ†Rr¬ùÖÐÐÐÐÐÐ0CÚGÉ )ê6´´´´´´R¤´¡”"ÕªAi h h h h h ˜!m(™!E݆–€–€–€–€–€–@Š”€6”R¤Z5(------3$  %3¤¨ÛÐÐÐÐÐÐH‘ІRŠT«¥% % % % % %`†þ´…®¯pí9"IEND®B`‚srt-1.4.4/docs/features/images/block-aligned.png000066400000000000000000000200501412557703600215530ustar00rootroot00000000000000‰PNG  IHDRÓZA% iCCPICC ProfileH‰•–PTWÇï{Ûm—¥ÃÒ;Ò«ÀÂÒ‹)‚e—+,UÄŒ@D%”PŒ†"± ¢ °g‘  |  ¢ò=$‰ùf¾Ì7ß™9ïýæÜ{Ï=÷ž7óþ"Ùññ±°q¼$¾¯³=cSP0÷àq€bsãí¼½=À?Úâ€VßwtWsýó¼ÿj¢Ü°D7Â;¸‰œ8„»vàÄó“€Ñ+§&ů²Â4>R ÂëW9bW×ÒBטûeŽŸ/ á4ðd6›1‰3R8Hb ÂúÏÃgá‹ñøËøü4~™ BP%X¼\ÂNB¡†ÐI¸M˜",E‰êDk¢1š˜I,!6¯ßH$%’ɇEÚG*!%õ‘&HïÉbd-2‹¼…œL>D®#w‘ï“ßP(5 “LI¢¢4P®QžPÞ Q…ô„\…¸B{…ʄڄF„^ „U…í„· §  Ÿ¾-<'BQa‰°Eöˆ”‰\Y¥Šˆz‰Æ‰æ‹6ŠÞÉ©‰9ŠqŲŪىMRQTe*‹Ê¡î§ÖP¯S§hXš:Í•MË£¡ ÒæÅÅÄÅÄÓÄËÄ/‰ è(ºÝ•K/ Ÿ£Ñ?HÈIØI„I”h–‘X’”‘dJ†IæJ¶HŽJ~bH9JÅH‘j—z,–Ö’ö‘N•>%}]zN†&c%Ñɕ9'ó@–Õ’õ•Ý%[-; » '/ç,/wBîšÜœ<]ž)-_$Y~Vª`£¥P¤pEá9CœaLje”0z󊲊.ŠÉŠ•ŠƒŠËJêJþJYJ-J•‰ÊæÊáÊEÊÝÊó* *ž**M*T ªæª‘ªÇU{U—ÔÔÕÕ¨µ«Í¨Kª»ª§«7©?Ò hØj$hTiÜÕÄjškÆhžÔÒ‚µL´"µÊ´nkÃÚ¦ÚQÚ'µ‡u0::<*q]²®nŠn“î„]ÏC/K¯]ïå:•uÁ뎬ë]÷YßD?V¿Fÿ¡˜›A–A§ÁkC-CŽa™á]#Š‘“Ñ^££WÆÚÆaƧŒï™PMÚ º·¡Ý x¹zõzì­îàý³ÖÇÛ§Ì癯o†oïFêÆí7.úÙûø=ô×ðOöïØÐ°èX(Ø´nÓîMýAÒAQAÁ¸à€àÚà…ÍŽ›mžÚb²%gËØVõ­i[on“Þ»íÒváíìíçC0!!!Ù^ì*öB¨khyè<‡Å9ÎyÁer‹¸³aÖa…aÓáÖá…á3ÖG#f#m#‹#ç¢XQ¥Q¯¢]¢+¢—b¼bêbVbc[âðq!qxb¼^Ïùi;†ãµãsâ – ÇæùîüÚD(qkbG ùù$k$“<‘b“R–ò.5 õ|šh/m`§Ö΃;§ÓÒØ…ÞÅÙÕ¡˜‘™1±ÛnwåhOèžî½Ê{³÷NísÞWŸIÌŒÉü%K?«0ëíþÀýÙrÙû²'¿qþ¦)G(‡Ÿ3~Àê@Å·èo£¾ 211 129 Ô¬³™IDATxí | çÀOB‰„YìKÄK¥(¡h½ÚúìÚØžêkãY¥¥R©¥v¥”*ÕòªZµ4¶j-õú QÄ©FB"{P{¼s&™13¹÷º½{¿Ìœã÷ÛæÞ9ÿ™3ß:ßq{€,L€ üßÜÿïoà/`L@"ÀÆÄ7xLؘHþ&ÀÆÄ÷xLؘHþ&PÈ‚õk×BæÅ‹¶Šp0-ÈÈHî6É-¯hh`æ ³EN]¾7nÝæÀèÞ¿pç6œ³PÉØ4&??é ÐÀ@³ÙF_7OOHHMAcbÌàøõk‰ûL³ápœ“ãìøH& !ÀƤÁÁ&à86&ÇÙñ‘L@C€Iƒƒ#LÀqlL޳ã#™€†“G˜€ãؘgÇG2 ›“¶š’NŒÐìò‰ÔÓp&+ Ê–*!+B¥À'žX?õë‰$Ø›¤ÓgàÔ٠ȸpJzyA…¨Sµ ÅÉt‘D(cÊÁ—~—nü>Yó]>F]Z¶€Ñë ž… çË3r»w!zá"ȼtYR³oû¶¦1&2 Y_} ¿9šïûû–’ ŸÎ—窡šy‹c×[4$‚³îß;a,ÞTf’{÷ïøEKC2“îW¯_‡!3fY4$â@—·>úö=& aŒéÆÍ›ðõ[0Å‹…Îϵ€reË(iÿÚ~OOWâF$§Ÿ…¡3gÃö¸}FVÓªn‹c7h"t?tmÕ*h›ûï-\lõ;œ!L3oã®ÝÒÊlÀÔ¡ƒ ¼^]HÉȀådX¹u;Dè¯ÄøW§/Àf.éifÙyè¢>ÐÊ)Á³PîíJ™= G¤üKÙÙÒ¢Ó\”íj¦fÚùëA……¯··dH”PWj7oP_ÉÛ²g¯6b€j$³Ò•k×áæ­ÛÊåíÞT1$JlÙ°’G¹?©ItAD˜š)ãÂ×kU®¤AQ½|9Øu0÷IEïݽw ç=¥4 ¡'òôaƒ!":Æ`šÙV§d /Ø6ÿ#ÈÉÉ2,ý SbJªæ *âèž"Œ1¥ž;§ððõñQÂ(å]B¿”} h4Çˆâææ&õ þÖ¡´kö¬pÿÎdîîî¾>ÞšŸ¤fݶ½û‘t”*¡½?481"„1Q?A-EtÃßúøÅ«W kLµ«T†5Ó'ƒ;‹–Ý'Q³çjúÖƒºwÓraLc¢¦›Z yx¨£àá®_ÿã¦&ßHª™ØŒò_ÑküÃfͣɧ”̺իAÇæÍ”¸«B“w±b4¿¢ê#©EßìSçqØx²oÜ€AÓgº¯DƒTÓqÄW¤\ˆÑ¼"¸,„æd¹u玔>õq?Éb– ‰úIKcÆbSßW(B)]²¤&óÒ%%L¬Ë¹KiäDía1>ê#ék$á\ åË–€0ÆT¦´ç¶‹o«j§ã§…ÒS‰FyXŒM€œ³Lü|©¦iWµ\,yoŒp5’|%„è3ÑÉÐÄœ<«M±¸/â//H«¥ýö›|¾Ò’%ÂÃؼ{fœíþ|kH8™œOç§C‚Á»xñ|éÎNƘÚ4z¦-[® {Î\¾Vmû ÔóO§{ëVÎfÄ¿ç±?ÿ'߯Ò=aI¿û„Õªi)Ë©i´—h–{Ì«ý4Êë é.òMâiàˆ!Ðò z‡« ‰05ëЬ)ЂÅñŸ}éYç–4Ò7ªooèô\s%ͬôw>ü§.¯› L„2&"Ø0¤l˜5]Z“E+Æiø3ÀÏh2Ó¬ÿå¦R^µ ¿‚&“ †¿”–£üÉ„' LŸIxR|‚Làؘˆ³™€½ؘì%Åå˜À#°1=g3{ °1ÙKŠË1G`cz Îfö`c²——c ÀÆô@œÍì%à†KÝX+¼b9.<ÅÍ!Y˜ÈO 22R“hs„¼„'40HsÙ"IÎíùf6Õ5ú&$$À|ÏÌì’’’àÚµk6±iLÕÊ—‡Cx`(nifñÀ¥MÄÁì7ÝñññÌ!ƒÞ&¸Ï¤'Âq&à 6&ÁñaL@O€IO„ãLÀAlL‚ãØ€ž“žÇ™€ƒؘLJ1=6&=Ž3 °19Žcz6'm]±… ­n:{þOM…´Ì,Ü· T¯P^ò ¨?ygÅ]ÁA¯[6θo@Oô´*¥O¯WôÙ†ŒßÅU''’n@jIIÜw¾Þ"ˆMc²ºhï 9yØ=!ì;v<ß/tiÙFôНbOåË{Ò Îæ`IŸI“§Áº ›¤¬Þ/›b·¦cÇ¡GD_K8”´6¸ËëBô2(‚ÓÌ#ÿ;ýÆO²hHjn—<üÃ\׌"€sæ9|ÿÃÅœù»®þ­GÕJ®>?ýïÛ¬™ô…Ÿd|{Ü>ÍÆ“äí QZ°c_<ëEÚs|[Ü~hûlã'y*B}wº'Ž™ Ô99ëd¨f"y&¬!,údžÅŸ-¬ó2i±“…1&õÞÒäí`Õ”IRS¦kËç êf:üÛIÓÓýû90úÝqp}™QŽ‘Ô~ºA}ðÄo­­ë Œ1 ìÖ’ÓÒá$þµxº¾Ò'([JëºhO[ú*oÙò¯`ÏÞ8ðÃ]mŠê7™Eî£÷Èà G%uëÔ®U ÔƘÂëÕúS y üçæÔIÐLWF“i H"n\?mƇ’F³¦O+èÛL’‚£¹²” „oW¯…ß°UòÿÕ pôD$Ø«A“ NþülÝøô»urTú|©y846þ–É7Ñ?ÕˆQ£%ûõéÍÑwÕ¦Í?jX=’xâáxÏ^Zï(²î“ƃˆ—{ÈQ— kL;ÒÀi„Uýø7^SššLƒEfô1œü=ªT®oŒ2˜vö©“xΩ`8zH©ZÒ° ?XÆá诗—¼Ô¡¦¬«" ë¤œÍÐ$ÑÜSÔœy–•¥I7Zd×÷À²/¿’Ôš5c*<¥rœm4]m铞wý‹£GÀµß®€eK>…‘QÃazHÙ»(ä½÷'â+ä×m}•Óò„5¦åb`˼90¦_Åû.¬­c?âvN(—/_Q£ÇJªE õBëQM»tš’ÃöÂvlzuC5Ç„ Û¡˜±¹Í`éù~áô™3JZ•*••°+B“?ºÙ,âé©Ó7[¶A»¦ÏB¼¹N¤ž†u:ÏÛÁ+¸’ÙûíE ,/™¡¤,y¸œúòž†Oìd\øÅ4¸p×jR“7výFèÛ;<<<4g´ôŸË•xíZ®÷´N'#Ä„;¶ÿ»µn©À¡ªÇ˜hè5z¯¬Í£´zÜßÀý‚ÉÝ:w’Ðà”é3áÖ­ÛRœVF|¼àSeáïDB™Ò¥… %DÍD$ÞèÒ âOh\Ö“ {µÐš½‘8‰Éb|ÿˆü;ìÚ½â„/¿úÖÆnÀ•Á@“¹rs÷y|G¾>@BÔLD£Vã Þ~ †Y™ÑŽìÚVL|_30! E'œˆ‘›u–ð*T–.Yo,e“‘aÑ'­U¤ôOæ}ÅŠ³t¸KÒ„©™H{ê7½úRè×¾œ»x2/_\èX¦4PSÐÌÒ±}[ ?3 ‹7&`6&{(q&`6&; q&`6&{(q&`6&; q&`6&{(q&`6&; q&`6&{(q&`7ÜÛÛêî¿+–/‡7oÚñ5\„ ˜@dd¤Fi›+ äõ`¡AšƒÌIºpîà&òf÷¶žwðN³sHJJÂ}'®å3›ÆT­|y8„† ¶?Y>-žp‚G /‰ƒÙo"Âozc’9èo;î3é‰pœ 8H€ÉAp|Ð`cÒá8p“ƒàø0& 'ÀƤ'Âq&à 6&ÁñaL@O€IO„ãLÀAlL‚ãØ€ž€ÍI[}agÅÉýd*úrMD‡WY¸ÝWyÜ ¢~`(cÐ\Õ\=999ê$«aÚÁÇÇÇj¾Q2’“OÁ1Ü.7U¡ÍTªU«*¤SáŒ)5㌚7’œ^†¿Òúwl¯O6T¼kOû÷$‚ÿÚÑPú«•¡m½¦Íœ +W­Q'KáÍ›Áè)E$ïB5ó~EדÝÐ¥Š%C"‚ó¾] wþ7X³&-RÄЪOš2]1$Ú2¹ú=®P¡¼¤óÎ]»aààápûvîN¯"€¦f¢Åë³V|£aBn7I6ízh@dPñ©dÔ}ô~Û 9,7ó¨¹#»¤¤ÝLÛ »£Jú¯]»^R¯C»¶0sÚà‰û*Rx ºgŽ™€»»&ÁúßÃË=º A˜š)÷”NLyè8*âe˜€[äÒŸÚ#Æ¥ìl8y&MxOâ$¼¼Šƒw‰ùþ . ï¾÷¾ô“ôtž9m2nf/Ìå{ì(ŽcIÚ™ ‰ÄÝÝ]2rQJ¢ö[%%¸ð?aj¦ØŸÿ­Á ÞÈ¿ç ­!¸BÉoùnòóöÖ”5CdÑgŸK~nIש“&HÆfd½Õ^/ô¯ÜÝÅ×a²ÐÝ ‰½ƒ5Î`%Ì£ílRºnõjP÷š>„U=õ‘RpP‚D×®RYÑ£§“™$Ýê,X¸XR¹;î¹Þ¤ñ3†W?] ÉòõÊU£ÙüãVeóþV-Ÿ“‹¹üS˜š‰žÉ’}]E¿/ ËiÅqßé‰èøªUXC9É4ŸsæÎWtõÖ›JØÈRØ¡ÍùçÎÿV¯…_âöAû¶/âæý¤ üI÷–-šC‹ð¦Â`âÝ(R_Hšc¢?µÏ¦‘xSmù%Nlø0yÏÛüãIOª•Jûù^gYAÚ´Ì;#¥èì'/^ò…bHM7‚Å ?‘¼' aLÙ8Ÿ ª‰àJ_ÉQËä/–¥òê2F ¯ßô½¢No”1‹ÜćçÌÙsaÚŒ%•«ãD-Í©…5l Å÷bM5bÔt[zE$B4óJà‚^bÞx Ú4Êí4­ Cò R µïX"¼Ð(LˆáâÔ¹¦þI½ºu$ïê†SÒŠBÔ´•]mNˆ‰†^8a/ïI²7n? 2\ª±ÓÒÓ`ÍÊJž•¯sJ²5“'6PM¤–Æ8à Kô¶®–”Œ uÔ°á}û5oHúõémX=õŠÑr2Ù¨6¢Y6$*K0ÑcÞ–;œpp –"„1ˆ ¹Äe(ž8¯" ôU ‡_ºú°%—1âçêµßIjÑì»ÿbD-êtwƒ’%¼™å|å4ŽvŠ ÂSeÝHäßV–[¸½”z€¢¢ÎðärFú¤&Þö?K*uBoŠE‹{éúÚ©®ãÈ®%QÏ=ɺ–Ê93McêÖJ;_°jûi¯:Ú¯n º¯WKå @uÔáT|ÚÊŽk†Ô0¤ŽÖ”òÁVH€nKe˶í@Öõ²í§JRˆ |„1¦°Z5¡NÕ*  ]AÇoC›!oÂR\%KM\FòLÍšrÔ°ŸôÊ,4’e6éß/·H£v´O~°‡-[·Ã„¦JHš6i 󿺚‘0ÆD ¢ô×öpî‰FïÔó÷†^“&ëš™™%¡ªê!£$<0 ? C!¡¯ 5ƒ.=" EëahTîÜõ%§O¤œp%¡Œ)¤REøæƒ мAý|LšÖ …M³g•1ƒÈó'tØi¢V¾¶´6oé’OáÝwF1 9Š‹¡ÏefJážÝ»Â¿¶n†À€).ÂBÌ3©A”.ésqÉÌ\‘œ–.eQ©hÞªauY#‡ßŠôgf!ƒzíÕ~0 _ɈèM[?__é¦B8"šˆwFy„hî‰úG,L€¦F¨©²tU„jæY:ANc…SA¹R|žÂ`cþñ lLåJñy O€IøKÄ'XP°1”+Åç)<6&á/Ÿ`A!ÀÆTP®Ÿ§ðÜp)ûkg¹qãFÈ0É‹xÖp:°F 22R“esÍ<A î]#Í7˜ B®êIjÔ0׫úKËr‰X«`lÖLz˜gLÀ:î3YgÃ9LàO`cúS¸¸0°N€É:ÎaŠÀÿwÁ dk|IEND®B`‚srt-1.4.4/docs/features/images/non-block-aligned-5rx10c-deleted-packets.png000066400000000000000000002302721412557703600264300ustar00rootroot00000000000000‰PNG  IHDRKбô. iCCPICC ProfileH‰•–PTWÇï{Ûm—¥ÃÒ;Ò«ÀÂÒ‹)‚e—+,UÄŒ@D%”PŒ†"± ¢ °g‘  |  ¢ò=$‰ùf¾Ì7ß™9ïýæÜ{Ï=÷ž7óþ"Ùññ±°q¼$¾¯³=cSP0÷àq€bsãí¼½=À?Úâ€VßwtWsýó¼ÿj¢Ü°D7Â;¸‰œ8„»vàÄó“€Ñ+§&ů²Â4>R ÂëW9bW×ÒBטûeŽŸ/ á4ðd6›1‰3R8Hb ÂúÏÃgá‹ñøËøü4~™ BP%X¼\ÂNB¡†ÐI¸M˜",E‰êDk¢1š˜I,!6¯ßH$%’ɇEÚG*!%õ‘&HïÉbd-2‹¼…œL>D®#w‘ï“ßP(5 “LI¢¢4P®QžPÞ Q…ô„\…¸B{…ʄڄF„^ „U…í„· §  Ÿ¾-<'BQa‰°Eöˆ”‰\Y¥Šˆz‰Æ‰æ‹6ŠÞÉ©‰9ŠqŲŪىMRQTe*‹Ê¡î§ÖP¯S§hXš:Í•MË£¡ ÒæÅÅÄÅÄÓÄËÄ/‰ è(ºÝ•K/ Ÿ£Ñ?HÈIØI„I”h–‘X’”‘dJ†IæJ¶HŽJ~bH9JÅH‘j—z,–Ö’ö‘N•>%}]zN†&c%Ñɕ9'ó@–Õ’õ•Ý%[-; » '/ç,/wBîšÜœ<]ž)-_$Y~Vª`£¥P¤pEá9CœaLje”0z󊲊.ŠÉŠ•ŠƒŠËJêJþJYJ-J•‰ÊæÊáÊEÊÝÊó* *ž**M*T ªæª‘ªÇU{U—ÔÔÕÕ¨µ«Í¨Kª»ª§«7©?Ò hØj$hTiÜÕÄjškÆhžÔÒ‚µL´"µÊ´nkÃÚ¦ÚQÚ'µ‡u0::<*q]²®nŠn“î„]ÏC/K¯]ïå:•uÁ뎬ë]÷YßD?V¿Fÿ¡˜›A–A§ÁkC-CŽa™á]#Š‘“Ñ^££WÆÚÆaƧŒï™PMÚ º·¡Ý x¹zõzì­îàý³ÖÇÛ§Ì癯o†oïFêÆí7.úÙûø=ô×ðOöïØÐ°èX(Ø´nÓîMýAÒAQAÁ¸à€àÚà…ÍŽ›mžÚb²%gËØVõ­i[on“Þ»íÒváíìíçC0!!!Ù^ì*öB¨khyè<‡Å9ÎyÁer‹¸³aÖa…aÓáÖá…á3ÖG#f#m#‹#ç¢XQ¥Q¯¢]¢+¢—b¼bêbVbc[âðq!qxb¼^Ïùi;†ãµãsâ – ÇæùîüÚD(qkbG ùù$k$“<‘b“R–ò.5 õ|šh/m`§Ö΃;§ÓÒØ…ÞÅÙÕ¡˜‘™1±ÛnwåhOèžî½Ê{³÷NísÞWŸIÌŒÉü%K?«0ëíþÀýÙrÙû²'¿qþ¦)G(‡Ÿ3~Àê@Å·èo£¾ 587 464 ð6ö@IDATxì]|Å~tBo¡×Б"UàOïAŠ ½7)AA ”"Ò:"¤w‘®Ò{ 5tþóÍe–½ËÝq—ÛÙ»˜y¿_²»3³»ïûvwîíÌ{oc½fBJŠÅ€b@1 P (ì2Ûn©*T (ŠÅ€b@1 à (cIÝŠÅ€b@1 P (œ0 Œ%'ä¨*Å€b@1 P (ÊXR÷€b@1 P (Š' ÄuRGá±2:«VuŠÅ€b@1 P (þ ø½¾â‡YrHªP (ŠÅ€b@1@¤Œ%u(ŠÅ€b@1 p€Ói8ý~Ï÷¯ÑoþgÖã• Ô°\º¹A[ÿ/­dö¯¡ÁQ5*¢ÝJL»ŽªÏ‰v·¨¦pL»WU¿ª]úh·¢¿W)¯F–œ±£êŠÅ€b@1 ˆñ (c)ÆßŠÅ€b@1 P (œ1 Œ%gì¨:Å€b@1 P (b<ÊXŠñ·€"@1 P (Šg (cÉ;ªN1 P (ŠÏ€ËÑpî0uùúuzþâE¤]âÆ‰C™Ó§TŽ‚‡Óá'(v¬XT8o^J’(‘Ývî¶ux+®^¾F/ì`ŒÃ0fÌœÁîÑ=|Dý›bÇŽMù æ¥ÄIÛm' o^¿IW.]£¹²S²äIE±iK™ÃßÐßž °;a”>c:†1ùù%4 ›8‘LŒ¸Þ'Ÿ¢» cÚtþ”3O%Jìø¾:½”‰Q¯ë¥ —éჇ”:M*ògxÍY}έ»wyßd ú2ôif‰×ñÖÍÛtáÜEºvŸÒ¤MCù 䡸ñ¤üLØ¥Mư»aôàþC»ç…iÓûS‚ Ħԥ ŒBá—/_ÑÅó Ïâ+¶ž- +eË‘UT›¶”‰ñÕ«Wìwñ*ýûÏYòKäG¹òPÊT)¥c3ü)ؾ?vûØ¡â§7l ÿ”©´úGÌHj7p­ß¹S+ÃJÓÚµiÊ—ƒ)^Ü7*ºÓÖê`oìݱZ7ìèð¨{þÞÆ0DƒÇSïNŸÑÖÛE_ÖoH£&‹Ô½|ñ’濈F úš·1!˜š´úÐj_Ù²0ÂHš8j2Í™þ£Ž£' £ZoÒX5°! ããp;üZ0;ÄJk`>~0ÕmXÛª\æ†,Œ¶:oß´“:6µ<÷0–ð ˜%²úœð'O(gêa,;–+UvXod…ì눞‘ƒ¾¢›wY©#gvZ¿×œ´1²0vmD?d…Ëv#xÌjÑ¡™m±áÛ²0BуûÓ O†Ðé“g¬ô.Sþ=3yeÈd Âª±21þã(õíÚŸ„—¬4íýEêÖ§‹U™Ño,ƒŽüèI8?’ªTTªP!«£ÆŸ’%N¢•={þœõ¤½GŽPöL™¨aõ'NlšB‹CCéõk¢™Ã†ñöî´ÕN iå1û1„àG¡hÉ"Vg‰?>%MöãógÏ©}“®üaÍ’-3ÕnP‹âÄŽCóf. Ÿ–®a_ÓØ©£µc`$b@ï`:vèO­Ì+²0nþu 7”`8Ô¨[&­^ö a&¨}_úeûJÊ“?·)eaÜúÛn(‰ë„a]²šÎž9G}ºô§…òS@îÑ£^ù§OŸÒÐþ#õE¦®Ëêsž<{¦á¨S±¢¶.VòdË.V¥/eÝ«P£j4ãÏ`^6’TíýÊü‡õ닎=!›8,Œ•kT )“‹ÓX-7¯ßÊ·ãÆ‹gU.kCFŒv`¿3èGK”.FÕëTå3ß7—ö°—û~ÝÐüŸ~Ë긲0ž=}Žš¼ß‚Ÿ«JÍJT¾rY:uâ4…Ì]JGOa3é©aÓ¬t1rÃpcI(שqcêß±“Ø´»Ü²o7”`Xmš3—Ò¤´ ¥Õ*Wžª¶kKKÖ…Ò°  JŸ& ¹ÓÖîÉ$âM¤Ç§]y×¶=ÜP‚aµlý"J•Ú‚±rŠÔ¸Vs‚¡Ð/¸6mX±?^Û.­èÌ?ÿFzÓsz2 •FcÄ0ÞÚum͇P¡rç T©¨eDiÃÚM¦K‚.£1äÊN#¿ÂÜú'®eš¦%»Wª•¬M·oÝá—YÆ’,Œâ¸XΛ±¿éUª^!Ò詾ìu£û¡oÙ¢EiѸñbÓ«K£ïU€ù²ïPþ#[§Áûôõ”‘/¾9†ƒ#"ÆØ¥—ýY¼˜ c©j­JŽÔ‘Rn4ÆÝÛöòk—†ù«~Ðú UÊQÍ2´o×~^ÿ6×#Áqʸi\½×¥¯¿E±˜Ën/ãG|Kß›N >ª§•óJÿÅ6ðXn*díZ¾O¯V­5C % ¤ÒE,#6¡Û,Ãùî´u[‰;ü´äg~ôN=Ûk† Š/DÅJåu¿­Û—ø×¢}SZ³m ÑÏj*Okàƒ+î`,øî;|¸sÍBp³ãMrå¢ãò^úçF¼7nÙHë° 2:©|ÌO òàþ¾ôµî`ºÃ7a›r>Á¾.ѵq‡Ww®#¦ßvmÝÃ?ls{ð²¡ä*Nw0::æ3òª†Íêsÿ,Gí¼UîÆÛld R¬ä»VýN‚„ 4õ1‹ákâFLÁAšµûÈÊ B_ ÁÔ|™d‰W¥p\xs³a,¹xW¹ÓÖöXÞ܆EÁШ­Ï‚ûü¿çµªà¯~l£“¸‹Ñ¶û÷îóâTi,#oöÚx³ÌSŒp2{î¼¹¼ Åá¹£‚ñ«àqüxƒF}Φ:R8<¶¯TD×~ÄþܹŽû÷XúàÆ-Z¹¸s>o´u£=ý<³láJ^Õªcs{M¼^æÆ…ós}CW¯ç¾KØ€#ô÷Sçòò÷þW’’$}ã }àŸ;…ŸRòäɬ4Çl \ pu%Ҧᖮ[GÇN¢øl.8WÖl|¤¨JéÒX¹7ïÜáÛÓ¦ÕÊÅŠˆšC4Š;mÅþf,×,_Ë#ºà§”#g6*ÆŒŸr•Êj§†Þ˜v`xÔV2F8Üݹ}×¶Êg¶ÍÀˆŽë½9æReKšŽ]FtT¸®OŸ<¥ؼú·c¦ðaðwK&LW™-20¢£Cç\¨hAªß¤]¿zÝlXVç3²ÏÑøèɓԪ?î’…EÀÊ“‡T«Îû6};3Ö¾Ž'ÿ>ÅÕNÊ¢mçΘO‡~?Ìï[8—b?° ØuSÉfàÃ9ŒÆhOïÅ?.çÅÝ'Âа×NV™Ña 6ªMkV„²à¨VÔµw':ñ×I>-Ì-Àl1# "Lˆ¢Fd±^}‹º·E=ê÷qwÝpcÉ/%üûô… „?½T-S†BØÜf\Ü{ðf*"±ß›)ÑÞ/¡å8=r«­Ø_æ2a„n°bm-ÙòUþGÓæO"P÷ï½Áèg'Bˆ©(„[ûš˜‰ÎyDÞ”­ðÆ –͉LŒˆ|ìÚ²§Ìg3õÇGÆÏ_hN݈$B`†·DFŸ, "œ~‘ÖäçÍ›­àš1ƒVMžBY²X•ËÚuïÞã*ÿ0u^$Õ€²vÕ:ú~Étn(Fj`p,Œ¶j""WàmÿqkÛj©Û21~=eeÌ’‘fLœMÓ'ÎÒp,ßB™X¹Y" cÕZ•¹A?ràW< ÒZܼq›6¬ýM‹v|õò¥4˜†÷pJ” ¿×†ÒÅ-[éÚŽtä§Õ4¼W/`Óž=4~ξ.œ³°ñâ-ÝiË.ù_éò¥hû‘ßèÀ™=täÂ~ÚôÇ:ê?¤/?+Bo§c¹Qõz¿|9ï”d5=:¼Y7±è81>zòðHi<ñ–ebÄ›OËŽÍØˆK Á€†\»rUoJþë-šW- cȼ¥j$b/q§~]Oç6m¦k;wщÐu4gÔ(B@ʹ˗©sðà(hµ]d]ǧ,=£ƒs—Ï¢?/ ãWÑ´'ñrø3mþu+_—ýOF[½Y±Vs†Æ°™"#Fz—/XÉáàÅSH³º­y¾7±-{) cç^¸‹ fkð"Z©XM$5kò$ RÈÃ%$\Ä´Z²$I£CH Ô² êú1ǰvÛV¾LšøMBÆû#¬ Ç Ä?Ujr§-ßIò?`Ä(R ‘"†;to«9¸n ÝÌ5H’ô F{ÃOD ÿÔ’5vÿðf`<~ìoú¸UWî‹áŸqçD÷5ú21"êoðègÈ”Éü å‹§ÜÙ³³—Àp‚Ï%¢ã S›c Ê¼Ž˜ÂÀ§1ÐáSHxÎ~”Aþ%$’•-21 Ýœµˆ¯"àB¢ÎŒ¥LŒÈüÙpZ¾h7|˱OÀ·pýšüj«×®*¦LŒFMâ|E–z%q’DܸŸCÒç UËIÅ‹Yj¯!<Ö›pÃçû×8jfUÌ:˜Ùl¤Ht(¨LÂÞ¾:5iB½Y¦îÌYR/pì6t(áíPH6=‡O¥Ø~ɶâXo[Æ+¨5¹tsƒ¶îl‹þ°„Ï‹vÈ`ܲCSêØ£½æ ê0üùyÏ/µÔú(GnŸ~Ì)E;,á"r3éËÅúâµ?j™¿EÙÛ–™ýkhM¼ÜÍœô&zASL·²ò·%T0búRWìtÕ—0=xŒ&}=U3ˆ…☚úìËÞTš GE| £=ýo\»Aå UåF"¢E£"zŒÞîs–¯_O£gΈ”¥~µjÜ­{”ÓøZŸƒÔ%£¿«E¥âºat°cvÔ–ùF%-„þ:z»ÏŒìÌT«<êÎ잊¯a\¶`M;M{)ø`÷ì×MKÜ(Ê]YúFðŸtîg¥6¢þz}ÞjÕ«åôzŒ~¯-îBV'‰Ø0ÜXÂqaÁi;ŒåRJòúú§´89ÛS@”!}Àù+—)}ê4oÚv§­8¾£eT:. á¤{/ì>¥b‹+Ó*/_¼¤‹.QZ–@+‘ÎÅ‘nF•ëoW;.…Ñ1û®^Ç',!%5Æc#KˆÒHÀ"ª<‘èp‘Ã&nœ8Qþl†£«Æ’¸Weõ9wïßç#ä‰XäXÆt騇°=ó^ðÕ>÷ëUö¹ˆdÌéÛ•þÌÙ½¬¿Ž¾Òç$¡î Î0¼­Î1Bgü&Ýb³ðßA™~æâm˜lë} #úÞ+—¯Ò³§Ïø7áôŸÍ²ÕÝÕm=FÓ%W•ô…vQí¸|AwWuÐß ît\®ßÚ)Œ¾p<×AÝ1–da½zù½°sã°ë˜1s‡§½yý&]¹träÊNÉ’Û¿ŽááOèï?OPØ0JŸ1k›ƒüü:<¦¬ ™=|D'Ÿ¢» cÚtþ”3O%Jlþ½*£þº\ºp™>xH©Ó¤"†×L‘õ<Þº{—ÐߨôeèÓÌYõú߸s‡Î^ºDa÷ïSºÔ©éܹ)^\)?úÓjë2îÕ°»aôàþCíöVÒ¦÷§ Ø«2¼LF¡äË—¯èâù‹„gñ[Ï•²åÈ*ªM[ÊÄøêÕ+öûr•þýç,ù%ò£\y(eª”Ò±þlß¿Ÿ»}ìPñÓ6ÊTVõ/^¾¤™K—Ð&ðòIR›ú ¬Ú`ãë´Ú @ëwî´ªkZ»6Mùr°iµLŒæ*V$¼±wÇ>jݰ£Ã£îù{ÿQÔ7xùâ%Íÿ~ô5/1!˜š´úPß„`$M5™æLÿѪ¤Óô1ûÁ‡à‡¯hÉ"º¢øñãSÒdÖ×#(zÓ±CZµµÝØüën(Áp¨Q·MZ½ìÂ(LPû¾ôËö•”'nÛݤl˸õ·ÜPÊ’-3ÕnP‹’0¬+CVÓÙ3ç¨O—þT P~ ÈC &ۃʨ?ÏÓ§Oihÿ‘ú"S×e=Ož=ÓpÔ©XQ[+y²e«Ò—²0BñóW®På6­ùZA6’T›aÍœ.ýÉúæ£'OJÇ&N ë^­\£¥H™\œÆj¹yýV¾7^<«rY²0Þºy›:4éÊûÑ¥‹Qõ:U)vìØôýwsi{ñí×}ÍÿéY°¬Ž+ ãÙÓç¨Éû-ø¹ªÔ¬Då+—¥S'NSÈÜ¥4qô6C‘ž6ýÀJ#7 7–„r7¦þ;‰M‡Ë²Í-Ö|·fÍéä¹³‘ÞlÄŽ[öíㆌ°MsæRš”–a·ZåÊSÕvmiɺPDéÓ¤»H_ »Ê‡tp'ÀÛVO»¾õtñ6m»´¢3ÿüK;6ï²»†„ñЮkk>„ŠFƒ:P¥¢–¥ k7™f, Æ+;üf{pëSœ¸–iš–ŒÇj%kÓí[wxçe–±$ £8.–óf,äoz•ªW ­·ë«L]—ñ<@Ù¢EiѸñ¦bqt2{É ¥F5jÐô!C)¾I†ƒ#ŒF?]zÙ!Ç ž0–ªÖªäH)åFcܽm/7”àÒ0ÕZ¿S¡J9ªY&öíÚÏëñ’j–qʸi\õ×¥¯¿E±˜.!ãG|Kß›N >ª§•óJÿÅ6ðXQ:TÇÓîE!4ºO6=çxÞ1díZ~ü^­Zk† J,H¥‹XF>B·™7ôïXW1â˜î´uGÙm[´oJk¶­ #úEšžÓŸ»à»ïðáRÌ5 ÁÍŽ7È•‹WøÒÿ¹Š1o<Ô¸e#­ÃtRù æå°Üà‹ð¸N®bà›0ŽM9Œàè Ñõs‡[W1bú /¢‰_ ðº¡äFwïUÛcÿ8s!/jج>¥IkÞK¶­ζ]Åx›,AŠ•|תßI0vø×˜~ñAq#¦à ÍÚ}de¡¯…`j¾L²DÚÈ’« ïßߥ¦;àíð–g+0–0=wæâÛ*ŸØv#”u§­O€‹P"ø«©sÿÞ}¾ª4Ž fN`ÀΞ`„“©xØsçÍe€6rá.Ư‚ÇqEúœMu¤£”ÁG®Ï˜;4¸Šq÷¡ƒü°­?ø€’%±žZwç|Þhë×A(ËZ|±Zul®¯ò©uW1(œŸëºz=µîÒ’Mp„þ~ê\^þÞÿJR’¤¾y}]Å(ü”’'OfuR¥NIpy@=\2eÉhUoÔ†4ciéºutìÔ)þ¦’+k6>úS¥té(é ‹ø&‹Ò€dL›6Ò1D„"WÌ#1š©·;çZ³|-ZƒŸRŽœÙ¨Ø{E©\¥²îâ­mÑqý±×Òi—*[ò­ín #:ª;·ïÒÓ'Oé6¯þí˜)|üÝ… ÓUf‹ ŒÚGç\¨hAªß¤]¿zÝlXVç“õ<Âo§Uÿ~Ü$ ‹€+”'5¨VÝ+£0FcüëôiÎ!¢§†,¢}GÒmÖfb>K劗 fuê˜ñedÜ«V7 ÛXüãr^T¬TQz'Âаm#sÛhŒ0†Õ¦5+B©iíVÔµw':ñ×I>-¿ÓÌ-Àl1£0ˆEÈb½ úÆÒÛ¢õû¸»n¸±ä—Àþ}úŸ^ª–)C!lî?ûáuGî=x3m‘ØïÍô8†_BË9Ó:-ÔgÉnŽ?­b[Öóx‘}’IHV'Ÿomd.=Ř? €«·+"ÚX¯+’UBD¾ÎÌuOîU[=çLû‘!\=¹ƒD•¶û˜±í)Fi›¿P>+u1-U´Dn,!"×›â)FGº¯Zò3¯Â˙̈?çáÖíØÎ?K¢vñêU6m*/ÂPoT¤q­Z|·YË–ñïljc,Xó3w$ÇÈS…’æDRÉÂ(0ùÂÉÚ?zl¥ r aúR¶B«:W70êжQ'>4\3°:™4ÂkÎÁ²0.üa1á CþBž±lÐ3#æÖ1½‰3DFø(­`N£¶3Zõ uøÜ"ëyÜsø0á{lzyN¾™À‹0ª“ؤïRʈˆ7ÈŽÓŠB¸è—_ø&ü¥Ì÷ª^oLû#›5Æ’7DÆ\y-/¯Ë®²‚…è0D¬Br[ c«6daDP‰­û’qé7‚£€›ˆL‰ÅNîÐy"Ú„}çCõ«Uê[¯¤e^;y#tp•´uUô/ÝÜðÖÝðùŠB™-$’EâÇýæ[´‘e׆àÁ\ÁæŽõ¾È>u‚6ý{ði´ÃP0ä³ÁŸPn6D:ú˱Úwáà„˜(qäèÆqÓÆD åqò/³ ­ÖÛ;|Ô•g0oðÀ(Òîm{¸%g/žFª–ÓôuuÅ—0ÚÓ á‰ÆRT¿ §Çèíç±fÇ<[ùâÅ)wöìì%0œàs)>¬»ÙrÊÃÊÝýóèmŒÐ½e¿Ï´>ßÙÄGÐW°oxBiò /ùº;ÿô½ý< ½?ú’}vè'p?BOE¯z#>ùüÙp þ¥åا@à[¸~ÍF­ì§ÍË"}¦êmøƯ‡N U‹WSE–z%q’DœáÒ×é1ºòPc_de^øÃÍ7eÈÒܲCS6ïÝž’§°ø: ßŒ9’Åk$ä4ÁqgNz½`¯ýÊß–PÁ"oüBìµ±-ÓßðÞÆxôà1šôõT>º¤×SSŸ}Ù›J³á⨈/a´§ÿk7¨\¡ªÜ¸F$eTDÑÛÏãòõëiôÌ‘R à¥,¸[÷(§ Ð?ÞÆˆk„”+-ûqõjí’a¤>¨U+êÞ¼Åa>£îŠ£·ŸG莑݂™Šss—Ï¢²=OQ¢¿W}ã²+hòØiÚK™¸fˆÈíÙ¯Òe®.} ãÚUëè“Îý¬TÇ w¯Ï»S­z5¢œÞBÑTc H0XGì0–) ËêëŸÒØ)¤8å2¥OÆãapýCíjÇeF«;Âà =FWjŽÈ÷ÂîS*6Z`ÖÔQTàêox_ÁøäÉSž¨ÑEˆÒHy|ØÇ1ÚbA›¸qâpGLÛ:W¶õ}åy¼{ÿ>!OÄ¢{3²dQ1ôØõÏ£¯`„~álDùóÇJ‘,©Ç}¶£/=À™P÷ lGUô÷ª¯`ô×·Ø(?üwAæIÄŸ¯a|ùâ%]¹|•à‰Ïdé?›eÄutf,IqðF^$„ÙâO† 3Ι%«ŒC»|LÙ]VDbC`DÄ–7£¶$Â㇖‰2Rx[db´Å¦Ÿšµ­“¹-óyDêüy[db6¤GÈ-›WaʼW2’<%H&Fè†QÛ‘OuvwYaøašÑâþøª7´TçT (ŠÅ€b@1à%”±ä%âÕiŠÅ€b@1 ˆ (c)z\'¥¥b@1 P (^b@K^"^V1 P (ŠèÁ€2–¢ÇuRZ*ŠÅ€b@1à%”±ä%âÕiŠÅ€b@1 ˆ (c)z\'¥¥b@1 P (^bÀå Þ^ÒOV1 P (ŠÅ€tœ%¥T#KÒéW'P (ŠÅ€b :3 Œ¥è|õ”îŠÅ€b@1 P HgÀåϸó #éZxý7ŒF‰5ùPúëèÎwšLVÓ£ÓEõ;MÔäõÕóh2ùžNÿ<Æ„ë¨úo“¥ïsœZ,9cGÕ)ŠÅ€b@1ãPÆRŒ¿ŠÅ€b@1 P 8c@KÎØQuŠÅ€b@1 P Äx”±ãoE€b@1 P (ÎPÆ’3vTb@1 P (1ž—£áÜaêòõëôüÅ‹H»Ä‡2§O©<üÉ:zêݹF™Ò¦£ÜÙ²‘_„‘Ú‰‚‡Óá'(v¬XT8o^J’(‘¨2m)#€\»u‹.]»ÆùHž4©iØÄ‰dbt÷š Œ^^½|^عWã°{5cæ ‘Nþ„þþó…Ý £ôÓQŽ\9ÈÏÏþ½úèá#:yüÝemÓ¦ó§œy(QbóïU™õ]ºp™>xH©Ó¤"†×L‘u¯Þº{—ÐߨôeèÓÌYõú߸s‡Î^ºDa÷ïSºÔ©éܹ)^\)?úÓjë20Þ¹wî?|¨ÃÞJzJ?¾½*ÃËd>/_¾¢‹ç/žÅWl=[@VÊ–#«áÞv@™_½zEW.]¥ÿ9K~‰ü(WÞJ™*åÛTò¸Þð§`ûþýØíc‡ŠÞ°üS¦âõøÁ>m*}·h‘U{?SÓU«Z•?bV»hýÎVåMkצ)_6í¡–‰À^¼|I3—.¡/&Là8' Hmê7°Â,{CFw¯¹Lœ{wì£Ö ;:<Åž¿·ñ~4€‘4qÔdš3ýG«ö‰“$¦Ñ“†Q­ÀZyøãp;üZ0;D+à Ú?˜ê6¬mU.sCF[·oÚI›Zž{KàÎ,‘y¯æ¬QÝ!ŒcÇR`¥Ê묅QèxêÜ9ú|ÂxÚ´g(âË\Y³Ò+­ÊdmÈÂØ¬oÚ{äˆSµÇõëG7qÚÆˆJ™ÏãÁý‡iÐ'CèôÉ3Vª–)ÿ™<‚2dŠ%KœD+ ݾJ0ŽêU©ÂG“–„†ò7¹ÖŸ÷§Ý!‹é\¹xûgÏŸSàžü¦Ïž)5¬^ƒâĉMÓBBh1Ûçõk¢™Ã†iÇ–¹" #tþ럨Ljátðøq™ÞzlYݹæoUÒÙQÁ{Ñ’E¬ŽŸ½e&Möæ^Ýüën(Áà©Q·MZ½ìÂèQPû¾ôËö•”'n~Œ­¿íà†R–l™©vƒZ”„í³2d5=sŽútéO å§€Ü9¬Î'kCF½¾OŸ>¥¡ýGê‹L]—u¯>yöLÃQ§bEm]¬äÉ–]¬J_ÊÂÅÏ_¹B•Û´æýnA6’T›aÍœ.ýÉú¢£'OJÇ&N cÍrå(e²äâ4VËu;¶óm³FÏd=·nÞ¦Mºòþ¨DébT½NUŠ;6}ÿÝ\ÚÃ^ ûu@óúÁ »¬ YÏž>GMÞoÁÕ®R³•¯\–N8M!s—ÒÄÑSØHzjØôY°ÈpcIhÚ©qcêß±“Ø´» È’™uý˜º7oN‰üüx›>mÚÒ;uùú/[·hÆÒ–}û¸¡#lÓœ¹”&¥eØ­V¹òTµ][Z².”†Qú4iìžKF¡Ñ¡cÙæÍ¸ªÝš5§“çÎFzÓ“ÃÙ1ÆèÎ5w¦—‘u-:4£ŸvuzH eãí¥]×Ö|è;u JE-#JÖnÒŒ¥€\Ùiä7C؃[ŸâĵLÓ´dç¨V²6ݾu‡w^fK”ÑÅq±œ7c!Ó«T½mÝhùñÑ×›µnô½*ô.[´(-7^lzu)c¯Q#¹¡Ô¨F š>d(Åï?…±OÛvvñàÅTKïWˆl ÛÝÉ B£ŸÇÝÛörC ®óWý õ;ª”£šeiß®ý¼/{f‰Ñ§Œ›ÆUÿ q]úú»Q‹¹á@à.1~Ä·ôݸéÔà£zZ9¯4ð_låö¡Šæ/@Ÿuè J8üÞ/_ëâÕ«Ú1CÖ®åë½ZµÖ %”(XJ±Œ „nÛ¦µ÷•w0BçŽ6¦Ý‹BhtŸ>lºRþ<¬<¹ƒÑ¶FèfÔ1 ¾ûæÅ¹<¤xÃ\¹x…/ñ/o<Ô¸e#­ÃB:©|ób•ÜÀ—¾öÏŒBwø&ŒcSŽÀcÒ×%ºÞîðêFL¿áE2ñ‹^7”\ÅéFGÇœ¾d1¯jÈý³µóV¹;Ïãm6²)Vò]«~'Ašú¯1ýâcâFLÁAšµûÈÊ B_ ÁÔ|™d‰W%G î=´ü˜¤Ö ;àÍñ–g+ÂX:sñ‚m•ÏnÛÃeÇ÷ïÏ*}Vq7s„ÑÞ!Üikoo•Ý¿wŸŸ:Uç†mØÝ0{î¼–©eoéìîyaü*x?Ü QŸSŠ”)Ü=´Ï´®÷Ÿ;ÚøûÐA~ˆÖ|@É’¼™vv績ÔÖF{ú!xæÇÕ«yU—&Ùkâ³eöžÇ…ós}CW¯'ø.AàýýÔ¹|ý½ÿ•¤$I£Ïõµ‡Qø)%OžŒcÿR¥NIpy€ÀÕA–H›†[ºncnÒÍ•5ý©Rºô[qà&Þ}èoW®Xq¾„E|“Ei@2¦MË—ú"‘+fŠ‘ÍÔÛs™ÑÞ5wGGOÛ®Y¾–G¸ÁO)GÎlTì½¢T®RÙ·öæõ›ôÇ^ËM©²%­Ú££ºsû.=}ò”þaóêߎ™Â‡Áß-Q˜0]e¶ÈÀˆ¡}tÎ…Š¤úMêÑõ«×͆eu>Y÷*üvZõïÇ}@²°‘ïByòPƒjÕ½2 c4Æ¿NŸæ"ÚvjÈ"Úwô(Ýfýh&æ³T®x jV§Ž©PÆhŒV7IÄÆÜU+ù^´‹äËg¯‰Ô2£ŸGCjÓš¡Ô´v+êÚ»øë$Ÿ‡Oææ`¶ &D##²X/ˆ¾E݃ûΣõû¸»n¸±ä—ÀF}úŸ^ª–)C!lî?“Í‘Ó-ó’ˆÂ¨TªßýÞËH6Gø6é+Ò q–aù†ÊÄÊÍY«ÖªLsģ‘¿â)Xò1w‡›7nÓ†µ¿ÑÁß-,¯X$¹,‰mô+”(A¯ ¥‹[¶Òµ;éÈO«ix¯^ü4K?gŽÃS"RJ N ¢¥Ž\ØaõÞ½ÉöüfattÍmõ‘±]º|)Ú~ä7:pf¹°Ÿ6ý±ŽúéËOµcó.šþÍ›Çöü›Xtܲ…–·ÓÑ“‡SÜxÖïxóiÙ±q $^kW®S£êMéÏÃÙNÚ¶,Œ!ó–òå&­>¤ÂÅ IÓß•˺Wtrê×õtnÓfº¶s]GsF"™œ»|™:vE=CÚÈÂø„E2BŠ(@«¿›J7ví¦[{öjíðgÂ3j†ÈÂh«û²_å혥0Û±[ÖóŒé]¾ÀÒ'áNH³º­y¾7±-{) cç^¸?(‚dð"Z©XMj\«9ÍšüƒÉÙË­Ö(Š+†KWÄMˆùoŒø Ì?¨e+õ×nÛjWU u#dÔ'}è½Â…µvI¿ñà·—\ o ÿT©µ}d®ÈÀ(SߨÛ ŒÎ®yTtvw`ÄhR ±$†y;to«9*o µMÇ?~ìoú¸UßübøgÜ©RÔ‰%¢çÀ£6¾_2vÛ¤=è½:~ÊR]˜ãl)#¦'ŒœÄº?`=z&𛹔y¯"1cÊdÉÈ/AÊÀ"eɼ1c8¼ýÇŽ±Dº÷L* cB† Ò¬N]>’Q„Ñ#UŸ!f¥1‘…‘ƒˆø‡éñoçÿÈ·qlfRQœTÆóˆãîÝù;µý°¶EN¥_÷üL›üJ˜žÃKZÓ:­ nfˆ,ŒiüSÓÊ‹ÙèüXê §ÖZÐÀ‘ýiùúE¼o¶ÔiåÙ֯Ù,‘s od¶òïÅ‹<‡Ê‘Fzùx›ƒßü[`€ég!èØ¼)ž`ô¦ÞîœÛ(Œo»æîèdtÛw‹á‡D\[9öÏg‚r¤ÀŸ+’6}Zþ`·nÐÏ­£ãB™·ÄŒ÷âþWнLþÈ!×xóËã_ˆ oÞÊÙÞ‚HFÝ«¶Š±(^!×oߦTÉ“‹MÓ—žbLÁ Aˆ˜ŽÓÈš!ß|ðHžˆþ|ŽÖ=Ũ?îÆÝ»5÷–õê髼ºîÉóÅ'Œü–ë)q‘k(sÖLôýÒéT¿J> Œ¼p{ØO£`xO1BÇxñãQíúµøŸÐI€ñ¥Hž|¹D±áKÓŒ¥ ,ñ$ΜV Þ¾N×.ÜúèýÚlÊ®·U½Øx—9ááFß¶ÿw-U€¨½ETœ(7{é)F³õÊùŒÀèê5Š~Fìsù¢ÅHÊÏú^E˜|«úíùÛr}ô‹˜²sõœ‰uŸ:‰câ'$ìéç Æ´éý¹S·íqŸ=}¦uZpúÎ’Ý[=Ķ÷ª8–~y‘}‚HHV;Ÿouf,=Ř? €«¹+"ÚX¯3’UBD¾ÎÌuO1êu²h!ßDŠŒúŠxò<ƒˆ´Í_(Ÿ$LK-Q„KˆÈõ¦xŠÑ‘î«–üÌ«ðr&3âÏði8$ùÂgIô‚|IÃØgM Âië7ïÞ¡zìÓ(WnÜ༧S6ŠdOתŋg-[Æ¿—&Ú,Xó3SÀÈS…’ÖI¢ÑKYÖÓ“ãÉÂèÎ5÷DWöݼ~+=~d}¯"_¦˜ e+”уђ¶:ñ!íšÕi̤šþ°˜ðù ù yƲAÏŒ˜[ÇÔ"TÌᣴ‚9ÚþÍXhqx6Ô˜lDžXPFŸ³çða‚a¯—Çáá4à› ¼#‰Mú.¥¬ço¦…`Š|Ñ/¿ðMø™!²0 Ý ŸTtjÒX›º”ñ<@®¼–»å WYáAt"V!¹-†±U ²0"¨ÄÖ}É8‡ôÁQÀ…B¦:²„Ï4eÉ!H,™1]ZºÁ†©×lÙÂËò±·˜¾ºlªßÌ« ‰g!¬UÚ¶áíôÿf Nysä`sè•ûŸø÷_zï£&|ÞÅhdÜgýL™–‰8z²O`h²ëàA¾œÊ>é²6"áæÐžA$Þy¥„21ºsÍ%@Ó‰Otˆh5$–„sóÆ-ÚÈ2qCÐùtéÝQk?óÛ﵈¹SÇÿ¡k6ÓêÄʸicxH+: 8ˆã˜ðÀ(Òîm{¸¡…¶#& »H]ÊÄ(Uq7.ó^2e2ÿj@ùâÅ)wöìì%0œû\Šëâû•fˆLŒèK+Wæ}tµöíXØymþôìžä_BHÙ"£Ð]$¡DTv¾æâÜXÊ|[ulNÁŸ §å‹VqGïrìS ð-\¿f#Wþ˜ÕkWÕ«#e]&Æ £&Ѫū©"K½’8I">švìПG§ží©BÕrR0‰ƒj,á«Í½Û´¡Ùlôo Bðí·NMšPo–}[„ù£.ž.­¾mš±¯pÞÆ1ÖÍœE݆åÇÆçM ȑϪØûv“8†‘K™¡çº;´œRBoˆøƒ8JÝ/Ú±”‰ÑknGÇHÀ[;µ§…?,!7B‰ºe‡¦ln¿=wúåñâ¿yTlÓ ˆ6øØ.¤Wÿî<Ã,F—à' SSŸ}Ù›J³áb3D&F{úÇø´ ü Ì™÷*>¬ŠÜmuÁŸúÕªQp·î¦¥ ‰˜¦áSRˆDFºFêƒZµbþ£-ø¶ì²1>e/ò ׬á0pä ‘ù<6kÛ„ðüM;ûDâ{iB‘Û³_7«o]Š:£—21¾ÃoΞ2‡}gó'MmDýõú¼;ÕªWC+“µ‹ k½vtððXor3<ßo¹ÑµÕ—ãpÄcù‘Ò°¬¾þ)r@ú€óW.SúÔi<WÒñý} £žOO×cÆK7-oÅ®ð†{ÎÖ÷ÂîS*6}däôØ“'Oy¢FD!J‰'’ÙÿM‡à+mñÀ`D„QT &=F_yïÞ¿ÏGȱèÞŒ,Y£#W[.mûêóÎF[/1¬É’zÜgû"Fàƒ ²ÑÑcô¥ç}Ù-6JÿD‰ïSF³þyôŒ/_¼¤+—¯ü#ñ¹)ýç§¢‚ûè1ú½¶øéÙ;Ö›×e{µQ,C^$„ÙâO† 3Ι%«ŒC»|LÙ]VDbØ‚Qi2"Ó²ï2!…€·×QF[lHÁà ‘y¯ÂØœebÄ5ƒ‘;[6o\>íœ21e$iÊFqEöó˜ÿF1¦=1á^ }޳û_,9cGÕ)ŠÅ€b@1ãPÆRŒ¿ŠÅ€b@1 P 8c@KÎØQuŠÅ€b@1 P Äx”±ãoE€b@1 P (ÎPÆ’3vTb@1 P (1ž—£áÜaêòõëôüÅ‹H»Ä‡2§O©üáãÇôç?ÿÐ{÷(}š4”7{vJœ(Q¤v¢íŸ8A±cÅ¢ÂyóR'mÅ>F/ec„¾×nÝ¢K×®QîlÙ(yÒ¤FCxëñdb ò„Žž:Å®yeJùæ@IDAT›ŽcôK˜ð­:Ý@&Fwïk£±‰ã]½|^Øyã°ç1cæ ¢™¶|ôð<~ŠîÞ £´éü)gžJ”Øñó(v¼tá2=|ðR§IEþl?3EÖu¼u÷.á:ÚôeèÓÌYõú߸s‡Î^ºDa÷ïSºÔ©éܹ)^\)?úÓjë20âwåþÇÚ9ì­¤÷÷§„ñãÛ«2¼LF¡äËW¯èÜåKtþòÂzÎ,Y(€ý™-2ûœW וKWéßÎ’_"?Ê•7€R¦J)¢áOÁöýû)°ÛÇ?½aù§LÅ뇇Sð”É4séR«ö0~¾0>¬YÓªüë´Ú @ëwî´*oZ»6Mùr°iµLŒöâåKÆÉúbÂŽsÒÀÔ¦~+̲7da„‘4|ÚTúnÑ"+¸æSÓU«Z•ËÜ…ÑÝûZ&ƽ;öQ놞bÏßÛ¸qƒáÃiìðohÁì«ö‰“$¦áãS݆µ­ÊõÛ7í¤ŽM-Ï=Œ%×,‘uq¯æ¬QÝ!ŒcÇR`¥Ê묅QèxêÜ9ú|ÂxÚ´g(âË\Y³Ò+­ÊdmÈÂØ¬oÚ{äˆSµÇõëG7qÚÆˆJY¡Û¾£G)hä:ñï¿VªV(Y’¦¡LéÒY•ËÚÙçþã(õíÚŸ.ž¿d¥~ï/zP·>]¬ÊŒÞ0ÜXzô$œëèŸ*•*TÈJßøñâS²ÄI´² »vqC){¦LÔ°z JÊÞ^®YC§/\ ƒòQ£]úSBù) w«c`ãéÓ§4´ÿÈHåfȺŽOž=Ó Ô©XQ[+y²e«Ò—²0BñóW®På6­ù3X$ÕfX3³VŒö=yR:6qYk–+G)“%§±Z®Û±o›5z& #FöìÁ¯aÙ¢E©.3âcÇŽE“æÏ'h]‚Ó/ÓgXa—µ!«Ï9{ú5y¿W»JÍJT¾rY:uâ4…Ì]JGO¡ôÓSæÈ‚E†KBÓNSÿŽÄ¦Ý%¦—&D͵áìÎM>¢wÔ§›ìâoÿc? ci˾}ÜP‚¶iÎ\J“Ò2ìV«\yªÚ®--YJ‚ø4žÝ“I(4#T,Û¼×´[³ætòÜÙHoz`8=¤Ñ²d¦A]?¦îÍ›S"??~î>mÚÒ;uùú/[·˜f, àFctç¾:È^¶èÐŒz|ÚÕéire§‘ß aN}Š×2½Ô’íW­dmº}ëía£TöŒ¥y3ò7½JÕ+ÐÖ–§'’TiôujâÇgѸñbÓ«K{ÉdÕ¨AÓ‡ ¥øñâý§0öiÛÎ.¼˜ céý ‘a»;ThôuÜúû>~ 3¦MKk¦M×~O«•)K%>lD;àõfº¬ÝçL7³ÿAãºôõw£(sÃÀ•`üˆoé»qÓ©ÁGõ´r^ià¿ØËíCa>¼uýúÚ…Åp1ñv¹÷à_â_ÈÚµ|½W«Öš¡„‚ Ré"–7æÐmÛx_úçFèÝñÃÆ´{QîÓ‡MWÊŸ‡5‚+w0Í_€>ëÐA3”p~ø~¼_¾WåâÕ«F¨dø1ÜÁèN[Ãõà€y 䡯-i†…)¸|óò£>¸ÿæy§oÂ86u‡v ÷u‰®×Æ^ÝÁˆé7¼ˆB&~1À높«8ÝÁèè˜Ó—,æU-ØË:ü³|MÜÁxãö®þ{…‹Xýžú%H ÁzéwúLÁAšµûÈÊ BŸÁÔ|™d‰W%{ àŒ·ÿØ1^•/ §ÖdçÁ|oy¶"Œ¥3/ØVùä¶#ŒPv|ÿþÜ©Ò'wC)gíæÞCËqêhb ƒ;Ýiko•…Ý #ÑIåÎyzô«àq\µA£>§)SxKMÎ]¯; aÜ}è ?Lë> dIÞLɺsl_ië£=ý<óãêÕ¼ª ›Íˆ.âã»ùòq«~ÛÈ}—°GèI æóòòÅ‹37—Ä|Ý×ÿ9ês„ŸRòäɬ ¤J’à:Ë€,‘6 ·tÝ::Æ¢0¤›+k6>úS¥téH8pAo……Ñ“§Oèï3ÿÒÈÓùpaIæï„¹f,bLËA0Ìh+"‘+fŠ‘ÍÔÛs™×îC‡¸ZåŠwG=CÚÊÀèÊ}mˆò.dÍòµô÷Ÿ'~J9rf£bï¥r•ÊFÚzß¹}—ž>yJÿ0€oÇL!Dǽ[¢0ašM/ûví§ÐÕë©PÑ‚T¿I=º~õº¾Úôu× à·Óª?曲°QÐByòPƒjÕ½2 c4Æ¿NŸæ× ѶSCñÚÛ¬…3p¹â%¨Y:V#f\T£1ÚÓy¼/ÚE" {íd•±3†תEË~ý•jthO}Ûµg>g§x0\W&1w³Åè> &ôcˆÐÕ ¢oQ÷à¾ó¨Gý>î®n,ù%°„ÃIz©Z¦ …°¹ÿºÍõ»vRS6å¤DsGÖRýt\â?}{rþàÑ#}±´u¥)Å›‰qätË\4"o*•*EÝßM&FWîk÷5v„éðÆeûÖU¾ÊÿhÚüIÜ€G†ÏQ×–=Å&_¦Ï˜Ž&Îg5=÷âù Í©;xÌB°…·DÖuLá»ƒàƒŸ7o¶‚7jÆ Z5yŠiaÙ²0Þf/ªÉ XáÃgVlXO+'Mæ†b¤ÈÂh«&¢Þ-,ömdmËij,é3Є¹shüœ4›çÎ¥¬2jÛ²Wdõ9UkU¦¹3æÓÈ_ñT&ù˜ÛÀÍ·iÃÚßèàï–—íW,’\–ÞÃU(Q‚þ^J·l¥k;vÒ‘ŸVÓð^½¸þK?gŽ–¼ÙsPç&Máÿ0¦ WnÜ J,:ãÐß–ˆ0áÈ…:„Õ{[d`ô6&Ûó›…Ñqb8|* o5+*xebtå¾¶å\Ævéò¥hû‘ßèÀ™=täÂ~ÚôÇ:ê?¤/?ÕŽÍ»hú7³¬N‹7¶–›±‘¢@‚1¹vå:5ªÞ”þ<ü—Ö6dÞR:}ò 5iõ!.fõª52iEÖuD©_×Ó¹M›éÚÎ]t"tÍ5Šð¦~îòeêÌ"ŒÌYŸ°HFH±hõwS鯮ÝtkÏ^Í¡þLxFÍYmuÇè `¼”›íØ-ãNæÄ=ÿgËÔ"^<…ÔìØ‘GY‹mÙKY}Nç^>N6Á ]¥b5ÙhZsš5ùaˆ‘sY‹Mq9ôú õÆ}¾G:Œýþ{ÁF༽‹90;’«7oÒ‡½‚xØ*R^õŸ†KùžeÄÆÊõ2kÙRúô믩mƒ†,?Ó}Õ[×ã• ÔÚx£Þ  !ÜowžæYòUŒ˜Þ(ß²‡;ê“>þø eÉž™Š–(B?Âû¨Å¡ó©XÉwÝHú>Çïõ‡û>²äèLðA‚àÌ™d`™TÇô±¼ù¢-üYà+€·9¶mg!ÞŽhð£-&_Ý6 ã¿/ò¼YÀ‰4øó1 £½ûZ_oöú»Å‹ðS"ë¶3I›>- ÙŸ7OÀÍë7éà¾CÜ ¾LeòW¤<þ…ø %ÞüPÖÆI2LÞPò?×*cB®ß¾-V½²ôcŠdgY1§‘5C¾ùà‘ƒäÏù&ÂÍQ[ý§NâFä{·?nômû×RˆýEFo'ÊÍ^zŠÑl}£r>#0"Ý®]¸ÓþGï×fÓ´½£¢Š´}ŒÀhO9{÷µ½vf”]¾h1’rçsáyÔ}ê$ûôEÚôþÜ©ÛVÏgOŸiœ¾³d7ÿ3 zd]Ç‹ìDB²Úù|“¨3cé)Æü쇲+"ÚX¯3’UBD¾ÎÌuO1êu²h!ßDŠ–”†¢¾Þ[ëžbä…ò䵂ÿ`$‡FVoDÒyS<ésœé½jÉϼºLù÷(IRy†,!É>K¢äÎ6m*/Ò;ðÎ^¾Œ@ò”eÍýfÞ\¾‰9eñixúCf-[Æ¿—Æ7Ø¿k~æo yBZw3DF3twõ²0Þ¼{‡ê±ÏáÀ/ ¼1d‡zCdatç¾–{óú­ôø‘õóxåâš0r?uÙ ?Al,üa1á³%úçñ{gFøÀÑSnðQZ±!$Òߌ…Sø1Ñõ#&ómÙÿd]Ç=‡ {½àS6¾™À‹0 7€õíŒ^—…oLÕˆ\lcŠ|Ñ/¿`•ûöñÉÿdaj²YC:5i,ŠM]ʈÑ#È‚Ÿ% ßÄ[¹q#ß žE¬¥Œ>º"¨ÄÖkh÷¶½4¤ß¥C÷¶|)럡#Kø<€ˆlC’ÁŒéÒÒ 6L½fË®?.h_]6U8ÂéFQyöÐb …ø!…èÃëT¬Ä‡a!¿÷QÂ6>ŠÑ&È86çnÆG-ebŽžìS'bh×Áƒ(b!½!´6"áæÐžA$Þy¥„21~3w®6 ~œ…-WiÛ&‚YÆSÞ‘?«©¡21ºs_{á­»â3$"² Ÿ€±sóÆ-Ú¸vß7WޜԥwGí8èäàôvïý¯$‹~‹K»·íáÞh4b­­¯¬È¼ŽCØw+ñM1ä¨É=;{ gÏáV>¥üø–¡"#ú’ÀÊ•y]­};hƒ Ã ‚üKH$+[dbº‹$”$Ê—Ãb\ˆ:3–21"WÔ'cF3á‹4=H¥#¢8áçkÆw eö9FM¢U‹WSE–Â$q’D<ÿÛ±CòKשg{ªPÕ’jHÖµ4ÔXÂW›{·iC³Ùè,h!p.ëÄ"Þz³ìÛ"Ìu»t¥Ø,e9 ý|*"3†öèi5R„c¬›9‹º Ê-Ú#G>«bïÛMâüF.eb„žëvìÐrJ ½a Š#:JÝ/Ú±”‰1žîS ¶©%„îí•-21ºs_ËÄ™€eïíÔž-!BBm»e‡¦Ô±G{òóK(Š©WÿîÜ×£K«—YFP‰)µÏ¾ìM¥Ù0·3Sæð+0Kd^G|X?8uÁŸúÕªQp·î¦¥ ‰˜¦áSRˆJE@ #õA­Z1_BK/”øO6FÌX໣ –­$"q|h™Û7jÄ FÏšÉý‚Xñ&h‘æ_tîbJÒQ™}Î;…óÓì)sØ÷*ÒHΑ3;õú¼;Õªgñ—Ô*$¬H‰†ÃP±ÃØçJÒ°¬¾b*Í‘þáì ø*MBظ?K;›Ê™ }Àù+—)}ê4ƒë½ý݉L‘Ñ~wëFnjɼŽîÞ׎µ´Ô诣;‘)ÀÇì{a÷)›"ÑkŽÎ÷äÉSž`Ïcê´©  «þ„wÚQ5˜ô‘)¾ò<Þ½Ÿ'b9«2²džN믣¯`ÄõÅýz‰ùc¥H–ô­}öÛî_Ä|ý'@Þ†ÃY½/b„¾aì~ÅìDRöR”6Ujf\ô}¡Ïyùâ%]¹|•à‰oÂù%òsv‰\ªÓ÷9΢á¤K.iè#ô7ƒ;—¨ï’ £K4ù|#ýut§ãòy`:õ—zuÄD³Uý½ª®c4»x:uõ×1&ô9ÎŒ¥Ø:^Ôªb@1 P (ŠÅ€ ÊX²!Dm*ŠÅ€b@1 Ð3 Œ%=j]1 P (ŠÅ€ ÊX²!Dm*ŠÅ€b@1 Ð3 Œ%=j]1 P (ŠÅ€ ÊX²!Dm*ŠÅ€b@1 Ð3 Œ%=j]1 P (ŠÅ€ .çY²ÙOm*ŠÅ€b@1 øÏ0 ò,ýg.¥¢P (ŠÅ€Ù ¨i8³WçS (ŠÅ€b Z1àò‡tUÊúhu]­”Õ§¬W×ÑŠšhµ¡®c´º\•U×Ñ!5ѪB]Çhu¹*«¿Ž± 5²äŒU§P (ŠÅ@Œg@K1þP(ŠÅ€b@1àŒe,9cGÕ)ŠÅ€b@1ãPÆRŒ¿ŠÅ€b@1 P 8c@KÎØQuŠÅ€b@1 P Äx\ކs‡©ËׯÓó/"í7NÊœ>}¤r}Áù+WèÁ£‡”&e*JŸ&¾J[øø1>q‚bÇŠE…óæ¥$‰iuf­ÈÆ×nÝ¢K×®QîlÙ(yÒ¤fAÓÎ#cø“'tôÔ)ºs/Œ2¥MÇ1ú%L¨Û¬™qŸþùÏ? ã=~/çÍžÿGïU\/Wž]Y×UÖu¼u÷.á:ÚôeèÓÌYõú߸s‡Î^ºDa÷ïSºÔ©éܹ)^\)?úÓjë20âù»ÿð¡v{+éýý)aüøöª /“Q(ùòÕ+:wù¿|…°ž3K `f‹LŒ¯®‹ìwñÔ¹s”È/!åË@©S¤Ñð§`ûþýØíc‡ŠŸÞ°ü™!dO6îÞMö âUþ©RÑéõ¬š=bV»hýÎVåMkצ)_6í¡–‰À^¼|I3—.¡/&Là8' Hmê7°Â,{CFIçM¥ï-²‚ƒwêà`ú jU«r™²0>§à)“Ù5\j¥>0~;` }X³¦U¹Ì Ymu~Û³kÛÞÈmYq¯æ¬QÝ¡ª ÆŽ¥ÀJ•ÖY! £Ð?<ŸOO›öìE|™+kV:°b¥U™¬ Y›õíC{qªö¸~ý¨Sã&NÛQ) #tÛwô(A'þý×JÕ %KÒôà!”)]:«rY21î?vŒ:~9ˆ„—­ÔÔõcú¬C«2£7 7–= ç:ÂØ)U¨•¾ñãŧd‰“X•‰'Ϟѧ_%6#-Ÿ=N ƒzò›>{¦LÔ°z Š'6M ¡Å¡¡ôú5ÑÌaÃ"í'£@Fèú‰è1b8<~\†ê.SÆÐíÛ¹¡á^•*„Ѥ%ìúáí½õçýiwÈbz'W.—õô¤¡,Œví→¸O“&ND ׬¡Ó.P‡Aùhh6Êd†È¨×ýmÏ®¾­ŒuYKHŠŪ¶Ì“-»¶.{EFèÁÊmZóg° IªÍ°ff?¬=zò¤lhÚñea¬Y®¥L–\;~eÝŽí|Ó¬Ñ3Y1"ذg~ Ë-Ju™;v,š4>Áxé<˜~™>C]Úº,Œÿœ?OÕÚ·ãz¿_¾U-SšŽŸ9C?¬XA#¦O£ŒéÒR‹ºÒpn, M;5nLý;v›o]NgF¬EÜØ¶#GØy˾}ÜP‚¶iÎ\6M—’³V¹òTµ][Z².”†9œº{«Qh`4F¨P¶y3®I·fÍéä¹³‘Þô¢ ¦G»1 KfÂ[@÷æÍ٪׭O›¶ôN`]¾þËÖ-¦K‚£1bÚtòÀAÔ<0P›¦éÜä#z·A}ºÉ:µíì'³Œ%YÅq±|Û³«o+sÝèë(tÅÏ¢qãŦW—20ö5’ÿÈ6ªQƒ¦JñãÅûOaìÓÖòk /¦ÂXz¿Bdcض½‘ÛF_Ç­¿ïã×0cÚ´´fÚt­ß©V¦,•ø°í8p€×›é²b4ƯfÏâ—à£÷kÓŒ¡C)sÃdaÓáC¿û޾ž=›š×©«•óJÿù„ƒ7æ71m ‰R{²v-/îÕªµf(¡ DÁ‚TºH^ºm_úâ?W0BïŽ6¦Ý‹BhtŸ>lºÒbú"{:¹‚±hþ|¸TJ8|?ð¦¹xõ*_úê?W0ÂÏ£uýúZ‡,¸·ñÖ¹÷à_úê?W0 ÝÝi+öñ…etÕÛî\Áˆé7¼ˆB&~1À높;øÐÖŒŽŽ9}Éb^Õ‚½ÔÀ?ËWÅŒ7nßáê¿W¸ˆU¿ã— ë5¦_|T\Áˆ)8H‡?´2ˆZPŸ—c°åâ5y¿>a, œ8‘ƒýêÓO)UrûÃ¥;àmð–g+ÂX:sñ‚m•Ïl»‚ÊŽïߟ;UúŒân(â*F{‡¼÷Ðb@¤öq1ªád*ö|9íQà3eî`t§­ÏdŠDW½ÝáÐŒ»ä‡lýÁ”,‰} wÎiv[W0ÚÓ Á3?®^Í«º°Q__W0¾›/‡°ê·Üw p„ž´`>//_¼8%Mœ˜¯ûâ?W0 ?¥6ÁN˜e‚Ëäôyy6€´i¸¥ëÖÑ1í„!Ý\Y³ñÑŸ*¥KGºN;Ùð .p±¨B»zãF¤6°ˆ1}Á0£­ˆ;D®˜)Fb4SowÎeFt\»âj•+VÜõ i+#:ª[aaôäéúûÌ¿4rÆt> ^’ùñaªÙl‘Ñ•g×Lœ20Bøí´êßù€ÄæCþ…òä¡Õª{eÆhŒ>Í/¢m§†,â?´·Y? gàrÅK°>¹ŽÕH…×ÓhŒötž»Êâ´Ží"††½v²ÊŒÆXŽCkÕ¢e¿þJ5:´§¾íÚ3Ÿ³SÜ¥®+“˜[€Ùb4FD0˜`Wä͑à "çQ÷¶¨G«ÜÜ0ÜXòK` ÿ†3+þôRµL asÿ "B4‘^ o„S÷¸~ý)ëŒì‰~Ú"q„Ÿ‹¾9ð葾XÚº ŒÒ”âÍÄ8’9çAyS©T©(jìþn21®ßµ“š²©T½ÀП;j´©?>²0ºúìêñËZ—…1A„ï‚~Þ¼ÙJýQ3fЪÉSL Ë–…ñ63è!“,°Â‡ άذžVNšÌ ÅH .…ÑVMD9 ¼=Z´°­–º-#üͲ¤Ï@æÎ¡ñs~Ðplž;—²fȨmË^‘…±6ó+ƒA¨Íĉü¨`®ÜtýömZ³e³íˆt ²Ä¾uâÁÙ*”(A¯ ¥‹[¶Òµ;éÈO«ix¯^üˆK?gŽvtx±#̱- ‹/þÎ;Z¹íŠpäB9Âê½-20z“íù͈è81>•…·š•¼21æÍžƒ:7iBHk—È6jZ‰EúÛ¼HGY]}v9pÉÿda„_Ý©_×Ó¹M›éÚÎ]t"tÍ5Šð¦Ž·ØÎ,ÂÈ,‘…ñÉÓ§FöW7•nìÚM·öìÕÚáÏ„gÔ ‘…ÑVwŒ¾ÀÆË‹ÙŽÝ21b¤wþÏ–©E¼x ©Ù±#²Û²—²0~Ò¶-÷ûÄ,^D Ö äÁ]üQƒ?¾¼àÃ% Uã&Äü7F|0tÔ²•渽vÛV Sfæ~gqêv’— õs­ö†Ùð¦ñOeŽ“ž Œ€ý3#¦72ê“>ô^á¦2 #Áý¬‹ÚÆßÌñC o<èm¿ø‚¥º0ÇÙRFwž]3.¨ ŒBo8þ¦L–Œà(›%.DÊ’ycÆðjø ÁÍ ‘…1a„0\ 0ª‹Q¼° UŸ!f¥1‘…Q}0=þí|Ë+"ŽÍL* =daÜñÇôA÷n¼™L,_AGWÿLðUÂKZŽx’c=²ÖeaLË^R¶Ìû‘¿°ÀpêÚ´)éÛ—6ϧϤ•h> çèÀW"œ´$LdÆÍÅBVm?*ÉK– $ÔZ3u›Cü[„3—Øg!ÞŽhð£ÀãËK£0þ{ñ"Ï›¬H#€?_£0êñà‡vLŸ¾T÷ã®üÀ}Œ2o‰'áëγ1¢Ïq$ÅXD§L8 Jmd.=¹ŽÀ˜‚‚1§×5k† |_Tð¦xŠQ¯;’§ ÷–õê髼ºî)Æ¡là‚äš"×P¶Œi›B­Ðª%ŸÁAzD“{K<Žáü A`äƒÈ)/xÆ4céK|É?úµ$ŸÀQ)3ooÜèÛöÿ®¥ ûмL"*N”›½ô£ÙúFå|F`D˜h®]ø[rf ïÕ;*ªHÛÇŒö”Óê$nÜ8öš˜Væ FwŸ]Ó@ٜȌ6‡²Úħ„dei/¼)žbÌÀÕßm¬Ç‚d•@£¯3sÝSŒz]§,ZÈ7‘¢#†¾"žb‘¶…ò䵂„‘B$‡†»‹Y£ V è6<Ũ;”ÕªH+„ý,”U#6 7–ä«‹¢Ðÿ0 wÎ0ö‰ ˆpà…†ÔlßBC‚BøèëáéciÖ²eÜrð‚5?ó7´Yfˆ,Œfèîê9da¼y÷ÕcÓ®Fo ;rìwUר¶“…qöòe”-c&ªÊ¢?1$ yʲA3o._Ç4µ£Oþðþ“…Qÿl u=»¢^ÖRÆ=‡3ÇØ VŸ‰À[ì€o&p(xSÖ÷s²ðḲ0"â ‚¤…øÁoÿ˜"_ôË/¼>(fˆ,ŒBw`B6kH§&E±©KYó1£Ñ泤4€ÛÊÊ9F³áʈ L›ê}˜·þþ;õùÊ2-w™b¨±„ψ $DúñÜ[} Ç€ Ú×A6Õ·¬S±‰⽚°9õJü#¬0  㘈óÏ21GOö© íCv<È—SYvóµ 7‡ö "ñ6È+%ü“‰ñ™!†Á³°å*mÛDB0kØðH¡¡‘yX #bÌ£¨<û1Â(ea BÌ ã•‰ÑCú Û]&Æ!,Q.Üà÷‘;{vzô8œ=‡[µ)H|ËÐ ‘‰}I`åÊ,¢h ÿ”𣴂}ÂüKH$+[dbº‹$”¸ÀÇWÍ™15þɘÑÌÁûgnø"M| E'\WÌøŽ¡LŒÃ§N¥Ek¡ÿû÷u†q/üéz·nMÕË–•zI 5–ðÕæÞmÚÐl6úëR²wb‘A½Ù|©óu¶Ë8_ñ¶ŠÂ1ÖÍœEÝXšsó¯äÈÀgUì}»ÉöØFlËÄýÖíØ¡å”úâG©ûE[#–21Æ‹džžÂh²ÕY8ìÛ–¹-ãÀ.])6KÅC^ܧÐÓÊC{ô4mT&F{×Âѳk¯­Qe21âêøÁÁ¨ þ„Ô¯V‚»u7-m€LŒÀ4E¡bJ Q©HÁH}P«VÌ—°ß–ýO6FŒìâûŒÙ#ޏ’‰±}£F|°`ô¬™Ü'ѪB`ѹ‹)IGebÄo=œóÅu>Dý ìÚ•êW­&àJ[ÆbQ9ÃrÂc½ÉÍð|¿åFsE¬aì³iR¦p{Ê?–èx}£éÎ_¹LéS§ñx<^É@ ’/aÔ”2`EatL¢Ì{5œ…e#É* U…ŽÄ‰×ñmÏîÛðû"Æ»÷ïóòD,º7#KÖèé´±/bÄuÁýŠ©ÔÉ’ºÝgÛ^W_Ä|ý'@lõvgÛ1Bÿ0v¿bv"i’Ä„è0Of\| #~ûqÂøÅ7áôŸÍrçÚéÛê1ú½¶øééëź¡#Kâ ˜S„hT£}Þ6ú„‹Ÿ3Ë›<â¼f.ec4‹£s)ŒŽ˜ySîì^E§ŒÞ3¯£3>dò #F]|ÁX&F\ܯø´7E&F£Œ$Où‘‰º!ÂQD9zªkT÷—…¿ý¶ÑðQÕÑÝý,Þ§îî¥Ú+ŠÅ€b@1 ˆ! (c)†\hS1 P (Ѝ1 Œ¥¨ñ¦öR (ŠÅ€b †0 Œ¥r¡LÅ€b@1 P (¢Æ€2–¢Æ›ÚK1 P (Š€2–bÈ…V0ŠÅ€b@1 ˆÊXŠoj/Å€b@1 P (þÏÞuÀGQtñ@Z @iЛ´&½ƒ&]i‚ "H^¥QéÒ›ô&=@ BI„ªßüç²ÇÞå’ÜÞí\6dÞïw·Óv÷ýßìξ™yó&…HÀn§”)D¦”€”€”€”€”€”@ ”@BN)åÈR | $d))))))û% •%ûe%KJ H H H H H ¤@ $¸Ý †¤Žž8O7Ã.‘_í×V<îQ§),òUóÉþÚb¼öü]Š’“y §„z¼@éèök²Í‘Ϫá%pýùsºu÷µnWSÆ0·Ôró&5Jà‰“#K Gf%/ ¤¢TÉ‹aÉ­”Àk,ù>¾Æ•›¡Ie)Vº„,% % % % % %`¿¤²d¿¬’uIÙËKÖÕgf^Ö£Y2 % % %à2 HeÉe¢–7’HŽÊRr¬5xþþsà,yŠÑ$ ëÑh5"ù‘H Hp5œ£¸yã=g«¯¬ÉÍ- ùæÎk‘|7ò=zm‘¦DPçXÊŸ=uŒR¥NME‹—!O¯ŒÖE„ǯ3Ëùgl%„5¥qs£7|}-’ïܽK=²HS"(‹slQø;t-<œüóç§Ì_/ŒcbèÄùóy/ŠòäÈÉ1z¤OoK BÓDÖ#êüß Æ{äëãCE  /OO¡xl]\$Fõý®„…QôÃäã•ãU牋jsiŸDauE=ÞŠŒ¤Ë×®QÔýû”3[6*éïOiÓÄmƒ“F¼÷$„<=ÒS±‚~”-Káu þ³útn/ã›ö^%ïl¦%ú11¨AÕø+rÂŒåT»~ óµ=z@Ã>ëD»·o0§!Фe{újÌJ“&­Eº¨È΃©yŸÞñ^>xÓ&ÊÎ> (…Ô·ì’ ¨yí·-òŸ¿xA?üºœ†NšÄÓ§ F[½cQFkD«­‹(ŒÇ׳gÑÌeË, d`JĬÔ²n]‹t‘Q=~L3¦³:üÕ‚}`œúå0 hØÐ"]KÄ(õhÍóæ½{)à“þ<9{Ö¬¼q“uaqQmŽÖöI@vaQϪÂ3><_Lúž¶ìÛ§$ñcá|ùèðÊß-ÒDEDal7pýsüx‚lO<˜>lÝ&Á2zdŠÂÞöŸ8Aý¿ý†Î^ºdÁjÍJ•hNàHÊ“3§Eº¨ˆHŒOž¤_ g áu ö‡÷êMŸwïn‘¦wDwe)æñCÎcV¦•.WłߴLs÷ʘɜöôÉs¸VÝææ°ÈïWD Ò³gO©÷tüÈ>Ê“· ÕoÀFÜ(háLZ¿jý÷ß4zÂÏæò"cóËã£P¹ti‹[¹§u§L^Ìi1OŸšÃMkÕ2‡•@‘ü” ?žb#ó59}Ú"ÝÕQ×ïÜÉ%(-êÔ!Œ&-_¿ž¼uúbí ú…J.츢0nÚ³‡+Jòä¡wë7 Œ^ž´tÍ ¾z•ºFeŠ¥"l”É$ £šw<ãƒÆ§NriXT›£¥} Xd=bDðíÎø;XŠ$5aíÔìÊQÑçΉ†f¾¾(Œ «W'ïL™Í÷Q6ìÚÉ£®=…#‚ïöû˜×aµ7ߤf¬ž:u*š¶x1W´{Ž µs檡 ‹Âxáʪ׭+ç»qšT·j:}ñ"ý¼r%}3g6åΙƒ:4‹«GèTweIa¬u‡^Ô£ï—J4Áã›ߢ‰³,{áÖ'ìß³…+JPÂæÿº“¼³úð"Õk7¡®mjÒ†ÕAÔðòÉn9f}=ã¶nMCz|h×%ñ/›ø}¢e«µoÇËôiמ΅\ŽÓÓKô:У_Þ7½€¾íÛ³!TÎí€Î]¨dóf<¼vû6—)KЍôƈiÓéÆSûæÍÍS¬µyŸÊ½ÓŠn³Fm硃.S–DaT®‹ãœ  ÞÓÃGiãîÝê,—†õnsæíiŸ”²¢z?«à÷“1ßòì{ М‘£È=­kFèã“•Þt1}`­ï‡Ž©¢,5®·#k]^ϸÞ·ØÏë0wŽ´f6›e‰5í¨WµU xv>ÌóÑIuéñ»y?rÖßoÜ„æŽE©R¥âñ¼Ì”eÔÌ™4~Þ\ª(J(»-ô@¡7nðcrþƒG§V­Ì ° ‘B¯t/Ú¶ÏLf°MÀ”#ðA –”¼$€é·mû÷s¦§ ý2É%WJoÎò_øí:°N ì³’3ÝŠˆäìÿ¯LY‹vÇ#]:3,̾$g¨{@€…BÔ©e+žŽ©¹Ðpqßd£,9`.E/Ïše)4$Ø:+ÙÅ¿2„U&;ÆdøÞ“‘í5T!™*/{1¿BNJË8§›2…3óÝ A”5³í©ãp+9±–ÀÞ£GxR§–-)S†WæÖå^·8Ï,ZµŠÃêÉF}“;•+VŒCøãïÍÜv BO[²˜§×¨P™xñprýS씲X-vòaß ˜<€‚¯\OØ4܆տÐù3'vJù øóÑŸ*ÕëÙrŽ•Ò¿¥N•š¯–+R¼,Õkü¥eö? hÄ‘·y8‡¯I(<û§¬°ÃÊWÒ¯6ÐI¶¢ ÃÖ…óå§*eËR*–vZ ?˜ûÿ`È`6œš0lXºHz§^}—õä]rî Œh¸ö=ÊEU½|Ed.;ŠÀˆ†êÛ‹/æI ¹x‰¾;‡ƒWb6n˜®r”ŒT»ÙÐ>çò%JP;6ü}ãÖ-GaéržžmŽš¡ÄÚ'uYÑa½ŸÕSÁ¦&VÛÎ ZÆ?´lõ.Œ«W¨È굩ÅH…h|¸¾Þmñ¼à“Ñ:Úì²±Š†­r¢ÒôÆX)C­5¢ýE ºw£]»1›³ó|Z¶µÓ˜Y€«IoŒPˆ 0á›[´`A 8XmŒ¼ÄV=Zœ¤1¢»²”.ÉåjÈÂOMUkÔg¶I+ÈÝÝ44E W[7þ©.Js§}MÓZCyó¢èè{æ<¸s®éÓ›ÒÆŽN˜ x¤3-q‡Á.~jª[µ*1Û¤t±ØÒÅÎÿcùê­[ÕEiÌܹôÇô.YÚ©u•+1~ËŒó@XyS»re ‰ŒˆÄ¸qÏnj˦RÕ{‚cÆ:õñ1J=ÂmÆÀX£î‰ƒ‡ë$‰hs€ÅÞöɸE=«L¡M_²$Œ_ØÂ‹•›6ÒïÓ¦óN^œ:'ˆÂhÍ&Vä*x?îÐÁ:[h\$FØ›åõÍE“̧ïç¿Zì´uÁÊ—+·P\ê‹‹ÂØ„Ù•A¡ÇªM/O*UØŸnFDКm[Í«á.A¥b£6 Nd=qžn†]²{pÞ£¾uƒù>Ê@i˜¢qû&mÛ´Š¦ŽÊ1Àè»gÿ¯Ìx"îÜä#Hé˜rÿ~;´‡&~3€$•.W™~^¾ƒ¢YzJ¹ø9ï¿N™³˜–å+Y³rþ²'aE]b†âÊ9ê£{Ôi ‹¼e÷ÎÑÀˆÑØi`*l5«°¯¦Nå—…Ñ÷—={šo|Œ>¥góÇð_²ïØQà”¢äˆüDb„#¸ Ÿf«6FóžùÙõ¸7².C‡ò©eGxÖzŽŒp²:zÖL“QwþÆ´òêhy`Ô»ÍQx±§}RÊŠ<ЍGð‹Ó¨Õň8:€psÃg«Ü˜ˆÂÈAÄþ¡ƒ4uñ"ÊceÕ˜ºŒÈ°(Œ»¢–}ûðø¬À@:ôÛJ:±j5ÁV)ŒM7èÑwðEbS®- cÖÑܶpÍ3†>ëÒ…zµmK㤭 šÏäÈšMaC÷£îÊR|–.kš^¹~-$¾"æôâ¥Ë›Ã·ÃyO.@wXÜš®]½Ä“а%%a”¤¢%ÄKyÕÈFž’ é…#/ïöïÇaÃ~F!½0ªñ@I7ÀÔSÇó‘ɤ$g0ÂÁ¦•ñ+Ì–›g®T‘ÿ÷P‘–ãVW`w¦ÍIˆ?ëö)¡²¢óœ©Gð–…uâ@ÊtÄþåËe͇Wö¤$g1ªy‡óTeÄ¥c‹ê¬$ ;‹që¼€à\¾†°¬>îÜ´’M¡óóãïêò ë“5F0 6Œd£ ß D½Û¶£â |‚J·xFw›%α¿°ëWxj!ÿâ6r-“ÂÃBÍ ¾yòñp±’åiïÎtpß6³«¥Ðîíñ ²*NIwõñ*sî*nG…Á]»Bù˜Áwr!=0b¹yÓ^=y/>3¾þäSCÁ×£-@ê­NÒ¤q³UÄeiÎ`„‡ru[ÓÓgÏÌòýò¼a]Ä¥qgÛœø˜µÕ>ÅWVtº3õÞð¡í9r˜ÕpV ²Þ¾I]Æag1ªyœ±Ìä‚.Z0Úor£²Ò¶t‘¢0RÇÉðêÔ3Îb´¦Š­[ÇcðT.rÅŸî#K»¶­#lK¢¦aWiödÓ4\åjuÌYÇï%ì餦ÇÌøä±Cxl–<=MËY57-ï\±t©+Ø+Á#O•ªÖV_JXŽÌ²^µšàh4ÛÆ¤6RÞwìA9P>6_NžÄ“УPDÕå’2, ãí»‘Ô‚MÝ`h¼1dœTÆÁ¢0Îûm¡‹!…ž0/ד.àQLá*Ûá(ù¢Ž"0V(Y’‡cH\ý[>i2‡›%¤+¶L¢°)×ÕæhiŸ^DEÔ#xÅŠ7œ*\Ä1E¾líZ©fESø' £Â20a+ЇmZ+É.=ŠÂˆÑ#Ð’Õ&w (¬û}ófuÕ®¢0bQ‰µ‰5ì´|g2i)ŒHÒÕÀû)[&ýVoÎo:M)gÎ<Ém›MèW¸8-\¹›Û2¡Pvu¸Wî ÿ«IüвUqhÇ–µæuWl8ÆÓQ+溶©E—‚ÏðskÕmÆí™0Ú;u)Õkô.kýÓbl‰­r¾Uߎábý·È߯ÓðÐng eS؆l®Ó˜;ö/P€)Y™Aåv>,Š®øÍ›s?¶Õ‰2-·çÈ^×Ä*hT¿þæÞ O°óO‹a°HŒP•}á`„hKQüqô×q–†ÚS‹!¢HŒ˜^„A7”¢ìc„Q$8þƒ‚úmê4ª_Íô ÙƒK]Æ(õ¨æI '…·È6GKû¤ÈÀÞ£‘ÚœŽƒ?g+ŠLíWÛ&Møá+Ùþ– ø_š>üÕ‚{ñ¡œQÞG…ç>£Gñm‡°b+üô £`Ä–ŸË!Á†.l`[¨¬ÀFÚ®%Kò¥eŒ#¦M£eëÖRƒ·Þâö’Pî{ºO;uâßFGëÔo·‘ŒºAøÍz}—<½ $TŒç¹1Ã@4^ÎýKÏÿK§ÿ=L!—Îqå¦C×þ4|Ì\òòÊh¾ŒºƒÏŸ¢³§ŽÒ™P0;{ÀAé‹üMqBZæn AÓÖt™]×FÙÐ+©XÉrôŨéT§AKóuµÜbnS4ÑÊ뙸Ó.Æ<}§N_ ¦£l7xÁÅʸ¾lêLÖ°¨‡±Œò4óerììY:zæ ¾@˜®hU¯w1P˜m¡¦þß~Ë{u™K”á¡G?øÒpdXü>å¸Ç–Ì&5Ƈ™—ybXø&³Ý±þulÞ‚° ¸VŠ6FøÜ‚btœÕ9ê~A¢>äSW?Œ¯/.{ð¥mñú˜ÎXº”à4Ι%Ù‘”†NßOò6GKûdK ¥©Í©_í-ÞÆg#/°ý8ÃöÛÂèàÐ>¢}ú2ÿw¦m%Âc+Ï(ï#xÃÈn'æðOåPô £`|“M{ça³ãçÎ2/Öáü[sîòe ðL¶IyNæ‹È2 F|/‚˜²„öôð©Stãöm¾êŽœ{½ßÖ«·VœÑl½(ÖFN`_R]G–1TCìè{wÙÖ$ÙÉ;Ö8[É·>Þgå0•žùPÂhTêØ}m¬Ë)ñ/žS3ÏÆöS¦é”²‰]Óhù޼ÔFÃ?)¡Q–“›ÑòSB›“ÞG‰Ñho–cüØ£,énàí«ò,)))))))cJ@*KƬÉ•”€”€”€”€”€”€A$ •%ƒT„dCJ@J@J@J@J@JÀ˜Ê’1ëEr%% % % % % %` HeÉ !Ù0¦¤²dÌz‘\I H H H H H DRY2HEH6¤¤¤¤¤¤Œ)Dý,mÙºž=1&÷’+)))))))$Јí¥‰/CIWÝ۳݉rNr;ÆÜ¿F/™Wð×ã“{¡ôâå »¶;Inõ§ð{#æ1=g[žØ³¥‹rNr;¦ŒaÌ 8žÕ×ù}”mNr{óló›ÞÇ”€ñöógl³§¶+965QeÉÛ'7Ý »ôZ7\îiÙ¾0·.¿Ö³¤zDa‘·˜"á™à‘œ3S³íD®DÝ•“s%2Þc<½éök¯õû(ÛœdþƲ/ۜףÝÜRSÈÍ› ‚‘6K ŠGfJ KÿÑÆbHrã˜d5:&7y–”@I@*KI$xy[))))))ä!©,%z’\J p ¤"ÅŠP $YK@Vc²®>É|Ê“€T–R^KÄÉXr.Wžšu9 §–† K ^RY2|I¥^I@Ž,½’E²É‘¥d]}’ù”'DWÃ9"’›7®Ñs¶ÏšÜÜÒoî¼ÖÉæxä[t-ôÝ¿w—²e÷%ÿ¢¥(Mš´æ|%ðèa4=uŒR¥NME‹—!O¯ŒJ–ËŽ¢1ÈÛáJù úSÆLY\†M¹Ñu¶:àÙóçJÔ|LãæFoøúšãÖ[‘‘tùÚ5ŠºŸrfËF%ýý)mËGí1[~âüyмEyrä$ÿüùÉ#}zëK ‹ÄøàÑ#ú÷†ñùúøPÑÈ+ V#ŠÄ¨® +aaýðùxgåxÕy¢Ã¢ÞÇ»‘wí-B[†6ÍU$ £š{Û`õ9z†E<«xÿî?x ›¾Ù³Sz¶šÖ$£Â÷ æ:%äú5ºr=Œ¹ßxI…òæ%?ös5‰Äø’á §ó!!ä鑞Šô£lYÄuÓþ³útnoÝlÚ{•¼³e·È¹tŽ&ùœöíÚl‘ž¯€?­ÜxœöèÑöY'Ú½}ƒ9 &-ÛÓWcæØT¬, ê‰,¾`>Ÿ~]2›Éd0çxØ×3©U›n:qoßev:¼òwž%éëÙ³hæ²ee20%bÖˆ@jY·®EºÈˆ(Œ?¦ÀÓé‡_µ`§~9Œ6´H…ÑšçÍ{÷RÀ'ýyrö¬Y)xã&ë"Ââ¢Þǘ˜GÔ jüš 3–Síú-„áR_XFåö¶ÁJyGQÏj»èŸãÇdyâàÁôaë6 –Ñ#SFð¶ÿÄ êÿí7töÒ% VkVªDsGRžœ9-ÒEEDbyX$„D¯ý9xáÃW¹ti‹òîiÝ)“W‹4Œ*¼Ý¹a4¥IjR«½Á^NŒ¬œ8wÎ\výÎ\Q‚âТN>š´|ýz~^§/†ÐÞ _¨dáÂæò"¢0nÚ³‡+Jòä¡wë7 Œ^ž´tÍ ¾z•ºFeŠ¥"l”ÉÒj³$ £šw8s4þ;u’KâÞǧOž˜qÔªÛÜVùýŠ(AíG6K¢0‚q{Û`í µ!êYmX½:ygÊl“™ »vò|°¯Æ@IDATtë‘o›…uH…£ùïöû˜·£ÕÞ|“šÕ~›R§NEÓ/&(/=GÐÚ9su@ø%Da¼på ÕëÖ•3иFMª[µ ¾x‘~^¹’¾™3›rçÌAšÅ}OçØ¾º+KÊm[wèE=ú~©Dã=Žñ1W”4mM#¿›GiÙ‡Øíß³…+JPÂæÿº“¼³úðbÕk7¡®mjÒ†ÕAÔðòaÓw®"½1‚ïö-*söÛuîG!—ÎÆms6å>¶nMCz|¨Dã=~2æ[þ¢¾× Í9ŠÜÓÆ>ÅÉ~yß ôú¶oφP=øõtîB%›7ãáµÛ·¹LYâ7dzcÄ”âôaé}óæ„)KÐGmÞ§rï´¢Û¬QÛyè ÃÊ’£6Kzcä bÿæñž>JwïVg¹4,â}€7+¾EgYŽ: ÌA›%ímƒÆlçô~Vt1}`­oŠuäe©qÍZÖÙBãzcÜ~`?osçÈAkf³Y–Øv§^ÕjT1à=Úuø0ÏG'ÕU¤7ÆïæýÈY¿qš;j¥Jez‰ò2“Q3gÒøyó¨}Ófæt½q&©7†~¡†Žš¯¢„üu.Å>è1Ь(!^ªl%*[¾*‚´cË~4ÒŸŒà; }OZ¶ú ør<Ãi9]i$\j^0ý¶mÿ~ž4eè—ñ*J(ðfñ|¸TQ”û'ô@¡7nð£Ñþ´`„V§V­Ì ° ‘ˆè^´mž™„Z0*lÂ6SŽÀ%Øè¤õ}4:[üiÁ¨¥¬­{%Uš#Ϫ5¯s–ÿ“:°N l+FZ0ÞŠˆäìÿ¯LY‹vÇ#]:3,̾´`Ĩ{@€…BÔ©e+žŽ©¹Ðpqß$U–Ž4õB[t¡ m“r)°¿#LÃ¥èåY“¢,…†[g%y\ F0;$p 3l·œöJr‰0°÷è^¢SË–”)ƒåô\"§š³ï=0)Ù¼½ÍiF 8‹F¦ÊË^̯ÃдNÃi¹‘#‡M™ÂoñÝ A”5sÂï°^D•Õú>ŠâC¤#v-µ”& .ìȳª¾Mø;´hÕ*žÔ“ú‘´`,W¬‡ðÇß›¹í"0„ž¶d1O¯Q¡3ðâa#ýiÁ¨Ø)eɘÑ‚ûfÀä|åªEžžaÓpVÿBçÏœ Ø)ÁP M•êõ,x>ŠÇ3²ùä …ÓéÄ‘èîÝ;”Ó÷ ªð¿šÔ´U¾ÚqdÄm^6‡¯I(ê )+ì°rÅ•¤'FWò­å^¿nØ@'Ùª5L«ΗŸª”-KuªXÚ¢ 6)©™ÙC<+hY#îÞå…Õ+T¤vM›Zôv¬ï†kïÑ£<¹zù ÖÙÂã"0¢¡ºE1ObèÌÅKôíÜ9|¼³ÿÂt•«IÆÝlhsù%X7£·n¹–ÅýD½çX;6¤;J*5_Í[¤xYª×ø½GÂ-Ó1¢7F{Û`!$z)ϪõMüñ;OB{V6VѰ.#2®7ÆêLjݨ­øë/jн ìÚÙ‹žçÓâ°;ÆÌ\Mzc„B… ߣ¢ ZÀÁjcä%¶êÑâ$Ý•¥téLv(WC.~jªZ£>›û_AîÁ(¦–üMŸ ˯×óçŒ7_ý {hA%JW ™ó×Ñž“Q´ïÔ}®P!öL;·®µØàáÅ‹ÈJR1IÙ¸yÍŠé̺õºm;…ïÚMÇÿ\E_ò / ×ßÏŸo>+&vÕFVÍœE·öì¥;ûþ¡eL¡Áž «àlÒ•áðY#ãøc²uŽ^i"1-Pu·¡¶Mš°U&›º06òR›­ÌVmŒæ£jg×o0¿è]†å®.\SÆ;lŠuô¬™&£î|q¹î!ò}Ìæ““2eö¦té=({Ž\Üeɸ©&a' {Q‘.) £½m°+@ŠxV­ùÆôøÔŦkŸví]:Ê ^DaÜuèµìÛ‡¯¶H‡~[I'V­&Ø*¡“Ö Gw‚¹ƒ+HÆl:qÛÂE4Ì‚âÔ«m[7p m]°Ð¼x&GÖl ê> §¥ËVæYׯ…˜‹dÊäÍÃQw#ÌiJ Wžü<ˆ©5½9Ø-Á«505]»z‰GѰ%%9ƒ1)ùÖroØÜ€c;„³d2)³ÊtÒÊ—+³³š.…†Ò»ýûñ$¸ÀÏ(¤F5ž\ÌCð¸©Yï^\vh¸–Tä F8øCÏT˜¹Š°&ôü2WªHp†·f–iŠÕºŒ+â¢ÞÇâ¥Ë›Ù`íQæ,YÍqWœÅhoìj\êû9󬪯ƒ0œ§*f[´°ÎN²¸³G±Î Î5_Cùs禕Ìü¡æù(ðò ëé“:%[Œ`¶³ð]‡ŸBp ~ …ÄM‹»LY »~…ƒ)ä_œñç>rp—9M ÀQÈ7—Ƀn±’åiïÎtpß6³«^€ýíÞþ*«â”tWÅèj~¹ßUæ|T\õP÷óãi{ŽæGõœU‚ÔÛ£`¹yÓ^=y/>3¾þäSõ)IÖ£-ê­NÒ¤q³UÄeiÎ`„’‡)Wkzú왹ÑB¾_ž7¬‹¸4.ê}ÄD ùæy5Ý¡¤¹òè,F-m°+q©ïå̳ª¾Â3–™\ÐôhÍUš:yÖe’"î,Fe¥mé"E-ØÇ(?œ cÊ+r“’œÅïAëÖñ,tÎD®øÓ}n×¶u„mIÔt#ì*Ížlš†Ãt›B*×äÁÃûw†´:wú8­ýÃd,\±J-žÜ¨¹iy犥sø~iJÙ5+qCrŒŠÀ% ‡[ÿ–OšÌÙÇêäM6Lo86¯'ê}¨~ˆÎž:Jgþ=ÂÛ¼“õ½ËWìæ/èoK v¥ñ6ç‘1Ú{Û`»€© ¥ÍKÙí4Ä4"ˆ]'†*v ¥Íy“M{ça³ãçÎ2/ÖálÅí:wù2Ç„ÎëL¶IyNæ‹È2 F|/‚˜²?K‡O¢·osw3ß³zíõ~[ ¯ÞZqF³}ô¢X]8}IS±a­ÿºðÑçéfØ%òñ«P1‹<\†ØÑ÷îò-;¼ÙYbô„mÜ~#”`p˜Xù/žóͳ±}àôþà Eݺl(Œ‰ÉKk¾{Ôi ‹¼EÕ|¯ åÚ¨G"G±í9|¼³Ø5uô˜¹¸Æ^Ö,™2ÚU^¹—Çël4ëJÔ]Ã`„,à¨+³gËFécýö8ƒõy½ÊüZµáÃ\Ç·7 =Ø/P:º}çšaÞÇû¬Ãhozæã £å©c÷ݲK|ežG³6ç¶±Ú-mp|¸ÔéFksð>‚Ô[€¨ùu$l´6¢îßçþ‡2fð"¬Sö‰sÎ1ÆçÌu¾/P~±'œzÛ,Gñá¼0·ÔÂli1Çžñ‘e)¾›5ÝeɨXâãË‘†+¾k5Ý‘—Ú¨XâãËe)¾k5ÝeɨXâãËe)¾k5]¶9F­m|¥„vÕeIwomÕ KK H H H H H H [RY2výH¤¤¤¤’XRYJâ ·—0¶¤²dìú‘ÜI H H H H H $±¤²”Ä o/% % % % % %`l HeÉØõ#¹“Hb $êÁ;â¶io/,¯w%èùI.îE˜özz1FDEpÁ] wýh+FÞ3aÄRWW‘‹Uº»¿ÓëŒ1â~$¯¾×ù}”mŽ«ÞP±÷ImNâëÜæÜŒŠNôAIÔÏÒ–­ÛéÙÓ˜D/$ H H H H H H H $W $ä”2Ñ‘¥¬>¹5{ðNn‚J N)SFé/¹½y¶ùMN)Ø5À¶´Œ›*ÛãÖÎR’SÊ„ä"m–’Îë”çêù¢×IvÂ"«Ñ@•á +²"‘ž אָ:OtXÔûx7ò¡½±EhËЦ9BT£KÚœÈ;·èZè%ºï.eËîKþEKQš4iHFÁèèwÆ.Ѐù>¾xù’B®_£+×ÃáByó’û9EÃø’á §ó!!ä鑞Šô£lYœû>Ú#ÇÞô®|ðŸíÔ§sãxKlÚ{•¼³eçù{Ðñ#ûâ-‹ŒÁ#&Së½x™GаÏ:Ñîí,ÎiÒ²=}5fŽÃ/µÖÑb‘ìÅ‹çôë’Ù4iÌ`ŽsØ×3©U›n˜µFŒ‚JÒìÉ#iÙ‚é ðŽ;—ê6|Ç"]ddçÁƒÔ¼Oïxo¼ieg~P»èŸãÇã-‹Œ‰ƒÓ‡­ÛУÇ)pÆtúá×_-Êg`ŠÒÔ/‡Q@Æéš"+RFkž7ïÝKŸôçÉÙ³f¥à›¬‹‹‹zñ¬6¨ÿ‡fÂŒåT»~ ‡pi¬F…Qa>äÒ9ÖÞ|NûvmV’ø1_Z¹ñ„Eš½£`Ôú±/§¤È÷qÿ‰ÔÿÛoèì¥KjVªDsGRžœ9-Òíüg ŒOž¤_ g áu ö‡÷êMŸwïn‘¦wDwe)æñCÎcV¦•.Włߴ¬×í•1“9­zíÆ”)‹écdNŒ ìÚºŽ‡”^ͳgO©÷\¹Ê“· ÕoÀzun´p&­_µŒþc5:zÂÏÖ—…Ì^8w’¾Ö›NŸ<,„w{/* ãÎ-븢å¨Nƒ–|4iýê Þ{ÿ¢{ Zs )e/›N•{ó˜Ÿ{åÒ¥-®åžÖ2ye0§5¬^¼3e6ÇÕ »vòhÚ4¦×iÓž=\Q*'½[¿eôò¤¥kÖPðÕ«Ô}ø0*S´()P@} aaQÕ Ç<}JƒÆ§NriXÔ³úôÉ3ŽZu››ÃJ ¿_%(ü( #»BjðwпhiªU·åÌõk‹þ¥s§ ǦÜ@F-ß…QGQïã­ÈHz·ßÇ„Ñìjo¾IÍj¿M©S§¢i‹´ž#h휹ÁÒ:°$ ã…+W¨^·®Cã5©nÕ*túâEúyåJúfÎlÊ3uh÷=u´“tW–”{`4¨Gß/•¨Íc—žŸÛL‡Â (K5Ù‹ Ú¿g W” „Íÿu'ygõáéÕk7¡®mjÒöÁí?x ù°¡c­¤õaP®¯7F\·}‹Êüòí:÷£Kgãôô”{k=cÞü…¨×§Ô¾K?òððâ0:4ˆš¿múðlß¼ÚeÊ’"Ã[·¦!=>T¢6º˜^RëÌSlšMQ–׬ųýóç§éÆSûæÍ Óy Ú¼OåÞiE·Y£¶óÐAÇ•%+RoŒTìßœ  ÞÓƒB¹q÷nu–KÃ"ÞGx³â[4q–å(¡³À¬F>Ê®g» cF|Ì¥M[ÓÈïæQZÖQЃŒ‚QËwFÜö\Cï÷qûý\QÊ#­™ÍfYbÛzU«QÅ€÷h×áÃ<£ÛšÉÁŠÔãwó~䬿߸ Í5ŠR¥21–—™KŒš9“ÆÏ›Gí›63§kÆ™È †4ð^¾hg»ù»P6ÓÐáº?—ò´z 4+JH(U¶•-_•çíØ²†5ÿ9ø0h¾ê[‘о'-[}€|9žá4MWªNs Ãc÷Óo舂†Žš®›¢Ä/hŒœ.¯s<8’dë}¼É/õ¿2e-ÚtéÌ·ÀìKr![1ê`¡ujÙŠ§cj.4ü‹ø3œ²£æU¿-àXÛtìcÆ|ä€iª½ ^‰ÙFaºÊQŠyÖ|ºŒ»ÙÐ>çò%JP;6ü}ãÖ-Í|Ù:ÁhÏê9¶jsHÿv”:Uj> Z¤xYª×ø=§FaŒ‚1øü)^™=^ÐÂétâÈ?t÷îÊéûUø_MjÚªƒKWüW´«¶¾3¶žÅDÓ|!õ~«3e¨u£F´â¯¿¨A÷n4°k7¶÷<Ÿ‡Mæ4fà09ø°ê &´ÕE ´€ƒÕÆÈKlŲÅIªH*;Ön&®,iÔ*Ó¥óà,\ ¹@ø©©júlî¹»¿TçcõÉ’Ÿ§ò¤]?1gE«Ü xxÄsU–œ?|àØô†Fˆ$£¬ €‘1Ι2Š£†R]¹ZAˆ{YX—0¼ÆOMu«V¥ ‰ßS:•+uþ㘚¾d Oú¸Cuoܳ›Ú`‘{‚cÆZ “[°'¢±"Ea„»…±FÝ!7æÆC/ÒQØûˆŽ®¶nüÓÞÜi_ÓôŸÖlð!£`ŒbŠHiwÕX°pfÓº4mÞjf,¬½~‚Q áø¾3ÖåìŠkœÚõ>‚×9#GQ^ß\4iÁ|ú~þ«ÅN[, |¹rÛÇf!) cf:+h}1é{æ‚ŃJö§›´fÛVóJe¸Kp„þ³Ã3µö7 N*V©MëvÓ¶Cá´ëxýù÷iúdðX~–¥ÎŸ3>Þ+üµf9o˜0zT³®É†…ÕŠí‹/â=ßá ƒŒónsëZó´kà¸vÿ`¯ÔåjV¬HgÖ­§ÐmÛ)|×n:þç*úú“’¾eß>ÖàÌW·£‡•'P€Ãnu¢ 2£î6Ô¶I¶jÃdSÆF^jwîDGÏœVÕÖX$ #V `‰r—VïP…’%5aл°¨÷ þÚB[„Ñîã‘´~×%3y1a‘ÉõÐË8¸›ãP4V¤(ŒO˜Ò*QºÍœ¿ŽöœŒ¢}§îóN-ÒaÏ„wÔ!2FkÞãûÎX—õ>‚WŒô.^m2 (œ/Ÿ™ý†=z¢8J«QX›óY—.Üî‹dÐ-Õ¢9ÕíÚ…¦,Zd†æîžÖÖ;Z÷ ²”ÌcÄËü;vÿ”¯€Â½vü½Ææ-1m±xÞdž‡•`jgo^^¹°åÄ=PVŸü(ú½,½1ŠæYëõ]ñÜéã4°wkÎÚgCÇS™7ŸúÒŠ#¡òÀe'S† ä‘>=Ÿ÷îßñ³¡òºÛmžŽguêbÓ Ú§]{›#Ep7áóÁlÕÆhú}Út:»~ƒùEï2t(wuaóâ:'ŠÀxçî]=k¦É¨;?U:C‰÷r"ŸU,0ɔٛҥ÷ 8È…Ë’qS—q^N;Àœªš kãeN§ QÓ±ç„é6ŒêbÔîZà> l@®rc" #û—ÐwF]NTXÄû^w:D-ûöá«mgÒ¡ßVÒ‰U« ¶Jè¤5èÑ` ä …1›NܶpÍ3† 8õjÛ–Æ H[,4/žÉ‘5›0ˆ‰OÃétëÒe+ó+]¿bóŠ{wn4OÛµx¯“E½9Ø-Á¾ ˜š®]½Ä£ÊÊ9už+ÃÎ`t%ŸÎÜK/Œ¡W.Rÿ&Ÿp#€ŸQvE ŠК/8`T¦í:¶ham3ž‹y÷7` 5ëÝ‹_ Ò´’z”Uë¹êòÎ`„sNŒª 7h ¾,£ç—¹RE‚3¼5³fÇÉwU‚^Ϫ5¿ÅK—7'E°ö(s<¾âÌ…œÅ˜)“7ç.ênD.såÉÏÓ5oˆsAœÅ¨¾mBßu9W‡yÁë(ÖyÁ1®âk(îÜ´’uÔj~Б/ß°ž>ùÀòÛÊOJäÏmŽÂ"ìJá»?…à;%€JrlZ\¹VBG—)Kaׯp> ù·ÉϲùÓx:–Σ'gMÅJ–'<è÷m3» PÊìÞþ*«â”t»:= Îb´›_G #¶¦èÕ©!W~·hgž¦u–ˆs®²m;@Åãyñf,[Êó{´fŽ*_zòÄþÔ[¤Iã–@IñYÎ`„’£nkzú왹ÑB¾_ž7¬‹¸4.ê}ÄD ùæy5Ý¡¤Ùu4ÈûèÛ9¸+ÛpV òÍ•—5ÿ£šïľ3ê²® ;ó>‚Oe¥mé"E-؆Í%îbÊ+r“’œÅïAëÖñ,tÎ^ñßÅUéºOÃíÚ¶Ž°-‰šà;göä@ždË€Ó1pçjÓ¡'?Zÿ5jnZš½bé¾_š’¿få">"…‘§JUk+ÉB¢0 eZãÅEa¼ËFûtiB·Â¯S†­vJ©c¨idÑéâp&ù0v„D¹Xè4zö,­]Ù4ªäáxâÜ9îáÛ˜¦VÓ¼ßVFŸ0ä¯ÐæåzòÂ<Š©?e%_ÔQFØ(a8Üú·|’i«o7uØ0Q°,®+êY=vx/ßM}³Çl‡‚Éc‡ð¤Òå*“§gu¶°°(Œ*×ä<Þ¿“0­¨Ú䵘0T¬RKIz…QaÚžïŒRVÔQÄû^‹ùùq–—ÄÚ,)ücuØï›7ó¨«v …‹J¬}Em?À|~7Žãƒ …HÒudé)[&= WçNsæÌCwnÒ¶Í&£3¿ÂÅÉ–7Õå‹MCˆX-W•±E˜CÇù—‚ÏÐûÍ*ð9uØ `´ ôùˆ)vN¶®oš 6‘Á¶:Ì@Gb} -œA;¶˜Œ,û ú†üüãöêù ýã‚&˜§[ƒÏ¢.­kÄázô„ùT°ÉoHœL°E‡²Z ®óá*ÿ_Y±ßÏ@^»gi0ÚÆ޶hýÎq(E5*Td6 n´mÿ~n;€òÎ,ãÕP$£-ÜI‘&ò}œ1q8ß5Kè øe‹Oð÷PÙXwÄØ‡¬¡"EbD[òvý–¼îö~-Â>›Ø«à@ð¿G²‘A0*¼ÛóQÊŠ8Š|áçí³qc™÷jî­®O`[¸zëVË-P! Õ(´ÍùzÖ,Z¶n-5xë-n/‰Ñ´#§OsHŸvêDõ«UsžÝçèª,¹³¥Ø?H+–ý`Þ®œ`0Œ}Àò”eþ ‡OŸ>¡5¿/æÑŽÝ>U’ãq–þM£†~įíM@ðÅÔ£ï0®<Å9ÉÞ ÃÅ"1‚]ô®ŸR ûPñÙR6•r  ‚Q½•‚µk …ÿ˜Ø=Û”¸ˆcz6<ýiçÎ4oÅ óv%¸pz ƒ?p…¢n]6F{ä ¥Ìóh†ñöëÑ=ê4…EÞ¢j> ?oj¹áY…±uÛ‚ÄÇ;K¢Ócc7WUo# ¾ž:Œ²pÔå!;Û  ‰³t E_ºkŒÖxà J 1¥ ”Žnß¹f˜÷ñ>kÇ0Ú›žùxÃh¹ÓÆF|чß%}'ÖfÛS·FkWµ~gìÁh´6«u±µèD–ø®+#î‰v8Á"ñÝÞ%é"1jýΈ,ú}ÌÂà—”$ #?ekWãÓÝÀÛÕäý¤¤¤¤¤¤¤DJ@*K"¥+¯-% % % % % %ì% •¥d_…@Ê’@‚&†)K­”€”€”€‹$ •% ZÞFJ@J@J@J@J@J yJ@*KɳÞ$×)VÖc§XIàRRRúJ@*KúÊS^MJ@J@J@J@J@Jà5“€T–^³ •p¤¤¤¤¤¤ô•@¢N)·lÝNÏžÆè{Wy5)))))))IÀ)§”p.òô.À¯ã_ÌýëôòÅ3‰1™WnJ¨Ç§÷BéùË”×Ó+™×Vüì‡Å<¡/ŸË÷1~%‹œ”ð>¦Œ)¡Í¹ÍöCÄÞ} Q¢¼½³åâÛ¼ÎÊ’{ÚT|»‰1¡GÅøy)¡³¤zÄ·tÉËö°{])ÆÓ›ow"ßÇä]Ã)á}L SB›ã»ÝIBoœ´Y‚tR‚ë‰1¡÷@æI ¸Vò}t­¼åݤœ”€T– À”°[btòU‘§K è(ù>ê(Ly))ñÊ’xË;H H H H H H $c He)Wž&ÖS°¿&ÈÂRI(ù>&¡ðå­¥´K@*KÚe&ÏHAHt5œ#²¸yã=gKñ¬ÉÍ- ùæÎkÌ–í¿ ë×.ÓõÐz‰eÑù ³_¡8唄G£éì©c”*uj*Z¼ yzeT²\v@îܧð°PÊ_П2fÊâ6l$DbŒ‰yDçÏœ {Q”#ç”ßÏŸÒ§wý /‘ñœ^8÷/Çè“=(T”<=38Wœ}ýæMzöüyœ3Ó¸¹Ñ¾¾qÒ_¼|I!ׯѕëal ÿK*”7/ù±_bt%,Œ¢> ï¬äëã“Xq]óEÕãÝÈ;„z´EhËЦ9D{ ‘wnѵÐKtÿÞ]Ê–Ý—ü‹–¢4iÒ*ÙÂ"êñ^T$=ˆ¾— ïÙsä"÷té,£W¦Œ oZ¿¥ÊyzE¶9/Y›NçCBÈÓ#=+èGÙ²8ù}´C¾éñ_ùà?Û©OçÆñØ´÷*ygËnÎ?qôúvxº|Æœ†@¥*µ)pÜ”3׿ôGаÏ:ÑîíÌi4iÙž¾3Çe/µHŒÀóâÅsúuÉlš4f0¢4ìë™ÔªM7vÕŸ(ŒP’fOIËL·€…wÄØ¹T·á;é"#¢0>~üfLüŠ×¡š`ürô jج:YhxçÁƒÔ¼Oïxï¼iegÊBûOœ þß~Cg/]R’ø±f¥J4'p$åÉ™Ó"]‰lÞ»—>éÏ£Ù³f¥à›”,áGQõˆgµAÕø•Ä 3–Síú-„ãà DaT˜¹t޵7ŸÓ¾]›•$~ÌWÀŸVnlèØU¤7FðݾEeÎ~»Îý(äÒÙ8==WaSî£7FL¯öú4ÚwéG&/Ô?DÍß6}x¶o^í2eIFL›ûf5÷ó3ݦc/z§~IŠŒ¸M‡Øè««”%㇭[Ó*Q›ÇíösE)wŽ´f6©eP½ªÕ¨bÀ{´ëðažŸÁÊ!朠 ÞÓkX½:mÜ½Ûæµ]‘¨÷³ªðŒŽÜÄY¿*Ñ$=ŠÀ8fÄǼmд5ün¥eJtR’Þ»ôüÜ&tLe©&S]IzcÔò-uN½ÛœïæýÈY¿qš;j)»‹äe¦£fΤñóæQû¦ÍÌézãLRï6²*ófóGñtéŠ×PÆL™™ýçt:qäº{÷åô}ƒÐYmÚªƒÅ©+ªST=ªyÿcùO<Šï’¢h¨óE‡õÆhï·T4.õõõns AaB;V´`Aõ­øê[äÝðÀ"]ψîÊ’2…cVüÔ[¤‰³V»û«a}Ì‘ûæÎG æN ùsÆ›‹/X±‹råÉÏãѪeŸžæ2J@Yrþðí%¾J9½Ž"0êÅ[¼×ÑØ“u%Æ9SFq¶¡TW®V'^zgˆÄ¸gÇÐ+À‚e(úc&/qéÇÇ#v9tðÕ«„ŸšêV­JA¿§t¬C£Ðœ‘£(¯o.š´`>}?ÿÕ‚‰­ P¾\¹•bÜÁÀX£î‰ƒ‡[˜‘T$ªÑÑÁuÀÖZÀ›;íkšþÓš]œXœ`1ÈûÅ#Ð’Ÿ§ZsÈÎlZ·‚¦Í[ÍÞÄ) s‚¨z´f«¼º~b-4.£=ßR¡àb/.ªÍiR³Í ZF_Lúž¼<=¨TaºAk¶m¥Žçw‡«Q¤{ W‘-ù_·#˜¶ §]Ç#èÏ¿OÓ'ƒÇrþ±,U­!FÜ«c‡Cñ±T¨Gûº|eâêNØ æ“)©IƤÆd}Waܹu­y8Iëw]bÊîb¾Ò÷zèe ÜÍepEa|Ã1”(]fÎ_G{NFѾS÷y§°gÂ;ê …ÑšwÌd@Fç¥f]“­¤uQq‘íù–ŠÂ¥¾®¨6ç³.]¨”¿?Ýf«vÛ@¥Z4§º]»Ð”E‹Ì·wwOkëH­ûY!æ¿1âƒåÓ»ÊW@á^;þ~e„}hÿêÛµ)Ÿb ûýö×qZµå þŇ¥G»ºÜ1£b·„óm9CO”Õ'?Šþƒ{½1ŠæYëõ]ñÜéã4°wkÎÚgCÇsC­|:S^$F¬úûü«I4jüO¼gŽ-lÑ0<ôÓŽÜÕ…3¼Û{.0bu[¦ È#}z>·ß¿ãfcìu;¶›/µëÐ!jÙ·oŒfҡߘ-áªÕ[¥06ÅÖ Gw ¿s‡îܽK£gÍ4u'àÃÉ|aÁ‘õÃßL™½)]z‚ãB¸,7uG4Ø¢¹‚DaLÇž ¦Û0ª‹Q,£‡û>ƒtwc¯÷OFõ0=¾xÞdž„Ç;U_TCXF{¿¥Xu¸(0êÝæ€™Ì`ÛÂE4Ì‚âÔ«m[7p m]°+Q¦2Ùæ;±uW–â»a鲕yÖõk!æ"³&àa8kÆ^L,ÌýFþqñ+\œkÿVñ!`Ø0`ßbM×®^âII½¢ÁŒÖ˜Œ× cè•‹Ô¿‡É'ÜàgÒ £>´†MàI•°õ«Ë‹WŠõ¹¤Mâ~£˜š8x0÷W‚÷1îÜ´rÚt*æçÇÝ,ß°žyÃ~…4 Ì•*ò_Éæ¦å×èù!-!g˜üF‚ÿDÔ#X.^º¼™óí‘9Óg1fÊä͹Œº‡[Å ÂUæ qˆMp£úº{wn4›‡´x¯“:+IÃÎb´÷[š” is¾asùný4²ïÇôÝÀAÔ»m;*ÎÚ¦cG½K*¤Õýè2e)ìúÎ|!ÿâfÊê Lª ½›ÒåþÇ“°¨XISupß6WÿíÞþ*«âÔy® ;‹Ñ•¼:z/=0ÂݯN ù(KãíÌÓ´Žò¤÷yz`´Å“z«“4Žn“aë¤]e[“€Š«einé"E-®›&Å¡eä½{”+{vnÔ ÃnõCä !Ý/Ï+ïûJº+¢ê[)ä›çÕT¥’æÊ£³ýbÛã#wÅaÎ*A¾¹òòcRý9‹QÍ÷²ùÓx.Z0bhr£–oiRav¦ÍIˆç uëx6v°^­›ÐyZótW–vm[GØ–DMð3{r ORðbô´zåB~Tþ0Õ¶™¹)Îû°j´bé¾_°¿5+ñžFž*U­­$ =ŠÂ(”i…ñ.›†êÓ¥ ·ß©Ó°ÛÒæJëüP#‹N…ñ·es =X ù+ôôéZøãDÅ®zË¥Œˆã†];é!RSè4zö,žT»²iÄŒ–ÄÚ,ñûà “ß7oæÑ" p% ‡[ÿ–O2Mo`åò¦¦\BèQT=;¼— Ø« [ÙL;„'ae£ZV—Ó;, #VQ`ï¢|pÇùÚ?– Èlûjñ£è?Q¾ Û€ÚtèÉ®þ…QË·T4fmxÆþ–Ö~Þ¶`> ¿Ç!Á¼@$¥b7ÿ/¡=qžn†]"¿Ú ãyOŸÄÐ[eLÚ:œ æÌ™‡"îܤm›M£¨Ð…+w›7L]ùË4.°??¶Mp-p7ò¶yå Ò–ü±Û?Á ¯k›Z|98øÃœ:ìðQº”ê5z—‡µþ=p…¢n]NrŒà[@f #Ì pCn˜žõô 9â§çy4Ãx;é1N;ؼ/ ú=­üöãè ó©`¡bj"£Ôcÿ-¸çu(Eø¥I“†öïÝÊDšúãŸT­fCMØ”ÂîQ§),òUó1MK+鶎؆$ç[Õx¶Àv·øêÓè,”£íL©-FÛŸËÃX¦ ×°OZ½u«9m×’¥Üþ‰'Xý]c›[b*ÎÙ½á.P:º}çZ’¿=ÚÕá»` =:mØ&Á;¶¬åï$ ¯ØpÌÜ™³E¢Q£¼`tðÇmÍm4öÙÄ&èX‚ÿ¥áßÎæa­Fy¾Gýˆo;„Xá§£–o©VÜFisFL›FËÖ­¥o½Åí%1~äôiçÓNhT?“.¡ʇ¹¥¦æ`·s¹d_&ÒÃoFÐÃè»äé] ¡b<Ï} 0aO¡‹çÿ¥Óÿf[vœcČԡk>f.aŸ,…J”ªÀ ¥±ÿP8}Â*¡ËÏòl¼´#Xyeû’´ljîø/³ëáÚÁì°{Œ/˜çYì3æ(½|zbF%9FðÿíW}˜_—ãl´,˜°þ£ǯQó¶ ‹sŒ’ãafÔ¯lj e¶;Ö¿æïuæÆ´¼†?£Ô#”À[7Ãèì©£ü9=ÏüôÀî+ŽF±ý «¼UO*Ë¢n1·)šnäõô²Ì°Ãv%1lD óù§/ÓQÖ°`§nxÛîÛ¡Íþ•Űõ›lê,3?~î,ßÕûè™3tîòe~å¶MšÐÌ”ÓÇÇÆLI?¦K—œÆ}Ì®ï(ER6:}?ÉßGuÃêmÚ¼“è”Á ¶µq”Œò>‚ÿj5ð6í0¶_0ŒÔÔï+êóÛV‚ì:BFyÁ;Fv‡°½'AðTŽŽ¸dŒZ¾¥Zq¥Í @S–àgéð©Stãömæ3.}?dõz¿­…Wo­£Ù˜QRáÂ…ã=Uב%å.¬Â0úÞ]¾e‡=S÷™gKf`»±ª-¡U /^<ç›?fcûÀé1 ®¥wà*ŒÊ}ô:jéÉ*÷]Ê}ô:­Ÿ°Ml¡4¥aF‰Ù²å ÷XŸGÎàÕÒËSîƒzÄ*¶¨èhòñÎBÙ½³*Yñ£îßç>L2fðb«P²YlïI,ã1[ŠŽ ®aˆé(iYRî!òY½ÏÚ1Œö¦g>Þ0Z®Ç´±ßG<¯á7B Fßö´ÙŠìã;ñ}¯P‚õ"£a.-ßR{ä`¤6ç9s„ì'lÔ{ÂyzèS—öŒ, Q–ì©#•qä7ÿöðâHãlÏuT&%Ô£# —‘êÈ^Q–ì¹®‘ÊÈ÷ÑHµá8/²Íq\vF:ÓeɱñU#¡”¼H H H H H H H ”€T– W^ZJ@J@J@J@J@J ùK@*KÉ¿%))))))Ê’@áÊKK H H H H H $ He)ùסD % % % % % % PRY(\yi))))))ä/4‰Aˆ¸mÚC K]_WºaÚëIbLÞ5œê1"v¯Ä äž¼++îïD†ó\,Ëv%¸þ\¤„g5Ea|ŸÕ”ÐæD±C£Dý,mÙºž=Iì:2_J@J@J@J@J@J@J ÙJ ¡íNYÊê“Ûî½á’«„R„ƒ8 {ÃÉz4®Rijªa¯FãÖTœ¥ˆzL mNJxVSF·˜«\ÏIè­•6KNª„D$ó’d=&›ª’Œ¦ È÷1TrÊ(•¥”Sש”€”€”€”€”€”€Ê’BK–§È^^²¬¶8LËzŒ#’d™ ë1YV›d:åJ@*K)·î%r))))));$ •%;„ôZqñÒè×BfF!ëшµ¢'YÚe&ÏHB $ºÎÞnÞ¸FÏŸ?‹sª›[òÍ7NúË—/)<ì*…\:G^T°pqÊâ-N9%áÑÃh:{ê¥JšŠ/Cž^•,—Ec;·Ã™\B)Aʘ)‹Ë°)7‰1&æ?s‚î1¿A9r¾Aùýü)}zOåÖ.;ŠÄˆçô¹9FŸì¹¨@¡¢äé™ÁeØ”‰Ä¨Üǰk!ôðÁ}òΖƒ|²ûª³„‡Ea¼y‡P¶mÚ4W‘(Œjþ#ïÜ¢k¡—èþ½»”Õ¡ÑR”&MZu¡aïEEÒƒè{ ò=G.rO—>Á2zeŠÀ¨ðöòÅ º~í2] ¡—/_PÞü…Ù¯’í²£PŒõ½@ëþ¦üg;õéÜ8^þ6í½ÊÓìæü“ÇÐWƒº°Ê½lNC ×§Ô½÷i= aŸu¢ÝÛ7X¤7iÙž¾3Çe/µHŒöâÅsúuÉlš4f0Ç9ìë™ÔªM7 Ìš#m$Da„’4{òHZ¶`º(¼#ÆÎ¥º ß±H…ñ´Y@IDATñã‡4câW¼Õüã—£gPÃfmÔÉÚ©Gk¦÷îÜHŸ|ØŠ'geï÷Föž»ŠDÕ#žÕUãvî\f,§Úõ[(QmGƒÕ#:ª“Æ|Nûvm¶À‘¯€?­ÜxÂ"MTDT=ì@ÇìKíÁ#&Së½,£G¦(ŒàíÄÑèÛá}èRð V+U©Mã~¤œ¹Þ°H‰Q‹¾ 7>Ý•¥ö¡¡Á,]®Š¿iÝÝÉ+c&sÚ•Ëç©Ûûµx¼F¦Tµz=ºxá4­ ú‘æLE9sæ¡fï~ÀóŸ={Jý»·à}ž¼©~“Ö«s£ …3iýªeôßÿÑè ?›¯-2 #x¾pî$}3¬7>yX$„D¯- ãÎ-븢Å¡Nƒ–|4iýê Þ{ÿ¢{ Zs )•(z…qÏŽ\QRžS/†uÍï‹éjÈ>°3-Q– øÕB¢×…Q}ã§ObhüèÏÔI. ‹ÂøôÉ3ŽZu››ÃJ ¿_%(ü( #Lj`ç€üô/ZšjÕmÆ?¬=wú˜plÊ Da¬^»1eÊ’U¹Åq×Öu<îªÑ3Q1"Ø}1 úfÅ·˜ß’R³™—Å?M&(/CºÓœE-°‹ŠˆÂ¨E_MweIaZz¾_*Q›Çy3ÇðôÆ-ÚѨñ?QªT¦î–o®¼4sÒš7k,5}§#Oß¿g W” „Íÿu'ygõáçV¯Ý„º¶©IØ·ÿà1.þ×#µoQ™ãj×¹›–<§§Ç3]ø§7F cÔ°}—~|ÊP:4ˆš¿múðlß¼ÚeÊ’"F½1bÚtØ7³¨9Sô•iš6{Ñ;õKRdÄm:Ä/W)K¢0*×Å1hÑL>2Œ’õ¨¯ºœè°Þõ¨ð‹ÏÄY¿*Ñ$=ŠÀ8fÄÇü#Û ikùÝ@¾Ïp¾š®4&k^´`,^ª<Ÿ^…mšB°ýÀÈ"èFl}+yF9jÁˆÞy+ö +Š0 ‘ò/V†Ã‰¾Ÿ°ýDRaÖ‚Qá¶ 3&çøz}¨$öèF‰‡1-1ý†Ž(hè¨éI®(Å)N²ŒqNŽMX¾h¡S“Í'g|Å’,] Æ6²*óf‹v']:3ÿ˜}1iÁ¨¥¬œIª,)vJÖÆË5ÂèÊå üxäÀN~D/Ïše)4$Ø:+ÉãZ0‚Ù!S˜Qeé$ç[ Z1ÚºöƒûQ<ÙÛÛ4bh«LR¦9‹F¦ÊËîç_")¡Ä{oG0NùÎdW8hØDÊÏTG¼7L‚ G0&›NÝR Æ£wó{µ èB2fvê¾®°¨Wõ©€j‹sIQõ¨¾ÑËâQ|—EC/:¬7Æ •kR£æïÓ_k–S÷¶oS×^ƒéÂÙ“|Z¦+þ6¢‰Æ¥¾¾ÞíÕÔ<èÖ]YR†ý`ÌŠŸš0:qÖ rwOÇ“kÖiÆ_ÐIc±a|/*ÌFT"nߤm›þ4¯^ÀRÈhÕ²OOõ%yXYrþðí%¾qNp2AF'YJüt«o\‰Æü (Õ•«ÕI‹N%Dbܳc è`Á)ý1“—8÷ñ1H=Â5ÈøQŸr|ƒGL¡Ôl±ER‘¨zDG£Ù­ÿ´€7wÚ×4ý§5Ž/Ë6H=F1Å´äç©øÁ™MëVдy«¹±pœ:'ˆªGk6±ÊQÁÛ¡ë'ÖÙBã"1ÂÞÌ7w>Z0wÍŸ3ÞŒcÁŠ]”+O~s\t@F{õQøtW–*²eŠëv3å'¥I›6VùYESÇåÆÊ¨Äžý¿âxº0Ã^»ÂÐÎúâ†b¥nW^0å)©IFá˜4öd]…qçÖµæáðÀq?¸Ìýä-c¿bÔ¦coîßånämþìß ¿ÎV½ES~ø“`»å¤±bK”áÒ¢d™ŠAÑë$Qõ»º¿ö„ð¤tÌÏ}6U|Œ€NüfÜ~^¾Ã1©Ç'11œŒö0šÊ13¬¢ÂŠÎA}Zs{&¼£µëµp §†³DÕ£5 }ŒÎKͺ&[Ië2¢â"1ˆ{uìÔ":žÊ`EöuY›ó‡ËÌ;Da´W_Uwº+KxÑÔSe:ëØýSzò4†»Øñ÷³²”Õ'-\¹›°êü™ãó˜Û*•.÷?î/JʨçZá\ÌÚ6=ʺ‚D`tßZîá ŒçN§½[s¶>:ž'jáÑÙ²"1bÕßç_M2³xûÖ ú¤GKÞ1úiGúcó)‹E æ‚:D`Ät÷¬É#¹QwŸOGê̱öˉÀ¨p¡6üÍžÞƒ»,ÃÍ:Öç6h°E³n”sõ<ŠÂ˜.½É#¦ÛÔ£ºpÃg¸¼€W(K¢0ªëÓã‹çMæIXqìê)FQíßA}»š¿À±?ðUä7®_¡Ñ_öä+áz´«Ëýe¹ÂY¬(Œöê êúÖ3¬»²s¥ËVæY×™O5a™*|&á§œúAQbư>æ]a·ûÅø[)íê%T7lJž+Î`t%ŸÎÜK/Œ¡W.Rÿ&ß5p#€ŸQH/Œj<ð<`ØêÝ©•ÀsŒ´¤"g0Âo‹âÕºAµ|q à=­TÔƒà oÖBK²q LpcBl/ýjT0âÿí]˜EÓ.ÉYrŽ'p ‚"ˆà"I" "A‚€‡Ê§‚~€ˆ‰ˆ " ?H– dÉ99s p¿½ô8»·»w»757wv=ÏîÌt÷ôÔ[=ÓSÓ]U-ÚÑeÉ?qŘ!C&YõÕ+—¢]BMÝØeÞG qÅh®ÁSÕˆKƒWÛ˜³âu?®LjP; ×T± sç-(§P[7ª$G—,˜I­;Ä_<´¸b¾Øè (ÇA¶)Kg„– z¢Hñq¨0èlÕ¨R±’å7ú–ß3B¨ŠÖ­Z*w•WœJ·{WŒvóÌõ¬ÀwóNmjJå1¶zôý<VØÎ±£7æÌK$³q™ o¼Äc¶ì¹¥Q·g½ß»g|ä`Z'OþÏ"¶Ç£?F±‘¢œy¢+‹*ÏŽm\1†<ê·nY]«!î]|R\1šyŸ1i¤'Æ5b ¬±¥/£dÉ’Ñ&ñd—Á‰ÑŠ6°¢NŒ¶‰5ÅàBÍí[7ås¨¦  Û;ˆ#ú’jbi ôÑXz ëlâ¥/8â/팀p81*6fMs)ïðÊÆbív'Æf­;ÓáÝ…÷©ø"LK”'LW‚^Ç0Aqb D_€åXµTYªÍo¼Õ›fÏg„‘'ˆ^ M¾µÈSnþH/Vâ)il#BE°âïÔc ½X«±J’[Ô1îÿGØQÖåM@ˆ‘Ñ¡k?©<Éæ?NŒ`Z¹Š)¥ @AÄdV6U¾Õ[NŒæ¥”í€'ÿ0ôç&NŒow(Œ·“HE^ݧÀƒ©©w„²‹ée;ˆ£7þ“ ¥”ÌÆå281b¹³cÔ?E/‰¾©‹ð³k5wNŒÀ„Ñ]LI!H#€0:غ}/Ûl ¹1bdW½gZ½ùÏŒ…kÓ'ÆW[¼% Æú´‰„·ª"(À»õ·%è('Æ@ô…ÝÊícb̯ë¶(âÌÊëë¢J°Þ¸vE.ÙáoÊáÁƒûrZ 73‚Kš—ÁðuAœƒùô,Â+ÅŠaðû7ÓÕóG…Ñö`Óïß/$rŒkÇ»BéC F„ÐÈ’%;¡#‰+9½ᙊ^³R(f§=×E?†ÑÞT"ÆFË­ˆ)åÄvÄýzîìI‚Ñ·¿>;¶íé´v>PJáÕh9 #p!ÄÒ‰ˆÝð ‹ëŒ‹“0£/Ħ­“FžzN­Z¾³(K±aÎIe‚¹œÄlx ¦sŽM½N*£ÛÑI­µ´´´´´âEZYбë‹j h h h h h $ he)¡´”æSK@K@K@K@K@K ^$ •¥x»¾¨–€–€–€–€–€–@B‘€V–JKi>µ´´´´´âEZYбë‹j h h h h h $ ÄgiõêÕtçDå„"0ͧ–€–€–€–€–€–@â“€¿ ”±^î¤pá‰O2=vœˆõÒd*˜h1F^?MhŒ ½ÿ íxïÆi¹>™îsöÝúo¸W5Æ„}*îïߎ {wý Ũ,åÉ“‡:D‰¹ãºqû¡ už˜•¥É“KºhŒêñH˜ÛC;fÉ”N?¬ûœ„y‹\ÿîUÑh4u©ø¡m–üI'1åù]01MäXt;&òÖð´´œ(­,9±U4OZZZZZZŽ‘€V–ÓÌŒ<Æ\¿®Þ èv´GÎú*ZZZ& heÉ$ŒD½«§oGóêvLí¨Qh h $( he)A5W˜Õ#qžƒNÕíè ÆÐ¬h h ü[$£7\\ñàÁ:yò$>|˜îÝ»G!!!êµÊ7nÐÖ­[)I’$T¶lYJŸ>½×rH ¤¬ÏJ,Êx(0Fœ;E§N¡¿ÿ¾Gyò¢…Šz­ýö­´ï¯íô˜ÀZ¼4¥Ië#*¸xá;sRÔW„ÒgÈèµN;90FFÞ¦{wÒµ«—({޼T ¤¥J•Æ8^¯Áí}pÿn‰1k¶\Tð‰PJ“&×ëÛ‘ÈÑÌ÷™SÇèÖÍë”)KvÊš-§9˶}«ûœ .ÈþÆ€üùóS²d¬]¨·ËŠ |}Îå‹çéÔÉ#týÚÊ"Ú°Hè“cr¯|p&Z‰ñÚÕËtóÆ5¿ìfËž‹R¤L巌ՙVbT¼¡ÎÓ§ŽÒé“ÇèáÔ¯@añ{BeÛ¾eÁøð¡x/ž cGöSêÔi©Páâ”1SvllOúÒ¥K©GtàÀ7ï¼ó}óÍ7FÚÍ›7©E‹´xñb# ;­[·¦‰'Ròäÿ<¨”u«Œéà÷µ¿ÒWƒûЉcݮЬUgzoÀ0#íöí›ÔïÝ6´nÕ# ;u¶¤ŸÖ=xpŸ~œþ- û¬¯,ßïÓÑôJ³7ÝεëÀjŒP’¾>ˆfLþç(Ž?ÿŽ^¬ÙÈ.hÆu¬ÆxçÎ-õÕÙ†ÆEaüè“QT³^3s²-ûVcôdzÚeÔã­Wdræ,ÙhÙ†žEØ­îsnß¾MÙ³g÷É÷¼yó¨Q#{ïW®vÄ‹gØgïÑïk—»áÍ_°Í]¶Ó-ûÀjŒ½;7¡[÷Ëvßééëü–±2ÓjŒàmç¶ôŸþ]èÈ¡½n¬>S1ŒÂ¿O9råuKç>àÀ¸kûfЧ­Pº±ß©g8µïü[šÕ,ÊÒÂ… ©aÆ’×ÚµkSõêÕ…˜š6lØ@)R¤00`´ 3ׯ_/Gš7oNI“&¥¯¿þš¦M›FQQQr‹)k\€qgÍÊŸ©wç¦ò ÏU­I¸!S¦J%oX³‚‡Ñ¦îíȇ£N5ê4‘gNM¿,˜!1~òå÷§÷ï¢Áý:Óž]i–ìaëÂqÍŠÅRQ‚rTýå†r4é—…3 £0toI3m¡ÂEŸ´rl*áÀ¸~õ2©(©öN+°.š7M*Õý{¿A¡%ÊPÁï#¬1òìv4óyïn$ ýä]s’íû}Ndd¤CõgF‚Ø)V¬˜ù}Ÿã^Ó|£Iù  -E/¼XO¾X1*ºÏvv\æ p`|>¬6eȘÙ|cíJ×Gº£g1"ØM¼gÐ>õte «ÑPÎÒL›8œ¶l\Eáï·§±S—¸¹w80?z€Þlþ‚d½JõºTéù—èðÁ=4wæx;âcÊ‘#ÕkÜš ZŒË %~þ€›¹ÃYb‹$Œ$ù"Œ&Õ«WO~½íÞ½›²eË&‹nÚ´‰*V¬(÷Ïœ9C¹rå’#O±-ëëz¾Ò·í< Re óUÄ-7dí*OÈ#HIòEMz÷íÆ„¯í~ÞJ™2g•EwïØBíšU•ûKÖ5¦-ž M-Ó^{£›fÜ'¿ô¬Yºó¸ Jß÷îÞJĈ\˶Ýä*Àbª±~5×ÔåÛÝP‡®IúçŒPxÿÚùÕnÒ¤®ïÜ3j”¤Ë—.Ðûá#¨IË·…'Ë;£™ù)ãÿ+FÒú^J¸ßã:²”úÁI”2¾ûœË—/S–,Y¨J•*´fÍ3ä8ï;©ÏyçÍz´iý z¹nS4d‚Íÿçƒ6.@x¯šñà9mÙ ‚LZºþeɚÜ«}§`\ºè1âÒŽ²çÌC Wî3ú(Mj•‘XVo=£é‡7ÐNÁˆ¥¥‹fQí¯ÑÇC'Òc¹ 8'÷%6PšÀü´ü/#Ý_iI#OHÀ_Ÿ“Ä×ÉÁ¦Ïš5K*Jè`ü)J¨êÔ©ò2}ûö5%$<ûì³T¹re™·`Á¹ ¤¬<ñï×_æ¼?E ,,žÿ?ÉIë½ E O–y†Ê”«$óV¯X$·øÃKtÆÂÍÔ룡¢¼Ky42mÜáÂXüÉrr¸sÍŠræÎGøRsÑvF|¿Ò´ÑaFÒŠ+-¡Ý¸~Í.ˆÄ…Qˆ8{J*JÀשG¸J¶uËÕçØ "†‹qµ#¦ß (>üøË¥àxÍæÂèõb"qÖÔ12 5Á(J¾êõ—Î…ñ’Y•~ª¢[¿“2¥ëãy˜©±ƒ¸0b Ô¤eG7…¨¡èkA˜šƒ-Y®,­Xázð:vì#Ï«V­’e Xy’R–tÙRÖ³.«·üþ›¬²Qóö1V½uóYC£ž¤”¥“ÇYuÀËÖr ð9áÄè ÛÍëWer¦L®‘7oe¬N³#ŒLÕÃR¤DðPÖŽ#†¸ìúôûŠ÷1Õ<ØØÉÕçÄîêö”âºW·mY'4lÒ–Ò¥Ü0>®Â…ÑÛåà<³`Îd™Õ¬UoEXÒ¸0+YVòûKæJS<†ÐÓ¿!ÓË?[•Ò¦Ë ÷¹ÿ¸0*;%Og'ÌÖÀätü¨K_àÀh¹Í<Ú@wïÞ¥ððpÚ¶mݺuKÎïÃò¥—^’ùÐrÏŸwiÃyóF7<ƒ§ Þ(”•'1ÿÁ£ tïÞ]únä§´ïм}Kxt…Rµ ¨ÂsÕe>øÆ´ ãž„ЕË=³âýØNŒè¸¶ý±^b.W!ºâÌ% NŒè¨®Šv½+gļúw#?‘£‘¥ÊVÓUµ¸ E«—ãŸâCs‰Rå©î+¯Óùˆ3Ñ®oGGŸcæ{ûöíÔ¤Ii‚~ ޺͚5s³¿4—çØçjÇCþ’ì¦Ïð8Íœò íܺ‘®\¹H9ræ%¼`Ñ®j*™—¹N.Œæk¨ýŸfM”»ø`UІÊãÜra,_¡*Õªß\NSµoQÚuêK÷í2¦Åû}êEãĦêæÂ… ¼¨ =án/ï[äÝÞ¸\d¹²töìYÉk‡Üx^¹r%3†Lýúõ£«W]# (”6í?S2ê¤4iÒÈÝëׯTVϹÅËCl3ÁnÎŒï¨sÏAôfç÷Åtä?Ó-©S»ð˜Ë+Wù[7o˜“±o'Fçày£M;„À‰qýê%Ô«S7P˜?>ݶ—.Î…ñþý¿ièÇ=%¾¾GPá˜_ÄÑçKÊ”)%$Ø_Î;× >—-[fÛb¿\íxU(F éßí†p@ùuñl9a¡T£°8 £'›ðÈUx_o×Ã3›õ˜#ìÍræÎO°á™4v¨còìµ”+Oã˜{‡ cÕêõ¤B?ì³>¬!-30—.DÐo¿Î7¼ª€‹,Ÿ†CÇ‚ë?¾È÷iƒ ’éýû÷'m+ã,$Þ¿_æùú ¤¬¯:¬L‡±.®ÿ3l¢M{oÑêm¨c·þ2ýÛƒèÂù³ä2?“IR®½„ñoFxM¨áðð/ÆE £À)-NŒCŠIÃÜ#•ªÔ0Ο;-¼Ž*ŒÜí".Œð@‹2BZ”,ý´]p¼^‡£ÏÁ…ðEìÒ¥K„0§OŸ¦~øA:¤9rDöq^bHäjÇ»<þ0:8zÒbZ¿ë*ýþ×uújÌl‰öLxFí .Œž¼ÃH×ÂÇKÕ]¶’že¸Ž91þ¹i -|4µˆOEZ¾(â½íR‡ì[.Œm;ö‘&*˜­Á‡hƒj¡ÒIjê„a¦)\8F‚…;–+K*˜ä»ï¾KeÊ”‘_$éÒ¥£šnƒç[†  ×®ý3£Ñ9räÈPYu>çƬ –m»K£]ÒD°Áö]>4¦Û‹ámó±· høÂeΚ]nôgÆý{váÞýp¨4N´Sœž’ðÚÀ—ù/kú‡=[ÙflÉÓÆc†’Fë]z²³É¼^‹£ÏQÊ™3'eΜY†>É;7!¼ÉìÙ.EbãÆR‘Re9·í~î„é6Œêâe7z„€á3Èò0&²Öè\ÍWÂôø´ Ãe<ŽíšbTÁÚ”’« ÊñQgqµ#ú’jbiŒß–/KIÀ!/%xÁ dí .ŒŠ÷YÓ\Ê;.°øj|Æf­;ÓáÝ…÷©øBa¹rù­\6_„Û}˜kcqa3<œ ï[ Hìھɰ§kÓ¡aÙ1N²|¹0»oß>êܹ3©@’H+Z´(1‚°Vœ™ µk׎-Zd$—+WŽh¬/§2)«Î‰Í6Ð¥P'"ß~1¨»¼1Õ5àÐ[Ø¥x6”£?ìHj"”Gl]ûIåImMa¢b3™ÓÕþ„™+Èß*-6Ûû7Är'IJ*!a±).Ëp`IJXÃM·Š—|Ê_¯y†åG%±Ôɸo ±bSSïeÃÅÁ’SÚÑÿð­S%D*‰‹W»¦£½•‹)-ÐåNPGŸ3sæLéÅë¹xÓ¦Mé³Ï>‹SØ'õ9·„}åðÏß7¼R!OŒ¶nßKÚÂ)Ï#ð`d·r©ŒØ•^V…(qÆù³'ÑøQÿ1>Ê$XñžÚ*p£JíÖ)¡À÷ëång†wn§éÅZ¥pl1y–‹Ír',Ê’b±”`¼ ¥˜†«>àèÑ£r8+Ë*^ümƒé¸T}7DäiëbEg³MŠÊ7o<¸/­Ì"hÅTÖ|žûÁ¼dÕu Æ×¿Sø°åÀx7òŽ Ô˜,yr±ÆXvJ‘Òåyd¾n ûNoGxxb¤4.ë‹£,)9rô9pV‰ˆˆÏléыžãJNìsp¿ž;{R}[1MÌKVÉ•ëyDý)S¥V—‰óÖièºx']ï¤t"b7¼ÃÌ3ÁvF¼C±–(”_uV38Áà2ŸeÉòi833f$übC˜ª(RäŸØþÎ ¤¬¿z¬ÈCèuÏðë¾êÅM›¯@a_ÙŽM×Ý›&¶íˆN!œBv´£ ´_˜9ú„ÀÏ)ÄÕŽ¸_ *꘭T’¬Fð•A¼“ðsY}o°#dq•‡åÞqeHŸ¯% % % % % % %à$ heÉI­¡yÑÐÐÐÐÐpœ´²ä¸&Ñ i h h h h h 8IZYrRkh^´´´´´´'­,9®I4CZZZZZZN’€V–œÔš------ÇI ÆÐ§O»¢:|9Ç¡ö`èBÄ ™‚X ‰•.]:)¡!f†meÛ•ä…®iŒ<·¹Ï\vÝ«‰¹Ï¹tÞÕ× ¦Tb¥ÓuŸÃÒ¶6?ÿ†~õêÕ³16UŒA)W¯^Mwî܉±"]@K@K@K@K@K@K@K ¡J V­Z>Yqd)Ož<„/<•ø¬=d_bÇ—ˆÁ ¤).‘f5FçH .¼ƒÂ?'ºÏñ/Ÿ„’«ûœ„ÒRþùT¼ý•Ò6Kþ¤£ó´œ&›‡à_ó£% % %ÐÊR|H]_SK@K@K@K@K@K ÁH@+K ¦©4£ZZZZZZñ!­,ŇÔõ5µ´´´´´Œ´²”`šJ3ª% % % % % %ˆÑ..L=xð€NžËݸqƒ¶nÝJI’$¡²eËRúôé}–åÎà¾c+nŒE;Fœ;E§N¡¿ÿ¾Gyò¢…Šú¼ìÅ çèÜ™“¢LJŸÁ{;FFÞ¦{wÒµ«—({޼T ¤¥J•ÆgÜoߺA÷ï–³fËEŸ¥4iÒÅ ÊcÁŸÎÑÌÍ™SÇèÖÍë”)KvÊš-§9˶}«ŸÇ .úo”?~J–Œµ õvY²£ù"²¿¾rå åÌ™“J—.MÉ“'7±eßÊ{õÚÕËtóÆ5¿|gËž‹R¤L巌ՙVbT¼¡ÎÓ§ŽÒé“ÇèáÔ¯@añ{BeÛ¾eÁøð¡x¿œ cGöSêÔi©Páâ”1SvllOúÒ¥K©GtàÀ7ï¼ó}óÍ7ni÷ïß§Q£FÑ»ï¾+ÓÇGo½õ–[ܼy“Z´hA‹/vËkݺ5Mœ8Ñö‡š#€ÅVnB`:ø}í¯ôÕà>tâØA·+4kÕ™Þ0Ì-íÁƒûôãôoiØg}ez¿OGÓ+ÍÞt+%éÛáƒhÆd÷{ MÚô4ðóïèÅšÜÊÛq`5Æ;wnѨ¯HY˜ùÆ>E5ë53'Û²o5FO¦7¬YF=ÞzE&gÎ’–m8áY„ýØêçñöíÛ”={vŸ|Ï›75²÷~µ£·oß>êÙ³'-[¶L%ÉmÑ¢EiÿþýniÜVß«½;7¡[÷Ëvßééëü–±2ÓjŒàmç¶ôŸþ]èÈ¡½n¬>S1ŒÂ¿O9råuKç>àÀ¸kûfЧ­Pº±ß©g8µïü[šÕ,ÊÒÂ… ©aÆ’×ÚµkSõêÕ…˜š6lØ@)R¤pðsçNêСmÙ²Å-Ýó#Sˆõ´~ýz9BÕ¼ysJš4)}ýõ×4mÚ4ŠŠŠ’[Ïó¸Ž90‚×ØÊƒ —¹Þ5+¦Þ›Ê¤çªÖ$F0}ôèQªP¡‚AÃH°æË—OöEÛ¶mcÇe¾Ç½ú|XmÊ1³ù2ÆþÚ•®ïdÉì=ãÀxùâyêÖ¾ìGŸzº2…Õh(g^¦MN[6®¢ð÷ÛÓØ©îа!†ŒÇ 7›¿ ¹­R½.Uzþ%:|pÍ9žÆŽø˜räÈCõ·f@ãªÒre CÖ­Zµ’µc #IŠºvíªvm™2eä>¾jöîÝíËF\¾|¹T”ð¥·qãFÊ–-›ÌªW¯U¬X‘¦OŸNC‡¥\¹r©Sض\ÁplåÁîQÅP^ôq a #Iм}µlPAf¿öF71<º~_»\wÛbH_-Ûv“C¨È|£cª_Í5­·jùBÛ”%.Œ˜~ì7x ÕnÒ¤®G¬Y«NÔ¨FIº|éý!:¯ •%7iÆ|À…Ñ|å™SGË/=¼”Ö­ZbβeŸóy€*UªÐüùómÁâë"œß~ûm©(aÔ~Ê”)Ñ>h}ñdu:×½Úöí÷¼²Š<¥,U}±ž×2V'raÜüûJ©(eÏ™‡¾ºÔèw*U©AMj•¡?7­‘ùøHå&.ŒF&Y¯Ýà5úxèDzì1—MBÎ\ùhô°4aÌçT·Q+#Ýjœ–xÏš5K>xè`ÌŠ’/Æ»téB;vì áÇûòž:uª¬¢oß¾†¢„„gŸ}–*W®,ó,X ·Ü\ÁwlåÁñ×_æ_)fEÉ×u›´|›f,ÜL½>J™2»Yoe‹?YN—b®YQÎÜù_  ³b.Ú.âÂX$´½Ò´Ña:©"ÅJKh7®_ b€6K\€ˆ³§Ä”c‰¯Sp•lë–óy´ˆŸ‹qaÄô>DAcÇŽ7E ×ç¾Wq 3Íš:Fâ£&KÖæ,¶}.Œ—ÄȨôSÝú”)SX0ûbqaĨIËŽn QCÑׂ05[&.²|diÅŠ’׎;ÆŠçÑ£GǪܪU«d9(aže ÓsºÛÕx–³ê˜ #ø‹­<¬Â⫞-¿ÿ&³5o靖[úûá#ÜŽ=¸yýª<%S¦¬žty;1ÂÈT=ì!EJÍs 'rc1Äe'ЧßWô¸©Ž@y´<çó(/\å¹0®Y³F²Ü¾}{züñǹØU½Ü÷ª™ 8¡,˜3Y&5kÕŜźυ±Xɲ’ïÿ[2—^{ã©4=†ÐÓ¿wõË埭JiÓe`Ŧ*ç¨ì”<†2eÎ*ŽüèAÊ•§€bÅÒ­åʼÔ@wïÞ¥ððpœ÷­[·äü>Œ!_z饀@#>Þ¥9çÍÝH ^) x®ØAíà;kìûk»,~ïÞ]únä§´ïм}Kx­…Rµ ¨ÂsÕ©ÎoYt\ÛþX/Ë”«]ö{r291¢£ºzù¢xîÈyõïF~"GêJ•­@χù^¬1p¼žÊ‰ñÏÍks‰Rå©î+¯Óùˆ3^yàNä~·oßNMš4‘6 èkàÛ¬Y3[Ga¸0îÚµK6¼GŒ!íJÑÂf),,ŒÚ´ic›Çç½êyþ4k¢L*S®)Eó Ç1ÆòªR­úÍié¢YÔ¾E5jש/Ü·KN‹Ãá¢ß§®Q4Lžura„ ("xQzÂÝ^Þ·È»)¼q¹Ère îî m›iåÊ•4fÌ>Ým˜Ü­ÃÆû÷ÿ¦¡÷”÷8‚’g‹ø"®ç1eÊ”ì…æÎë‚ð+\¸°[:ׯ‹/J–ÿûßÿFcŽ3?üðÁaZ¸‰ë^õä¹Ó¿ÿZ&¿Þ®‡g6ë1'ÆAC&PÎÜùiòw_Ò¤±C “g¯em1.bÚáÂXµz=š9åámÝGLû§¥ÂÂÜáÒ…úí×ù†·#Bp‘åO:ÜùñE†˜ H4hLïß¿?9ب2äBp«oâÀߘ<¯#=P†-iÆ‚M´iï-Z½íuìÖ_¦;b]8ïRŒeBðšPÃáá_Œ_±öy¥pb,RLÅC~0²?wšÞhR™öîv¾%²Í¸0Â.Ê Q²ôÓAA±ê$®çfPR.]ºD#púôi©<ÀÉäÈ‘#²³ CLõpa¼s玼ô3Ï<#m—àÏceÐ{&xáÙA\÷ª'ï}ÁµðñRõE—­¤g®cNŒ0â^øhjžŠ:´|QÄ{s ª4Î-ƶÂö p’Á‡hƒj¡Ô®YUš:a˜'E ׎‘`áŽåÊ’ ‰˜IðìÂIºtéhÀ€¤¦ÐvïÞ„ 2å¯]‹n‹Ž ”#‡=Fz €ÙQ^-Ûv—†ÉhGTlßåCÙÉ€ÍÃþŠ·û÷ì0B¼ûáP9ϧ <™#¼þàE¯‘Ò/kú‡=[ÉP²TqŒWÄôâ˜áƒ¤Qw—žƒ‚âËÊ“8ŸGfÌœ9³ }’;wnBÈ’Ù³gKöá• EÊâˆ. L·ÁD£i ‚ðmÛ¶•y1…u‘…,øã¸W=ÙÂôø´ Ãe2Ò:¼ö"©.lª^.Œ™³f§)s׉ÑùiÅ©E›®Ô[ØJbä Je¸ÈreI¹ô_¾|Ùg¼l p^ySxÜ {à\N G›‹ B8›ÄѾ¹Œæ@ׯEoÇ\b¨—ùá“ÇS÷õe=#€ŸÝÄÑŒ‚{õûR&anÝ®Ž‹㎭ä—9¾ _~.?=šZþTø|ù!­ËµÍ"`Û·ûy|úéFÒÎsMW³{T1ÆL™2É+¨é83Ž‚ ÊC»Ì8îU3ì#xª °ÛàÕ6žÙìÇ\Ç×y‚k"Öfcrç-(?ÔBD„k<«KÌdLJ paDÝÉ“§±ëºöþT*JP˜BŠ7FΞ`tž±\YRq‚€ÒL˜ŽSÞj0 ”Ê—//OQ^!æóUDoBÀœÇ±Ï…‘ƒ×`ë,úÈÍQaÍ„9á“Çɤ9£Û›ËúÚ‡»y§65åWbfôèû¹¯¢¬éœ½1n^ê$Ù£øKÞÊY™Æ1[öÜÒ¨†ÝæŸúºÿHÏ“?ÄJ(>ë²ûyÄ’LŠÔ :æÚra,Y²¤dyõêÕÑXG°Jr ‰VÀâŽ{Õ“Å“FÊ$„:Éð¸KQô,ÃyÌ…QyÚ-îŠ[¨0`ZªTÙgå!––²ƒ¸0úâ}ñüÿÉ,Mæôø³\YªY³¦dK–(6$¨c '—*å2“cùײeKY®õæÎjÒ¤IrIŒ&ºC`¼ÿ½ñŽÅ“1×µáR?8)F/Ç{ŸóüóÏËømP(°´ Ö¦Dà[el‚]òÄI}NãÆé§Ÿ~’Mоùï¿ÿ–†ìH@ü¥ &xkîÓœÒç(F?ù°£\v°#´‚œÒçÌýa<}Þ]B‚‹=–+—/ÐÊeó´é?ýNéÒ?0l§`ùåG´ø§éò]„õ]Û7ËkµéЋº½÷Ÿ€±©’FžïGzŽåÊ.ލ°;w&HiXq<°Vœ™`”m2ça_wjz ÊQ»víhÑ¢EF±råÊÑÀµèŒŒví¸P5Æ@äÄ€•%ÔU¿Ô]†ÊWׂ‚Ó[ØÞx¾ìk Ûتø¢ 3Wbš Úó”ñÿõUL¦O·Š—|Êoo™>Ô¨ƒã_;ÿ qß –£Kf>15õŽP1\,ª,á:½ñïÈ:UB¤ÀâÕ®©ZoåbJ TYB}ÏãÌ™3¥¯çbàø(ûì³Ïâ6ÀI}l’0Z†ÅÈa¤þ½÷Þ“‹›c Î`(Pe ×àºW1²[¹TF cô¤Å–…(qJŸ`ógO¢ñ£þc|”I°âµðb† 9㯋gS¿^îvfxuê1^¬Õ8Ná-âMYR ‚øH0Ȇ’•ÃÕ€ùt¬gE½Át\ÜUýVmƒé¸ÔµoˆèÚ0HƪÕf»•ï”m0µâãÝÈ;2Pc2á]”%KvJ‘2•º\ÐÛ`”%u1ŒªnµE xÁ3X FYR×âèsà¬!îý4r´Ë;­Ï£×E}Iô×éDÄnx‡ÅÕãÏI<¸/f8NŠðwEL©|Æú¢>(–±Q–’Ų® Š!*,~V¦5Šù'Ž„ÕõRÆ@xà.‹ðòž!湯iwýÑ)#„€Sˆ£'¶T©\b=Óí:æx:?§F`CÐÐPGÀä¸W­T’¬Fð•Aô×ø9¬ÆÅ/ز¸ÊÃrï¸2¤Ï×ÐÐÐÐÐÐp’´²ä¤Öмh h h h h h 8NZYr\“h†´´´´´´œ$­,9©54/ZZZZZZŽ“€V–×$š!------'I@+KNj Í‹–€–€–€–€–€–€ã$ •%Ç5‰fHK@K@K@K@K@KÀIˆ1‚7XD°2MZZZZZZZ‰Uþ–;‰uPÊÂ… 'Vùȵë°’Ƙ°›øè±ãô@,¶˜&SÁ„ Ä÷‘×OÓÉ㽧 ‹fêçÑϲ°&hbïWuŸ“nÄX°xÿvÝ»ëP(Fe)Ož<24bî¸ K,? 1Æâ®rp‘·Êų²”"ùcr±àÄŒ1K†¤r!]ý<:øa‹%k‰½_Õ}N,o‡Kš:‰|wøcSÛ,ù“ŽÎÓpš¢œÆæGK@K@K ñK@+K‰¿5ÂÄ$ÇEK@K@K aH@+K £4—ZZZZZZñ$­,Å“àõeµ‚’€ž† Jlú$---¸H@+Kq‘ž>WK@K@K@K@K@K ÑK Fo¸¸HàÁƒtòäI:|ø0Ý»wBBB(444Z•·oߦíÛ·Ó¥K—(oÞ¼²Lš4i¢•S 7nÜ ­[·R’$I¨lÙ²”>}z•eû– #€œ={–Nœ8!å‘1cFÛ±© r` ´Í/\Û‡â^8wŠN8"ÜïQž|…¨@¡¢Ñ.y›ìÝI×®^¢ì9òR"”*•÷{õö­tpÿnY6k¶\Tð‰PJ“&]´:JˆƒÍF3ïgN£[7¯S¦,Ù)k¶œæ,Ûö­¾W/\¸@èo¼Qþüù)Y2Ö.ÔÛeÉjŒæ‹DDDÈþúÊ•+”3gN*]º4%OžÜ\Ä–}+1â½ríÚ5¿|çÎ[<Ç©ü–±:“ãyD§O¥Ó'ÑÇ(_Ââ÷„լǺ>ŒÒ¹3'èØ‘ý”:uZ*T¸8eÌ”%Ö<[íI_ºt)õèу8àÆÛ;ï¼Cß|óLà ³ÿþ4|øp·2P~¾ÿþ{jÒ¤‰[úÍ›7©E‹´xñb·ôÖ­[Óĉm¨90Øýû÷iÔ¨Qôî»ïJœãÆ£·ÞzË ³]Vc ´ÍíÀùûÚ_é«Á}èıƒn—kÖª3½7`˜Lƒ’ôíðA4c²ëÞUÓ¤MO?ÿŽ^¬ÙH%‰ ®·hÔWèÇéßiØAÙ>E5ë5sK·ãÀjŒž°cÆ iýúõ~ÙFÛµkW¿e¬ÌäxwnÛHÿéß…ŽÚëÆê3Ã(ü‹ñ”#W^·t¶o¦}Ú eð¨ûz†Sûθ¥Y}À¢,-\¸pƒ‚j×®MÕ«W`jÚ°a¥H‘ÂÀ€rP” 5nÜX|u§¡éÓ§Ë/¹¦M›ÒÎ;©T©R²¬6­[µ„â:²”úÁI”2¾ûœË—/S–,Y¨J•*´fÍ3ä8ï;åy—_~™–/_.Gí§L™âöA NÂè >LË”)#³`ò€iÇ@É)}ÎÒE?ˆ—v”=gZ¸rŸÑï@ÁhRË…qõÖórt;PŒ÷oŠ>ç|ü÷9QZºhÕnð}û,U®\Yæ-X°@n¹ÿ¸0‚ï.]ºÐŽ;䈛¿)€„Š16çÆøë/sŒ/1Šø(þd99Ì‹9rE9sç#|á€ÎŠ9tEEBKÑ+MÛÒ1"U¤XiYäÆõkªhàÛm–¸0*Æ#Ξ’Šðuê®’mÝr>¶ñs1.Œ˜~ƒ¢;v¬eŠ’(>³¸0úºàÈ‘#eVÛ¶mƒR”|Õë/ëy¼$F–@¥ŸªèÖï¤L™Ú`³/vFLÁš´ìè¦5}-Ss°eâ"Ë•¥+VH^;vì4ÏW¯^•çªÑ#¬ZµJ¦A ó$¥,<ènsâYΪc.ŒàoôèÑÒ¨Ò*^ƒ­‡£7ž¼µ¹·rV¦mùý7Y]£æíƒ®öæu×½š)SV¿u\»z™ÔÃR¤„ß²~3ìï¸1Žâ²èÓï+z<£÷©¿x,È´û^µ€å€«à¨FÌÚ·oO?þxÀ|YyFoÞ¥9Ã[Γ0‚çŠÄѾ¹†½µy ¼[vß_Ûå©÷îÝ¥ïF~Jû÷î ÈÛ·„‡[(U«Ñ€*>Ým˜Ügå¾2YâÂxÿþß4ô㞒˾GPál_Äu¯¦L™RB‚ýåܹsÝàáCžcv-öË…ñâÅ‹×ÿû_7|8€ãÌ?ü íJ¡(rFO¾á‘«ðöêÕË3›õ˜ëyÓƒ†L œ¹ólx&jà˜<{-åÊSÀ8æÞáÂXµz=š9åöYaÖ– s‡K"è·_çÓŽ­¿KXUÀE–?*& ÜùñE†xH4hÄ€P0ÄöFð„PC£“&M2¼‘”Ñ3Î[}|ÆøÆäy}»0újsO~8Žat ªÓ°%ÍX°‰6í½E«·] ŽÝúËôoG ¢ ç]Ê¿L0ýÁÛcÁœÉ2%ü‹qâëÛ=MÁbÒ`uWªRC–;î4½Ñ¤2ÁXÜ.â¸(¿ÒìM*Yúi»àx½×½Š3¼À§/ØÓ§OKå¶„GŽ!ôqvÆ;wîHÏ<óŒ´]‚ <çÏŸ/ÓaÏ„gÔâÂèÉûŒ3ä; ³ 4ðÌf=æzÁôŸ›ÖÐÂG}>àuhù¢ˆ÷æATiœ[.Œm;ö!؃ÂI¢ ª…R»fUiê„aœ)\8F‚…;–+K*@$<Üài€/’téÒFÔ¼Ù< ÓuÊ=wذaôÜsÏE2dÈ`ì{ .†Ž ”#G£çFN~ƒ©ÛŒþÚ<ž=FÉ –m»KãkÜ«پˇҫy‡ü…íß³Ã7ðî‡C¥Q¥[q€@pð°ƒ×ÆÈ é—µGŒýÞ­d¨ Ïs8Ž90^Ó‹c†’Fë]zâ`; :9ïUxHÁí¡O¸!KfÏž-ùÛ¸q£T¤b6ÈÂ\ „é6Œäc4 nôè‹aø ²+Œ F âѦǿüòKyc»ƒŠr<óǦÕÔµ]]©H„>Žæ,ÝA Vì%Ø*á#­Ãk/’ñy$ ¶ ÆÌY³Ó”¹ëÄèü4‚âÔ¢MWê-l%1r% „2\d¹²¤Œ²ávk&¼ˆTHO….¦ÊeJ– ƨÎǹÊ3L ÕªˆëylÖº3}Þ]xO‘†ÞPX®\¾@+—¹ õáv&¡ØA\Ç §Å?M—ý4>dwmßD{vý)!µéЋ°\'Y®,!°<(:wî,Iªx€—>ÖŠSd^'ÎsÁ]UFocD Êæ™±ž”(bd`Ie®ÎãÜraÏÀf‘CDü@~ø¡Ürÿqa ¤Í¹1¦Kÿ8ž´˜¾Ô]v0?‰&íŠ:Œ˜‰•Ž;&¡iŒ »…/Dœ3#±Ò¥K'%4Ä[J¬tæ² £~v ÿúÕKç]} bƒ%V:ý/ès®^=cóÅ” ,ª`^1Ö¦ h h h h h h h $@ ÔªUË'×1Ž,åÉ“‡ð…篟µ' àÓHcùaóßÐŽq‰ìGtŽÊŠKoGñÃÌ¿á^ÕýÜ (ëßÐç¨ÞþšEÛ,ù“ŽÎÓÐÐÐÐÐø×K@+Kÿú[@ @K@K@K@K@K@KÀŸ´²äO::OK@K@K@K@K@Kà_/­,ýëo-------ÐÊ’?éè<------½bô†‹‹„Gaô'$–Ï¢… RÆ ÅýHT»vm%4ÀÔ´aÃJ‘"…LÇß/¿üBâ&•#NÍ›7—#D“'O¦PË–-é©§ž¢bÅŠÉò™B¬'qCå“&MJ_ý5M›6H¹5*gÞáÀ–1ÂÖ¡CÚ²e 3‚˜«çÀˆ:…"(ÛºqãÆbÄ" MŸ>]~½7mÚTâ/UªTÌÌYT‚c ÷µE0üV³fåÏÔ»sSY湪5陊a”R|EïܶÑmÔ`ýêeôãôoåˆS:M(mÚô´hÞ4:qì õïý…–(CC¢ ß»IC?y×/Ü™íi°­ú3#A쨾ɜƹÏüb¢B… òÄH°æË—O>‹Û¶mã„­nŒâ›2gÎíZHX´h‘L·sôŒ#Fñ~Ä(h•*U¨Q£Fræå«¯¾¢•+WR›6mè·ß~ó*ŽDŽ>çøÑôfó$»Uª×¥JÏ¿D‡3ÇÓØSŽy¨^ãÖp\uúÓ¤¨æ|ýúõ(ŒˆÚ£¾ùæ¿ÕïØ±#jüøñr$EÄùÙ³g—ç=Z%GýüóÏ2 yçÏŸ7Ò7nÜ(Óq½3gÎéì8#xüzöìi|éÅÇÈW; %0êÓO?Âè‹¢ãǸ?þøc•ðÖ)íÈ}(Hõd‰ñ·¿zëù(Œ ážzoÀ0¿__3nŽê7xLÔÆ=7Œr8#E8ÿýðFºùšïô,óŸ«-·v,qÝ«búFâ/Ÿ@›)ÆòN¹WÁh5$Î-ZDݽ{7FÞc[ÀI½ñŒçTõ·ÂäÁ[‘Óœ‚Q|pJ,¤Áí}ºoß>#ž“`È)}N­úÍ%–Ú ^‹Ú¼ï¶Ñuíõ‰LLj“9ÝÜGÅ´¯0ú“åÞ³fÍ2´[1Å&îEß„¯Œ¢˜çýaT¦LyÒÕ«W“§N*÷ûöíKÙ²e3ÒŸ}öYìòxÁ‚F:çFðÜ¥K±}Š!' ¿usa|úé§©ÿþbÔ"­q}Ø~Ô¯__ ÅÉHçÞáÂÈ}Íñ×_ælžzº2‰)6¿—+ZŠ^iÚŽ’&ýgÀY(ZT¤XiyÞëÑí>"Ξ¢Q_õ'”ëÔ#Üoý\™\íÈÅo0õra/SZ¾|¹diìØ±n#ÿÁð—s¸0úâiäÈ‘2«mÛ¶Ò>ËW9+Ó¹0bd ôÜsϹ½O1££H(j—uËÕçìÚ¾YòݤeGzì±Ç EŸ:}ò¨´e22,Þ±\YZ±b…d±cÇŽA± c<1Z$Ï-Y²¤QǪU«ä>†=I)KBË÷Ìb9æÂfÅhš4ªda<€J91zcC)ÆfEØ[9+ÓìÄèë¾¶·º¶üþ›LnÔ¼½·ìÓ®]½Lª“ )R"ZùC>i}ú}Egô>Õí$‹ìlG‹Yuu\׬Y#yhß¾==þøã±æ‡£ Fo¼Âyfâĉ2«[·nÞŠ°¤qa,W®œä÷Ç”æ.8€!4¦á@aaa”!C¹ÏýÇÕç@¥Ïàîì”)sVi:€¼ãGùt€>!q% ^j 1œKááá„9oa()ç÷1úÒK/¹] ,Öû×_ÑÀåÈTÅŠ sÍ hÄbêMîÃsÊ“02B=vF;øävbDǵvíZÉÞ /¼›q*ˉ16÷uœ˜åÉûþÚ.KÞ»w—¾ù)íß»ƒ"oߢÂö¨ZTá¹ên5ï«—/Šç÷Ž´ønä'rdªTÙ ô|˜û"“n^Cÿ·d.•(Užê¾ò:8ãV—]œí ðÚlÒ¤‰´A_ÜfÍšÙ: Ã…q×®]²™àm;bÄù¢E? ›%¼`aëbùçlS.ŒÞx¦ 2ÚJÑðVÎê4.Œh+Øù Ç(9ÓòÑGÉŠÅ‹f(„¹‹ÕP|ÖÇÕçÀ{ Ó½;©Ð.[fżo‘wSx㲑¿9:ä:'«ì•Ãó‹ö mРAò:°W†ØÆ5S ¶M(/\Weú©S§è™gž¡?þøC›ç'ïß¿oœ_;ã ‹¯ëÚ…ž!j8|Ò¤InÞY¾x³*clîk«pø«öJ : ÅWç‚M´iï-Z½íuìÖ_¦;b]8Vîã¯`H1iÛ„ò•ªÔéçϦ7šT¦½»]£ÆH„Ê‘C{é•foRÉÒOËrñõÇÕŽ°«Ã¨'¦PìôéÓôÃ?È/uIJAŸeqaĈ>ý-l—àÏãùóçËt¤áµƒ¸0zòŽÑ\ ³ 4ðÌf=æÄSÕ—ŠOLWàemqõ9m;ö!ØU^¾tzujB ª…R»fUiê„a´)Rû–ï˜5'oûÁjÎb¸Ñ­:|`ÄHˆZ¶l™[žù@tHQÂ@V–Ã×›˜_68?àÒ\\î#Fò„T´¼Ø$8£7>0²de;zbDݪ]‡ æ™ð±SÛ@¼Ý×'¨/ ˜¾”T¾YšþÓF·/.¡4EaÄòÿfâ"·KœbJ'ָ̂ÑÌÞC*þŸ°ç1gµïŒ"<€qOŠNùÞ }”˜ž“éI Ö[Ü }Žê“6ì¾õÙðiQBqŠjѦkTï~_Ead[õQøÍèÔ9±Ù*ŒþnËG–”®˜:ýð?„hÛ  þ¢©"’*æÎAøz×ÎUža8ö$D!â¬ÄѾ¹7ÆC‡ɸ àI©”¿@ø³¢,7F3Þîks>×>ŒAׯEsåvÙúù›çÏ–=õê÷¥¬6/œ£[7H;&|A¾ü\~z&4µüÕ¯æúšÅ—Òº¼Q[žÇýgg; <:;wNí²n¹0fÊ”Iò-”¾hü,XP¦ —óhy \ͼŠ`É2ŽÒÚµsyQ™ó¹÷¹0ÂF „¸…ðîÃlL¡B…héÒ¥$UÊ‘4ij³ƒ8úÅwòä)1àºöþ”„¢DBa¢"ÅÅŠ.Û»'¼8¡¨s㺵\YRnÿ@i&LÇ -\&ÁxЙ—:QÆ…åË——§(oóù0b)¯8sÇ>F^ƒ­“#–À©V­š4ÚoÕª}ù¥ëe,¯ÁžÇ‰ÑOÞîkoå¬L+úÈí(Í„eN?$“räŒî4a.k^ê$™+-{niÔ ÃnóC䊞'ˆ:dÝÚÝŽX‚H‘úTÇ\[.ŒÊãXŒ–EcÁ*AÊ&Z‹¸0šÙ#Øò!Z|ª4—·zŸ £ò ‡ó™R¦LI•*U’I˜N¶ƒ8ú|/žÿ?™`»iÓeðW4Ny–+KÊîÞʃ ªc1H*Bó˜1cš>FxQ`ï¢^¸8†ó”)S°+?läóFÅ60!š5¨k×®*ÙÖ-FŒ„³“Ì⠜Ȯˆó}ø¿ÿoé}E›7¬¤!ƒzÈÃVí{ªdž­¿9:ä:' o5e›„-ìˆÄ b̧Šà’Æ%U:ʉ†(Ì‘«sÚ(±l„QÑGÕº7˜Ey±”ŠQ¯¸!Œ²î8#ø†gÖ¿ÃOyl·JÛ½{w ðdy§`4¯ Ûá¶í·gÏž1û:P jn=6óð(óÛç Û$Ø(‰xKQÂpÛxn>:јãWé(‡(¹õ·6ÎÅóˆõßü]7¾l–¸ú1R-å»N:É>G=“ÖS –œò<‚ÒŸ`'©ì˜€ýQ°ä$ŒÀ€÷ 0áù´Šœ‚ñÛo¿5Ú¶¾¸_ÅǨ[š7OòØÈÁ)}NëïJO[ôKÍ[w‰£×>xÄùë›bÊSýÉšš_ ôf@eèDÐÁàÆT?¼ÍÊÊmÚ´ÉMáQe…gF”˜nC7‚1¥ˆôlÔ‰òxÙ Ï ·r8 £!sçd¢¶âk7Px²¼S0¾ÿþû~ñ§ð‚Lн¯«ê˜~sþì%Û£Ê?[ÕMîXT×Sù¡¤X;έÚÒ˜)KbìŒ` ŽòP¶Ì×t?˜…t9ú,æ­ŒÕó‡­XÃ0NaÐÞNyÁ‹}0Âw(œè‡Äôx”ð>F‘ ÈI…—Ÿq_ ¿ ðx;ÉI±t˜y°Aµ%`oŽQÞðxKsJŸƒ° “Ú¢ƒÁ7VígÌåFoøUÚcØöI0Ä5ãú,è%Ch²Ò@6Jþ†«á¾ ·\,f˜#GJ%ùôG€ùô\¹rù­×_æ<'b4ógžÆè_Š÷j ÷µ]¹Ûv ˆ3G(kHXlŠ»•¹qýª4ÐΑ+¯XÀ8[žùànä`2™x³dÉN)RúÍçFFÞ–Ë¥À3XJýर§:l8RG;ÂYËI`Ñgñ2²dÚØ‰Ï#îW˜8Àè[™?"{ϲNèÂ$˜—ñä9Ðc§aÿW®\!8 b7Þ§Êî7Plª¼“úœî‹%MNŠðw)gî|”:uZÅfœ¶I#OÈ~‹û¢d¾2¬HGTXüb"ܼ… Ž©˜‘Æ/R¤ˆqŸ;\ã“çµ5Æ$ȽHÙ®À·‡e<— ðvµ”©RS¾OxËŠ1-Uª41–á,Àq¯Â8>Œ}ɉ#®…û544Ô×emMçÀ|N"ŒÀeWy9Æ7^«û¬[‰HÞñA–xÇ}M-------. he‰K²º^------D!­,%ŠfÔ ´´´´´´¸$ •%.Éêzµ´´´´´…´²”(šQƒÐÐÐÐÐÐà’€V–¸$«ëÕÐÐÐÐÐHÐÊR¢hF BK@K@K@K@K@K€K1¥Ü¼y3!(›&-------Ä*8¥tZP6« ÑÃAyò䱺jÇÔ§1:¦)âĈnÇ8‰Ï1'ëvtLSĉÝŽqŸcNŽÍ€PŒ#KŽA£ÑÐÐÐÐÐЈ h›¥xº¾¤–€–€–€–€–€–@‘€V–N[iNµ´´´´´âAZYŠ¡ëKj h h h h h $ he)á´•æTK@K@K@K@K@K $ •¥xº¾¤–€–€–€–€–€–@‘€V–N[iNµ´´´´´âAÿ’( @Ý{ÏIEND®B`‚srt-1.4.4/docs/features/images/non-block-aligned-5rx10c.png000066400000000000000000002316371412557703600234020ustar00rootroot00000000000000‰PNG  IHDRJȱ³Å× iCCPICC ProfileH‰•–PTWÇï{Ûm—¥ÃÒ;Ò«ÀÂÒ‹)‚e—+,UÄŒ@D%”PŒ†"± ¢ °g‘  |  ¢ò=$‰ùf¾Ì7ß™9ïýæÜ{Ï=÷ž7óþ"Ùññ±°q¼$¾¯³=cSP0÷àq€bsãí¼½=À?Úâ€VßwtWsýó¼ÿj¢Ü°D7Â;¸‰œ8„»vàÄó“€Ñ+§&ů²Â4>R ÂëW9bW×ÒBטûeŽŸ/ á4ðd6›1‰3R8Hb ÂúÏÃgá‹ñøËøü4~™ BP%X¼\ÂNB¡†ÐI¸M˜",E‰êDk¢1š˜I,!6¯ßH$%’ɇEÚG*!%õ‘&HïÉbd-2‹¼…œL>D®#w‘ï“ßP(5 “LI¢¢4P®QžPÞ Q…ô„\…¸B{…ʄڄF„^ „U…í„· §  Ÿ¾-<'BQa‰°Eöˆ”‰\Y¥Šˆz‰Æ‰æ‹6ŠÞÉ©‰9ŠqŲŪىMRQTe*‹Ê¡î§ÖP¯S§hXš:Í•MË£¡ ÒæÅÅÄÅÄÓÄËÄ/‰ è(ºÝ•K/ Ÿ£Ñ?HÈIØI„I”h–‘X’”‘dJ†IæJ¶HŽJ~bH9JÅH‘j—z,–Ö’ö‘N•>%}]zN†&c%Ñɕ9'ó@–Õ’õ•Ý%[-; » '/ç,/wBîšÜœ<]ž)-_$Y~Vª`£¥P¤pEá9CœaLje”0z󊲊.ŠÉŠ•ŠƒŠËJêJþJYJ-J•‰ÊæÊáÊEÊÝÊó* *ž**M*T ªæª‘ªÇU{U—ÔÔÕÕ¨µ«Í¨Kª»ª§«7©?Ò hØj$hTiÜÕÄjškÆhžÔÒ‚µL´"µÊ´nkÃÚ¦ÚQÚ'µ‡u0::<*q]²®nŠn“î„]ÏC/K¯]ïå:•uÁ뎬ë]÷YßD?V¿Fÿ¡˜›A–A§ÁkC-CŽa™á]#Š‘“Ñ^££WÆÚÆaƧŒï™PMÚ º·¡Ý x¹zõzì­îàý³ÖÇÛ§Ì癯o†oïFêÆí7.úÙûø=ô×ðOöïØÐ°èX(Ø´nÓîMýAÒAQAÁ¸à€àÚà…ÍŽ›mžÚb²%gËØVõ­i[on“Þ»íÒváíìíçC0!!!Ù^ì*öB¨khyè<‡Å9ÎyÁer‹¸³aÖa…aÓáÖá…á3ÖG#f#m#‹#ç¢XQ¥Q¯¢]¢+¢—b¼bêbVbc[âðq!qxb¼^Ïùi;†ãµãsâ – ÇæùîüÚD(qkbG ùù$k$“<‘b“R–ò.5 õ|šh/m`§Ö΃;§ÓÒØ…ÞÅÙÕ¡˜‘™1±ÛnwåhOèžî½Ê{³÷NísÞWŸIÌŒÉü%K?«0ëíþÀýÙrÙû²'¿qþ¦)G(‡Ÿ3~Àê@Å·èo£¾ 586 456 |å @IDATxì]|EôRB/!t¤wÞ éÒ¥ƒ EPPà£PªtD¥„Žˆé]¤ „ JI¨Pýæ?—=ö.wáÊÎÞ…Ìûýîvwvvöýßìξ™yïM¢ÿ‘$))))))))XH+E&H H H H H H H H p HEI>RRRRRRRV$ %+‚‘ÉRRRRRRRRQ’Ï€”€”€”€”€”€”€”€ $µ’Γ7oØÀ·Ù3gŽ+[¼>w9äQâÄ”¯@Þx#.惃‚)Q"‰1.Ňs ¡¯âYMDóä‰Uâ²ÍqHlnw‘ò>zêøíÐÛóJÁø>Ãï†SÒÄÉÈÇÇÇê3–(.¯7(Jw#"¬^,OH H H H H H H Äw ôîÝÛ*„8G”0’E©]¹òV ˆï'NGERÀ¥kT§eµøÅ*ÿ7/…ÒåóW%F«Š'B=†¹Cç¯\“mNüx$­r™žU‰ÑjõÇ«wƒ"éÂÙ€8y–6JqŠGž”HÈŠRB®}‰]J@J@J@J@J@J N HE)NñÈ“RRRRRR YRQJȵ/±K H H H H H Ä)©(Å)yRJ@J@J@J@J@J !K N¯7Gsëî]zùêU¬K“&IB¹³g•Ž„'OŸÒé€JÌâ§”*R„Ò¦Nm1Ÿ½y­âä‰;·B镌IÆœ¹sX,=êI?{‘…lJLÅJ¡4iÓX̧$Þ»{nß ¥ü=)}†tJ²n[‘Ÿ=‹¦‹ÿЃÈ”=g6†1?¥J•R7lÊDbD}_ºH÷ƬÙ<¨@a/JÆús­ð¤õV$F5¯7oÜ¢'ŸPæ,™ÈƒáÕ“Dµ9á÷ïó¶É´ehÓô"=ê1ü^Ý¡GQ–¬Y¨hñ”4™æŸ«"ñÁýôøÑ«÷ĉ¬Ù=(EŠqæÑꤌ o¯_¿¡ë!„wñ ÛÏç•—òå×?> HŒoÞ¼aßÅ;tõò5J•:,âE3eTD l«é[°ÿøqòé÷¹Ufƒvì Œ™Œç£˜‚ÔmäÚ~ð 1 ;í¼½iîÿFS²¤oÙ³'¯Ia=pŒº´ìiµÔ#÷ñ…’áiÔSÜëkÚ»s¿’Ä·ÍÛúï¬ñ±¢×¯^ÓòŸW‘ï¨ïy¾‰3ÆPÛέM®} #¤Y¾shéÂ_M @iœ<{<5öih’.ò@ƧÏhê„™´â'?öqÂôÑÔ´¥·IºÈQÍyÞ¿ë õlgxï¡(áЋDµ9Ï¢£©@ÃVa¬˜:•|j×±z^Ë¢ëI£¾£»™°¿€'m?ºÅ$MÔ(Œ}; ¤“ŸŠ“í1SFPÇíãÌ£ÅIQÁÛÉã§iÔ—c)èÒV«Ö¨LSæL¤¹,R˜dÖà@$ÆÓÿœ¥¡}‡3e𦠧ƒ¿ý‚ú éc’¦õÁ[MDƒ’£¢ŸñR<2e¢J%Kš”˜”þØ¿‘ +¤ dQ÷þu€+IJ}§eX7úm¦kW‚iHŸáT¼d1ò*”?^cT3ÿüùs7|’:I×}QmNô‹FMjÕ2î+;…óy*»Â·¢žU0ŽÑ‡V Ûów°AªÿqþQ `mÑ»bËh \Æ: kÒ3Xdu÷ö½<=i²dÏk( #F{°ï ÚÑ UÊQƒ&õøÌÅÏó–ÑÖ±Ö-ÿm‰Öp,–' ã5¶ºDÛ;ò{ÖmT›jÔ©FAä·l-Íš<—ÍLd§–í>±È“‰š*J C½Ú´¡á={)‡·{ŽãJ”ª]K—Q–Œ†á³ÆÕkP½n]iÍ6?p eÏ’…ìÉkñfÑù⫾q–|hß®$A©Z·}eÊlÀX§a-jÓ¸AI6fˆqªÂ§V+^^×>éÊ嫱zxqÞLÀI­1bÚ·¾]ø°)Xî=°Õ.kIÚ±u—nŠ’".­1zô¤I3Dz—¶9%Ij˜šéÄž•ú½)"<’7\z)J¢0*åbûË¢•¼‡W»AÍX£¦ê|¢÷µns~«•-K«¦MW]ºÕúY˜ÿ Ç?°MZ|LßÏDÉ’ë£4X¤Öû ²<úN©¢(Õk\Û;BÒµÆxxßQ^‡0cX¾i‰±Ý©Y·:5ªêCÇççßeî¡%X­1ζ€³÷I›¦ôý<_¾Ì`ê2}â4oÚBjñi3cº–XPVb­ ´µ<¿­[yÖA»•$$T(Q‚ª”6ŒÔøï3 áÛ“×Öûë‘ï·5¿óÛôÐݨ$!¡tù’T®RY~î¯m{ø»·£-û6Ј‰ÃL¦ïŒÜpÇŒ%Ê|ȇH1·¬tô@·Cnó­»ýÙƒ½ò6Z+`AU”Ù¥?zÌ·îögF…wØ"LcÓŒÀØÝ)¾¶#öÈÕžzĔۡ½Gxñã§1S+I¶â´£µ2ýq%?Õ²}sne-Ÿ«ÒíÁÁF”@å*–1iwR¤Lad³îFö`Ä´¨}·OM”!´µ LÇÁvI¹LQ:xòÇ„›9)ŠÒ,XËÈž¼æe¹òš<áæT¾²÷õ«×§Æ|7’ð¡Od/FKØ=|Ä“3e1Œ¸YÊãÊ4g1 TyÑ )èJ(VïíÆïÆLãåòý†Mo|`µlw9_Û{ägO=?bhƒÛtlib2`Ïý\‘׌–øƒ£Ìº•ù©Î=;XÊâò4{0/UŒóë¿y;·UÂŒžž¿Œ§Wþ¨"¥M÷Ö$„'ºÁŸ=»¤ Ò›pŽY˜9€`Þ Š„L½­Ý¶ÎRr6÷[0o>>BT·J#h·÷"#ùqάYéÊŽâ¯{ò*×ë±Ý²~+÷Ü‚]Rþù¨S|ª×®f¼5øÆT C¢æ”3Ƹ.2â¾ù)·9Ö#­Žžä˜+U«¨;vÑH¡^ŸG?§Ëlý‡)sùÐw™ ¥STz“ŒhäÐ0—,[‚š·mFwïÜÕ–Éý´lsÔŸ½t‰:Æm>ò0O·’… S‹ú xۦΧǾÖõxéb g;óª]¶h9úû4naø[‰}\[°zU¦õÀ‡{hÑß«]Ï“1ªÿaŒ’a)Ÿ¨4­1BòiåM[6ø3G¨ÎÔwp/ 8‰O…Ãìc"3Л´Æeʼ¥áA¬&xÙâÜ»¼Õר»¯©¢”*…ÁÅ;èÆ ÂOMõªV%?6ן‚)¿~H“êí4Œ’?UJC9£¢ìÊ«\/r›2†7h¯ælºÑ‚å³ ÊÓ£‡o1¦²î eÌô\ªÝôÄC<åzû.µjÐŽþ=}>.¶4=' £ß/k¹2ÂV”*WRSží-LD›R³\àŸÛ)x×n =xˆü·ÑR__‚óIð­[Ô{Ìh{Yu8¿¨z|ÎB €0*¸lýbú÷Ö ºpû-øu6O‡ýÒî?÷ò}Ñ¢0šóýdž­FÃg||õ$‘1»~ÅFN…Ú7íÂã¹)Ç¢·¢0öÔƒ›¥`–ÐÚåq‡¨Ås–!a€Biª(!˜"¦ÒÒ§MK‚ÛÿÀNiTßÏ9ÿ[÷íåÛtiÞ[|ô$öˆ b˜€<2e&{òò‹ÿ#FA1$Ø£W£1ëNÿÝœƒ´éÞb´4$­„ðÈ,˜cû‹×ã…séóÎ9sßNøš"ÚÏ©ãWˆÄï¾Ñ“Gpï ôÈžÛe|ÉõüŠO';ιíWŠÀˆ)Å“fsî/G˜ŽšÙΙv9Që6Gá.[æÌ”1}zJÅ‚æððà!L~™2…Ÿ>~îE>|¨dºQ`8EÌè8¼…ªÕªÂGÂ`² #gÐYB•ˆÂÈAÄüaJü§¹Ëø<‹õžV…ñèÁ¿©kë^ÜÔ1“þ<ò;í>ñ'aJ´vMºLô Q³°ïäÆ«Ù¨üTêÔ¦.½:ÒÈIÃi=ó&Wìz3g÷-ÕTQ²Vcb*¡'‚0Ñ3…†‡ó­úïÚÍ›ü •=yÕeè½_¦¼ÁSqI@àóà Ké`F7¾V¯_»Áã~7Bàç.¤F5ž¬Ù³ò—i˜K·ô<¨ó‹ÞwãÉc§x1[ª«E…=JòŸâ=>¤}GPVÑøP¾3mN\ü•+VÜxúnD„qß;ÎÔ#øÍðAzÎö} v’ÊtÍ Y=±:‹QÍ+£*æ­bAõyWí;‹qƤ8ë˜G,!ÌfäΛ‹~^»E®.ÀßW„¢q%9‹¼Ã+Ó»yc:j0Áä³Þ¨PтƳÂl_é¢(ݸmpû.V €G™¢Eùþ¾ãÓ”%R·âýfO^¥ ½··B R¡¢o1~XÚШÞ,;J¤nÇÒ#\Ê;7ïÎ{?ˆ‰1,fÚÒ]d FKXÒ¨–/I¢Š8o)¯è4g0b¹LÕ˜ÿ”^xǹ<žúØïX“•³m޵rCBC§òZY’ɘAðŽ3õÖ ±(èïÃÿð­úOéðåÌeyI&u^‘ûÎbTó¶tÁ¯üaX2X B©Î¯×¾³Úb% ßT…oLE•­`èÀÃóÖ•ä,Fk¼oŠ Áƒä"=û4U”¶ØOXjDM!wîÐøóyRíJ•Œ§Ú4nÌ÷¯[G7UÏŠ-¿sCpŒ8Õ¬hð‚²'¯ñ‚v¤ Ë’¨ ñ0ªV³ªñ”O«&|åÏ~&16øýÆ{6qªZ½²1¿»ìˆÂˆÑ†®­zñáàF> hÊì‰.3…qå’Õ„ž+†ùzÁ¢<ÿ3—Ži[e¤Q9/j+#l’60Qóߢ•£|`Ã9,½£‰jsŽœ>MXCNMOŸ=£3gð$ŒX¥±à¤¡Î¯Õ¾ˆzoðlÁ¾EùØâÓâ›VoÆ.U©þ¶Íæ ‚þDaTØ&D©AQr‰ÂˆQ#Ðú•›L`Áäž© ¯B^&çDˆÂóXP‡Y Í±Ã&r(0IIµ*!ÿÛ ‹û¸FMÊ™-+…±¡é-{öð´¢^^4´k7ãíšÔªMH ¸z•*Ú–pùðí<|˜ç™öõ0㢓öä5Þ@À–kP¼™$½{aá´“E”áí3¸§ñÎXiX§I|y,’Š)h4‹›¤ž'É–/Ay %ÆÉ²E+H Jùõè/ùP#Ï èO$ÆøÙ8ôxá2µnÔ>Ši ¦ÄrÿŒ•ÉÉ‘ÑPÀ¨Ïl0zt˜Eh‡­h⌱|+úO$FѼÛZ¾È6gìÜ9|å€åËS!OOÖ|F°±„hþh}A‘õˆi ,wö ËCÀùà%û ÁÛ „øJ+šDbTxÿuñ*¾ ç E±PÎ鱉± Æ|=Ö¯ÚÄ•ÞêlyØnß²“CƒmïzÂaŠÄ8Ãw6WÞk±ð*iҦ抽²Ô:׬W](¾$cY»Ã­à`Â<|‰ïv/ÄJÚÑ/žÓ¿—/Ó…+Atê d×§e½®þ;Ò¼Qÿ31ÌFŒ¥V Ràµ`žÿ|ÐeºB¥Ù”ÜÌo¾¥fuëÙ²'¯ñ"wB_>£pæ&›¿Ø»§ ’²†€óxñ2cLˆn‹ÈÄp5Å"·j#n }6aë»] ¼Æó#l>ìs»cüÔÑ|½35›XÔq"‚YJ¬ÂcüšµnÂÖa²(üQÄмwßåú›N0†‚ïÝ õkÕ¡_Í›g²ãÏ]0"¦V(‹)tþìE>wŽú|ò8ŠOGM›ïK©bmÙguŒ–ø~Ɖ%ljv/Ýœp»Žºû„î±gÃÕmÂ\ ¢ÓtêâEBû„5'›×¯ÏÜÌ—Ï’lJs§6§ûÀD†ßç£Hh×÷ £‚‡õ£/Gb¶–o=”m“ÉžUŒèèfèÄ£ÝÍãiPhKyÝcI¦Ì¢c†Q3D§†gí•À«œe(¿“gO`Ëd9f ë.ñ½À4ÚÓ³'ÏQXh)3öû‘ܰ[íen©®âJ‹Š|Æ¿CåY§È%bÃYÿY;ylï^:ÃG¶+g½ókQ ´°XIYX´^Œƒfó|êc„¸~ûeÏœåÃÙöäUßÃÚþé¨H ¸tê´¬f-K¬t`„AîÃ(kTl™Jyýê5…ܸIYYp¬Ô*{•X… H¸y)”.Ÿ¿*1šÉVd=F³`“˜Œ)×ðÆHÁ<§œ%w¯GĨA‡É™¥0ÂÎÜ¡óW®¹M›sÿÑ#>2žšyˆåÌ–-jí¼µ‚;¶9x^ï°lz¦èÚÒž½ëYv·gø@)UËz¼ ûλFð‹oR8›•€½<ÅÔ3ïÂcé¼;aÄ7ôö­;ôâù ¾Æ›z),K¼Ûšv7(’/Ý»wo«—h6õ¦Üš\iñ³•иȓצìö䵩@2#<™ð³•ðÀzz9Þ µõ>Zå“-KÒÖzDƒŒ0®&=ëá2\A"Û„ÀÏÕ$ºñ¼æ/èéR˜"1j© 9#$‘ÁFtoFgøtæZQÑö*Ë•8ß#×:ß=rä®ò)))))))x ©(ŃJ’,J H H H H H ¸FRQrÜå]¥¤¤¤¤¤â¤¢*I²(% % % % % %à HEÉ5r—w•ˆŠR<¨$É¢”€”€”€”€”€”€k$ %×È]ÞUJ@J@J@J@J@J H ΀“+—/§(¶¾‘$)))))))÷UœDà(-Ë ÄWáÞ £¯^½×/…‡ñ5œlY¦%¾ÖãÍ ;c|­<ß7/³z|ÏßGÙæ¨*<ïÊ6'WžŠõ°ŠzbºÐ½ê4ß32wܹù&%rØ¿¾˜ùÜõ81[íìõ`¦(½¿_¥OÁ—iñ´a=;w­§wñ•4q2¾L‹Äø.I¹÷ùÔ/’ò%LÞç÷Q¶9îý ÚÊlsl•”{çK•, _Â$..¥R\ґ礤¤4–ÀduyMï$‹“ÐBRQÒBв ))))))÷RRQz/«U‚’pW $"ƒí§»ò'ù’0•€T”Lå!¤¤¤„J@N½ ¯,\J@s HEIs‘Ê¥¤¤¬K@Ž(Y—<#%àŽˆÓë͆oݽËÝ{ͯMš$ åΞÝ<ÙxN7CC©P¾|”!]:cºùΓ§Oét@%f¡ J)BiS§6Ï"üX4F°U¢ÀÞ¹J¯˜›¶9%aõ˜3·uÁ{wïÑ훡”¿ '¥Ï`¹Ÿ=‹¦‹ÿЃÈ”=g6–7?¥J•ÒüVÂEbŒzE—.Ò}†1k6*PØ‹R§ÑÿY‰Q]A7oÜ¢'ŸPæ,™ÈƒáÕ“D½á÷ïÚK„¶ mš^$ £šÿ°ÈHºvó&=xôˆ²eÎL*DÉ’jþ‰PßÒd_ijúàþzüè‰É}̲f÷ )R˜' 9Qaôõë7r=„ð.¾aûù¼òR¾üy•ÓºmEb|óæ û¾Ü¡«—¯QªÔ©¨`/ʘ)£plš¾û'Ÿ~Ÿ[e:hÇòȘÉäü«×¯éǵkèÛ3xúì‘#鳿-Lòà Š5XÝFŽ íšœkçíMsÿ7Z·Z$F³U&BÐøàècÔ¥eO«¥¹¸Õ^¿zMË^E¾£¾çÉgŒ¡¶[«³¤Y¾shéÂ_MÒÓ¤MC“g§Æ> MÒEÃøôM0“VüägÂ>0N˜>šš¶ô6Iy £9Ïûw¤ží ï=%<z‘¨÷ñYt4hØÀ*ŒS§’Oí:VÏkyBF…ÇÀà`úfÆtÚu䈒ķóæ¥6š¤‰:õ¬öí4Nþ}*N¶ÇLA{´3'Eao'Ÿ¦Q_Ž¥ KWLX­Z£2M™3‘rä²>Har“"1žþç, í;œ)ƒ7M¸üíÔoH“4­4U”¢¢ Q¼=2e¢J%KšðšOe×î­½6J¢0‚ñë·oSϺð‘³lÉ›aÍ-ýËÚæ³—.ÙÍÑ D=«uÖ¤2f°ÈÖîí{yzÒdÉ,ž×:QÆð{Ô£m_ÞŽV¨RŽ4©G‰'¦Ÿç-£#¬Ó;¬ÿZþÛ­áX,OÆkAÁÔöãŽüžuÕ¦uªQ`@ù-[K³&Ïe3Ù©e»O,ò¤E¢¦Š’ÂP¯6mhxÏ^Ê¡Õmµ-¾_ût)øZ¬rážcǸ’l×Òe”%£a¨­qõT¯[WZ³ÍŸÆHÙ³dQ.¾Õ#¶UÂÁÅܽ¬/¾êûÎÛùÔjÅótíÓ™®\¾Jv²x †¡ýwëÛ…›"Sï=¨vYÃHÒŽ­»tS”µÆèUГ&ÍË^Úæ”$©aj¦“cýŠÞÉ.½%Q•r±ýeÑJÞëݠ&íݹ_}J×}ï#T+[–VM›®)Gm”D`ä;‰+I­6¤…cÇQr”kÕú}ì3ÈòÈ8:wŠ¢T¯qmkìI×ãá}G¹’3†å›–Ûšu«S£ª>tìÐq~T½HkŒs§-à¬Ò¦)}?Ï—”UC`2}â4oÚBjñi3cºÖ8k] =åõl݆¯ò£ÉC†°)9ëóŒ~[·òbuîbT’P¡D ªRÚ0âá¿O¿á~Q¦­ò°çþzäíØ½mÙ·FLkJN}ÿe>äC¤˜[V:z Û!·ùÖÿlÅX¤xajÓ©•±±4PEKá°?zìŽð8O¶bTÀa›f>(Àñâë;flmň)7tBA³¾ár%ÉŒö>«æeÿúãJžÔ²}sÊ’U¿¶9qÛŠ1‚(ÊU,cÒî¤H™ÂXü˜vqC²#¦Ý@í»}j¢ ¡­a:¶K¢HȈ’­ÌN>ܦ¬Ožàùл3'(J˜’»rÃü”[ÛŠÌÚ“×-ÀÅ01滑N±óèá#~}¦,Ö•e§n ÁÅÎ`„A©ò¢*RPnÄa/ÆïÆL㌌òý†Mo| †)Ku‡wÌÞ©7{E`+ÆÃ§Nò¢»|ò ¥Ok:nï=õÎoﳪæ'ëVl¯:÷ì >åVû¶b,^ªçÛóvêÒ§W˜`ôüóüe<½òG)m:÷¬_[1*vI2¤7©£L™3Ìpæ ¹òä49¯ÕEií¶mt.0÷P æÍÇG}êV©âÏЄï1o PάYc•¡xÒÁCEOÒ£ž|Ûs¯-ë·rï4Ø%å/ÊU.KÕkW³§ˆwæE£õÏQCƒ]©ZÅwæ×:ƒŒh¤"#îÓóèçt™Í£ÿ0e.ú.S¡aŠJoÃùh˜K–-AÍÛ6£»wîê Ëä~¢ÞGØét>ŒÛ|äažn% ¦õ¸dôEkŒçƒ‚¸ áe<ßo;{–"X;š‹Ù(U/_Ú7i¢«g˜ñ¬š<(ì`õ¯ëyR¹JeéÃ%Ã<Èc­1BòiåM[6øS;ïÎÔwp/ 8‰O…ÃÎt"3Л´Æ¨(C𖆱šàe Eé]ÞêkìÝ×TQJ•Âàâtã᧦zU«’›ëOÁ>ºöÐÃÇo§*Ò¤z;e£”‘*¥áž£¢”$¡[…2ì@á)cd ?5Õ¨û-X>›u«Ó݇!(OªVÓ1eÚ‘{‹Ä[¾˜°ûY‹§™ ›dp ã«—¯ŒÜð‚c…«HÔû˜"ÆVá~ß½Ûžï¢E´iÎ\òÊ“Ç$]Ô(Œp–ç¬X‹u8Élر6ΞÕÄX4Nõ¬š³ ÏÛ%óáÉÝ?ïb~Zè±HŒßÏõ¥œl4eѬŸhá¬ÅFëwø e1ÞDµ# c½ÆuhÙ¢å4iäw<ÌJQfâp/,‚vlýËèÕø†yЋ"M[¸š*ÐÅ­þ²g/…8Hg~ÛL â¼ÃõtúÒ¥vãPŒ¶p!\ç]M"0º“ùý«Ô¨DûÏüE'®¡37ŽÓ®¶Ñð±Cy6j/œùöE4¿Öžã]Ì NŸ…:LáGŽ×a¿ä¿¿£lÛu¨gÕœ‰?6l5>ãã«'‰ÄˆÞõ+ Ó‰èt*Ô¾iÏM9½…±÷ ûO8Ä Z»\#jÓ¸-ž³Ä 3¢HSE .‰˜Ã|7FzàÊ?°SgÕ÷sÎÿÖ}{íÆ‘.Í[KýGOžÄºñN@™2Ç:'"AF|:S&0baÞ=úw5ìîô7ía;r¯ ç.ÒçòK¿ð5ŸWw¤G¯‰Þ}£'àÞ?¯YHÏí2¾äƒz~ÅÂYècX)#¦gLšÍ ¸¿a:jæh]8sÈ÷A3¦OO©X0Â<,É/S¦pvŸ;G‘:úÍ׊˜2&Èbû&M©v¥J|´&¡£çï]¡[lñŽŒ"žUó[bJü§¹Ëx2LûŽÿm  ¤DêVÂ(ézoŨ7¿ŽÜïVˆAA*TÔ±zÄ=áRÞ¹ywÞûALŒa1SzŽð#â-0Zâ+jù’$:. a‰g0b¹p›Ó‹ç/Œ ÎçñÔÇ~ÇœåXÔû–YR(oK2)yDnÅXÌË‹³w(Æ«XÍ+Q‚gõ9=÷yVÍù\ºàWž—ô V‚Pš_£Ç±³Úb%‹š°‹©¨²JsE ž·®$g1Zã}Óšßù)tÌDzöi:õ¶íÀ~¾ÔˆTÈ;4~Á|ž„á]G¨MãÆü²ÅëÖñõà”2VlùcÄ©fE}<¦DaT0¹ÃØžF=5a1Ž0åªV³ªÉ9[0ÚеU/>ÜȧM™=Ñe†À¢0®\²šÐsÅ0¿B/X”çcæÒ1¥ O=HFØ$m`¢æ¿E+ FùÀ†sXÂFõ>9}š°¾ššž>{F#fÎàIÍI£Ó:“¢0³ tàÄ ÂT¢Bðô[õÇüöQzˆgUÍ7¦ú¥EÉ$ cÁ"†Žëú•›L`Á ž© ¯B¥Ø$ƒ€Qá@bn²€@›c‡Mä(`"’4QBÈÿv,p$èã5)g¶¬Ɔ¦·ìÙÃÓŠ²ÞËЮ¦CØò%¾:y’oç³eI¶Æ7` ¡×Ó¤VmÂõW¯RåOÛòãȇø(.šöõ0]ÜXEb[å¼¢KR([‰û½°pÚÉ¢fƒðRöÜÓäö#Ùò%È:~äß.[´‚þÚf¨û¯GI…ذè?ülú¼p™Z7jÏóªÿ¦-˜ËýS}^‹}‘ÑPÀàrƒFï;•Cð>qÆX- ¼³ ‘ßys2ˆ|ÇÎÃã³Õ(_ž yz²à3Ö.í5.’;´>Š HŒh[}êÔámtýîݘk¹7_Ð|[“„øJeUS¢ªUgõ×Å«8ûp®P Qx,•+#bAùz­_µ‰Gá®Î–÷€-áö-;9+°1mà]Ï[š¦‰Ä8Ãw6mZ½™j±ð*iÒ¦æqé”%³z èN5ëU׋yaš)J)Ù0ßàÏ>£ŸØ¨z@ ¥e½®^mÛÒ`U[qåWÎm;pÀ#IIƒ2„hHŒb…2¶ý¸˜úÇËÆ’% ÒlJK¥XZ‹‰gÐøO$F°j«<4†eRVÑî=°;­\²Æâ}¹Svlž»;7ðV_å£EjÂâŒÊ}c«dÉß>nŠ­€úìÃ}W4‰Ä8hx9£J° PÓQ_ÿo0UaCÄzHŒ–øO³\ ìô"‘ïc¯6m ±Ù0Ú‚ŸBÍë×§1ýúë@$F`Z0f,7Xÿuóf¾n&Ò0B?°sgêß¡#…“èg#ºý~ã8zôë*¥ˆÄؾk[Âû7gên‰õÏ‚çí€aýb­Ï©œ×r+#â]ý4w©±Á7¼û}ÓŸ7k¨% ‹e%bÃYÿY<ÃíÝKgXàÈvåÊ[Ë+ÅÁèú‹”…EëõȨí4B\¿}‹²g΢ÉÐ÷6¢uöz°[aŒ%T'NGERÀ¥kT§e5›KB=ÂèøáƒG”‰M§è5]d3ƒfo^ ¥Ë篺 Æèèç<#¼ˆàFÄYr7Œæx ä&M’„]šŸ³õ8ìÌ:åšÛ¼÷=â#ã©™oNˆ1 s.q–ÎG„Ó¹ë×Ý#ð|@¹²f£BùòQª”)cåSž<}J§(q¢DTªHJ›:µrJ·­hŒN7CC¹<2¤Kç¶D”ÈîëEb´·ÎífÞÆ îÜ ¥WžÕ$ìYÍ™;G¬Rž=‹¦‹ÿЃÈ”=g6Ê_0?¥JeùYzE—.Ò}–7k6*PØ‹R§ÑÿY‰Q- ›7nÑ“ÇO(s–LäÁðêI¢žÕðû÷ í%B[†6Ír·÷QÁI×nÞ¤Q¶Ì™éÃB…(YRÍ?ÊíbmEÔcäÇôèÉ“X÷R'd÷ð ”É“«“„í‹|_¿~C!,žÞÅ7l?ŸW^Ê—?¯0,Ö ‰ñÍ›7tûæºzù¥JŠ ñ¢Œ™2ZcE³tMß‚ýÇ“O¿Ï­2´cydÌÄÏãc9aÁ|š·j•I~(>óG¡OêÕ3Ib V·‘#hûÁƒ&éí¼½iîÿFëöB‹Ä`¯^¿¦×®¡ogÌà8gIŸ5oa‚Yô(ŒöÖ¹HœG£.-{Z½Å‘‹ûøG  ÍòCKþj’?MÚ44yöxjìÓИþìé3š:a&­øÉϘ†ä0}45mém’.ò@Fsž÷ï:H=ÛÞ{(J^$òY-аU+¦N%ŸÚu¬ž×ò„(Œ ÁÁôÍŒé´ëÈ%‰o æÍK'6l4Iu cû¡Cèè™3q²=mØ0êÕ¦mœy´8)ò}ÐTQŠŠ6§ôÈ”‰*•,iÂkòdÉ)}š´Æ4ÿýû¹’ŨYݺ|i¿?ïÁuùf8ö[M,Èó¿xù’ZÀxÏ\¹¨eƒ†”$IbZàçG«Ù5ÿ1ï÷Ç7–-rGFð|þòeúbâ:yႦìuG…Ñž:×T {Ê>ìe+–6É‘œõ.Ó¥û¬îþsW’ ì4lZŸ"m^÷aÔh`÷¡ôÇþT¸X!^ÆÞ¿p%)O¾ÜäÝ¢1¥e×lôÛL×®Ó>éxÉbäU(¿ÉýDˆÂ¨æ÷ùóç4nø$u’®û¢žÕè/Œ8šÔªeÜWv çóTvíÞºËûƯ߾Mu>ëÂÛÝlÉ›aÍ-ýËÚ¢³—.ÙÍÑ DÕc£êÕ)cú ÙÚv`?O×kÔLÔû~/‚z´íËÛ£ UÊQƒ&õ(qâÄôó¼et„u‡õAË[bQZ'ŠÂx-(˜Ú~Ü‘³[·QmªQ§‘ß²µ4kò\6ŸZ¶ûDk8Æò4U””R{µiCÃ{öR-n½òä¦Q}?§þ:PêT©xž!Ÿu¥}šòý?öî1*J{ŽãJ°]K—Q–Œ†¡¶ÆÕkP½n]iÍ6?p eÏ’Åâ½D$jc~wÙ±#xîÙº ^åG“‡ aS”ÚÍ»:ba« íÁhO^[ï¯G¾e>äC»˜W/(z6 Û!·ùEЦ6Z+¤¡*Z¢véñ£Ç|ënö`Tx‡-Â46Í|P$ÝÜåùs—÷Snè„‚f};ÂåJ’­Ïõ¸pÍj~»Ž>>ÜËÖ{ë•Ïž÷1‚(ÊU,cÒî¤H™ÂÈî˜vq3²#¦Ý@í»}j¢ ¡­a:¶K¢ÈeŠ’5@Ÿ>$™UŠÂÁ“'xvôîÌIQ”®„Ü0?å¶Ç–0‚ÙéÇsJ·eÜƬa´T„=y-]華Gñ[gÊ·RûàþR^ôBE ÓÉ®âÙÞûÆ…ñ»1Óxq£|¿¡2~`oÑn“?¾>öÐÆÃ§Nò"º|ò ¥OûvªÙžrÝ)¯%Œ–øƒ£Ì¯›7óS}Ú~j)‹Û¦Yz‹—*Æùõß¼`«‚ÑóÏó—ñýÊU¤´éâOýZ¨Ø%eÈžcRþ2eÎH0sÁ¼A ™z[»mcžlÆ-˜7õ©[¥Ê;1à>|êÏW½\y¾…&|yc€rfÍÊ·ê?Å“*z’–õäÛž{éÑRÛã³y·¬ßÊ=Ù`—”¿@>*W¹,U¯]íÅÞ»{þ9jøÐTªVÑ$?©Èˆûô<ú9]fóè?L™Ë‡¾ËT(E˜¢Ò›D`Äp>æ’eKPó¶Íèî»zÃ2¹Ÿ¨gv:‡ã6y؈wÉÂ…©Eý.}Ñãù  .CxÕÎ÷[EÇΞ¥ÖŽæb6JÕËW öMš8ìÙgR9vhÑÒ­—mÚÈ“ÑÉ.]´¨¥,BÓ´~¡ù´ò¦-ü©wgê;¸œ¿Ä§Âaƒ9‘™èMZc„2e ^Çð V¼lqîñ£¸½Õר»¯©¢”*…ÁU:èÆ ÂOMõªV%?6ן"7ÌI óð¶¨]©¿üácÃÒÄØ2©ËUB <ŽŠR' ÛQ³¬'FKuî Ûv]–2&z!æ=‘u?¢Ëg”'kBPþžT­¦i'¶:}; 0¹ö³O37É à@ÆW/_ ¸ÇLÁ+°oS‘¢žÕ1¶:ðûîÝ&¼ø.ZD›æÌ%/¶h¸$ căœý9+VÄ‚'™ ;¶ÓÆÙs¸’+ƒÆ ¢0š³ Ï[ï ÆÁæyD‹zÁï÷s})gžœ´hÖO´pÖb#„õ;ü(K׋Da¬×¸-[´œ&üއY)ÊLî…EÐŽ­ÑÉ¿ ƒ+o˜Ç¸(J¬eÁ5+T ‹[ý)dÏ^ =pÎü¶™& Äo×ÓéK—Z½<¢”áÐùcÆÝý£-\×yW“Œ¢1Ùëe£Fku.Z(¿JJ´ÿÌ_tâÊ:sã8íúg ;”ßúÀîC´pæÛÆÆœŸ]Ì nÝJC¯tòœ ”4™i=žN=Û³‘‚Ò ½}—Z5hGÿž>o^œ°cQý~YËÝÛvnM¥Ê•Æ¿-‹zVá`øçv Þµ›B¢ÿm´Ô×—àP|ëõ3Úö,æq—÷1šy,‚Ê/N›çͧ°C‡)üÈQ£ñ:ì—ðŽêA¢êÑœ÷uþÉ×1;¡··¨÷1»~…¡MBçM¡öM»ðxnʱè­(Œ½õàöŸpˆA'´v¹FÔ¦qZï<~;ákn@©œS¶ð’=y÷ÎøyÍB:xn—ñ%Ôó+ÎBÃJ1¥8cÒlnÀýåÓQ3¿ž[‘Ï*‚.fLŸžR¥HA9XPB„%ùeÊïø¹s,HîC] ŠÂ˜’áµoÒ”àc´®ò‡#gÖ¡Jx¡þDaTß Sâ?,ÿ•'Á³ØÑ€¡ê2íÙñ>âþGþM][÷â^µˆ™ôç‘ßi÷‰? Srè µkÒ…`* ‰Â˜Å#3mܹšÊO¥>LiêÒ«#œ4œÖo_ÅÛV`ËœUœ`Ú$ÉŠ11•Ð3§«!!‚ñ“~àüc\‰%”;o.úyíBj^·-ýEÜ·ž_X• xg1‚ÇdÉ“‘wóÆü§ðŒ¿XT¸hA%Yó­.ŠÒ Ô T¬@Yߤo®}ú±7›¦lr^9(à îðï;þ·1€rN‰Ô­x¿)ézoŨ7¿ŽÜO Œ¶Ö¹#üiqÍ­ƒ‚T¨¨é³ WøÎÍ»ó^by ‹™¦³õžiTË—$ÑqYKü9ƒ1kvnÀm^î‹ç/Œ  ¼óxêc¿c·r¬Å³ª”¥Þ†°e…ÊkaI&åœ[g1óòâlŠñ*VóŒ@” ÅYF}NÏ}g1ªy»j%?DŒº 9ó>ƒâQ[¬dQH˜Š*[¡4W”àyëJr£5Þ7­ùŸBÇL¤gŸ¦Soà…¥FÔ„xHãÙR% Å@û÷îGR3¶ÜÉí°0™{þ˜1”„Y¢6óäÅëÖñõÏ”<+¶üÎ{qªYÑÔóHÉ£õVF­ùt¦HŸN“6ÛÝÚUàÁƒQ+:uŸµ9îõ>¢- g£ã°×§˜²Þ¤£˜Ý©Íyýê5ݾu‡`‰%¤ÔKJ9Š×Ý Š¤ g¨wïÞV‹yÛU¶šÅ¾ˆ{WZüDâyòŠ(Úæ2Ec´™ FxŸ‰ð@KÉÖYB˜WêQFsl³à ù¬Âè× EbDA(”/Ÿ+ªÏxO‘µTŒ ;°#ú}ÌðAzÂÏ•$ #”>e¹½ñ%Öû†ò~RRRRRRRñERQŠ/5%ù”Ð]RQÒ]äò†RRRRRRñERQŠ/5%ùã8¿@IDAT”Ð]RQÒ]äò†RRRRRRñERQŠ/5%ù”Ð]RQÒ]äò†RRRRRRñEqœ\¹|9E±õ$I H H H H H H ¼¯p8à$GJäÈù¾Ê†ï…Ñ‹W¯$Æx^à ¡/…‡ÑK¶8dþbyâymYgÿæå;|½1ÙæX—Q|8“ÞÇ„€1!´9a!õÄttów,ÎÈÜrçæK˜”È‘Ãüº÷æ81[£K˜HŒñ»JB=¾JŸ‚/Óâù+J©_$åK˜È÷Q¾î.„Ñæ¤äË´¼ÏmNªdiø&q=o ÞFé?²ºÔ]\r‹Wç$ÆxU]’Ù÷\ò}|Ï+XÂ{ï$à¥Dd˜^|ïjVHbT CîJ ¸Xò}tqÈÛK Ø)¯(Ù)/™]J@J@J@J@J@J I@*J  ²ÂP¨F ñ=‘€|ߓД0Œ¤¢”`ªZ•°Wqz½Ù[òߺ{—»÷š_›4IÊ=»y2=yú”þ½|™">¤ìY²POOJ“:u¬|JòŸ Ä,tA©"E(my•k´ÞŠÆ~CÃÃéfh(Ê—2¤KçGl"Db|MgY? \Y³qŒ©R¦t £#‹Ähïsíÿ¶\sçV(½bá/Ì) {sæŽíÍõ$Š.]¤û‘(k6*PØ‹R§±þ>*åÞ¼q‹ž<~B™³d"vž$ªÃïßçí“%,hËЦ9Bîö>*Â"#éÚÍ›ôàÑ#Ê–93}X¨%Kªù'B¹]¬­ˆzÄwåÑ“'±î¥NÈîáA)™÷³$£Â÷ë7o(øÖMº~ë6a¿@ž<äÅ~z“È6ç Ãuûæºzù¥JŠ ñ¢Œ™2 ‡¨é[°ÿøqòé÷¹U¦ƒvì Œ™øù§,嘹sèǵkMòCñùaÄHjݨ‘IzSºAÛ4IoçíMsÿ7Z·Z$F{õú5“ÉúvÆ ŽsöÈ‘ôYó&˜EˆÂi‚ù4oÕ*¨óù£ÇÐ'õꙤ‹<…ÑÞçZ$Æ£ŽQ—–=­ÞâÈÅ}\±A†gOŸÑÔ 3iÅO~&ùÓ¤MC¦¦¦-½MÒÕûw¤ží ï=%”«‰ªG<«6° cÅÔ©äS»ŽÕóZž…Qá108˜¾™1v9¢$ñmÁ¼yéĆ&i¢Dal?t=s&N¶§ F½Ú´3'EaoÇΞ¥“&RÀÕ«&¬Ö¬X‘ŽK¹²e3Iu ²Í9ýÏYÚw8…\¿iÂþào¿ ~Cú˜¤i} ©¢mˆâí‘)U*YÒ„×äÉ’Sú4ii;âJ’g®\Ô²ACJÇz­+·l¡ 7¨Ç¨‘|´¨0]½xù’ZÀx%’$‰iŸ­ö÷§ÿ˜‡ÿãÇó¼¢ÿDaßçÙÈÚ'ÐÉ 4…a¯M„(Œþû÷s% ŠQ³ºu £HkXýaô¥Ë7Ãé°ßjú°`AM±[+LF{žkk¼i•þ”)? (/e+–6)69ëA§Kÿö}Üû×®$åÉ—›¼[4¦´LAÚè·™®] ¦!}†Sñ’ÅÈ«P~“2pðüùs7|R¬t½DÕcô‹FMjÕ2î+;…óy*»voÝå}ã×oߦ:Ÿuáï` 6‚äͰæfUŒòŸ½tÉnlŽ^ ªU¯NÓg°ÈÖ¶ûyº^£f¢0b$°å€/xV+[–š2>qâD4›­¬å¬Ï˜ÑôÇÂEe u¢¨6çZP0µý¸#g·n£ÚT£N5 "¿ekiÖä¹”=gvjÙî­áËÓTQRJíÕ¦ ïÙK9´¸Å”Òœ‘£¨ƒq»wÛO©L‹ætUüþŽ“¢(í9vŒ+IPÀv-]FY2†ÚW¯Aõºu¥5ÛüiüÀ|êÎâÍ$j,VëОsÚ¯}º|-VOŒ8‹Ô£WžÜ4ªïçÔ¿CJ*¿÷Ϻ҇>Mùþ{÷è¦()ÀµÆhÏs­ð zÛ±G{ú⫾qÞÆ« 'Mš9–56Í)IRÔR'v]ýŠÞIGØè”%Eé—E+y¯vƒš´w§áÃçÔº6ñáY5mºrèÒ­Œƒ|'ñl«† iáØq”®[¶x‘F‰Z×ãÞ¿ñ:Ì™5+mY°Ðø=­_µUhÝŠœ8ÁÏëi¦¢u›3wÚ.ýOÚ4¥ïçù’²j̦OüæM[H->mfLרªŒÅ$6î鼃ùï.Í›+·GE¢Wzøø1ßâÏoëV¾?¨s£’„„ %JP•Ò†ž²ÿ¾}<;ýÙƒ|÷l݆¯ò£ÉC†°)Jíæ]±‰°UŽö`,[¬8}Ý£‡QIÂ=`ëñqšüv!wîØz[]óكў¼º‚xÇÍŠ/Lm:µ2*IÈŽi·¢%Šð+?zû>*EÁa›®C> »;¹KݸËûˆ)7tBA³¾ár%ÉÖçG‹z\¸f5¿]GÖQ‡=–»‘=Ã""9û•K•6ùž¦J‘Âë?L»¸ÙÓæ`Ú Ô¾Û§&ÊÚ,¦ã`»$Š\¦(YûãçÎñSE½ ³œPj͸½CýÎÞ?.Œ–Ê~øÄðά¡rhé>Z¦ÙƒÑž¼ZòèlYî? ¥*T$ö”èwc¦ñ[Œòý†>Èø³·sÉõ®¨wyŸ:ÉeÞå“O(}Ú·Ó°.©'ojO=ÂQæ×Í›ùû°YŒøBÖ0–)Z”CØô×Nn«„=Ï^±œ§×(_ž™¶¤áûîþg­ÍQì’2dHo!SæŒsÌD‘©·µÛ¶Ñ9æÕ„aÜ‚yóñQŸºUªÄÂ€Ê ð€¢ŸGÓÅ+WiÒ¢…|ˆ°"³oÂÜ2š0¦â@Z4'Å“*z’–EóíhVŒh´Ÿ:ÅEP½\yÑ¢ˆU¾Œ¶<×±˜°eýVºøoÁ.)|T®rYª^»Z¬;‚ïȈûô<ú9]fóÿ?L™Ë‹Œ¢2J¦ÖÔtìÐqòß¼J–-AÍÛ6£»wîªOë¾/¢v:‡c6‰)ý,Y¸0µ¨ßÀ©ÑwyÏñz‚Wí|¿Uü#ÁÚQþV/_Ú7ib2B¡G¥ŠªG5ïË6mä‡èd—ŽQ2ÔçEïk±:S„Ú4nLëþü“öèNC»ug6fÜñ æ*³™‰‹Þ¤u›eÊÚ1x⪠^¶8÷øQÜÞêkìÝ×TQJ•Âàâ ƒlüÔT¯jUòcsý)Tn˜Û¤vlšIMP†–ùN6¾ ê)¸41v-êüŠ[ùã¨(u²°}…1S°½=X=1NZh˜{†‡MíJ•D‹ÂX¾HŒ¶<×FF ¹€ž–yo«FÝhÁòÙ\yRX€QßN”C¾Íž3ÍZ<ÍdJîÕËWFî1SF+\E¢ê1EŒ­ ~ß½Ûžï¢E´iÎ\‡]¯Ýå}Œ`TМ+LðáN2vl§³çp%1VDÕ£9›ðfTð~ÑÑ`lžGÔ±HŒ°/Ë“=ÍX¶”¦/]b„°{Ù2Ê›#§ñXôލ6§^ã:´lÑrš4ò;®¤(3¸A;¶þE'ÿ6t´ß0qQ¤i W³Bº¸ÕŸBöì¥ÐéÌo›i Aœw¸žN_ºÔGÏüÔ»m[‚‹?)Ðí°0ªÍ¼0N]4x~)F[8×yW“Œ®Æd~½0 NŸÏ\Xõò>^‘my®Íe.â¸JJ´ÿÌ_tâÊ:sã8íúg ;”ßêÀîC´pæb“Û¢§Ö©g{6BäCP¤@¡·ïR«íèßÓçyý~YKA—®PÛέ©T9SïVc&vDÕ#œ ÿÜNÁ»vSèÁCà¿–úúzèÁ·nQoæI¤‰ÂÍ<A劧ÍóæSØ¡Ã~ä¨ÑxöKxGõ QÍyǨ ”_tÈõ6â‰ñ 3Ø^þ»a:N…õìɽ©•cÑ[QmNïA=6Mp,Ag®v¹Fl­-žóV)Ĉ¹(JĦ¶¬ZyÛ»—ΰ)´vNN‰LýùgšÈF`¨}ˆ+[£;÷îQëA¹k*œÞôŸzËXÙ0ÒÅ éjZ¼n-}õý÷ÔµEKi„ú”Mû""èìõ`—bT+ƒ ÓpéD¯N‹8Jç#ÂéÜõën…S5:uäp}¿Â=áìŽlݵÅÒsm©Îß…ûtT$\ºFuZÆž6{×µêóóg,â.µhx¶ìÛ >e²F=>ýœ ÄÐ÷_Çýy Êz>æùpŒÐ RÖ.Û;G)ìÌ:åš[=«`Ìß!f×ãÝ»7?ºö×.Ê”!ƒú´Mûîò>vgqé6°ØvS¿Æ;¬jæûÇõ`gt¿~êS6í»ãûˆ©åŠmZóÙŽ‰ƒÓ€NlÂb-“»`<ðÏ?ÔôsƒWëü1c¨C“¦tƒ9Æôgu78I!:ÛK§£î³6çªKÛ¥­|ùâ%íôßŧߞ=¦<ž¹©l…Ò4ò˱¼Zí¿œÊU,c/DºIÎPï˜÷ÚRšŽ(YºÒ`sBO,.ÊÁ"¤Nbèñ"/ìW`€^Çæ„H² W{.8ƒÑ“»k…ñjH‹œ€Ÿ»VÕx,=×êózï—)_šßѴ㢬ٳÒÈIÃyØÜ»{N;Åí–`»TµX-*ìQ’ÿ $ÐãCÚgqºäÿ‰¨G°\Žyn*t—u²\IÎbü ½Á0V™‚ScÉ›#?|%ÎîC}?kûÎbT—»óða£IH§fÍÔ§\ºï,Æqóçqþ8³cSî–/gNÚÀ¦M‹zyñ4„Ðq%9Óæ(|'KžŒ¼›7¦¡£H>ë݉ -È•$ä)ÌöE‘¦6JÖ˜¼Á‚šŠxëÉf-¯zù’¤1ñ\`Õ‡|ßñ¿á”ë•HÝŠ÷›’®÷ÖYŒzóëÈý´ÀˆþMúöáúŸ~ìͦf;аk´Àh‰9Kϵ¥|z¤Ý 1(H…ŠÚð>ª–/I–³ÈšÝƒp›óùâù cƒï<žú/ æIT=†°e…ÊkaI&åœ[g1cQС¯b5ÏD RœeÔçôÜw£š×¹«VòC„aÉ£$ªÏ»jßYŒŠ§xÉÂEL ÀŸ­s®$gÚœ¸øÞ´æw~ºjÊ”68ÏMMG”À K¨ ±qÆ/˜Ï“ÔÆº?­_Ç• ‡*ôœEÃùË2~ˆ9de¹Xôƒ¯[Ç×?ãìoÅ–ßy#NÕ®‰Â¨ï¶ÞCÆ{÷#©[âvhˆÌaâ$lÄÐ$ £=ϵhÜ»·ï¥§Q¦ïãíÛ4cÒl~ëj5 v8X¹d5a)õûø‚½?ÆØÀ¨Ól°IÚ°Ã/ÖoÑʹ¼LäÁù‰3ÆðcÑ¢êñÈéÓ|ÝJ5ÿXžfÄÌ< £jåWOë}QáÙÂôŒò±Å1¦ÅWýñv¹-ßü' £Â60!J5¨WÛ6J²®[Q1jZc£¤€Âwwîä‡Jðf在­ˆ6¼ÂÄÜJèð¾£4vØD¥Gÿ®|+êO³%„üW<Ø@0g¶¬Ɔ¦·ìÙÃyGeUEI…‘ ¼¡Õ`/,F`<ˆ(HíÒØ¤Vm>„͸ò§m ÇXP£L ilŽÝÑ*y6þ‰Ä°åK”áüC'Or®æ³eZ¶ÆÓ7` )½@Y¶;›HŒ3—-3}_`®Éu»~‹¿Åã'P‘ü±—ʈ•щ‘íy®€ðÎK±´ˆâÁ†ÿPtî……Óέ»øµ‹ >ƒ{ËAoä«üQEæå–”ï;¹‘i⌱Ƽî²#²Dzu(±FbÐòôdÀgì=Ü˧1€kêA"1¢-ñ©S‡·Ñõ»wãN5/Ùʰ[!¾‚ÄŠ&‘Þ•“p*šß X(çô؊ĈXP_N™ÌŒ¹çJ/Bñ \Žâ­ »^=Ö%ÙæÌðM›Vo¦Z,LIš´©y|·s§þåU×k@wªYÏNHT]&ËÈZá·‚ƒù‡»„ î…PT¢_<ç†Ø®Ñ)¶^"¿Â¬?sÜ7ê&A¯_ JÑ™€:t™Ç]‚‹?<0~7žÔq— !ö¯ÊF~ع ÆÌo¾å£Ö0¼+ýë%ÞeJ—«1‚Ï“&ñÞÜZëÛðÀã?Œ¬92~ïÙS cC¯®Æ¸ÿÄ?Æ*1|—Ùœ™ÿ:ù4#¬æm/¹K=Úó\Û‹1ôå3 x@ù‹½{Z+)StÐpœ¤À‹ìýb^kXqQ´»Þ…|ggCÕoƒÐ!¾R(‹…tþìE>…†x%OGñi¶ió}é# q—ÔücQÝ% ~¥ ¤§n¬|G)êîºÇ]ºúY…+7”ùÓ¬}:uñ"osðN6¯_Ÿ‡9)È–`r”Üå}ÿ ª}ÄÛ˜3lÄë»]¼r…Û„~Ë [G÷ëO‰%r¦»¼`3]X0_"›;ñü¹ Ʋ웙‹ 8œ¹@˜ÆózéÚ5Žåó˜RŸÍCnú2šµ9÷]Þæ %¦ÙÐ.=yŽàh’¿€'ý~$uéÕÑ$Z·½UùŒÙ_†SyÖ)²Fš{½ax Fר$YX´^eúÌÏXc~‡)Lp ÷`¡äS¾ÃÅ!®ß¾EÙ3gÑdèÛÏÑ­ÉÊÑtG¼lâFw«G{Ÿk[êÖ¯7Ô#Œ°>xD™Ø´˜â¥fí~ÑÑÏyðH¼™³f¦ªe¬]£¤?{ÍGvaté(9âõ&òY½ÿèOÍbRådµ˜*vÇ÷ÏëMö‘ý }ºw¶Ù¶Ô­;¾à[½¬‡-8âÊãnÁëö¼bV"ëeÍ”Ùé™G¼ÞDµ9¯_½¦Û·îì!±Æ[ªÔ†õBãª#[ÎÙâõ¦ÙÔ›Â\ùà僟-„×+Ï»{ÈJY¹*çmœ%]Ï­hŒzb±v/‰1¶dìyVíÉûNÚ¥ ὆Ÿ-”2e Ê—ß±÷+Uª”¶ÜBó<"ŸUýºƒá¯HŒ¨<¯XÐÙ•$#ð¹‰Ä|ðdT¼]…E´9X¬[Y®Dol®±¤Õ¥¼Ÿ”€”€”€”€”€”€”€Š’B“—H H H H H H $ HE)aÔ³D)% % % % % %à€¤¢ä€Ðä%RRRRRR CRQJõ,QJ H H H H H 8 ©(9 4y‰”€”€”€”€”€”@Â@œá‚®ßàR8ïâÅEVEÀÍ€ñfÀøþ×㕘g5ìŒa.‘ï…«Ê ~ÿë1 f!oÙ®ºê)Óæ¾ áÛ‘Úœà[o×o´ödÄpråòåÅ"WK’x_%ЛE£·FqŽ(d ÏR»rÖC{[+8¾¤;]5¾`Søt$°rm|ÙÊzŒ/57Ÿ²ã–O|9›êQbŒ/OcÜ|^|ò˜ë9qåJð6JÿÑqÉGž‹'õO*J²™ $ ßÇQÍ d‚W”LMK RRRRRRvK Á+J‰È±Õ±í–´ /](| oêQCq¹mQ ¡F·}À$cšK Á+JšKT(% % % % % %ðÞH@*JïMUJ RRRRRRZK N¯7Gnvëî]zùêU¬K“&IB¹³g•®N¸~û6=ŽzBY2f¢ìY²¨O÷Ÿ<}J§(q¢DTªHJ›:µñœ^;¢1Ghx8Ý ¥BùòQ†téô‚f¼HŒÏ¢£é,ó¦Œ|ø€reÍÆ1¦J™Òxo½vDbÄsúïåË ãCþ,ñô¤4ï鳊ú²åÝU¯¢ê1üþ}B=Z"´ehÓô"QÕü‡EFÒ5ãéÁ£G”-sfú°P!J–TóO„ú–&û"0âý{ôä‰É}̲{xPÊäÉÍ“…‹À¨0úúÍ ¾u“®ßºMØ/À¼Ö½ØOo‰ñ þ‹ÁÁ”:UJ*šß‹2ðpˆš¾û'Ÿ~Ÿ[e:hÇò`J%Úyø0µ4ŸòÈ”‰‚¶ï0ÉŬn#GÐöƒMÒÛy{ÓÜÿvø…¶×;C$F{õú5ý¸v };cÇ9{äHú¬y Ì¢Da„‚4aÁ|š·j• (»óG¡OêÕ3I·çÀ]êñ)‹;6fîV‡kMØÆFŒ¤Ö™¤‹<Uæ<¿ëÝ5ϯå±(ŒxV 4l`•ÕS§’Oí:VÏkyBF…G|t¾™1v9¢$ñmÁ¼yéĆ&i¶¸ËûØ~è:zæLœlO6Œzµig-NЬÇcgÏÒÀI)àêUVkV¬H ÇŒ¥\Ù²™¤‹:‰ñø¹sÔó£˜2xË„ýQ}?§¯{ô0IÓú@SE)*ÚœŠN¥’%MxMž,9¥O“Ö$M9ˆ~ñ‚¾úþ;å0ÖöÅË—ÔràþÀ{æÊE-4¤$IÓ??ZíïOÿ1ÿÇuˆQÁëy6ñÅÄ tò¬Û\¦(Œþû÷s% JC³ºu £HkXý¡×Þå›átØo5}X° Í|:“QƇq%IyNÓ¥IM+·l¡ 7¨Ç¨‘|´0]ÒƒDaTóþ®wWWľ(ŒÀ¥P“Zµ”]ã¶p>Oã¾èQÁ7Fë|Ö…¿ƒ%Ø’7Ú›}T1zöÒ%ÑÐŒå‹Âبzuʘ>ƒñ>êmöóC½FÍDaÄH`Ë_ð:¬V¶,5e |âĉh6  ťϘÑôÇÂEjèÂöEa¼|ý:ÕïÞóýqšT¯jºpå -Ù°&.\@9³e¥ŽM}„áÒTQR¸ìÕ¦ ïÙK9|çv!Sx %â¡61ÂÅ{ŽãJ°]K—±©¹Œ¼ÌÆÕkP½n]iÍ6?p Õ麸pÔ;CkŒà±Z‡öœÕ~í;Ð¥àk±zxqáˆëœ½½;¥,­1zåÉMÐþûwèÀ†MSñÛ ù¬+}èÓ”ïÿ±wÊ’»Ô#¦JçŒE||ŒS3½Û~JeZ4§{¬AÛÿÏqrTQr—zTžlßõîªóŠÜ×úYUxŇgÕ´éÊ¡&[wªÇA¾“ø¶UÆ´pì8Jž,™&Ýå}ÒÕðq5…N©¢(}\3¶"lž_Ëc­ŸÕ½ãu˜3kVÚ²`¡±Ý©_µUhÝŠœ8ÁÏëi¦¢5Æï~ZÌ«àÓ½iѸq”ˆ™Þ€ò°)ðqóæÑ÷?ýDš45¦ó“þ¹Ü˜󙘪@%â#j‰ü¶nåɃ:w1*IH¨P¢U)]šŸóß·oÝñÏŒà»gë6tx•M2„MQ”AwÄc‰'[0–-Vœ‘*JÊ­z ;wøÖ]ÿlÁ»Ž.Í›+`Á³Þ:èáãÇ|ë®¶`Tx·'¯r;lã+ßöÈÎŒ˜rC'4ëÛš)IöðéL^[0Z+ášÕüTGÖ¡=–»’-Ã""9û•K•6iwR¥Ha„õ¦]Ü”lÁˆi7PÖ­M”¡.Ÿ4çéh ÷ýp¹¢4rÖ,ô»¯¾¢L,‘­e³{î©ÎëÎï#e~ݼ™³Û‡öº3ÙReŠå6ýµ“`«‚ÑóìËù~òå)]š4|ßÿlÁ¨Ø%}`æØ„Ù%˜9€‚®;¦Ø"!Sok·m£sÌ« øóæã£>u«T‰ÅÏA6$ˆÊ-W¼8µgÃfwÂÂbå&Œ) †ÍIñ¤ƒ‡Šž¤%F=ù¶ç^z`D£uøÔ)ÎVõråíaO“¼"0¢‘ ð€¢ŸGÓÅ+WiÒ¢…|è»"³ÛÃô²Þ$£-ﮞ8E`ÿ°Óé<|³ùḢùK.L-ê7pÉè‹ÖÏñ*‚Wí|¿Uü#ÁÚQþV/_µÉMLF(ô¨O­1ZâyÙ&ƒ::Ù¥c” KùD¥i±:S„Ú4nLëþü“öèNC»ug6fÜŒæ*³™)€Þ¤5F(CP– WÉŸß<äqî]Þ&Ùy ©¢”*…Áņ«ø©©^ÕªäÇæúSĸa"„ÀÐîiÆSÖY"õTEš»u>Å­üqT”:YؾŒÂ˜u°`=1Nb†x xØÔ®TÉAŽí¿L$Æí‡R;6}ª&(ùË|'ëúá…ÑÖwW_Ô¾(Œ)bluàhðûîÝ&ìû.ZD›æÌÕÍõZƦ̃æ¬Xa‚p’Ù°c;mœ=‡+‰±2hœ £9›ðfTð~ѱ£ùi¡Ç"1¾,Oö4cÙRš¾t‰ÇîeË(oŽœÆcÑ;¢0z3;2(óðÎL“:•(XˆîFDЖ=»^‰ Š,k'Þ­f… tq«?…ìÙK¡Ò™ß6Ó„Aƒxip=¾t©±dX«Ã•±+s}/ÿá‡ÆtóÅh épךì5:QkLΖ§FxÁ)Càó™ «3Þ'îTE<óSï¶m ¡+ÐAÝf£¥µ™wÑ©‹úy4ŠªG[ß]gŸC[®…vtn§à]»)ôà! ðßFK}} =tô^{3O"½HÆèçÏ9Œèož7ŸÂ¦ð#GÆë°_Â;ê¹Óû¨æ£.P~ÑqÑÛˆ[T=Fx—ÿn˜ND§S¡F={rojåXôVÆ/»våvž˜]B'´D3îÈ5ë×_’'ׯÁX jGSE ÃÓx1ß‘ — ìÔÙh¤½uß^~kL“Ÿ?Ï`ÀGÜ%dVÏ­ZZCä‘Iƒ<97úÓ#¦4ãäûåª\ª”®‰AÞ¦~=ŒygŒç=r|daÌ—¼ë·ß²púVŠÀhÏ»«G…ŠÀ¨ð #ߌéÓŒbs° „KòË”)ü4lÎ`{¦‰Â˜2ÆØfÍÅh?:+‡#g^¡JDaTצÄXnø°Â³XÏ€¡àCÆÿüCŸôïÇÛ—ùcÆÐ?ë7ÐÙÍ¿l“ÐAkس`¬–…¨}Q³²Êž_~å(M}Ûµ£)C‡Òîe¿e² Ô4z³&|Øf€ƒ,S"Þdn©æ„J†ŠÁ²¶Ì_À{qHƒ=‹b¸¥\ƒH² W{.8‹QÁãÎ[­0^ áq±€¡ðsÒ £>²S† ¥¦Ÿ÷åïžc¤¹ŠœÁãW{ÞÝøˆmŽ5*Ç<7Âп5%È­3õŒ0%¤LÁ©yÍ›#?ÄJ ®$g1ªyG`TÅ$¤S³fêS.Ýwã86èBàL%–P¾œ9i›6­Ù¹Ÿ¹Ax»ŠœÅ¾aóŒÎ ~ !À/b~Šç(£‹¢tƒ5‹‚†{Í %Ð8ï•+7Ï«~<äûŽÿm  \«Ä]R¼ß”t½·ÎbÔ›_Gî§F¸‚6éÛ‡÷~c Áް"ì-0ZbN½|IÒ¤I,eÑ-ÍŒö¾»º2»‘3ÍŠ29Äò åe¡-\IÎb,æåÅÙ?ãU¬Æ‚@” ÅYF}NÏ}g1ªy»j%?DŒº 9‹Qñ¨-Y¸ˆ $Œ"ð3L\ôý4a@uà,FUQ&»Jè ª¨gŸL2ip ©¢„^5™·„ú£€Ø8ãÙ² ÅX6IF3'¬m†àƒ°PŸ‡E?¥ÅëÖqYyyWlù÷‚ÒƒDaÔƒw[ï! ã½û‘ÔŒMµb8‘¹1Ll͈ßV^Í' ãOë×Q¾œ¹¨óòÄ04è9‹ò<ó—e|SÓÖ–ñá4ü…Qýn*ìZ{w•ó¢¶¢09}šÁæ0Yú½×3gp(è!«Û9QøP®(Œðl! !>¶J¯Óâ«þøƒŸƒÍ‰$ £Â;0!J5¨WÛ6J²®[Q‹2…ÊÐ f£¤0€©ÊÆ;9Æÿ³wðQ]üÑ !@¨¡C tiÒ¤wPÒ»4¥jP”¢H  (UùPC•Šô&=@BH¡% è7ÿ¹ì±w¹K®ìì]`Þïw·³³³³ïÿfwöíÌ{o rk¯€Da„ ¦JÕ6Ë»¢Ó Sá0ñIš)Jù¯xú € BŠßæVé;9ÿhÌ‘V¢¤¦°Uý¤Ü o¼×‘Í¡7à ªByMgö zÌ7‹ÄCØò%Îí;vŒoƒXÔòàÄ`š† %å+ð'ãLæ¡ }Ÿe®É {õL‚àû‰“’¸&)äd†HŒ0~…㢺ìE„Ñ#ÅB9éåª+£“â×ìt‘dz ¸0€‡_±bôèq<{w§±6¡$#ú’6o½Å<‡vòå!à|€ÒZ¶&'ñ•$V4‰Ä¨ð®˜„sRÕ›DbÄtøGS§0cîõ\éE(Ø*Þš0WÑc]B‘'ÑÊà?¨é›orÛf(öŠýÜð=¨IíÚB›T3E «/ïÙ“±Qh• !*ñûÌh8›U\ù•cæÛt‰«q›{?¡ŽMß}OƒYèrÔùVb``©Kk1™×­Å¾HŒàoSHˆ1f”Â/¾ðY ǯ”Õb+c†D—kð©(Læ<+ÆùæùZî‹Ä0` ¥eáõ¡Ä+÷)xÇTò„‡è6ò)£¥¶°öìZ*«UžHŒX$/Œ¶à§PûÆ)p𺅉˜æ3oSLCÁû!@¡Ú½;³ìÊ÷Eÿ‰Æˆ]¬·=ò`MV"1öy÷]>P0åûï¸ $¼R‚òûiÿšUêWoEbÄ»†øJ;âºðî 8Ú7j¬fCH: óÀ±ê‚sp×.:É9WF¼ûͽÞáÓžs Ö¦öœ!ËJ H H H H H H ¼"ŠÒ+ÒЦ”€”€”€”€”€”€ýŠ’ý2“gH H H H H H ¼"ŠÒ+ÒЦ”€”€”€”€”€”€ýŠ’ý2“gH H H H H H ¼"ŠÒ+ÒЦ”€”€”€”€”€”€ýŠ’ý2“gH H H H H H ¼"H6àäò+)þ±kW~EÚA”p‘8‰ÀQ üÅ*¹ˆuñ—Ž<ËÖ7ú‡*ä/ þb.ºÂÅ;·é)[ÃIbtQhtÙW¡Ïß¹CÏØó(ûnUó*Ü«£‹n./wϸ†£µª“Ì]¨HIºxîøKÝiåHó˜Î]½Ä”ˆüÖd”êóÓ²å3°L‹Ä˜º›òUhÇ„,ÞzùŒìsR÷­J¯Â½*1¦ò›4‘ýt^Yù&É¡‘6JÉIç%9öY]Îï%AøjÀíøj´³D)% %à^Š’{µ‡äFJ@J@J@J@J@JÀ$ %7j Q¬¤!ƒ­™¨úe½úH@¶£>r–W‘PK@*Jji¼¤i9eór4¬lÇ—£% ))Ô%©(¥®örˆ[9áØÜî$ÙŽn×$’!))W@Éz½9‚ÿÖÍHîÞk~nºtéɧ@a“ì»±ÑôøÑ“ŽÑ#sfKbš'²>~L_ºÄ0Æ‘OîÜTºX1òÌ’E(K•‹Ä¨¾^øôàÑCÊí“ãUÕç8Ò?‰ÂªG;ÞŽ¥«‘‘tïþ}Ê—+•÷ó£ é“öÁ© #ž¿û“è“'efžÁzÈv|þï¿v=’Â¯ß ¤K.L¾ì§7‰Äø/ÃÁÞ‹ÃÂ(‹Gf*SÜ—råÈ!¢¦OÁá¿vÑàž-¬2½uÿ5òΕ‡OHxLMkYoį箦MÚëzÌ_|ÔƒöîÚdÌC¢e».ôùä”>}“|Q;{¦6ƒY­>tëVÊÃ^ (%š6±ZvÅ×_S›o™öü9}÷ËjútÆ ž?; €z¶Û¤ŒèQ!IóƒhÞÊ•&²2"h\ µkÔÈ$_äŽ(Œãã)pîÖ†¿˜°Œß~@þÍš™ä‹Ü…Ñœçmû÷“ÿ°¡<;OΜºe«yaû¢ú{û'aYÅ¢Û/Of|CÛ0Q²H:ºöW“Mý>Ë”Áë&ì8ˆ>îÛ×$OëM¥„øGœ¿œLªX¹¦ ¯˜Æîé•͘÷ôÉcº~£6Æ´’(ê[JIÒ?ÿ<¥¡}ÛÒÉc¨`áâÔ¤¥?mJG«–ΣëVÒÿýG¿þÑX^dâQB<¯/„+š\*c†Œ”Í3«1/áéScºUýúÆ´’(U´˜’äÛ3lâÃ/&ѱ³gMòݱ׶EÆ{öp% JCÛ† £H«7nä#n=>CûWýLåK–t®Mç‹Â¸uß>®$+XÞiÒ”¼<³ÐO6PèµkÔwl½Vº4•b£KŽ»´£šwÜ㣾š¦ÎÒ5-ªÏ±§ XÔ½ ¾1øVÏü¬ÀFZ²~ª{©b4ôÔ… ¢¡ë…±Y:ä-»ñ:êĦ=|W¯Q3Q1øÎyÖ~ýujÍ>¾Ó¦MC³—/çJö€ÀqôÇ‚…jèÂÒ¢0^ §Æ}zs¾[Ô­GjÕ¤³—/Ók×Ò æS|y©kë¤z„V@5U”¦:tHý>øLÙMvûzµ7izé×·ù ÷mçJ°Å¿ì!y‘: ZRïŽõhÓúU4tôdÊÇtÚ˼-÷ßïÐÆô{ߦ*qó®œþMŠekwéÌË îÜ….„]Mò…—bV 8jÛ¢5FßÂ…Úÿ]º°aSÎ툞½¨|›Ö<ýÇ®º)JЍ´Æˆ©Ò9c©K›6ÆiÕþߣÊo·§;¬CÛsä°ÃŠ’»´£";l¬ZÅ¿ððBÚ²w¯ú®i­û…y[ú'¥¬è­Ö÷*ø6ùKþ‚}·iSZ0~eÌ Ïȼ5YiqD/ÃËÕüzø(U¥õ’~Äš—×r_kŒ»ämX o^Ú0ŸÍ®$šs4®U›ªù¿K!Gòãø@Õ‹´Æ8mÑ÷œõ÷Z´¤…&²jHaf¾2aÞ<újÑ"êÒªµ1_kœ©Â˜;ø÷Ÿ8îîýF•$dT¨T*U©ÅíÞ¾oSó_?ÿ´å*š2b›¾óNÍP,òþzÙr|ˆTQ’PvZøBEܼɷ©ùv=Ú·7vVÀ‚ _ë ¸–mòøÁTö[L3`I©K˜rÛyð gzÖ§Ÿ¹\IÒSz VÿÌ/ו}ÐÀ+5Óí˜XÎþ¯U2éw<2e2¬Kj&L»úúû›(C=Úµçù˜Ž‹ˆ÷þHŠÒ±C†!R|Ý™“¢(E„…šJuûߌà (µfÜÞ)­¯ŸR}q ÊC®—P9v”*zß)‰ÃêqwkÇ€Y³8¯ÓF¢œÙ-OoX#¸\ûã<ôh׎²e}a2àrÆ3G™eëÖñ« `£½©*—)Ã!üöç6n«„=Ï^±œç×­Z•™xòtjýSì’r˜96åfï ˜9€Bï ƒ'dêmÓúŸÙq§vIEŠùñQŸšu[q•3´3¥M“–{Å•*[‰·x—20{4áØ˜;<×Ç ¾“ø§xÒÁCEOúeÓ&:Í<·0T]²HQªY©5¬ij—¥ðƒ¹þîcF³yã´„¡ÂŠ¥JÑÛ›¸ýœÑií?~œ‹ªN•ªŠÈtÛŠÀˆN*úÞ=Jx’@ç._¡/.àCßÕ™M¦¨ô&÷²á|tÌUÊ•£ÎlÈûæíÛzÃ2¹ž–}Žºâ”ú'uYÑi­ÛñL¨áã^µA«Vò—l óÒ…áoªÕX»¶2¡õkÑÏK~3¨£Ï®”¨dX*'*OkŒu˜"Ô¡ysZ³y35íÛ‡FöîÃlÌ.ò©pØÒÎf¦z“Ö¡ AYÂ;·tñâ&pàUŒc)y7šœd玦ŠR¦L»“ka—?5ÕªÛ„Ù"­¡Œ ÃP¢@p÷ß±åwuQZ8{Íùa.Z‚<ˆ3óðH:Çš9³!ïQ⨄±° „G&ƒ;ŒsñSS£Zµh³EÊ”ˆ-Sâ|?\Å×ïØ¡.J“.¤ßæÌu‰û¦ #vôÄø%3ÄÁæA¸“%ã–}{©›>Uì–Lž¢ë‹GF„Æ™hÀ=}ôJÇ>\E"ú`±µÒ·¨vŒaÊ:¿q-ž<™ åCîϼÜ‘ôÂ/8e<(p¼®1[Db,]¬8õïØ‘:µlɼ3 6t7؈Kæ]tüœ¶ÉÝ?¢0ÂÓnȽXØŠªåË'Ç‚ðc"ú0mkÿ$ »€¨vLHô<ƨàºyAt{ß~Š>ð—ÑñöKxFõ QÍyǨ >\ñᢷ·HŒá]¾Þ0ˆN…šõëG0\׋Daü¨W/nç ‡|„VhÛ†õîE³–-3B˘Qœ#BZãU4H`j ÓcY½²FzàÊß­ïp8<×¾ûÏ &WÉ•;eËîM™2{Pž¼ù¹ÛÿÔo 1vNŸ8Dq÷bÉ3ë‹U£KJEˆwÊ™;¯’%t ŒxÈ0§÷v íÖÝh̼{—Éõa(è-Á°.? l—ñ¥S§ò2°[ýŠ»‘1‰' É 7^{MW1ˆÄˆ o_<šygLä_äPȧ¼×§Ÿòéd=ÀŠÀˆªƒæ ¸“‰'¦>\µîsÞm韔²"·"ÚüfN4öÅÔ)Fs1Wy„2‘3HëP%¼R ¢0ª/…)ño—^¬ð,V¼ÃÔeD¦Ea 9r„Ú}0˜÷/AtäkéÔºõÛ$| 5í×—`⠉˜— .ì\ºŒ4@iØ©M9’v,Yjt”É›3—0ˆš*JÖ¸¬XÉ0¥r=2ÌZc~ÙŠUŒé˜;Q¼#Ä((ší›Säµ+< š+ ö' Åè,9^ª”-g<ŒáÃÔBZa¼Aï Âa#T~îBZaTã‚OË´HŒ3™†2ô}–¹&7ìÕ3 ëßOœ”Äý3I!'3Db„ñ+Œú¡Õe/"ŒÁ(Ê!H/W]‘¿f§‹ìsìéŸ4d¡"‘툥6o½Å<‡vòå!à|€ÒZ¶^%ñ•$V4‰Ä¨ð®˜„sRÕ›DbD,¨¦NaÆÜë¹Ò‹05°%T<­aGk¾¦¨ü"1N ¢•ÁPÓ7ßäö‘Pìû¹á=zP“ÚµE@2Ö©™¢”‘¹Í÷|$­Yù…ì0 ‡á*Y<½¨c×ÔS\ù‘%ûèèÁ=ü‡ ïS„aTph÷îÌv°«(X&õŠÆˆ]¬·=ò`Lµ#cŸwßå†éS¾ÿŽÛÈÂ+U!(¿Ÿö K@Q‘ï †øJ;¼û¤ö+p…mÓ°á¬ÿ¬Õ¾+ä0 yœª4èa­H’|T£ëqwÙr#yÈ;Ñ;IÁÄŒû¬Fž2³I…J›ØáZ+ÿüù3ºF¹ØºnÊÔœµ²¶äLJ sW/Q';‚# rï±å(r{çHq*åîýû|ä) ó’+À‚¹é=Ýt&&šN³E_fŒgÙÈÞ©ð0·ÁÏ\¯„ŠCæùˆNÄYr÷v„’ …É™õÂŽÄ%Pèå3nÓçØÛ?ÙÒÆîØçà~d¶“9²y¥ØŸÙ‚ÑŸGð­^ÖÃÉ•q7Œàõ{×`VÂ+«'Á ÌYÏ>wÂøŒ…Â= Å›ÕKa%×N);Çb0žd,û÷ïoµ¨f#JÊ`qWül!„ÀÏVJ—.=m*ikq!倞LøÙB0t'ãA[x–“JÉžvD‡Œ0®&=Ûá2\A"û{û'QøE·#îW,èìJ‰QKɉľàɨx3:ç3çŠÂ¥OY®Äþ9WScnGçH H H H H H H ¸«¤¢ä®-#ù’p¹¤¢äò& H H H H H H ¸«¤¢ä®-#ù’p¹¤¢äò& H H H H H H ¸«¤¢ä®-#ù’p¹’ v‰3sý”~ŒZê$†…Ȉ0^ñ‘¸x1pƒZÃ#¯s.C/Ò¹é|¤aÀ—cäKߎʢҲÏÑëIsðÈ—ÿ^}5úœ—¿_½¢Z{ÕÚÓlÀÉå+VR¼ÙÚmÖ*’ùRRRRRRR©Qœ,\ÔÏîÈÜ©M@ør ¿t®HÀ© £#‘€SFG"ÈJŒî'G"s»Šä9’}NòòI-GeŸ“ZZ*y>•ÈÜÉ•’6JÉIG“p# üGzOhºø—‰ÙŒ/SkJ,¯€¤¢ô 4²„(% % % % % %à˜¤¢ä˜ÜäYRRRRRR¯€¤¢ô 4²„(% % % % % %à˜¤¢ä˜ÜäYRRRRRR¯€’£äþ[7#éÙ³’œš.]zò)P8I¾’}›"#®Ðý¸»”+ù•®@éÓgP·= ógNPš´i©tÙ×(‹§—ñ˜^ Ñ#úNE݈ ¢ÅýÈ+[½ ¯sýÖ-úçÙ3ã¾’HŸ.òñQv“loÇÆÒUCåÞýû”/W.*ïçGÒ›Þfñ têâEŠ»Góæ#¿¢EÉ#sæ$u‰Î‰ñáãÇô÷¥K cùäÎM¥‹#Ï,Yœ‚”†ÒØ}¾HŒjfÂoÜ Rnú˜è´¨çñnl4¡¿±DèËЧ9Dö7#‰Â¨æßÖ>X}Ž–i÷*ž¿û&˦Ož<”9cÆdÏò“@IDATËhuPF…·çÿþKa×#)üú BºDáÂäË~z“HŒÿ2\,îÑŰ0Êâ‘™Ê÷¥\9Ä¿|Ò-‹þð_»hpÏ–²Ü­û¯‘w®<&Çî\ “?¦!ÛLò‹ó£µ[^º|Ìâ9|ÔƒöîÚdR®e».ôùä•*“‚íˆÄŸ?F¿¬˜Ïd2šs0iµïØG#îm«fÏáÃÔfð «…C·n¥<ì…¨&ܸŸÌø†¶8 Î¦’EŠÐѵ¿ò<(H“æѼ•+MÊde Dи@jר‘I¾ÈQÇÇSàÜ9ôÝ/¿˜°Œß~@þÍš™ä‹Ü…Ñœçmû÷“ÿ°¡<;OΜºe«yaû¢žÇ„„ÇÔ´–õ—Ì×sWSƒ&m…áRW, £r [û`¥¼ˆ­¨{µóÈô×ɓɲ<}ôhz¿CÇdËhqPFðvðÔ)úåtþÊVëU¯N ÇSÁ|ùLòEíˆÄxøôiê÷ùX¦ (+ÆD÷í«ì Ùjª(%Ä?âLædÊPÅÊ5MÎÀ4vO¯l&y7"è§]þÕæWº"ÕoÔšòå/D—.üMΞ0–ý矧4´o[:yì,\œš´ôg_séhÕÒy´qÝJúï¿ÿhâ×?Ë‹LˆÂž/]8M_ ¢³§Š„bÝ QÊñÒ«Q±¢IùŒ2R6Ϭ&yMx«gÂ(J6‚Ô²~}*ÄLŒ¨œºpÁXvãž=\I‚ÒжaC>Š´zãF~^OÆÐþU?Sù’%åE&DaܺoW’Š,Hï4iJ^žYè§ (ôÚ5ê;6€^+]šJ±Ñ%GÈÞð¢0ªyOxú”F}5M¥kZÔóøôÉ#ŽúÚÓJ¢¨o)%iÿÖÎð¢0‚q[û`ûAÚw†¨{µY:ä-»Ef6…ìáùæ#Þ k) #Fñßò!ïGk¿þ:µnð¥M›†f/_NP\Ž£?,ÔAÊUˆÂx)<œ÷éÍhQ·5ªU“Î^¾L?®]K_,˜Oò奮­“>§)sl[ M%å’º¤~|¦ìZÝN÷!W’š¶ê@ã§-¢ ì%l‰îÛΕ$(`‹ÙCÞ9sóbu´¤ÞëѦõ«hèèÉ”›MÙéEZcß]ÚÖàìwî9„®œO2ʦ6å:ïwè@cú½¯ìZÝ›ü%HßmÚ”ŒŸ@3$2Åɾ… ´ÿºtaæ¼¾={Qù6­yú];uS”øÙŸÖ18'`,uiÓ†0M êßñ=ªüv{ºÃ:´=G;¬(ñÊøÓ£š…«Vñ/<¼¶ìÝ«>¤kZÄó¯W{“¦™Žê Lu1míƒUlMj}¯Žèex¹š3}†}Ä)ŠR‹zõÍ Ý×ã®Cyÿ[ o^Ú0ŸÍ®$ö;kÕ¦jþïRÈÑ£ü8>Põ"­1N[ô=gý½-iá„ ”&aþº03™0o}µhuiÕÚ˜¯5N—sc¸ èÓ s¬*I8üûOØP÷~#Jö+TªN•ªÔB’voßÀ·îôgFðíße­\ˆF|öÃi:EéN¸Ô¼`ÊmçÁƒ…¨êõ¨Uû®Ü«šplÌ^6¯A êŠO:x¨èIZbÔ“o{®õ˦Mtšy§a*­d‘¢T³R%jXÓÔöìL¨AAÍÎnà U+ùƒs÷.7¬SµunÕÊä+Çüúè´ö?γëT©j~Xø¾Œè¤¢ïÝ£„' tîòúrá>ô]Ù{aŠJoq/ÎGÇ\¥\9ÖÆ­éæíÛzÃ2¹ž¨çñëÇÆ íLiÓ¤å^»¥ÊV¢Æ-ÞMvÜ„1 w´Æhk¬!„«q¯š_tÉo¿ò,ôg•• ó2"÷µÆX‡)Bš7§5›7SÓ¾}hdï>Ì>ô"Ÿ ‡élf  7iÊ”%¼J/n^Å8–’w£ÉIvîhª(eÊd°;¹v‰ðSS­ºMØ\ÿʘÑ0x)E ?~«.ÆÓ0ÐÞ¼†f/ZOÞ7÷ðH:Çš9³!ïQ⨄±° „ŒiY¨w"LW} 㧦FµjѪéßP¦DwÚ¦€æ¬X¡.ÆÓ?3Cíµ[·Ð¯³ç0ãBË¿d†x xÇ5¨a°Ñâ‚ÿDbܲo/u1Âì–Lž’¬Òhr‚;¢0"lÄÈDîé£ÇP:+m«„«õ<â#„ð;¶ünÂÇÂÙ“hΨpQ}FEa´µ¶öìšÅÉQ÷ª9[ð¼Uúª»v5?,t_$F؆öÉO3–,¦o¿plÚ±d É_@(.uå¢0¶dvdø‡gµgªPÒnÅÄІ;Œ^‰ Š,¿½¼Zµš (xw(í<E!'cè÷?ÏÒ°ÑSxmpÿ_¼à+cÍOØ *W±*Í[LûNߣgîse ù°_Ú³ã“È1ÏŸ?Ç!—’Œ.dáâõªU£sÁ)bç.Š ÙK'_G“† ã%áþÿÍâÅÆ³½ƒ0²°n^ÝÞ·Ÿ¢üE+™2‚ý¼Ý,ò•!ð ÀñIâ-Y:G«<‘K+Î ¸;R§–-™w†Á†îqiÀ<Ÿ;ë0{½ÞDa„§ Ü{µ›ª–/ï0-Nõ\@œ©Ä*Z ­e&õºw㣿«7m¤aÝ{¤ZŒ`¶²ˆM‡ŸBð‹x} r%ÄM…ë¢(ݸΔð+Ë·øóML;bÌS‚òÉoˆŒ[¦|Ú¿g >°Ó€`{wmæIÅûMÉ×{ë,F½ùuäz×X`IPYÕ YÖ×—çí;v”oÕD R/y—òVð¯ÄĘ4l¸ú—§µÀh „zù’ôéÓY*¢[ž3¡àašÕœžþó±ÃÂqß‚…Ì‹èº/êyIJB ù|1Å¡äé¹u£=}°ž¸Ô×ræ^U׃ôÜ•†03ýü;° ”†<ó2®Øw£âQ[±Tiö1º€Á˜&‡ç­+ÉYŒÖx_ÌáÃL¤gŸ¦So!;ƒ K¨éæk4¦aê Sl U­Q'ÜCÆVèÂÙ“ôÇoÃàj5ëóìæm .œk~ZÀ×?SÊnX»ŒcÄ©z­J¶Ð­(ŒB™¶³rb{”8j œŠGÙò# µÑ5<Û@j¦<°ØÇÔÚÊ?þ@’0o ºs7–Ú²é #27†‰]e, ã¢ÿ­á_®æWè ‹^=s龋)Móå_”rZoE`„M†ÀÍ«gÌäìÃËǾ ÐŽÅúD='Žîçë«©/ÏV˜9e ϪX¹eÉâX8 u¶¤Ea´§¶…Ogʈ¸WÕü ?B”jÐû;¨é–…±LâÇêŠD%¼À~ݶï:º€R—­[Qá@b j×!spÚTÎLCD’f#JO™+ôˆþœ×º [Q¾|)&úíÜf00ó-Y–z øØˆÅׯ½Õ¤?Þç½ú„5Û°˜.¼Ý@ˆ¯T¶‚a˜vK8ÿJè9z¯uUnÇûŒ2>7Ëñ*y ¶ý‰Ä°| d:–gjÕÒ¹,˜¦Aá2ê 6—ôkžŸ Ñ–¤P<¶¡áosï‚ü x(Gª¢ÝbD©Í[o1ïƒ<Ä< ˜qS¯eëÁ_ &A3™†2ô}–…hØ«'ÏWÿ}?qR÷Oõq-Ò"1Â@ïPˆê2%£G0h‡rÒËUW$F-Ú@‹:D>s§å« TI1ßÒÌûí!•ErÇMùN )Ö!£=}pŠŒ:Q@{U 0 ç ,¤ª7‰ÄˆXPMÂŒ¹×óV„p-áú;8Lؘ¶aËšˆ&‘'ÑÊà?¨é›orûH|”+¶¯Ã{ô &µk …—n<#kW»vƒ½¸£(±JÖŠóÓ1^<ÔX§íòÅ¿éìßGÙ2(‹§uí=”ÆN^Hž,­¦Úõšâa]7¬svùÒYnÔÈç4ø#¦<Ñí8 )€eN®²úPw(»FDøe*S¾2}¢z7lÚN]­]éø·(.ÖõÁô—Ÿ¦ çN²Q²PÂúv Èûø5oÓÉ8ÉÚø÷,.’Åõ‰¥ 6¸‰"ü}ÂÓ'|åìåP:Î ±AÑ—?`î´óÆ~ždˆ³Ií7ùƒy’}µa¾ø[ƒ# ŸöïOãÀbФáœî9zÄèʉ¡à[ÌVÇü×­M[ÂjÞöÒ6W}+îžË1"Þ”¢“çÏÓ™ÐK<îǃGøtÕw̸Û<•=8ïÄ?¦ÛLn®nGK}Ž­}°MÀT…Ü¥ÏKÑí1Æ0ˆÕ”…*vJºKŸó:›ê.È>ÌN^8Ï¢SG1ÏÚstáêUŽ ®óØ‚ãùX¬!GÈ]0â}±Š)Jˆ£tô̺yç)ó k×ïu2‰Öm/Îhv Ô@UfünÒ°á¬ÿ¬Ür˜ŽªƒÑõƒ¸»|o6-–=a‹°FÝŒ ¦Tþùóg|!Ç\l]7-†¾c®Ÿ¢ðK'Ü cJò²÷x|ø:wõu²# #ÚFÇ÷Ø’¹½sØ4]ÏBD²5G6/›ÊÛ‹#¹ògÙ~*<Ìm0BÂÀ<¹rQæÄ¸<ÉaH騙˜h:͇t×vDŒ,Vmm­¿”ðáø‘¸ ½|ÆmžÇû¬Ã(ofà £äi×Ѳ‹µ21‘¬Ï u¯>Çž>Ø.u¾»õ9xAêe=Ôü:’v·>îÝ¿Ï_ú^Y= ^`ʺoŽàÃ9î„ñ „÷ _¬ñ¦^ ËQ|8ïs;ɰþìÃÞi6õ¦\‹ÕÁ•?[ _oE‹—²©xºtéY ·’6•UH4FQ|ÛS/0Âp×ï,tBX6µHŒÂ¸šDb4džP® ‘Ï#Âàçj‰ØìéƒEÉB佪¥‚ä ~‘Á¼Odgøtæ\Q¡ôi5h/>M¹í½¸,/% % % % % % %àΊ’;·ŽäMJ@J@J@J@J@JÀ¥Š’KÅ//.% % % % % %àΊ’;·ŽäMJ@J@J@J@J@JÀ¥Š’KÅ//.% % % % % %àΊ’;·ŽäMJ@J@J@J@J@JÀ¥Š’KÅ//.% % % % % %àÎH6àäò+)Þlí6w#y“°WœDà(-K˜ØË”»”¿sý=cK¼Ô#Æg/7Æèȳl¹lZÞÃ]î={ù¸xç6=eëèÙ²„‰½u»Kùóli¬ù(ŸGwiÇøxúUÙç8vo¸ÛYal髇f‹À›ó˜ldîBEJò%L^æN+c†4| ‰ÑüÖH]û9Ò<æË´TÈo{DøÔ…(-[Ë´¼Ì²xó%Läó˜ÚîNS~_…~Uö9¦mžZ÷ÒyeåK˜$Ç¿´Q²ºÒ]rbKeÇ^Œ©¬Ia÷?’ éˆÜÜîœW¡_ŒnwcI†DI@*J†ÙEQòuz_Œî!i¡\¤!ÙB¬Wå¯B3¾ õº_äu\.©(¹¼ $RRRRRRî*©(¹kËhÉ—×Rš.«KN½¹LôÚ^X>ÚÊSÖ&% XRQ,`Y½”€”€”€”€”€”@ê•@²^oŽÀºu3’»÷šŸ›.]zò)Pؘw/–>ˆ3î[JäÉ›Ÿ2fÊlrèñ£tþÌ J“6-•.ûeñô29®ÇŽhŒÀ}'Š¢nDPÑâ~ä•-‡s°°‰1!á1ó¦>Æüظ8ºÿð¡qßRÂ'OÊ̼Ò‚»éß—.ÎõÉ›J+FžYœÃ舒HŒ VlÃoÜ Rnú˜è´¨{õnl4¡¿±DèËЧ9Dnö<*b£oSdĺw—råñ!¿Ò(}ú Êaá[íèè{FX‘Ïãóÿ¥°ë‘~ý!]¢paòe?½I$Æ®ˆ¨(ºFY<2S™â¾”+‡“ïGäà“n¹æÃí¢Á=[X>Èr·î¿FÞ¹òðã#ùÓÉc¬–ÅÑãfR‡®y™Ç,ðeÀG=hï®M&ç´l×…>Ÿ¼@·Z$F{þüý²b>͘<šã ˜4Úwìc‚YôŽ(ŒPæÏO+—Ì1ewÜ”…Ô¨ÙÛ&ù"wö>Lm²z‰Ð­[){éƒ:AÔ+òy“|Q;"1>}šú}>–)ƒ×MØ;p}Ü·¯IžÖ;š*J ñ89™2T±rM^3°¯mO¯lƼ: ZP¶†‘131²#˜§”¯™X@È¡}ÛrŪ`áâÔ¤¥?ûšKG«–ΣëVÒÿýG¿þѼ!û¢0‚ÙKNÓƒèìé£Úòn§M„(Œ{¶s% ŠQæíø(ÒÆõ«øWû'C»Ðª ‡©d© Úb·RÛ£„x~/ö+š”ʘ!#eóÌjÌkV§ygËnÜW'6…ìá»Ò¥­ûöq%©XÁ‚ôN“¦äå™…~Ú°B¯]£¾cèµÒ¥©]r„ìµQ…QÍ{ÂÓ§4ê«iê,]Ó¢îÕ§OžqÔoÔÆ˜VE}K)Iû·nò<‚ñ‘aÔÓ¿.ýJW¤úZS¾ü…X_ô7]8{Â~lž!ªíyÏ8Ⱥͧ‰zoÇÆÒ;C>äAk¿þ:µnð¥M›†f/_Ε³ãè mæÓ™‚¢0^ §Æ}zsÖZÔ­GjÕ¤³—/Ók×Ò æS|y©kë¤Ï©3XÔçjª()c¨ßŸ)»·½|l1Ê‚¢(Õc-èà¾í\I‚¶ø—=ä37ϯӠ%õîX6±—íÐÑ“)7.Ö‹´Æ¾»´­ÁÙïÜs…]9Ÿä O/lÊu´ÆX¸h 8<ºôBžü2=û¢6o^:»¶­×MQR0¾ß¡é÷¾²kq;¢—á5?x†M­)ŠR‹zõùa¿¢EiNÀXêÒ¦ a Ô¿ã{Tùíöt‡uh{ŽvXQâ•9ð§5F5 V­â_xP&·ìÝ«>¤kZë{UaþõjoÒô ÓÑAå˜Þ['û+IM[u ñÓQö‘àJÒ£=ï½pký<î:t+Iòæ¥ óÙìJb¿Ó¸Vmªæÿ.…=ÊcT[/Òã´EßsÖßkÑ’N˜@ʪ!…™‰Ä„yóè«E‹¨K«ÖÆ|­qº1÷êeAc›wºS®Ü†áÂàßâyÝû4*IȨP©:UªR‹Û½}ߦ†?KÁ·—´rý!ñÙW §aŠR<ØD8{]KËV¨B}}bT’p ØzÔmØŠ_îækÎ^V×ó¬þ™_¯+SŠòåÊÅÓåýü¨GûöÆÎ ™è *°|PÜË6/ü` ŽØ(¥PeЇ-aTN‚-¦Ãß©•,Ý«B±¸Éóˆ)7|„‚>0ÇåJ’³2·§í)ë,_Zžoéy¼Ë/ñÆk•LúL™Œ—ƬKj!K1íêëïo¢ õhמçc:."ê&O‹øs+E Ìëþ·„ãìØm°ï±C†é |Ý™“¢(E„…šrË}kÁì˜ÀYÌ€ÒtH:?#Éa´„çáý{<ÛÛÛ0Rh©Œ»åEEGÓ²uë8[؈QrƒnåA/ã["¹¢É³wê-ÙÊl8˜Æ€Y³x-ÓF¢œÙ-OMÚp—±÷^Õ„Y7y6Œ¶óïEY½Rgû)íaO;ÚSV©ß¶ÖžÇÊeÊpö~ûsÁV £çÙ+–ótݪU™ €aŸg¸ñŸ5ŒŠ]R//îs{{Ì@¡áâ>´…L½mZÿ3÷j‚] ¡ÌÔ¬ÓØ ¥ßVÿÀ³Q¾LùÊ< M86æOçõ1D}®âI=IKŒÂùvð VŒè´ŽÙÇEP¥F]á¢0¿À/›6Ñé‹)c† T²HQªY©5¬ij_g~ö—üö+ÏFùJ‰•RTô½{”ð$Î]¾B_.\À‡¾«3[(LQ9JŽŽ(‰À¸— ç£c®R®ufCÞ7oßv–&牺W/0ïÌ1C;SÚ4iùèg©²•¨q‹w}q“ç1ôâ.{/f·jé:uì/º{7šòù¢ªoÔ£Ví»:îÙç`«ŠjG5;–Þ3êã¢ÓZ?u˜"Ô¡ysZ³y35íÛ‡Föîß@sV¬àYvíª>ÄÓ[öí¥N#F˜äÃ~`Éä)&Cã&lرwDIF„T™hÀ=}ôJÇBu¸ŠDÝ«øÈ!<ÀŽ-¿›À[8{ÍùaÁæÎ!r“çñSŠ@J¿«Æ'™­Ákhö¢õÌ0X|ûŠjG5&¤­½gÌˉØõ<‚×ã'PaŸü4cÉbúfñ ǦK–P‘üDÀ±X§(Œ-™ hЪ•ôÉŒoX˜ªPÒnÅÄІ;ŒÉ‰ Š4}ªÕl@Á»Ciç‘( 9C¿ÿy–†žÂy‡ëéâ_YűyÃjÞ)aÔ¨^#ƒÍ «?¾ž?nõ|½ˆÀ¨ï¶^G/Œ{vüaœj œún! ‡zժѹà±sE…쥓¿¯£Ià úöXg³Øª¸ðå†8IP~#nuáÒÅŠ3îŽÔ©eKæa°¡»ÁF\ôìAÇÏUš…ž&pCîÕþmªZ¾¼P )U.ê^…³Áæ}a´ýÐ Ú{2–6†\¡É3—J®G\¥ÀÑ}RbM³ã¢0>a ?¨\Ū4oq0í;}œ¹Ï?h‘û%<£z(Œæ¼[{Ϙ—±/êy¯á]¾Þ` P²H#ûÍúõ#8èE¢0~Ô«·ó„C >B+´mCz÷¢YË–¡e̘Á˜Ö:‘VË ñåEóÝé+·¾Ã¹§®³ûÏ /‡©Šå‹fòcðøRróÌú"¤€¥•øBåÌ—oEÿ‰À(šg{ë×ã…³'iä œµ>ýŠ^{=åé.{q$W¡èdËš•<2gæóÜC»u7%ïÞeñtÜ«ß.7<œƒ;w±8B„ o_<šygL¤_gÏ¡ó7ò^Ÿ~ÊÃYX¬\ãL£ïÞ¥‰Aó ÜÉġҊÕêDÞ«p&É–Ý›2eö ¿EX’©ß®ä¼œ>qˆL5ÑZeN£¢0fb÷=SlÍÅh?B² Dœi@š‡*áµ&ý…Q}¥äÞ3êr¢Ò"žGðräµû`0÷ª ¤#ÿ[K§Ö­'Ø&á­i¿¾Û=HƼl qçÒe´xòd‚Ò4°S'š:r$íX²Ôè(“7g.a5z³ÆeÅJ5ø¡ë‘a‹ìß³Å8U×öÝ&e x|ÅÁN ö,P¾Ôyí ßU<äÔÇôL;ƒQO>¹–V#Â/ÓÐ~†˜€Ÿ»ìˆ@Šñ 9_®¨LÕukÛÖü°Åýü,j÷Ô#©õ ¼^tZÈs9ƒ71š*Ù´iøâË^½!Ð݆ ùIŽë•¡Õ½jÎoÙŠUŒY1¬?Ên%œ±À„³³eóæÜÝ»“„Ëü‹ò<½L’0˜á,Fu½É½gÔåôN;ó<‚× ìÄ ·J,¡¢ ÐZö‘V¯{7>ú»zÓFÖÝôÝÊOÒéÏYŒ`v¤ˆM‡ŸBð‹@åJ88®T–ÌVEéÆõpÎB ¿²YY¹x6χ{<¾àÌ©Lù*„›üðÆpJ™½»6ó¤âý¦äë½u£Þü:r=-0b™‚=šqÅ·EÛÎÆ©YGøqÎ5¶¨¬•‡nîÊŸøñ~þXÊ£<3™?õò%éÓ§K¦¤øCÎ`„‚nszúÏ?Æ Ç} 2/¢ë¾÷ª%†±¬B>_Lq(yznÅè›Ø;’„m¢ùä/Ì·®ús£šï”Þ3ê²z¦yÁ§âQ[±Ti¶ac‰`º˜&‡ç­+ÉYŒÖx_ÌáÃL¤gŸ¦So!;ƒ K¨ ±qæÏ äY–Œu1ƒðõ Ž]ð­ù_ó6÷ë5?-àëŸ)Ç7¬]ÆG¢0âT½V%[èVF¡LÛY¹(ŒwÙ¨àà^-évÔujج=Á.)mbp4;Ytº8E>JQ*‹¸y“&Îâ» jFA•cØžºpGºEúýކiC¤Õ´èk£NæWè ‹^=s龋é>ei帨­Œ°I¸ùoõ ÃÔ9¼lpìÛ€Q°Lêu¯ž8ºŸ Ô«)ž­<0sÊžU±r Ê’%«ú°°´(ŒUkÔã<=¸‡0•¨úä?~[Áw«Õ¬¯d ݊¨0mË{F)+j+ây¯e|}9Ë+m”þáöë¶m|×ÑÕ”ºlÝŠÂóXP»±˜ƒÓ¦rÖ`6!’4QzÊ\¡G ôç¼"€`¾|)&úíÜf00ó-Y–,EI]½Ü0l¯¸â¬Œ%œ9οzŽÞk]•Ï¡Ã>£L ÇÍ2±k²T‡y"1‚?,_™Ž%Æ8Yµt.íÞn0¨2ê òõKú5ÏOÐèO$Æ%ß}mœb ½p†zu¨›„ë‰_/¦â% qA’Ô(Ën(^i‡ð÷·¹ÅN~t<#-DãV¡Á@‹1Z¢{öŒÁ¡Õ­ZÙ|¤£r[”×ËUW$FK¸]‘'ò^;},_ nòÅ|K3G“‡ü9TÉ7å;] ‹Äˆ¾ä­&íxÝç½ú„u3Ÿ=û‡{»â+!H¬h‰QáÝ–÷ŒRVÄVäóˆ8nMÂŒ¹×ó(Üo[Âõ;vp(p­oÖ5M"1N ¢•ÁPÓ7ßäö‘E;vö,‡4¼GjR»¶Pxš)J™»uÏ÷GÒš•ß— çX× #EÝÙ1Å•_AôôéÚðër¾Û­Ïp%;Éu|÷ÓŸ4áÓþ¼n,YB¬¥~pÅ)ÉI2Db»øªRbF)ìC9ÄdIÑTÊiµ‰Q½<‚yø…ÿ„Ä5Ø”}ÛÌlHzxÏž´hÍã$¸"L¿Ï¼Õ†³¹|x« £BX³ ”Ü×KÀ€,æN>ª»…05áÃ!ÜvGɹ‰ÑßX{¤¬yg©ŒÖy"ïU,¥ØlmÁO¡ÆÍß¡Á#&:@©ÈÆ­HŒ`£º0w@ _„a„¾{ߺيÆhë{†ƒô'òyìóî»Ü©dÊ÷ßqHx¥*ÏÛOû3“æ´"šDbD¬:8Ñ(}0°À»/`à@jߨ±hh”† gýgí*»B³À‘Ç©JƒÖŠ$ÉGu0º~w—/ÃáͺäèI⋞%¶ÐóçÏøBŽ¹ØºnZ }Ç\?Eá—N¸F[ä`O™˜H†1ôåÆ~€Î]½DªTµY4¸WaX}-+’Û;GŠSbñ‰ ¥ª—°v1”EF(yØò&èDœ¥31Ñtš-é.Íñ ¾&]:JGâ(ôò·yï³~ £¼™Y 7Œ’k1UìŽÏ#úᨛï”úl[ÚÖÝúU{ß3¶`t·><ß»ŸÇòÊêIðSÖ}³¥2gÙHû©ð0·èsž±ð@‘QQ„V¬ñ–ÅÃ6Á.uÞ9ƒñ$ dÙ¿u¶IZ³%¥V,VWZül![$¥.„(\´¤²ë’­hŒ.evÑW#Œ“mõ@³EARĈ²àjB;ŠÂhŽÍ|Îü¸¨}‘÷*F[,9˜ˆÂb­^‘qMôÃE‹—²vy]òEb´÷=# °èç1s0ÁÏ•$ #”>e¹½ñijÌ­7óòzRRRRRRR"% %‘Ò•uK H H H H H ¤j HE)U7Ÿd^J@J@J@J@J@J@¤¤¢$Rº²n))))))T-©(¥êæ“ÌK H H H H H ˆ”€T”DJWÖ-% % % % % %ª%lx€ð0Ãbsˆûñ²RdØMbLÝ-Ɖ‹OÝ@’á>,ò:?ŠxJ/+…]¿Á¡!nd5’œdŸ#F®z×újô9‘\¬gX<%}H燑ºeX #9|Éœ\¾b%Å›­Ý–\eò˜”€”€”€”€”€”€”@j“€Ã' õ³;2wjŽ#d%F÷“À«ÐŽŽDv¿–Jž#G"s'_£û}îU‰Ñýî;G8zú%2wrò‘6JÉIG“x¥% ¥Wºù%x))))))ä$ ¥ä¤#I H H H H H ¼ÒŠÒ+Ýü¼”€”€”€”€”€”@rŠRrґǤ¤¤¤¤¤^i $GÉÉܺIÏžý“äÔtéÒ“OÂIòÿ}þœ®G^¥ë,οÿ>§ÂEK²_‰$唌ÇÐù3'(MÚ´Tºìk”ÅÓK9¤ÛV4F‰¾EQ7"¨hq?òÊ–C7lÊ…DbLHx̼)OQܽÊ›¯õõ£Ì™³(—Öm+#îÓKþæsçÉOÅJ”¦,Y²ê†M¹Ðõ[·èŸgÏ”]ã6}ºtTÈÇǸ¯$žÿû/…]¤pÏé… “/û¥Dá7nЃG)·wNòÉ;¥âšÕŽwc£ íh‰Ð—¡OÓ‹DaTó}›"#®Ðý¸»”+ù•®@éÓgPš1î^,=|—,ßyòæ§Œ™2'[F«ƒ"0*¼Ùû.UÎÓz+²Ïù—õIQQt1,Œ²xd¦2Å})WñïGMŸôÃí¢Á=[X•ûÖý×È;WãñSÇÿ¢/Ǧ+¡çŒyHT¯Ù€§~Oùò2æ?fñœ>êA{wm2æ!Ѳ]ú|òÝh‘çùógôËŠù4còhìRÀ¤yÔ¾cžÖëOF(Hógާ•Kæ˜@²;nÊBjÔìm“|‘;¢0ÆÇ?¢¹Ó?çm¨æ?›8—šµî¨ÎšÞsø0µ<Èê5B·n¥õdX7üºœ®±(÷cGö¤Òå*Q1ßÒZ@H±ŽG †(åP^jT¬hR>c†Œ”ÍóÅ×íØXzgȇôðñcªýúëÔºÁ[ìyLC³—/'t~ÇÑ šÔ„§OiÔWÓ’äë•!ªŸ>yb„P¿QcZIõ-¥$…oEaã7"è§]þ ú•®Hõµæ¨ ½pö„plÊDa¬Ó eËñâc@¹¶!;‚ù®^£f¢0Úó.Uã‘Õç\ §Æ}zs–[Ô­GjÕ¤³—/Ók×Ò æS|y©kë¤Ï©V5U”¦ ÷ûà3e×âöÐüáÌëSæ/ÛlÆ®U· ù7¯DGîáÇñR=¸o;W’ €-þeyç4 í×iÐ’zw¬G›ØËvèèÉ”› ëEZcß]ÚÖàìwî9„®œOò…§6å:ZcÄ”êÀáÔ¥×òððä—éÙµyËðÒÙµm½nŠ’(Œ˜* ø"ˆÚ¼ÓÝxOwì6ÞnRžbcîÐ6ꪗ¢¤`|¿CÓï}e×âvסƒ\I*7/m˜ÏFhÙ‡¨q­ÚTÍÿ] 9z”ÏšÅtŠtÁªUü ¯Y:´eï^‹uë‘©õ½ªðŒ¸éA¿(».ÝŠÀ8y܇¼ŸmÚªŸ¶ˆ20Ú•¤5Æ^>¶¥Š¢T)‡z’Öíy—ê…Së>gÚ¢ï9ëïµhI 'L 4iÒðýÂÌ|`¼yôÕ¢EÔ¥Ukc¾Ö8]fÌÃF”@¯½^ÓøBÁ~¦LØpÂH(ø÷Ÿø¶{¿‘F% *U§JUjñc»·oà[wú³#øöï2€V®?D#>ûŠá|1EéN˜Ìy±cÙ U¨ï OŒJê‚­G݆­xµ7o\3¯Þ-öíÁˆ¯òöz›ÜÓPöýʼƱ<¸Ÿ¼½„«ßމå—~ãµJF% ™2YRžG%¶sç”' »;ÙÓŽîŽÅö`Ä”>BAŸN˜ãr%É&ó|{0šŸ«ì¯^Ä“ø É•Ûò”²RÖ[{0ÚSÖX¬]Óž>Ón ¾þþ&ÊPvíy>¦ã"¢nò´ˆ?—)JeÊWæxþÜ´–ϯb†Z+~œÅó«¾Q<³¦êŽÚÃóðugNŠ¢j~Èåûö`³cg1JÓ)—ƒH{1Zªîáý{<ÛÛ[_#`K¼XÊs# JOŸ8Ä«öõ+gé.Ï«\¦ çá·?·l•@xg¯XÎÓu«V%/OÃ( Ï`³ Ïê´Q£(göìJ¶ÛnmG·¦bÌŒÇFÛù÷¢¬^îß~ L{0*稷p”Y÷¿%<«c·ÁêCn“¶£=eÝ cÄž>G±KÊáåe!··7+Xç…†‹ûÐ2õ¶iýÏÜ« vI0„2S³Nc€UkÔ£æmÞ£ÍVSßNoQï£éÒùÓÜXSl“ ?¾b1eÂ49)žtðPÑ“´Ä¨'ßö\KŒè´ŽÙÇÙªR£®=ìiRVF(÷ØýøäI<]¾t–Ξȧ7*V®Au4ׄo{*ùeÓ&:}ñ"eÌJ)J5+U¢†5Mmë0E¨Cóæ´fófjÚ·ì݇þ¾t‘O§ÁÆivÀX“KîeSqPªª”+GÙ÷ÍÛ†b“B:îˆhG°ygŽÚ™Ò¦IËG?K•­D[¼ë’Ñ­1†^<Ã[È+[vfï9‡Nû‹îÞ¦|>…ª­Úw5Õ£9µÆh‰çßVÿÀ³ñ^R” KåDåiÑÖw©(<–êÕºÏ2e ýXéâÅM. /[»ÿð¡I¾–;š*JÊ´ WñSl¦­¡Œ_ åcNܧ@Z²ðkZ¼à+cñ%kB(Á¢|ÿʵÓÃ#‹±Œ’PÜÊ=´ìÆ«”Ój+£V¼iUžÌšÀÙ†B]£vC­ ¤XHŒûvo¢ýMx€’?yæ ]_<‰.ϡ׮~jjT«­šþ eb3 -? ûä§KÓ7‹_8GìX²„Šä/ ãáF&pO=†Ò1' W‘¨vÄGávlùÝÞÂÙ“hÎ’ cbr‚“;¢0ÞcJhÅß&áN2[ƒ×ÐìE빓M’gˆÂhÎ&¼¼]{3?,t_$F[Þ¥BÁ%V.ªÏiY¯>­ZIŸÌø†<³xP…’~t+&†6ìÜA<ɯŽp&¢HÓ®sëÞJ;DQÈÉúýϳ4lôÎ;\OÕÊ2a°½>q/J…úuiÄ=À°o0Ù2yÎb.¹šD`ŽÉ`êeóeô¸gÇÆ!ðÀ©ßé₉±˜oêØm]Ðí¨ëÌ»èM:÷÷1¾¯Ç_½jÕè\ðFŠØ¹‹¢BöÒÉß×Ѥa†—Ãö˜2´Ø„ Œ-_¿Žç•,RÄx¬Y¿~tæÒ‹xš „@¯öoSÕòåå\‘ÕŽp6ؼ/Œ¶ºA{OÆÒÆ+LÑ]Î=z¯G\¥ÀÑ}‡ë&Ïã“„Ž¡\Ū4oq0í;}œ¹Ï?hqöKxFõ QíhÎ;f0 üâÃ¥^#ƒm¤yQû"1Úò.…K]¯¨>ç£^½¨‚ŸÝaÞ¹FŒ  mÛP£Þ½hÖ²eÆËg̘Á˜Ö:‘VË áÞóÝé‹t·¾Ã¹§®³ûÏ×Gî¦z·âÓjS¾£ÿm>Ië¶ŸãC¾x©ôë܈]Tì”p¾¥ÀaøBåÌ—oEÿ‰À(šg{ë×ã…³'iä œµ>ýŠõÛ˧3åEb„wßÇŸÏ  _ýÀ¿Èñ’…í¦?Þ‡³p†w[ÏFx±eËš•<2gæsùC»u7^ïÞe¬*äÈj÷Á`ÞÒ‘ÿ1ÛÁuë ¶I7Ø´ZÓ~})*:š¢ïÞ¥‰Aó ÜÉÄh2V,8!²aä›-»7eÊìAJˆ°$S¿]ÉÁæ ¶gz(Œ™Ø=ÂFs1ÚWy„€‘3H¯P%¢0r‰˜_¾h&߃g±žCqQQm}—&ŠAèµîsÀp^6ý¿sé2ZÌXKø•lÚ”²W¯ÆåÛ\¬ñŇ¼ä]ò þÑŽ`¹lÅ*FÎc,ôGƃ:$œÅ˜-›7çòÞݘ$Ü*¦z™4$a 1ÃYŒêz÷ïÙb4 iûnõ!—¦Åhë»Ô• és¾acùN“¦4þƒiÚÈQ4¨Sg*Ëú¦¿G»Ë•(¡Õ|«‹¢tãz8g¼„_Y#Å Æ‘jÂWMÅÊoð,,q*SÞÐ9>°“ï«ÿöîÚÌwï7õ1=ÓÎbÔ“WG¯¥F„ðØ£]iѶ³qjÖQž´>O Œ–xR/_’^Ç¥/,ñr-7*«êX÷ÛŠ¥J›œ&%Xel\åÏ“‡pÈ[ýð¸BÈ÷-ø"ª¾’¯çVT;bY!…| ¾˜žTòôÜ:‹Ñ7±?>v8$ ÛD òÉ_˜o]õç,F5ß+Ïæ»‘Bw!g1Úó.ufgúœäx^Ìcõs¯Üäγ÷˜¦ŠRÈÎ`ÂR#jBlœù3y–ÚX£F õk—ò­ò‡éµm,dH Ìï8КŸðõÏøûÛ°vÿBÀˆSõZ ”l¡[Q…2mgå¢0ÞeSOƒ{µäö: ›µgËÔ|GiÚÉ¢ÓÅEaüßÊ…„/W ó+ôôéZúýt¾‹©iõ2>JÛM!{èùQSÄÍ›4q~ÏjPÃ0Ò‹ŒV$Ú(ñöO’_·m㻥Šã6I7ÿ­ža˜Ò€‡Ž} T!t+ªOÝOPêÕ„åifNóàÁ¨V~Õå´N‹Âo)ì[”—-ö1-þÇo+d¶|õùVôŸ(Œ ßÀ„%D@»à[½ÿDa´ç]*³ˆ>ïÕçÎXÐÞn ÄWBXÑ$£Âûêå†çÎÅ?Ò•czlEb´ç]*«È>gRP­ þƒš¾ù&·Äø±³g9œá=zP“ÚµEB£tãY»BصìÅEù‹™NY*ŸŽ½p3` Ëÿ¦³eËp\ D%îÚ{(¼°î•Bå*Tå/¬'ÅFà tõòy~ ÝŽcå•%I2°é8„Ø¿ÊêCÝ¡ì°sA ŒOXDY¬æ(Å?¸Eq±®Çþ¿ü|0‹Ûr’)¡„õí@ˆ…}üš·éäÐPxü}÷Àx”ð+ TBÑ…­Žù¯Í»=¹á,oÇŸ»´#¼7oߺAçÏç÷éE‡vð,šÀÖ#¬ùfc;P™}IÑLnT®ú¦%^ìa ’6’…ùû³—Cé8ëT°â6¢hе+Íû¹ÉPõëlº¬ 3ü>yá<_ûø¹stáêU^a§–-iÞ¸@ÊÇâ•X£Gññ4÷§Ÿá>dõ;J7ž<£Ø»w\ÞçÀ€q†ÐŽè›Ðçà™lÜüj%wyÁízMyƒ~Ëz îFèûùœÄ–Šp0ôƒ»<ÀˆÝ1l-I"ã#\ rŒö¼KíÅí.}¦ýW1E q”Žž9C7ïÜa1áŠÐ7cÆÐÀ÷:™Dë¶c4û¨D¨ªÌqÅ¥aÃYÿY;¸+ä0 yœª4èa­H’|T‡—߃¸»|[¦î³ÈÌ0ŒÌÊ"qÃ{-9o„çÏŸñ…s±uÝ´ú޹~ŠÂ/p+ŒI„êdFL$Ãú’ct³v|¤…”ž æÊ•—2&Æ4r¦)ãÃй«—¨Së´yýxá­vïÁÊíƒòxç4/’dÿÞýû¼ãðÊêɼMr™,i’¤°*#ž¹›c±j]:JGâ(ôò·yï³~ £¼™Y 7Œ’k1UìŽÏ#îר›o[úì”Ú×ÝúUàAÖŠÜ #pÙó.µEîÔçã5EbÔRA22ì@B$F°ckÿäë6ŸŒ"úŒ’+Ë•ØÌŒF55æÖˆ'Y”€”€”€”€”€”€”€[H@*JnÑ ’ ))))))w”€T”ܱU$ORRRRRRn!©(¹E3H&¤¤¤¤¤¤ÜQRQrÇV‘yçÊK¹óø¨ O‹Âx76ÚªÛ0ú2ôiz‘(Œjþc£oSdĺw—r±6ô+]Ò§Ï ."4-cܽXzø .Y¾óäÍO3eN¶ŒVE`Txû÷ùsºy•®G„Ñ¿ÿ>§ÂEK²_ å°n[¡íÔ´­é“~ø¯]4¸g «¼mÝu¤yŒÇOŸ8DŸêÅöª1‰Ã©ï OLò³À—õ ½»6™ä·l×…>Ÿ¼@·Z$F{þüý²b>͘<šã ˜4Úwìc‚YôŽ(ŒPæÏO+—Ì1ewÜ”…Ô¨ÙÛ&ù"wDaŒDs§ÎÛPÍ?0~6q.5kÝQ-4- £9Óû÷l¡aï·çÙ9Ùó½…=çz‘(Œ¸W›ÖJúa§àúzîjjФ­²+t+ £Â4>RgLþ˜„lS²ø¶H1?Z»å”Iž¨QGò§“Ç$Ëöèq3©C×É–Ñâ (ŒàíÔñ¿è˱ƒéJè9V«×l@S¿§|ù ™ä‹Ú‰Ñ}Ak|š*J ì%BgY±rM^3dÌHž^ÙŒyáW/RŸ÷êóýº [Q­:éò¥³´vÕ÷´`ÖÊ—¯ µ~§;?þ 9´o[~Ã,\œš´ôg_séhÕÒy´qÝJúï¿ÿhâ×?ë™…<_ºpš¾DgOÕ‚¶-¢0îÙÌ•$( ›¶ã£Hׯâ_íŸ íB«6¦’¥*h‹ÝJm¢0îÛ½…+IÊ}êɰnøu9] »DcGö¤Òå*Q1ßÒV¸J!ÛMÚQÍåÓ' ôÕÄÔYº¦EµãÓ'OŒ8ê7jcL+‰¢¾¥”¤ý[7jGŒöô¯ËŸA¿Ò©~£Öü¥ŠÑÐ gOØÍÁ3Dµc-([Žœ¹ ÙÌóõ5…#CØû£Ø¯W{“)ðí(-›qYþÃL‚â8¦/-X¶Å¢ ´Î…Ñ}AkL¨OSEIaÚy¿>Sv-nÍ›Ìó[´íL¾ú”(à>ù Ó¼ãhQÐjõv7žpßv®$A[üËòΙ›Ÿ[§AKêݱmb/Û¡£'ë:ä¯5FêÒ¶ÇÕ¹ç6y>É?èÈŸƒ6ZcÄ00F »ô§Y¥gÿQÔæ-ÃKg×¶õº)JŠµÆˆ©Ò€/‚¨ Sò•©™ŽÝÒÛMÊSlÌ:Â:.‡%7iGEvØ®Z6ã…d>Ú«.':­u;*üâÅ3=èeW›­µãäqòlÓVhü´E”!CFm0:X‹ÖíØkÀÇ9ÁG©¢(ÕcÊ¡ž¤5ÆCvð6ÌëSæ/ÛlìwjÕmBþÍ+Ñу{øq=ÍT´Æh¾ ¢-]fÌa4—þF% ûí:ôƆw¾°]ÿþßvï7Ò¨$!£B¥êT©J-~l÷ö |ëNö`ßþ]ÐÊõ‡hÄg_1œ/¦(Ý “9/ö`,[¡ ŸR…-šB°õÀˆ"èfb{+ÇÜekF|•·g÷°¢$:(¿2¯q8î'o/á*Ìö`Tx„-ÂÜéc9¾Ã•l·Ý:‚ÑmÁXaÌŒ˜rÃG(èÓ s\®$Y”$ÛŒINNÌX½,ˆ§ðA“+w>kÅ\–oÆ6¢zíõš&ýN¦LFþ1ëândF{ÊŠÀé2EI±K27TÆh¦-@áW/ñí±C{ø_wæ¤(Ja¡æ‡\¾oF0;&p3 ¬¨=ߟ{1Z÷ðþ=žíím)´TÆ•yÎb„A©ò ûú•sŠ›µã¬i;ÂQÓ)»•é ÇÁj¦³í¨GnÒŽÇïåÚù÷¢¬^Ù5ƒ'º"gÛŽ2ëþ·„³Ù±Û`Ñì:T¿=Ë”¯Ì¯ñ禵ÜV ;p’Zñã,ž_õzä™õ…Ù Ïtƒ?{0ÚSV4!So›Öÿ̽š`—ƒ@(35™ ’š  <¼ŸŠ—(£>ħÐpì!ó¢&Œ) †ÍIñ¤ƒ‡Šž¤%F=ù¶çZz`D§uüÈ>ÎV•uíaO“²"0¢“ºÇîÇ'Oâ¹ÝÝÂÙùÐwÅÊ5¨NƒæšðmO%"0e/è˜ËU¬J­Úw¥Û·nØÃ’æeE`“Xÿ4fhgJ›&-÷Ú-U¶5nñ®KF_´Æzñ o¯lÙ™½ç:uì/º{7šòù"¼\Ñ®ê‘QÍÍB…Zc´p úmõ<ï%EɰTNTžÖ«Ö¨GÍÛ¼G›7¬¦¾Þ¢ÞGÓ¥ó§ùT8ÌU&FÏDá±T¯ÖmÕ,ñ¢Ež¦Š’2ÔÃUüÔ„ùÒéAk(cÆL<»^ÃÖüáœ1yº÷¤’l$%æÎ-Ú¹õw£—ܨ\;=<²¨«äiÅ­üÑÃIމÈQŸÎÔ©'FPרÝжí:W$Æ}»7шþ&ü@ÉŸìÀIfkðš½hýÿÛ»8)Š¥_’³ÉùŽ ˆ ò@@‘(ˆAP D}„Cå)‚P "ñA‚ÈÉ‚HFrœsŽ÷õ¿–^f÷v÷n÷¦æèúývg¦»§§þÕ3=5ÝUÕl£€Í R½Ù„7£Æûz‹ŽÞ٢ǒa_–-G3¼?ÖÏcÌ”¥”=g^÷±ôŽƸê RølU”žT®ˆ³ïRŠOJ’4éÅg}Õï#6LF¾Ó¡'ci®ŒxaØ £:ï—Š ¥Êj÷xK)N M“÷õ¸dá/î!ð¨¾# ñ¼’óE¦†MÚpü–3§Oð½üè!å]Tž˜N°Õr‚¤0Â3nÈ[Q¬Ä“N@ñ{ )Œ°£›»ü9J®bìœWÓÃëÕÈç—}:óHxT·7éûÉ‹ýòeg†ÆkW¯2›l×ù*©Là-ÏÍ®m°ýžÑJÏ×±ŽÏº¤0z_ £.P~ñáRñ9—m¤w©cIŒ0Øžyg:z ¢UãçTŸó³ŒI‡AIaŒ«¾àƒ%[’lU”ðY§Ç0\Ö¤e'ºvý*»ü/þß,·¢”ñ‘,4vê2‚§ÓŽ­èêÕ+l›T¼äÓ ÊXçV8ÌÛ_ ”u‚$0:Áw0×pãö-¨K›ÌÖ{õcCÄ`xŒoYIŒðî{¿ç7‹'ޡޭêòGÁGšÐÏóÿöp`p´yG#¦¸‡ìÍÜm;õ¶™ãà«“À¨¹°ùfN‘’Ã’ ˜æÛMª²Ílϼû#}®[)ŒÉS¸‚,bŠÍ:š‹0rFX „*qBQ’ÂhmL‰ÿn 'Á³ØéiE)Œ­ZLíZ¸”¾¨ÏG°·ø‘Cûè“¿Ão­^{Žãa9V c\õk{Û¹o«¢ä±â—á¬C*f‡•àŠŠ˜HøiBÀ>(I G•á+yVØ)ÁžEzëò÷ïá]k§¦óœÜÆ£“|ÆçZva<°o7uhåŠMƒPø… Ù…ÑŠ‘;wïOmšUçÑÜÇHK(ŠFÄeÁ9è…gòÄ€€çô©È”„@wCÇz‡QX0!>±U¤øÝÑÀSªP”üñ_ŒéÒeàªÏž9ãzºÆ)“† ÜIˆ/Fk½ŒªGZê¼ÒÌš• ûñÅ8T…Ó!p¦Ž=˜#W>ž6mZ¯þΙ1‰š¶J¸xgñÅ|qÑPN‚Q”+íôhÁ"±bСÐÑêѤÂÅJnòÕüî +Z¶h.ïjï7îô6¾æ7”ëÙ.å­›UcÅ1´:vû<VÄα£/æ¬Ë—$qpé _¼Äcæ,9Ø€Û»Þׯ»?p0•“3O„wGãƒ1£XVHS¶œ1EçÄ6¾#îôÇkW/Á.Q‚×.!)¾­¼Oý5" Kº‡]J¢5?¡öã‹Q{ÔÂÑÀJ0]Á ¦É±\TBR|1úãÝ—¾à¯l|ÒÅçdïs—þ>›°Ôˆ•çÛQœdÞ…A¨wl‡?W,¤/z» ì0e§ ý )ÿ7Œ×?Ó鳦Žã/Œ8=U®’NÝJae:ÈÊ¥0žQ£ m›×$ØëT©ö’ ­?"Á ¥0þ4q8+õæ×týú5;òK>ÄÔ´u]Fb+6IcZã7`ØT†€gùXzÇ ’À¾×¯YAPê­„ÑîŸÀIð`´*¿ÖrvïKa„·ö-úe‹cL‹ÿòóì*[¾gy+ý'…Qó L 5|ýÞ:ý'…1B­ š9u¬$˜ªÌWž© ƒÜzÔûÆ`ô…ع ¾„m#JXÊ@e#€ – 9uRy±ÍŸÁ\¡1­QR‡*åi¶zË?[í6­_å^º£Y«ÎôLÅjn4˜3ÇùÐŒÕ.Íaöa€Q&ÐûÊóƉùfIŒÀåK 3ÐÚ;1N&L‹üÂií»ö¡xÅááZÿIb3¢¿{è{×ö¿©yƒ 1˜ù¤ÿèá"bŠg‚$Æ%jY¬™…/¢$I’Ð*õåä”!‰1žâ·ítIŒ¤‰5Âà&—ÌåKù9ÔÓŽ½”-ˆ$‰}IeµÜúh,'…u3ñB‚·ñ•œp<ĨÛhòx—âïk,¼î4IblØ´ õê Œ¹Ç²Ò‹Pþèm®K–€£U»î¬8q‚ðŸ$F°m\ÇŒÒP â²*š:ßî­$FëòÚVÀ›õK“$Æw:ôR†Ú‰X‰×÷)ð`:ê]¥èbJÙ ’Äè‹ÿÄJ!%qp IŒX‚†ëmÁOÓóªoj«<ÄœZ•]#0aTÓPÀˆ Œ 6mÙÙ1ÛAiŒÑÕï™&oÞ©`°ýIb|åÕ·x `äàÿ° $¼R5Aù}»}GŠJb F_ÐØíÜ>¤¦¿üƉ]´tµòH[G¥*5‹ó5QŒU/œ;ÃËpšf¸uë&O¥áFFàHëÒþ.ˆs0žIyŸØ1ô}êÐFÚ·s}Xaô‡=ÔôSÆ]÷9Æ0kÇkJáCF„ÉÈ”) ¡‰/…{;Â#»V…8XÌáö<žWýFyS¨n%·#fT8¶#î×£G ¼õÙqmÏpkGà%WÞ‹vQ¸a.„±€“A‰žbñi 'Œ¡è qië‹Ç6±žóöÛoû-nÛˆ’¾·…GO\¼zЈÞ^lº[œ“;oÙŽ¤KctD,1=콊Ù©QON=œlG눱'²G’1Ú†¿’Ñ:¸_óæ/$ÛP±Ô.‰ÑN)³%1âÂéÒ¥ç_@&„3¥0ÛÛ ÓVcn;3u       $´Œ¢”Ð-`®o$`$`$`$`$`$¶0ŠRØ6aÌHÀHÀHÀHÀHÀH ¡%`¥„ns}######°•€Q”¶i cFFFFFF -£(%t ˜ë      „­†8°o'3Ž8÷+Üû7CC¼ÇÈoä*þc0ŠHÖ´£íb=´o ×yýôfÛë— ýãÂhú›[Ä<6 ”èAxw=´+V¹ 89qâDºxÑsí¶Xk4ŒŒŒŒŒŒŒî! „p222’Ö¬YC*¸‡äà“ÕP¢û¬(ŒC‰®Æp|²f0úË=—ˆ‘¤Íך>çžk9O†Íóè){õèAhG™;P¥@Ò¹_ò’¾_Äv8L;†]“†ŒŒî EéþocƒÐHÀHÀHÀHÀHÀH D E)DÁ™ÓŒŒŒŒŒŒî EéþocƒÐHÀHÀHÀHÀHÀH D E)DÁ™ÓŒŒŒŒŒŒî Œ£ø·nÝ¢ÐîÝ»éúõëAð¢óE.\ µk×R¢D‰¨dÉ’”6mZ_Å8-˜²~+±)ã¶ÂxìèA:¸ݸqræÎOyóòYûåKhÛßëé!…1²H J•Ú?FTpòÄQ:zø€ª¯ ¥M—ÞgqN|(Î%c”ÀxõêeÚ±u#;{вdÍEy# RŠ©b\Û© ŒhïÛ73ÆG2g§|FRªTiâ)ÌÚÑ æðÁèÒÅó”!Sz$s6k–cûv÷9'Nœ ô7¾(Ož<”$‰X÷éë’œ&q¯ê‹>yœØCçÏ¡Lª F>¦0&ÕÙŽmíÄxîìiºxá\@Þ3gÉNÉ’§XÆîL;1jÞP硃{éÐèöí[”;oõ{Tg;¾Áxû¶z/î§öl§”)SSþE(}†LâØDžô¹sçRÇŽiÇŽÞ}÷]úæ›oÜiˆÑôꫯÒìÙ³ÝiØiÚ´)5Š’&½ûSÖ£2¡ƒ?–þF_öéJûÿqåÔ—iؤ ½ßs€>¤Ë—/R÷÷šÑ²EsÜiØ©Y·1õülXŒŽèÖ­›ôã„oiÀgݸ|÷O‡ÐK ßô8ש»1BAúv`oš8æî=,P{}>œž«VÏ)hîëØñÊ•K4øËžÜ†î‹ÜÁøïOSµÚ ­ÉŽìÛÑ›éKæQÇ·^â䌙2Ó¼û½‹ˆÛÝç\¾|™²dÉâ—ïiÓ¦Q½zÎÞ¯R툗΀ÏÞ§?–Î÷À›'_Aš:ÏÁ@¼êêvcìÒ¦>mXû‡.ïƒn½Rƒ×[{'‹ÛŒn\·’þÓ£-íÙµÕƒï§ÊV¢¨¾#)kö\éÒ7­ÿ“zvm®Á½ì·îE-Û|è‘f÷íŠÒÌ™3©nݺÌg5¨J•*JóKI+V¬ dÉ’¹ùÇ(SõêÕiùòå<ÚÔ¨Q#Jœ81}õÕW4~üxŠŽŽæ-N¦¬û‚;KþB]Ú4à+Ð5“Àˆ‘Àöê=ƒ~ô‰'ËS¥ªuyvfü¨´zå"Šú % 7Ï[zGã¾½;èÍFÏ2ëªÔ¢rÿzžvïÜBS'¤aƒ>¦¬YsRí—›ŠA ™Á&ƒ 8‰aêœ9sòp5FŽ0‚ä0ŠT»vmþjÛ¼y3eΜ™‹®ZµŠÊ–-Ëû‡¦ìÙ³óˆS\Ëú»ž¿ô`Nâf¬QáQ¾)1r„$„Q¤÷Þy™ð•ýÃ/k)CÆG¸èæ «©Eʼ?gÙ^÷TÅS‘)9íµ7Ú«¡Åmü…gLjҩƒiß®õTªR3¬z¤Kaܺy-­P#q›·çaS\Ó‹/VvMW¾Ó¡'µj÷o^âzl`4)ŒPvÿÞø½¨Úĉ]ß!¸V½ªÅèô©ôAÔ ªßø¸Âò(.íhejìÈÿª´„î÷øŽ(pRªÏ9}ú4eÊ”‰*T¨@K–,±BŽ÷~8õ9ï¾Y›V-_@/Ôj@½¿øNâßý˜ÐpyýaÀsÚ¸NΞ»üÊôHVEý¦‡ ƹ³~P#--(K¶œ4sá6w¿å¢~õÇ™ÿÅkÇjîá h¸`ÄHÒÜY“©F×èã~£è¡‡\vc†÷§!z±ÙËÏóÿv§ûÂâ/Mœ X;‘¿“CIŸhBQ’üÕ(] ã)5¢*ñDY~'yrׇ7ò0CãIaÄ´¨~ã·=”¡ºª¯a:¶KRd«¢´`ë¡ ¤™i ‹-â](UÞ¤¥;]ö?Á”õ®ËîãÕüÎUÖkÔ2Öª×þ¹„Ë`8Ô›´¢tàŸ» òa´/Ú„&IŒ¾°]<–“3dp¸ù*cwš“aPªôˆ‚Eí†â·>iŒƒ¾pÙtíþ%=ìgzÃ/s6eHõ96±gK5Rí¸nõ2æ¯nýæ”&íöðj%R}ñG™?ᬆMÚú*"’&…±p±’ÌïÿæLeóÜVFϾÄ饟®H©Ó¤ã}é?)ŒÚ.ÉÛ± ³40síÛëÒ$0Új£Ï5еk×(**ŠÖ­[G—.]âù|>>ÿüóœíöøq—œ+WL#3x”€àuLY>Iøžk ëׯÑð¯?¥í[7ÐÕË—”çV$U®Z‡Êé•àÇNbD§µî¯åŒ¹T™˜J³”0$1¢“:«ÚõÚµ+<>üëOx²xÉ2jŠªº¤õJb\£>Ð1-^šj½ô:?v8ÆõHès¬|¯_¿žêׯÏ6è—à•Û°aC{Kky‰}©vܵãof7mº‡iÒØohãÚ•tæÌIÊš-áåŠvÕÓǸ¬uJa´^Cïÿ[µjåÁïÂ… ièСԧOêÞ½;=ëA@¡Ô©ïNÃè“R¥JÅ»çÏŸª¬>_r‹;F×V‚ÑÜO‡S›N½éÍ6¨)È»S,)SºðXËkwøK/X“ÃbßIŒ0ÄÁÃF+™NAãòÅs¨sëú0 ,6p‚c/\\ ãÍ›7¨ßÇ_·^ƒ(‘rÂH(’ès€%yòä 6PS§Nõ€‡ÀyóæQ<Ò¥¤Úñ¬RŠ@¾ÿ*ëp6ùmöúú»™¬$Æ(`s‚Fo6áy«ñ¾Þ¢£w¶è±$FØ—eË‘‡`³3zX?7Ž1S–RöœyÝÇÒ;R+V©ÍÊü€Ïº*S†ÔT@ͼœ:qŒ~ÿmºÛ«á¤ÈÖ©7sîýøC\¤õîÝ›ùïÑ£Á@[b!ñæÍ›œçï/˜²þê°3†¹ ¸÷Oœ±ŠVm½D‹× ·Û÷àôoõ¦ÇÕœrHP rzÚ)ŒðŽÐCàQ}GÄ• )3IŒù" ³‘?î‘rª2ŒãG)ï¢òƒö)LÚž&pCFØŠb%ž Ž'Jô9à pPÂN:¥B|\¦C‡Ñ?üÀÎ'{öìá&vð—:¤îÕkw<û0*8dôlZ¾é,ýñ÷yúrèf öKxF )ŒÞ¼Ã ׇKÅç\¶‘Þe¤Ž%1®Yµ„fÞ™NÄG§¦VŸSñÜ6éCñ­Ææowe³ÌÒà#´NåHvˆ÷Ý7¦dÉ\7îwlU”t È÷Þ{üqþI“& õìÙ“ô<ÜÒ¥Kç†pîÜÝ‘ˆŽ ”5kÖ Êêó%·0\5nÞ t$[¶ýÈ=Ŷ[ i[ç„}<× (ã#YxNN`ܾeƒ;ÄÂ{õcCD'e ‰AÞà ï |‘ÿºtû!ÿ¨SÇ +%0bªxèÀÞl Þ¶So'›Ìçµ$ú}¡lÙ²QÆŒ9¼IŽ9!L¦Lq)+W®d%J—•ÜJ´#øEH¦Ø0š‹ \å"FÎ ÛC•p­1ÿ¤0Z¯„)ññß ä$x;5­¨yÂø×ªÅÔ®E-6õˆú|ý4wÍX°•§OñÖêµçܣ˚©­F¼'ÇN]¦FåÇ”¦W›µ£.Ê6#fÚ®Wò]j«¢¤]üáZk%(yóº†ÿ áXsÓCçÖòˆæ BGLYkRûÚÅÿü¹˜³«¡OæJÁ7\¥Az8’îü!š7È)‹;—ÓFã}»©C«™„ ÀÏi’ÆhŃȿ»÷ç$Ì¥ûº¬åíÚ—À¸aí þ"Ç—ã Ïä!„´ÀO‡xÀŽÛ¾QÃ.ë‘ès]ðÉ'=ꚆTÞŽ<‰v_éÒe`öΞ9ƒM=]ã”i€F+0FÕ‚ë¼·P)Öóã»/…q¨r!p&b a&G®|ü‘¡"WãY3cR|ÙÓùRqq„®@,Âv]>e% ÊRDÁ"î³GelU”0ŠBpI+aêI{°åΛ³J—.Í[íµb-¯#ukï·`ÊZë‘Ø/tÇÍ‘P­„ùÑû\l0†.VŠ·Ú€îü-[4—÷´÷›5/¡÷%1Â¥¼u³jüõƒ˜»}ž p%1úd]¾$ÉøJ¾ÊÙ™&1s–lÀéëOÕ¤çÌa'¿uIõ9þ.¸ÿ~w–þøs'íH´#XÅK´võRÞZÿˆ”-»«¿æÁ?)ŒV–'Žþš†%ÝÃ.%Ñš/½/…Q{Ô*âzÿj!,^òi>ÄrQNF¼ë<úlÅñW>Ôt[¥jÕª1#FŒp{µ!Ac˜¼xñâ\¦qãÆ¼2dY;ŸÑ£GóÒ'qBToP0eùÁ¿²*"(hšòœ8sÇ«Å/ =ˆ,Æeà‰šòÃ8°"¨¿YSÇñ— Fœž*WI'‡ÍV #äÕ¶yMÂpp•j/©Ðú#ÌX # úñåŠa~Mð;òK>„mD†;#:_j+6IcZã7`ØT†{ù”êIõ9Ë–-ãµ*­àÁÛ¹sgNBP\˜8Aí¾á-‚}‹~ÙâÓâ¿ü<»ôdÙgy+ý'…Qó Lp¸5|ýÞ:ý'…£F ™SÇz@‚ÉÇ|å™ y5c?ÂïXP®XH_ôvä7iér.‰ÃÐJ$ í4ßgaÝ6x¶é?:†ûgŒB6$Ha\¢–EÀ½…/"ÜëÔ åä”kIaDÝáBR}·~ÈË+UªT‰Ã›`­IÀÕÆãø sФÚ}IeµÜÅïógðòp>À Þn ÄWBX'H £æ}òx—âç ,¤š$…±aÓ6Ô7ªƒ2æËJ/”•3§OÐÂyÓ&\ë+©Ð5NÆ¡£h¶RÞñ¾Å`Ħõ«ÜösÍZu&,%&I¶.aF·mÛFmÚ´!$i… ¢AƒÖ~³£-ZЬY³ÜÉ¥J•¢^½z¹×‹ÓÁ”ÕçÄeìr¨mûöîÀ7¥¾< º(;ïƒbôñGo“^W廣U»î¬8éó±­¦l>tì%kºÞÿnÒBwDo—m°K_ N ŒXêK^¢qÓVP‘bO*â3/ØPû¨D#–/ñM·2¬™ÅtÔ»JÑÅq¨.íè‹xzÖ¬Á âìÅ®)h_åbK v Ô'ÑçLš4‰½u½önР}öÙgñ N}Î%eO9ðóÜÞ§'F›¶ì̶ƒ¡†~—çx0¢[¾xzì²wŸ]aH ãô)£iäàÿ¸?Ȭúƒò l”Q§Çu.¡¼wïìiW†wn뎽è¹ê/³Mp\1y—‹Ë&¶+Jš ÄJ‚¡6l’b¢Fˆ€½{÷òºnv–Õ¼Ú†Òiéú.¨ˆÒ0ÌÅÊÌVoÝÞºu“ Ì¤‚cÅVÖzžû¡¼`õuÔzv;㾞Ƈ­ÆkW¯pÆ$I“ª5òP²ä.#ëuƒÝ÷v„''FHã³^X(Š’–£DŸÇ”cÇŽ©g6{îbáîøR8ö9¸_9ÀÞvL ‡ò‚Õr•zQò)õeâ½ 7Œt^½“N©wR‰^`Ö‹P‡F¼C±6(_lÖ37¡à²žEÉÖ©7ëÅÓ§OOøÅ…0=Q°àÝØÎ ¦l zìÈC8uïêþêÅ ›;oÙa›n0z6M\Û2„ 9ÑŽ:ˆjBa–ès¿p!©vÄýš7¡°€)ÑNÉ!I`_éÔ; ¿p »1¢ï ud,¾ò°Õ˜;¾Ì˜óŒŒŒŒŒŒÂIFQ §Ö0¼      „•Œ¢VÍa˜100000' E)œZÃðb$`$`$`$`$`$V0ŠRX5‡aÆHÀHÀHÀHÀHÀH œ$`¥pj Ë‘€‘€‘€‘€‘€‘@XIÀ(JaÕ†######p’@À€“'N$„î7d$`$`$`$`$`$`$p¿Jàí·ßö -`ÀɇzˆOĺm÷+mÚ´IEú¼ÎkÓݯ7nÜD7n\§ìù'üÛ(.>}NÚ”‰øýHÆF)tLÞ½#è{‡Õ9}0†,s¢‘€‘€‘€ŒŒ¢$#WS«ÓpÍ;}Ug¯÷ `tV¢æjFFF±JÀ(J±ŠÈ000000xP%`¥µå î{OfêíÞk3ñ‘€‘À=/£(ÝóMh      HI  ×[|.zëÖ-:pàíÞ½›Ýï#""(22Òo•GŽ¡ýû÷s™ôéÓû-wáÂZ»v-%J”ˆJ–,IiÓ¦õ[V:C #øŽ«<¤1ÞVíxìèA:¸‡È™;?åÍ_ÈïeOž8JGPe RÚt¾ÛñêÕËÊË`#;{вdÍEy# RŠ©üÖ)!î¦;·ofŒdÎNù¤T©ÒÄJø]ºxž2dÊBdÎfÍrlßîçñĉ„þÆåÉ“‡’$ë>}]’ÓìÆh½Ð±cǸ¿>sæ eË–J”(AI“&µqdßÎ{õÜÙÓtñ¹€|gÎ’’%O°ŒÝ™vbÔ¼¡ÎC÷Ò¡ÿÐíÛ·(wÞê÷¨Îv|+‚ñömõ~ÙOÿìÙN)S¦¦üŠPú ™Ä±‰<ésçÎ¥Ž;ÒŽ;<¼ûî»ôÍ7ßx¤Ý¼y“Lï½÷§1‚Þzë-28@àËW_}•fÏží‘×´iS5j”ã´F‹«<<„ tðÇÒßèË>]iÿ?;=®Ð°Iz¿ç´[·nÒ¾¥Ÿuãô—¾éQ Ò·{ÓÄ1ž÷@ªÔi©×çÃé¹jõ<Ê;q`7Æ+W.Ñà/{²,¬üã¿?LÕj7´&;²o7Fo¦W,™Gßz‰“3fÊLóVì÷."~l÷óxùòeÊ’%‹_¾§M›Fõê9{¿ÚQƒÛ¶muêÔ‰æÍ›§“x‹ð0Û·o÷H“>°û^íÒ¦>mXûG@¶»õH ^o°Œ™vco×­¤ÿôhK{vmõ`õ©²•(ªïHÊš=—GºôÆMëÿ¤ž]›+Ep¯û­;EQË6z¤Ù}`»¢4sæLª[·.óY£F ªR¥ŠÒüRÒŠ+(Y²düoܸ‘ZµjE«W¯öH÷>@@ÈêÕ«ÓòåË #S5¢Ä‰ÓW_}EãǧèèhÞzŸ'u,¼ÆUR¸¬õ.Yø uiÓ€“ž©XðÀ%O‘‚Hï¯ÌÛ7QŸîmh˦5Ö*bì/Y0›•$( U^¨Ë£H¿ÎœÄÁ¾>ìИ&ÍZM =ã<© ŒËÏc% #oUkÖ§Ô ë¬iãYÙìÑå Š,ú8å‹ð?²k6J­<^¿v•ú}âúȱ¦;¹/ñ<^½zÕ A÷gîµS¸paë¡ø¾F0½wï^*S¦ œa XsçÎÍ}ѺuëÄqY/ q¯þ«R J—>£õ2îý¥ ]ÝI’87j&ñôÉãÔ¾eîGŸx²El+… ÇUbàîTŒi£ž]]£A9Â’&__^ë”áì×Þh¯†D·ÑKçëâ[ Cûoܼ=›"ó·»Ò‹•]Sy‹æÏtLQ’ˆ)Çî}†Ò‹ê¡MœØõx5lÒšêU-F§O ¿TDz¢ä!ÍØ¤0Z¯Ý,þ."‰ñwÞa% £õcÇŽñ1ë'»Ó¥îÕæï¼ï“U|ÜiE©âsµ}–±;Q ãŸ,d%)K¶œôí¸¹î~§\…ªT¿úã´fÕÎǪ4IaünÈgÌz:¯ÑÇýF‘^5$[öÜ4d@/únèçT«^wºÝ8m5æž-.g2dÈë¡ßýE‹q0o‚¢„)¹;=íh¼ËÙu,…üÅUvañWÏê?~ç¬zZú+â‘þAÔ ã`.ž?˧dÈðH°§†\ÞIŒ0(ÕzDÁ¢!óLAöuÒ}á² èÚýKzØÏôFè`ãv¦äó7äKIa\²d 3ß²eKzøá‡å¸‚ô½j½4Nfü4†“6ikÍÝ—ÂX¸XIæûs¦Òko¼Ë Ómeô<á{W¿\ú銔:M:Qlºr)ŒÚ.ÉÛA(CÆGfÈß·w'eÏ™W³bëÖVE Þh k×®QTTaŽûÒ¥K<ŸÃÇçŸ>hæ¡ ?îÒ˜såŠi艹ƒ+<>üëOx„®xÉ2ô¯JÕCç:È%IŒkþ\B蘋/Mµ^zŽ;:®xœ)ý<®_¿žêׯÏ6èkàiÛ°aCGG_¤0bq^¼Œ Äv¤èGa£T©R%jÖ¬™cž}’÷ª÷íõóäQœôx©r¤• ï2ÇRK—©HÕ_lDsgM¦–¯V¦­»ÑÎm›x*ÎÝ?užI`ò®S £V†à-ÿQOû@xÙBQº¨¼n¥ÈVE .í h[iáÂ…4tèPêÓ§uïÞÝšëþÙ³®ÑLúî”>1UªT¼{þ¼œôµ°•Àh­?ö¡¼€` m%þ4q8µéÔ›Þló5+ä}âòä+h«C’—/žC[×÷`öŸ œà14îQ .AŽ(Ia¼yóõû¸sÜ­× J¤+ФžÇäÉ“3$ØM:Õ>á!V @t©)Œ'Ožd–ÿûßÿÆ`N2?üðÁÓ¡X¤Iê^õæž·¾ÿŠ“_oÑÑ;[ôXcï/¾£l9òИáýiô°~nc¦,eq_IJ#…±b•Ú4iì7Ê«º«šêOM”‰Ã©Çè÷ߦ»½Ž@Šl}Щ€à²/1Äü@ZïÞ½9½Gtøpp_žÚh Àu>¡IcBcò¾> ò@5ë6¦‰3VѪ­—hñºôvûœþí Þtâ¸K)æ„ÿࡇÀ£úŽP_¯ÎyŸHbÌQ˜ à!?T‚Ž=DoÔ/O[7»F]CYP§Ia„§ Üþ¡X‰'ƒâÉîÂRÏ#>Ê  œ:uŠ*àСC¬8À¡dÏž=ÜÇÙÅ_}R¯\¹Â—|ê©§ØV ž~ð0ÖÆë°_‚·$u¯zóŽQ\ .ŸsÙFz—‘:–ăí™w¦ñÑ©©UãçT<7×È¡N“ÜJal®œ~`ÿ ‡|„Ö©I-V¤qß pÃI–ÌõqãN°qÇVEIDL$xpáK$Mš4Ô³gOÒÓf›7oŠýtéҹ˟;Ó(kVg ò$0º†ÉŽöŽhܼ!£,±eÛ¸ƒ›»wü/n·oÙà?ðÞGýx^=^y²$Fx÷Á[Þ_7“~]ºÇýÔ© ‡³’ÝŠK`<£¦‡ìÍÜm;õ‰/;O’|t1cÆŒÞ$GŽ–dÊ”)Ì>¼o¡D9AR¶„)6˜E` ¡?" yóæœ[è.d߼êͦÄÇ7“á¡«½R½ËIKaükÕbj×¢+QŸ Ÿæn  ¶l“ðÖêµçHôHaÓõJaÌøH;u™•OPš^mÖŽº(ÛHŒ˜A¡ŒÙª(i·ýÓ§O{ð‹mÞ¼.#+_ÊŽGa¯œ«¿é!hkDþ¡Ss‚$0:Áw0×€èü¹˜í˜] ï‚â3|`ßnêÐêE®¡ðsš¤1Zñ òoçîý9 séNuZ7¬]Á_äør|á™<ôTdJþéøâCZÛ7jXE ¶ïôóøä“wGÐŽuMQ‹»S±Æ 2ðôœG¾|ùøÐ)“‰{ÕŠûŒªƒçÖy¥™w¶ø±Æ¡Ê=„À™ˆ%„Y˜¹òñGZ„Š\guÎŒIâøp)Œ¨;iÒd›®]—OYI‚²Q°ˆ{ÄìÑø8ÊàÈVEIÇBpI+a N{¥ÁP0X*]º4Ÿ¢½?¬çëHÝ:L€5Ob_ £¯¡ÖYèŽ+;¢½Z sÀöí⤬ÙbÖ[ËúÛ‡KyëfÕøë11:vûÜ_QÑtIŒ¾·._’äN|%_åìL“À˜9K6à†·õ§¿êÀ?Òs扰Šßºœ~±Ì’&ýñ§¥¶R‹+Æ,/^¼8ëD ÒÎ21 Øœ q¯z³8qôל„p&év)‰Þe$¥0jÚBE\q 5LE/ù4b¹('H £?ÞgOÿ?ÎB@dIÏ>[¥jÕª1ÓX†D{ª!Ac¹xq×0Œã_ãÆ¹$Üç­ÕèÑ£y™Œ8!¸$…Ñ Þãz²*ê)hšò9£F4Mûq+8^-éêdu^\¶¨«móš<\¥ÚK*´þˆ3–Âcw|¹b˜_¼ÇŽü’a‘Ay¢8Aa“4ö§e1~†MeHð²A>–°q‚¤žÇeË–ñZ•V ðàíܹ3'!Ð-Ì œ )Œ•*Ubö~S‰šà­Œà“ Ê•+ëdѭĽjeSýpF5|ýÞ:ý'…£F ™S]m¦qa»ùÊ3äT[)Œp ñŽõ犅ôEo—A~“–.çÝîm;+DtWx¶” ,W‚µ&Ñ/iÃj|¤9ERÑ·"lËÏ?ÿLåÊ•cõ7n°Ñ:°!¾’uªQ¯ô½:y¼Kq‡sRM’ÂØ°iêÕAså(ÜPVΜ>A çMg˜p­¯¤Âº8AR‡Œ¢Ù?Oàw>Ô7­_å^2«Y«Î„e¶$é!¥¥ùu:^³f á×’`Ñ^Û´iC:H$Ò°¸" ¬ýf%`[Gž¬yØÇWžRƒbÔ¢E š5k–»X©R¥¨W¯^¬8¹ƒÜ 'ŒÁÈ#˜‹–®¦[×Q©JqŸ—ÇêÌ}{wàO_ Þ]”­÷MYMÙªÀ6Å}7i!!f ¢8ù_Å8}Ü´T¤ØËøÊÁ1þ½ñ/ñMU²ò‰é¨w•ˆ!âPéÔA…qWÂcôÅ?¼ kVˆ`qöb×ô¬¯r±¥]?½™6o\›à}ΤI“Ø[×{aï ÐgŸ}¯ÐáÔçÀ £dXX\Fèßÿ}^¨kj†BáÒç€wŒè–/žža =Û¶0$áÒçØô)£iäàÿ¸?Ȭúƒç-¼•¡,…Bá‚ñ·ÙS¨{gÏ÷ÞG­;ö¢çª¿¯mâ÷c =ÇvEI7âÁø6IvQ#DæÏ±®›õ†ÒiIcÔõÛµ ¥ÓÒ×¾ ¢fÃø«O[ílt~¸lCy 5ï¯]½ÂA“(/¢L™²P²ä)ôåBÞ†¢(é‹I`Ôuë-bÔÀ“F—¡R(Š’¾–DŸÇ”cÇŽ©{?{8h± Ç>¡`Öoí}z§¡¹¯'1Ø6w3#´s[ݫǎ¤ƒû÷Ð×)gîü”7¡W»zõ2íØº‘Î=EY²æ¢¼)E ß÷êåKhçöÍ\ö‘ÌÙ)ߣ‘”*Ušu:• ÑÊûáƒÿÐ¥‹ç)C¦,ôHælÖ,Çöí¾WOœ8Aèo|QžôH³ûÀvEiæÌ™„›T£F ªR¥ŠÒüRÒŠ+(Y²dnþQJ£—_~Y}m§¢ &ð\ƒ hãÆT¼xq.©êÕ«ó ‘©FQâĉ髯¾¢ñãÇStt4oÝ• ïH`ËÀܪU+Z½zµ0‚Ø«—ÀL›ÇÎaüK,Yø uiÓ€+z¦b5B§’\}Y¢Ó±~I/Y0›•$(;U^¨Ë£H¿ÎœD5ú°Ccš4k5(ô׳|ñ÷iãÆãýnݺ¹•$$<ýôÓT¾|yΛ1co¥ÿ¤0‚ï¶mÛÒ† x¤-аÿ½Š1˜6—ÆøÛ¯?¹¿À¬J’¯ëy¬íbN\S¶¹ _6 #jÎ\SÁÈâôRƒîÎ éè  .ÁE.œ?§‹Šo¥0jÆ9¨¦{0¾Ö£t²£[ÉçÑQ .&…Snø 6,Á”$\_ #êöE_ý5'7oÞœí±|•±;Mêy<¥F”@%ž(ëÑï$OžÒ ³.NFL»ê7~ÛCª«úZ¦ã`»$E¶*J ,`>ã³äÉÙ³g¹=j„ƒE‹q0oÒŠÒΞ6&Þåì:–Âþ† ”vñj=’}ñä«Í}•³3mõ¿suõµ ¹Ú‹ç]÷j† ¬ãÜÙÓ¤ôˆ‚E–µ3Sã /\v]»I§÷=½a'_u9}¯úâA:M ã’%K˜õ–-[ÒÃ?, #`ýR}]Ž2°kµoßÞW‘4©ç±p±’ÌïÿæLe³ÜVFϾÄ饟®H©Ó¤ã}é?)ŒÚ.)mºô2d|„o¸o¯œ`ëÔ¼Ñ@×®]£¨¨(Â÷¥K—x>†Ï?ÿ<çûûà ¼téRÎ~öÙgy Møøq—Æ ¯8oÂ(*NF'øæNbôÕæÁðjÙm¯çS¯_¿Fÿþ”¶oÝ@W/_Ržl‘T¹j*óL•€UŸ}úP÷îÝ­Yû={öäcx[h¥J6 Ã:M§OÔ¡Ο—’¾¶­õ‡Ã¾“}µ¹2€¢êÓý®mŽaüøÓÄáÔ¦Soz³ÍHòI0 åÉW0†Rµ|ñêÜÚÓköŸ œà14î³â@‰®iù@%<ò¤0Þ¼yƒú}܉¯Õ­× J¤+ФîÕäÉ“3$ØÎL:Õ>á!›;'H ãÉ“'™ýÿþ÷¿1`ÀIæ‡~ x¡AI”&)ŒÞ|ÃóVãíܹ³w¶è±Ôó¦{ñeË‘‡Æ ïO£‡õsã3e)eÏ™×},½#…±b•Ú4iì74à³®jª?5P&§N£ß›NÖþÁ°Ž@Šl}Щ€à²/1ÄÃ@ZïÞ½9áöýå =:zôh·×‘6ÚBpOh’À˜Ð˜¼¯ïFmîÍÄ1ŒA5ë6¦‰3VѪ­—hñºôvûœþí Þtâ¸Kñç˼:fü4†S¢úŽP_Ýž±fòEfãpÔ ƒJÐñ£‡èúåiëfר+'û䈒FxšÀ ù¥†oR±O‹ÂÖòR÷*>ÊðòF¼\:ÄŠl÷ìÙÃ}œ­@T&…ñÊ•+|Õ§žzŠm•àécm¼û%<£NFoÞ'NœÈï$ÌN8eÄ­yzQ? ¶gÞé“ðñ¦©UãçT<7×È¡N“ÜJal®œg`ÿ ‡|„Ö©I-V¤qß pÃI–ÌõqãN°qÇVEID xpáK$Mš4„Q=m¶yóæìcŠN»à0€žyæw™téÒ¹÷}C'Êš5«»œäŽFI~C©Û ŒÚ<žƒ=G{€4nÞ ­q¯" d˶F@»wü£Úí[6¸C ¼÷Q?6 ô./9xÒÁ;ãëïfÒ¯K÷¸ò:5ápÞçHK`<£¦‡ìÍÜm;õ–`;¨:%ïU]„k9› (!Â’L™2…ùƒ÷-”('H #p0ņ|Œ¢ÁU}1ŒœAN…*‘ÂÈ îüaJ¼ÿþ|Ïb§†J<óתÅÔ®E-V"¢>A?ÍÝ@3l%Ø&á­ÕkÏ‘é¹# ±ÆŒd¡±S—©Qùñ¥éÕfí¨‹²Äˆ(ÊH‘­Š’6À>}ú´¿x i·oeg×®]# '@ÁÒu8W{€éáY‡-"ƒÐ©9Aà;˜kHcŒ­Íƒá5Ô²0?ó^Í®†°AÞsÞöí¦­^ä<„ À/.”9KvêÜÝÕAc.Ý©NKㆵ+ØÞ _Ž/<“‡žŠLÉ?*_|HkûF¸ˆ&Þe¤ïUoṩéèQ×ô­>–ÚJaÌ!³¬§à¬üçË—2iÂhÅ4gÎwä-\ÞRÖ|é}‰ç<Uîñ n½r,!ÌÂäÈ•?Ò"Täj<«sfLâ2ÒRÁwÒ¤É86]».Ÿ²’e)¢`÷ˆÙ£‚Ž2¶*J:‚KZ SpÚ+ †‚š°ÄIåÊ•ÙXñ—´¶¯óõ¶téÒ¼«=#t:¶:R·ö~³æIìKa”à5Ô:%1ƵÍCå=®çºã®à’VÂ<÷}»8)k¶»Îp…oݬµ!–GÇnŸ[O‹uߺ|I’;ñ•b=)ž$0fÎ’ƒ ¸aÄmýé¯:°Œôœy"âÉ}ÜN—¼W}q€e…4é?},µ•ÂX¬X1fyñâÅ1XG Jv–‰QÀæ)ŒV61[BA(­åíÞ—xÁ£ö¨-TćOó©¨â%ŸæC,»äIaôÇûìéÿÇY,éÙg«¢T­Z5f^ÚS úë:Ú6ò1Ü{ðàAzå•WvIˆ¶í‹7nÌÉpŸ·vT8ˤ`Ä À )ŒNð×kHa ¦ÍãÊk¨åʪȮ i“GÑ5 ¢iÚ£XÂrH׋ùm›×äaì*Õ^RKŒðkÀ Cp,éa~Mð¬;òK>Ä´^å‰âI`„MÒØŸ–Åø 6•!ÁËùÝ?âD’ºW—-[ÆkUZAÀƒW#Ð-Ì œ )Œ•*Ubö~S‰š0-Žà“ |È:AR5ïÀ§"wðc]Fz+ñ<‚gŒfNuµ¨¿‹ÎÑ|å™ r*È­F8xÇ‚úsÅBú¢wGÆ×¤¥Ë¹„þ’ØY'¢»Â³ ÊFjÖ¬I©×‚¢£=×úöíë…›*‚Gz–4)R¤Ï™-Z”¶lÙB=öÃ>C© ¬ÕãÔ|³Fà€· Î×1Nà¶«ƒi~ñŤ¿Q^Ф0ÓæRØt½/Ôj@£¾íËÊO“—ËQùg«sÀ²?–Îç"D rGá3¢¿{-¸]Ûÿ¦æ *èjÜÛOúf·Õ% gê€B·]Ü—«Ô [S ®%…u‡ IÝ«X%k„A™Àr%XkÏ¡6:ÆGšS$…} ¶`„råʱú7ØhØ_É:Õ(‰W £æY˜„B†wIBÔóذiêÕAse£n(+gNŸ …ó¦3L¸ÖWR!Oœ )ŒCFÑìŸ'p?ØMëWÑ–MkR³V KPI’­Š‚–ÁS¢M›6$RÇ«€»?^øXûM“uÝ7ïÅsum¨‘((˜WFØy(P ÄÀèÕ«—Û\Ÿ'¹•žÍ:‡4(‡øü…ãçLÿ¤0Óæ6ÂñYUš´Óѳ©oïܹü¬F–@ðé¢ì‰¬æÆ5y/ž«Ó¯^½Â»ïtè¥"Ç&âQ¥9j=8M˜Žz·k^ON§Io¥0úâ;±RAI,²òUÎî4©{£ˆ)„Ñü4aÊÏ>û̱и®FÔ=fÌž†‚Ç1B€0BÿþûïǰåL¡?IŒˆëœ ®]»ò6!þ¤žÇW^}‹ÃŽŒüŽ'¯TMð¼…'/®íIa,\ô ÿÝ@^7Sã@_ݺc/z®úË:IlûÎòët¼fÍÂ/”HÛˆãkØ$Ù9D˜?ǺnvÔŽíní㢥«U0²uTªR³ ÅwAE؆5VضÚ]‘å„kJqBÆ$I“R¦LY(Yò–ÜÐvOÜHûv­ŒÞ(®^½Ì¶U±ô.ÛñõÓ›ióƵaÓçÀ1娱c¼h7Üúðx^õe§T_–FE↘^oÒÿqI;uHõ9;ãϹuë¦õ? BX\S1£r»Güã‚#P™‹Ç6ñ»#žc눒•D{ÅÏnÂTFÁ‚í®6¤ú¤0†ÄŒÐIF„Å÷_q&O‘’& \H£7¶)Ry'9z,q¯Âè7! ý N#®"22ÒßeM—Àh§‚d‡0¤žÇtª/Ã/ÈnŒPú0˜d«1wB0×4000000’€Q”¤$kê500000¸ç%`¥{¾ ######) EIJ²¦^######{^FQºç›Ð000000’€Q”¤$kê500000¸ç%`¥{¾ ######)  89qâDÝ/uqS¯‘€‘€‘€‘€‘€‘€‘@BK 䀓=ôóŽuÛîWÂ:sׯ_çµé Æ{WB;nܸ‰nܸNÙóy®~ï¶ZLÎOÞJ7ÌóS0÷Xʃð<>„>ç܉=tùÒ…€OXÀÈÜX£ ¡èïgE Ò1Þ#÷LæýÞŽ.ßæPû÷³¢”)]b^ÂÄô9÷Ìcç—ÑûýyðûãƒÐç¤M™ˆûU¿7²Ê06J¤còŒŒŒŒŒŒh Eén~ÞHÀHÀHÀHÀHÀH Œ¢H:&ÏHÀHÀHÀHÀHÀHà–€Q”èæ7àŒŒŒŒŒIÀ(J¤còŒŒŒŒŒŒh ôz‹dnݺE Ý»w³û}DDEFFƨòÂ… ´qãF:uêeÏžŠ)BiÒ¤‰QN' üÚµk)Q¢DT²dIJ›6­Îr|+…@Ž9Bû÷ïg™¥OŸÞqlú‚/_¾Lëׯç6Ï•+cL•*•¾¤ã[ ŒÁÞ×Ò o«çñØÑƒtpÿ13w~Ê›¿PŒËÂMvçöÍtîì)z$svÊ÷h$¥JåÿyÔ>ø]ºxž2dÊ¢Î˦“ÝÚÝŽ'Nœ ´£/Ê“'%I"Ö}úº$§ÙÑz¡cÇŽq}æÌÊ–-•(Q‚’&Mj-âȾñ^9wî\@¾säÈA)R¤XÆîL;1jÞPçž={hïÞ½„ý‚ Rt¶ã[‰>çöíÛtôð~úgÏvJ™25å/P„ÒgÈ$-:ýõ×_ÑÇPÂwÖœ9s¢UhhŽÇïÝwßuŸpñâÅh{—QŠO´ té.§wT‡]«V­å›6m­â ébAoà #˜¿qãFôÀÝ8GŒ4&ïÂã¥K—¢ß{ï=76Ýöhó)S¦x³Ôq¸` ö¾äïKþäçqõö+ÑÁü¾þnFtž|cȽa“6îz–¬?cÝ&z›*uÚè>ÿë.çëº_œî>/c¦ÌËú:ßš¶üÕaÑçà^Õ2ðµ6mZ0MçQ6\îUÍÔÖ­[£«U«/úðP)\0–/_>.ïöçûÉ‹£Õ^ Œ­;EÅ«ÏÑ ÉöO¢™3gRݺuÕ½HT£F R ¥4¿”´bÅ J–,§ãï×_%uƒFš5jÄ#CcÆŒ¡;vPãÆé‰'ž Â… sy„¬^½:©›Á]>qâÄôÕW_ÑøñãIä­»rá Œ`#k­Zµ¢Õ«W #ˆ½z Œ¨S)ÜÖ/¿ü²©HE&Là¯ö 0þâÅ‹ÇΜM%$0s_Û#`5KþB]Ú4à2ÏT¬FO•­DÉÕ×óÆu+=F –/žG?Nø–0ÒTµf}J:-Íš6žöÿ³“ztyƒ"‹>Nù"bŽ_¿v•ú}ò^@¤3%ÚñêÕ«n¶uæNP;ºo²¦IîK`¿}(S¦ ?ƒAÖܹsó³¸nÝ:IH1ê–À¨>®)cÆŒ1®…„Y³fqº“£f1ˆ÷#F?+T¨@õêÕã—/¿ü’.\HÍš5£ßÿݧ $%úœ}{wЛžev+T©Eåþõ<íÞ¹…¦NIÃ}LY³æ¤Ú/7•€ãª3¬Æ|þüùhŒ¨š£¿ùæ›@UGoذ!zäÈ‘<‚¢ âü,Y²ðùC† ÑÉÑ¿üò §!ïøñãîô•+Wr:®wøðawz0;á‚<~:urá%Ĉ’T;*0úÓO?ƨ‹¦}ûö¹qüñÇ:9èm¸´c0÷u° õ—u&ÐþâµÇ£1"„{êýž~uMœùgt÷>C£Wn¹à.‡ó1B„ó?ˆäN·^óÝ®}8ÿ_•jðÖé%©{UMÙ0õâ ¶™b-.÷*­Zµ*ã|õÕW£¯]»+ïq-N}ñŒçT÷·ÊÌÁW‘XÓ£úØd,ÊŒÁã}ºmÛ67F<'¡P¸ô9Õ_lÄXjÔy-úÏm—Ý}Q»ÎŸp:Fš¬éÖ>*¶}1|l5æžôOøzÁè‰užöF?îZžáìÙ³î“ÇÇûݺu£Ì™3»ÓŸ~úiRC«|ùä“Ô£G5Z‘Ú}}Øz¼øâ‹|¬”&wºôŽÆ`îkiŒ¿ýú‡æâÉò¤¦Õ^®`dqz©A Jœøî ³R²¨`á|Þ…ó1í<Ž9Hƒ¿ìA(׺cTÀú¥2¥ÚQŠßPꕨ^¤4þ|fiذa#þ¡ðŸs¤0úãéë¯¿æ¬æÍ›³=–¿rv¦KaĈè™gžñxŸb&G“Rô®èVªÏÙ´þOæ»~ã·I/­†„ºªÏ:°—m—ø@àÏVEiÁ‚Ìb Ååa€á%â"ÅŠs]´hïcXÑ›´¢´sçNï,‘c)Œ`V¢±¥ãAT*‰ÑZ)¶*Á¾ÊÙ™æ$F÷µx|Õµúß9¹^£–¾²cM;wö4é*¢`Ñå}ñ!§uíþ%=œÞ÷ôFŒ“lNp²mf=ÎÕIa\²d óвeKzøá‡ãÌDA)Œ¾x…£Ì¨Q£8«}ûö¾Šˆ¤Ia,Uªóûã?²‰ `ôŒ©7P¥J•(]ºt¼/ý'Õç@¥Mç騔!ã#l.€¼}{åt€»Ÿ¸R< Þh 5„KQQQ„9neÉóù˜7}þùç=®€Æ„gÉ•+Wèï¿ÿ¦^½zñˆTÙ²e sË hÂjº÷á!åM‘¡'H£|s '1¢ÓZºt)³÷ì³ÏÃf¼ÊJbŒË}/æãxò¶¿×sÉëׯÑð¯?¥í[7ÐÕË—(¯²5ª\µ•y¦ŠGMàûìé“êù½Âóÿÿþ„G¤Š—,CÿªTÝ£ìš?—ÐÿæL¥¢ÅKS­—^§ãÇ{ä;u ÙŽÀïÌúõë³ÍúxÚ6lØÐÑÑ)ŒXÔ¯ÚAƒñKý(l”ðr…m‹uÄŸ ýIaôÅ®2gàd|dk%ÃW9»Ó¤0¢­`׫œ x†åßÿþ7ÏLÌž=›03¡L\ì†â·>©>¶“P–vlÝHùuÙ.k&àe‹¼‹ÊëVŒÍË;«í“³ç‹ñëÓ§Çå”a[Œ2˜g…ÝŠ¦Ó§O»Ë¨‘ìÞ~ÿý÷œ¯ŒÝiÁì„F_ü›2L%‰vô…iêk–qÂÃÆIïEIŒq¹¯ýÉ#PºžKmÎ]çkû$_Ï"ÒÚtêížëÇ9†ýä~Öô9Y²åŒžõûrü}>:¢@.;fÊRÎCœã´’T;ÂŽNËÀ{«P¢Õv ¦ ˜.}ì’¼±Ya¿¤ÜÌbñ—.½ùƒ7£¾g¦NêÔq8a„·ôG}£=•±~P˜¼ ‡KŸóÚíÝý ú©™¿oýã’èf­:»1ÿgÀ8~J÷ƒ±m5FoìÖãDêÁ°tÌõ’ç/1Är@ZïÞ½ù°OQF×îë!fl™P^¹§rúÁƒé©§ž"uò±u>òæÍ›îsjGcBañw]§0ÂD=Úà Ëov¥KbŒË}mŽ@õ &¨f]õµ9c­Úz‰¯;Ao·ïÁéßêM'Žá}üå‹(̶L(_®BUN?~ô½Q¿Ë)’ˆ‘|ú[Ø*ÁÓÆÓ§Oçt¤áu‚¤0zóŽQ\ ³uêÔñÎ=–ÄóÝ—ªN7˜«À›Ú)’êsš¿Ý•`GyúÔ êܺ>Õ©I-V¤qß pCK–,¹{ßö«Ö佪Ƭ†=ªÂW FŠóÑóæÍóȳ¨Î(ZÃr9|µ©©þ¢Áyø©à•Öâ¼ÈSvQ1òâ’}ñ#Jv¶£7FÔ­ÛuÀ€ÞÙA‡k;ˆ¯û:h€êýåÛ’Î×#J~^éñ¥¥¦hŒAþߌšå‘§ÏÅö×¥{¢UçÄå´WÉo`O:ÔýÛŠýîszDIò^µ¶ÕâÅ‹Ý÷íÉ“'­YqÞ—{U(ùòPVFÎŒSMãÄ—µ`¸`´ò„÷Žï§ìw¬Y!í‡ FÀ}OªN~o*e>ZMÉq:FÐBõ ‡>G÷I+6Ÿ‹þlàøh¥4E¿Ú¬]t—î_FcD[÷Q£~øÝÝésâ²ÕݶŽ(ic\5]¦úໄ(Úyóæå„@QR!så |µá‹çj0{"ƒIÖ ’ÀèßÁ\Cã®]»8îxR(ù v”•ÆhåÑ×}mÍ—Ú‡¡#èü¹˜Ïcö.Û¾@óú™³d§ÎÝûs°8yâ(mX»‚í–ðåøÂ3yè©È”ü{±²ë+_|HkûF >OúÏÉvxnj:zô¨ÞÝJaÌ!ó­¾üçË—Ó”[yŒ<‰)ŒV^U dŽÓ‡´-\ÞRÖ|é})Œ°I!.!¼ø0 “?~š;w.© ”<‚†xuNDŸ£ùNš4Çxk×åSRJ)e‰" Q+ ¸líõáp¢ÏïÖVEI»ö#¸¤•0§½Ò`(ˆ¬Ë—hCÂÒ¥Kó)ÚkÀz> Ö@ÚûÍš'±/…Q‚×Pë”Ĉem*W®ÌúMš4¡þý]/âPy õoí¨`ׂHÞ(‡àNF'xë5¤0➀ç#ìÐ^yåBû!ÂzBÆ`îkiÜeUôZдɣèŒéÑ4íÇQ<×øG"‹qòO‡ÓŠ%ó<žGxËér1VSuj·Ìl“4ö§eäý0l*×£Œ¹9¯û§CôåD·Rí¸lÙ2^«ÒÊ<gèÀ(šýóîÃð·iý*Ú²i CRžo„å™D)S°Æj¨ ,ªÎ…Èã¼…ZËãR«V­ŠVkÁy”Cyå­¦Ø<Ê↓*‚³Gy#ZyhÄ(LB8aT#cø´üôV}åÍ]6\0~ðÁñ'x …Âc°÷u0XµÑa\ u™)sÖG—~º¢‡Ü±@.²Õe°…Q¤êl<Ê¡=Ô4Zôбs<ÊZÏÓû0üFy‰ë´P¶¡,Š+Ñç`anmø«Ÿ?lÕš„ñ €ö—{¼¨QwˆýšV^Æ(…FåÍ羯•'_Hx|N±˜v˜Òíˆ-œ‚|9AùÂã+-\ú¸þ[qaýŒ»áœJ_£ÏÑ}á×iaG]Ô'­Y³†ð %Ò6¢-Ãø6I†¨á¢ ×[,L˜5kVJ¡ì D€ùóìÙ³¬7PÖ¼pÄhåÏŽ}ƒ1°%îÕ`ïëÀºr-]­®­£R•šÅ¥¸G™ çϲ1vÖì¹ÔbÄi<ò¬×®^áà‘IÔó˜)SJ–<ðóh=÷êÕ˼ Œ.C¥ë§7ÓækæÏc –ˆÀÎý¶cª8ŸGܯ0k€·6yµ q^¸aÔ¡¬ËzÄ_8bOgΜáY DâÆû4¾3-áÔçܺuSˆP!,®Q¶¹Ý£áñmÇ‹Ç6q¿HϱuêÍÊ0¢½âáÆ-P @lÅÜùhø‚ ºrG cBbò¾¶ÁxW"ÁÜ«Á”½{¹=„þ÷ÿïëjÉS¤¤Üyõ•kZŠ©b-#Y@â^…ÑoBþú““F\ ÷kdd¤¿Ë:š.øÂ‰$0]í͘Ðxíîs°%"t'%Jˆ‹šk       Ü 0ŠÒ½ÐJ†G######‘€Q”Dìæ¢FFFFFF÷‚Œ¢t/´’áÑHÀHÀHÀHÀHÀH A$`¥»¹¨‘€‘€‘€‘€‘€‘À½ £(Ý ­dx400000H   Vf¦ã~¥íÛ·Œ÷Aã>íx`ßNn)Äý¸_éÐ~FÓçÜÛ-ü <Æ¡Ï9uâH¬[À€“³fÍâ ‘±Öb        Ü£p2 ¢tâ5l       Ø"c£d‹M%FFFFFF÷£Œ¢t?¶ªÁd$`$`$`$`$`$`‹Œ¢d‹M%FFFFFF÷£Œ¢t?¶ªÁd$`$`$`$`$`$`‹Œ¢d‹M%FFFFFF÷£Œ¢t?¶ªÁd$`$`$`$`$`$`‹Œ¢d‹M%FFFFFF÷£þiÎs9ô©IEND®B`‚srt-1.4.4/docs/features/images/non-block-aligned.png000066400000000000000000000273751412557703600223640ustar00rootroot00000000000000‰PNG  IHDRÔÎF“,å iCCPICC ProfileH‰•–PTWÇï{Ûm—¥ÃÒ;Ò«ÀÂÒ‹)‚e—+,UÄŒ@D%”PŒ†"± ¢ °g‘  |  ¢ò=$‰ùf¾Ì7ß™9ïýæÜ{Ï=÷ž7óþ"Ùññ±°q¼$¾¯³=cSP0÷àq€bsãí¼½=À?Úâ€VßwtWsýó¼ÿj¢Ü°D7Â;¸‰œ8„»vàÄó“€Ñ+§&ů²Â4>R ÂëW9bW×ÒBטûeŽŸ/ á4ðd6›1‰3R8Hb ÂúÏÃgá‹ñøËøü4~™ BP%X¼\ÂNB¡†ÐI¸M˜",E‰êDk¢1š˜I,!6¯ßH$%’ɇEÚG*!%õ‘&HïÉbd-2‹¼…œL>D®#w‘ï“ßP(5 “LI¢¢4P®QžPÞ Q…ô„\…¸B{…ʄڄF„^ „U…í„· §  Ÿ¾-<'BQa‰°Eöˆ”‰\Y¥Šˆz‰Æ‰æ‹6ŠÞÉ©‰9ŠqŲŪىMRQTe*‹Ê¡î§ÖP¯S§hXš:Í•MË£¡ ÒæÅÅÄÅÄÓÄËÄ/‰ è(ºÝ•K/ Ÿ£Ñ?HÈIØI„I”h–‘X’”‘dJ†IæJ¶HŽJ~bH9JÅH‘j—z,–Ö’ö‘N•>%}]zN†&c%Ñɕ9'ó@–Õ’õ•Ý%[-; » '/ç,/wBîšÜœ<]ž)-_$Y~Vª`£¥P¤pEá9CœaLje”0z󊲊.ŠÉŠ•ŠƒŠËJêJþJYJ-J•‰ÊæÊáÊEÊÝÊó* *ž**M*T ªæª‘ªÇU{U—ÔÔÕÕ¨µ«Í¨Kª»ª§«7©?Ò hØj$hTiÜÕÄjškÆhžÔÒ‚µL´"µÊ´nkÃÚ¦ÚQÚ'µ‡u0::<*q]²®nŠn“î„]ÏC/K¯]ïå:•uÁ뎬ë]÷YßD?V¿Fÿ¡˜›A–A§ÁkC-CŽa™á]#Š‘“Ñ^££WÆÚÆaƧŒï™PMÚ º·¡Ý x¹zõzì­îàý³ÖÇÛ§Ì癯o†oïFêÆí7.úÙûø=ô×ðOöïØÐ°èX(Ø´nÓîMýAÒAQAÁ¸à€àÚà…ÍŽ›mžÚb²%gËØVõ­i[on“Þ»íÒváíìíçC0!!!Ù^ì*öB¨khyè<‡Å9ÎyÁer‹¸³aÖa…aÓáÖá…á3ÖG#f#m#‹#ç¢XQ¥Q¯¢]¢+¢—b¼bêbVbc[âðq!qxb¼^Ïùi;†ãµãsâ – ÇæùîüÚD(qkbG ùù$k$“<‘b“R–ò.5 õ|šh/m`§Ö΃;§ÓÒØ…ÞÅÙÕ¡˜‘™1±ÛnwåhOèžî½Ê{³÷NísÞWŸIÌŒÉü%K?«0ëíþÀýÙrÙû²'¿qþ¦)G(‡Ÿ3~Àê@Å·èo£¾ 212 206 ~BžÚ"òIDATxí]|GŸ$hƒOp§Å] ÅŠwŠHÐb…RœBq(^\ …* ¥EŠ+å EŠ%Hà®-úÍlrǽä.É{IÞÛw7“ßËííÞÞíüïþ·z3¯Q€…`âÏx9 Ÿ„`L(~xD€ `ò©DF¬_³®ß¾m”ÌñŒ€¥ ÔÕßP‡öˬ›Ñ*‘¡÷îÁ£§O T©RVQYWÏàà`xøð¡åq¸rå \½zU#Š4$T¦téàÖP…ýü 3[!Á+e 8‚“Õ E÷:((ˆq@¢#÷¡¬ðV`†ÊiP󅬀Ê w™utL(§AͲL(+ÜeÖÑi0¡œ5_È 0¡¬p—YG§!À„rÔ|!+ `8±ë*åi2ùô…‹péÆ È˜& Èžrøùºª8R\÷§µëáêµëÐ¥$I’DŠ29«§OÙsç!,, ||| WÎP¤paH–,©³Š`×u¤!Ô+ü,kÙ†_àËŠ¢@ã*ïÁàí IâÄQÒ̱1ý÷_èÚ½—J¦ŠïV€À®¡qÃú¢h—.]ÿ€îpôŸã®.j”ëKA¨ÇOŸÂêß¶¨…óN– U~²dÌ Æíøß!8‹Õ¾äÙ³g0{î|hÕ¶£Ô¢ãW+VÁ™³çDüô©“`ùâù0h@_˜2qlùe=x{{‹´‘£ÇFÉëê)µaÏ>xŒo%E&ôî#»úìû)Qbûí–m6ûfÜ¡>C£f­`ÖÜyfT/V:íÚ½GW½Z¨_¯ŽMž\¹rÂÇýúˆ¸ã'NÂãÇmÒ]½#¡vcÓF‘´©RAÅ¢EÄnN\é^©x1% 6ï? †Íض}§úvnӪ̙1ͬªêêE&N’'OÔO*_¶Œî1Y2¿ùâÆ›ºÇ¸*RŠA‰«·Þ|ÈøŽâh%oÖ,°çðEµØó/ q")Š­-f¼†«V© ½º@ñbEaÏÞýñznÙOæááKF_;i^ÀÙ³g“J%)žÌ ×®© ¤MZ S Mª”6ûw<„LiÓØÄ™i§{`ðòò2“Jñª þ-\¼Tœ³Ê{•¤ÃÊåM¾'𾡔4ÒÐxäýÛ÷ïÇë ’ídL&ã;rï=þ)2|è`%(ÍÖå„ÒF*‰"½½l--.oò2éq–"×ïÜQ‚b{ãî]›}4šÂb ~ß±š·n§’iÞœR“‰îŠ„Êœ!½ú„œÀ…ÿij©“ç/¨i4ºçé)E‘Õ2q a 2uïÕWœ<]Ú´°æûÕP£zµ„¹X<žUЧ³žf‘#5ýÖâÚ=’ÿ<GBBTu›T­¢†9`^® Ý;…L¾™2Á÷߬€¢… ¹…Â.ïCJ5Ê”†‰ËWªý¨)+WÁ÷[íü׬ZUÚ°˜ñ“¦ª¶Çö/^?5R(Y¢¼i`K“ìô „¢Ï2>íÔFÌ_¤™LBÚÔ< ¡dÒÀéàجY³9å‹™ÑjºîÇo¡PÁw¢=Æ™‰RŠ®‡Kô}ÑZíèEK L³>‹F¶ûV®äL\¤¹–§§‡ZZ–cv 9sÖ.eÃDBŠ% 䇟q¹þ½‡ ÛÑ™°3ê›.-Èš]w<Ž¿[¡<„œ_ËÇS¹EvZ]y…¹[<¢RJŽ†Æ‹§Ì§ìò–p¤ås´¸ Œ@ 0¡bˆ“{`BÙƒËÄ€*€8™°&”=hñ±Œ@ 0¡bˆ“{`BÙƒËÄ€*€8™°4ÛôZ/ꕸX P²0Œ@T£FbŒáJ e¹Oa¿Ìº­|ó<ÃÏð-í›ðìùsË{†‡>þ†„Ê“5+ÁÌ…ÑØ¤•Å?Ñ?v!Ôò8xár0zJ•*eåÇAèdˆ÷¡ ¡áFÀ~˜PöcÆ9C˜P†Ðp#`?L(û1㌀!L(Ch8°&”ý˜qFÀ&”!4œÀØÊ~Ì8#`ˆ€áÄ®aŽN8q>Î^ƒ;@ô®P(w.H)‘ݵV_÷ô'CCÑéÜQ‘F†k¬`ê9®Ê “bÑIj´sOžeiuóÞ=»xìEãð‘¥SýzЫE3𴀭Ⱥ“ÿ¬ğëwÂm¼É›Ç„:VƒÉ®ytB¦™ÉÞ¹L"E“l™ÓC£‰¼¿“Ùeò·K²|ã&ø|Ér¶Ú¿™ßþ ’ÉJºÇT;ÉŠ…5y?ŽNH «’6µkŠÚhHÇö0ùëUðãï;`ý»¡K£EãX@VPã«\û°¶þqûŽø:[‡j(’Ò¥J‚/gé–=q$o—º99RŠjë¿„ÚT#µx¿šÚ´óòò„kÕT!9xü„6{àÁãÇ0jᳫi¨ß±þi%ŠƒT)Sêþ’£UaÙDŠªG³&|ñ’pFMvεâ—>ºûvT­"SV®3VÑW«çKôbyôØq%“ÝrmÂRªxþ|@?=ùqûN5º¼›¸4Q ì`à÷ƒA°iß~5÷Û9sÀ©Ð7~²Ô“B/¼Ñ5Kf?øî‡5r^ãßÛ @ÅwËCfI?+’‚P‘Ÿ‹Ó.Ÿÿ‡]‡«þ¡ZÕ|røùF>Ôtûäåþ3t˜ ÈàíDÿÒJ„:uúÍpy‹6í(l¶cG€Ö-›ÛÄɰ#%¡¨ï‚ŽŠ©P¤0 BVñËV¨~²J¿ó64Ç>¥2`cýIÇS§OÛ¨Z=³)\.ãüäÆM¿‰´£ÇBŠ)¤s, %¡2¦ñì¾áÚí;âaÚìèN¸†úw€ìèÑά²a÷^ØyèoU½‘]üÕ5Ò°+W…–ÞÞÞ°béB(Š/TEºtVí: ¿»ÃG*ïU‚”ø5±,"Å(_d0f ì“?ê+°Zÿrð@‘|ðÄIè7m&¼zõ*òá¦Ø¿zë¶ð¥(C/š³¢|1y=tþؾنL„Ety4rè`Ëc =ŽÏ…L"%¡´•+Tú·i%¢È«áI“vίÿY«6³î è&•~O¼™.8‡ÍŠ£ _3 ‰Óp¹ž¼‹ƒŠœ9kŸƒ6%_Bm¥iò‘EòðžÈË+Š®e4.¯Þº%Ö÷E9ÈÍ#ÈY·VÖîÜ¥ÝUÃtMö’gǾ­[¨ñV ¤ð~ÓÄ{©d—×Pÿâ²£zý>† ]ºÁºèbCëÙñ1xk)é¼uo¨× I (_©|…ÎËõä¢fÀ*W®œz‡¸,Îå5T24Óå<9p~Þ½šV«"j*-"ÔYW$Ÿd«‹•rÅu[£LiÈ•Yßâæý@qâM«Ihäp3£Ð@Äã'Oàö;°výh‡KѼ"µZ–}µRU½ Ž„Ê$.'ÑGjf|û½Ñ›Œ+:~Ph…ÄË—¯`ù/›Ä:>:ŽÈ–‡JÍ(5Ê–Š]¹yK% TtkÒÈàHsD7mÔfÎùR 8ŒŸ4 èÉ’%Åçá%|9¬ûy£P´W÷@È>½TJKA¨¶uj‰‰\šÌýá÷íâ—/[6¸ró¦:'“;Kfœh-x\˜„A {`سoáÄþН¿5kÆùp~*D —ÓU«ãË5°«Â guyŠÊNƒÓû÷ÁožšªªÐÄ®ÒQïÞ´1¬3 Þ·”E1‹Mº{zHqËô6$J”–-^}{÷ס~‘‹¶éðKŠÿrÖ xKÂO¥¨¡5ZÛ¹A}èP¯.„ݸ ôÁ¡oº´«t"œ•et@g Ÿ•„†Í{÷ì=ºu…ËaWàÆ#{vÈ(ùÜœ4„R6§5{VX·§èÌ[ch@"Gölâg|”<)Ö~õËs¸$&A€ e’ÉjÈJŽûÀ¥0 L(“ÜHVC˜PrÜ.…I`B™äF²r À„’ã>p)L‚Ê$7’Õ×(zEYµr%<~úT/‰ãË#¨‹áJ eýXa?ýO tÏfÂÈà›7à~ÄÆ80ôx‡Þ¿ðó#1$Tž¬YáHp0>H~Fy-ïß»Ê80ây÷Bƒ0Ä #á>”2Ï8€ÊÐ8 #`„ÊŽg@€ åhœ…0B€ e„ Ç3 À„r4ÎÂ!À„2B†ã`B9gaŒ0œØ5ÊÐñdIöÌ¥Ëp }D‘3ëì¾™ düÆ0úê®=ÿÉÐPt† »,JÁ²eÊ©Ð(¤™…VÅ‘+Ÿ‹×¯ÃuôÄ’ÞDz¢‘–¢yó¢ñK9ë©tê4ôÿb¦j>LyXÈZê0ôFQ›YÚkõÆv€zè7ɬzõ*|¾ô+øûtÔU d³qxçŽP8OnéÔ—†æä3pü$A&2„O ™&¹óà|Qã¹N4qà×}ûUíZDL!¨&P«eáºõ†ƒD®!sçÃ݇o^ƲÀárB%Mò¦cI¸ã°iG[EêT(Ö»4dJom+ ŸÿÑô¥Úš~VòZ©ôIgòظtÄPáÙRÁ€ž"•lârB¥Oí£bR½L)Ý,m[ùΠ[AþÆU#ä'‹¤UÍ÷­ ²ªãΠ¿Õpi„ DBË—ú´jíêÔVÓž8 Ð ›LârB¥x‹V‡‹‘#æ4Ï8£œÇ Ûu»v 5¨¿@­­"DíüRÙBmT¯\¢˜Í¾ÞÒ$›œ¼ãrB‘¾ÊèÝi\«'7î†RÚÛ9sèbª8jîí:þ–®‹K°’¢Å!« HiåµëÚ]HœÈåÕ6å‘‚PMªV…¢*œVšG–{öŠ(ê[ù¦K9Ùtû—ð¡QFµòeËj:ý¢SˆVÉÐjEh”óìå0±Kµ-KÒŠò2ÖÆ¹2,¡W©,æˆ>Ó¦Ãy\·¦5}¾Ûú»ØmTå=%ÚÔÛÓßÔÔ¹,8÷V·By›ûÛrèè0z,Tëñ QÓÊ.„S+iÕ}RÔ—äÝ}T@ødö\1ÉÛüÓaâ-õ-¶*“¾ïã€E¦MdÀ,ÁË mâæ² ¡ÑÀÆ8g€Z,ŠhûUG}Ëaø ‡báX9ÎÕ[)j(C«Ì•áaá"2ÑänëZ5`l·i?*‹ï›xÿQøÈ=4V™ÌÕbH~ѯônÑLm¹hÓk–+k&‡ÌéÓk£¥KQC)HPaÅèðô¿ÿD»9uŠ@_¦ZMz5o ô³²P«Å¿ÁС^]¸vû¶ø)σ̃4RJy€hu±Œ_c*åã­ó ÚŠ)´λºýW’¦ÉgÑ9# L(ùî —È`B¹ñÍã¢Ë‡J¾{Â%rc˜Pn|ó¸èò!À„’ïžp‰Ü&”ß<.º|0¡ä»'\"7FÀíG¿Ö+ÿêÕ«áÑ£GzIÇXÀÀ@] WJ(‹K•*¥›Ñ*‘ÇŽ…gÏŸ£xëY\ÒÞãà›7à.V¶:¡÷ïÁ£h>r5$Tþüù!-ºZP/ÐÈ‘à`|ü´Ï—åÂø‘ã± ¡–ÇÁ+e ñ<=܇2B†ã`B9gaŒ`B!ÃñŒ€0¡³0F0¡ŒáxFÀ˜P€ÆY#˜PFÈp<#àL(@ã,Œ€†»F:>ÍG‡@ÚæóõÍ¥K–€,±M·a÷^´ôt:|P’Dc•ÌQ“²ËèÞÇÍT“ý~¾àéá‘зÇ)ç-T˜+·nÁ/hü²h¾¼P.’Ùf§6ÒE¤!Ô=ôè=jÌxØôÛæHEhß¶ ò‰©ÝnB×5£-º·¬QÝP[üŸ/Y®Z–UÀ"Õs ­x%Ͷ±Åtzòï0`úlá?Œ¬#É@()š|Ïq­\›öþ*™ |ü;¶‡Õ«‰gaåªo`Þ‚Åîô\ØUV2ê8bþ¢ó(.\ÈL3Ù+lñ~uaÏ2ž ½]ÆN€ûn¼ 9¶8¾/^¾ÄÐbA&Ú—E¤¨¡–¯øΜ='0 ìÚôýH5j¹M1ôè 3Ъlþüy¡&>DfZlºR/X»>F•ÈÀä«Äqä"uÁA@vêHÈqÏISáú*&ÓÕ±ÉèNb¤½ýO—°¤'W5N¶à÷Ifÿúúµ±‘nw†=íƒßãè‰wò7ž oáGpñ- 5 o‰¼¼`Ǽ9º­˜øÖ×ÑóEm_9z&óy!HÔw"Yº|%~vîyB9õ–i¼¿?2 ¡ôš¶ŠÎzÛ‡OÂqI—*µ^²˜ƒ¢·7ÉÓÿžé#c¤½8Ø{¼³uv9¡Hán…Þ·qR³]§.pøÈQøçÎa3§gŸ~p\ã'ÈÃSŠ";û>© ‰y^[ñWüâÅKÃc8!apy“Ô«[»Ô«³MÌCyZ´io£u=ôü­Lø¦Hám“f•ZA£|ÿ¢«#yôä©H"W0ñ-º–|âû"&8Ÿ4¯ûÓ&Á¨áCÀ7Ó›‰· ä‡þ}{ÃàAýU¨S¦H©†­ȘÖG¨{ýÌê 5¿¼Úþ”Þ±—pHQC‘zde©Ý‡­Å–!½zõÒâj’ C‡Å–þeC§lV”Œi±¸s_PæAD‹°I—Z¿ŸeEÜœ­³„¢f^è…‹à‡Ãæ%KŸð·±Æo[¶Š`:tPœ%³5­ùEŒ†’Ófò†NÞÒµB¿Š¼“+‡Œ·­IÖÝÆF'’¢É·pñ2è÷ñ'ÐwÀ'@½Z¹ÿ>(+)ê׫£M²T¸FÙ2ª¾;ƒþVÃJà×}Š ­ñË–ÑznT\½•‚PÕªV8\»~/û žââOZѱK7xÑoèâ/â­ø/ÖÚ"VŒLýzµð’N8Ðrœå7ÁN\yAÒ ç·#¥""¾þñ¨D¬”¢É×s~‡k´þt¾˜1[üráçÊ’$ÒdÖô)¦Y!«;£s-zÝö×A1øÐyìxÈáë ômÔÄÑep>¯qÄËI';G9)j(šÜ7{:´iÕBUY!SÑ"…`ŲEbh]M4a@[«hÃZUé#Âï'Œ…8úIBÃè ™ZÕ|¦öémø•ö<Ž„UAiu׆Êì1/éé™Pk9Œ®¬oè,€Ì0ÓÏÈ(ºþéâK+%.^º„»ÿBÖ,Y C†ô Ó„‰eQìÜ)Lï¶.)—wú¸îìåË$qbÈ’!(“º±TËîÃŽß¾-L1ˆƒÝŠÄ1ÃÉGᦹx!E“O«#MÜ|çmm”Kà Ǜ/24yḛ̈<Ê~bz¤hòE~XdÚ ÎjìȤµNYJLÏJçÙá(FÀQ˜PŽ"Çù˜P: pTT¸Å½&”*Ç8ˆÊAà8# ‡JŽcD€ å pœÐCÀp¥ÄêիѾC¸a½ŒÇX»WJ(먬îþرcðìÙ3°<GÂ3ü´¦°_f+óBÑ¢Ô#\l$†KòçÏ/ÖòYýA"àhM£ÕqxÖ¦Ž#¡¬ù§B /4ãF8 ÷¡ŒáxFÀ˜P€ÆY#˜PFÈp<#àL(@ã,Œ€L(#d8žp&” qFÀ&”2Ï8€ÊÐ8 #`„€áÄ®Q†„ˆÿ }Ì’¨€. I’$†—¸ŠV~N …Ô‹/W¢DŒ)TÒ¤±µ4kxÉb‹ùÑ:ŠÚ {ölP=jmÂK®fŒÅÛ°{/\GO,ÐlZ¼Ï±Ê÷)¼_¦”ËÌQǾ´±ÕÊÎãÖ£‹ÇÁÃFŠ\íÚ¶Ö%¾œ1kŽð¥wú‘Æ@Û6-A1)¥wŒìq±Áá:¹žŒ~†µþ²½*¾[fOŸ ) ²)Çɾݴo?:¤^"ŠÙ²FõXjý»a Z &!oŽ®²ïîÒ&ß¡¿ÃÀÁCÑý;n¢J¦¼yrƒÇöH V@¶ÎIÆŒ›+W}Ý)¤N‹-'OSÉä¶Í›7m „É^|Ûûwwöðx$ä Œ˜¿Èî{u - +d²;sÃæ-°7[‚ïtB> šµ‚YsçÅJ©ýÇÑyÐÇýпê~Ô¼1t°zê_¹‹Ø‹Ã–­¿«ªõìÉ4NÕȉ‚R[ïÚ½G=Î!Øl;b4,@B9"K7l„ãèéRq:¡¶mß gΞú“éå93¦E‹½Å©YS©bHáCV›AëÞ†œ ¸‹Ø‹CÝÚ5aÒ¸10ìÓA@Žè´B/™\¹rЍÿ"-ˆ7ø· œ »"JÚ¼z5˜üQ¯X—úÖÚóZ'Žÿ¼{`¬ó%ä.”¨Z¥2ô·,NíÙ»?Zýôûèg$‡S“”þ„!yÀrçÎôÓòILŽHèœî&•Šƒ® ë K¸âèelä ¾8†Í_(íÚ¨”|Ûö%›s$Ä1N'T÷À.6Ͷ¸(E¾¤¦~1Sœ‚š„"½¹ãrî„ÎW®\½*^FÔgÚ¶}‡(.9V¨…#cî$þè~ÇÏîÓp*ìÆMÈ—-¡§ ®ÖÝé„Òöâ¢ü«W¯`ÈðQp ûd$cF ÓmÆå ™7®8¬ÀŠ%ËV¨E¤ÊÒ…ó µ›¹u„L;°6^·k·Ð}\Ï@HlÇ\• XœÞ‡Š=^¾| £qDˆænHhÔ«!¾é¬$ɰ?Y»V ¨P®¬P›œÒ5iùaŒMhwÇè&ú_µ`±Pc`»6=´È$N¯¡âªüÓ§O¡ßÀÁ°}Ç.q*ê3L™øy\Oëvùûi:ïpåH@÷^ÂA@wعí7Sú"~…Þ?[´T8œ#çr­jÖî¾¹U u }µëÔE%ScìÈÎ9 ’&M*°Î,P\~¤ÌEÑu'ßÎ,ƒ3®õó{`ÿ±Ä¥ª—.ûŽƒ=‡Šßþco3:-âB±Ÿélq›êüùPèÐ9”¡ñ]¡ßÞ.uÆæì›EM]²<¤7}P¤p!µ8´ÖÏŒrþJøð:é6iÅ׆*ÎA÷²$äBµÆ+¦a†xLp BQ“¦M{¸ &I&Žû š5i0Ȫ^}úÖmÛE‘ü GñT$UÊ”JÐT[Ÿ)€¼ÜëÉó/Õ‘>oì_’GÇT8Pãl‘žPôVîÝw€J¦Å æB•÷*9'—_/sæÌ¢ ¿nÞ7o † éÓÛ”iÓo[Ô}šß3£ø7øè§'´:½^¿"iö P,_^½ÃRê5ŽêL>K-9=8Ê$¦© ò tlßVcž Õ:ýûô‚é³æÂ¿BS"Ï–-+<ö\íWÒ\}¡×Ç2rkâòQ>OÏ7N¡óÏ dwîÞš_‰­xbí宊N=q½ÚìSÕ…°4ø  ÒÐÙ­›~v«Õ"Š^Ú­öІµÇè…µÇzºÐöËk¨w+”‡Gô0ŽQšn7ŽŒ­ZujÕ„Ú8ÿBD"Bùøø ››eê \¡‚´b©VåX…3¦IãP¾XÜŽƒ\N(;ÊʇF @oc?__ñcPäBÀåM>¹ààÒ0qC€ 7ü87#`ƒÊÞaâ†*nøqnFÀ&” ¼ÃÄ &TÜðãÜŒ€ L(8x‡ˆL¨¸áǹ_$ÜÝ?HÙ~üø3™@>—¯KI™L {–rò[Érlr ’@N§,+,AòLÉ+òм"¯ÖÉ+L  —W×ÿõ¿ýM7y'›Í<{þ"¾ù¥D°ûû?â›_Ê”|¹™-lñÍ/ŸøqA)û”=[ùŸ€ó ä˜óY!yE^‘WäU»y…©ÔFžüßurŸçA‚øiIÊ Ÿ .¤Hò ¤¬É”>É"Ì.ZBÅYÁqæ Ù‚м"¯È+òjͼÂTj +ïüøY*Hߨ›/%åIhýR2hø%¯È«žäÕÊÚ—¼êm^a*Mð·eüÜ–RúÄ­©ï?|Ì<ÅEX\vL§³|¥4·ÆeGP„Å ËAÓ´s¾i:ÙòL^‘WäyµN^a*MUÁÉs@r|™Üê– 9¾ÌÁeÆ—é 8ÙÊÆâ­L Ÿk‚¸ˆ¬˜@^ŽPGÒ%‡Ú­L@^‘WäyµN^a*Ð_0ÀTöŽÇÇÇ?þ, -Í(ŠBJWë‡ÂTš0u£Ãä=Ð "(6FXGc*µy||Ì0€fžåQS¨ÍÊøÐŒ8v ¦P›8Pm*­àcÿÓ¦М)ãT:€q*­ÁÜ€.`îì ˜ `*˜ `*ð„Qè‚ù|aëBc*M˜Ng:N^å=Ж£X˜ÚOŸ¿`*MÓ·éþº™ûÀ:¼|õÚ—®w÷˜ @mˆ¦ÐDÓhV(èV(h‡OŸ¿Ø¤=©°>w÷Ö½~yu½dœ @cæó…÷ ­ k•X¯:¦ýSLSLSè+ïY^™þÐEQh˜Ú¿ýý¿ýþ¦ÐðFòÑôå=ÑôZ!ˆ¦ÿãÇŸ˜ @mˆ¦ÐDÓh8š>@­DÓÿúíûÁËW¯ÙØ:Úöø^zÿá£ÝHò¾£_™Ng\Eû½íñR rjüÿî÷ÖÑš”{Vº^^]Eq<²±µ¸í·øßÝ?üöûòÚÝOÈþ¹Šö{Ûc¡F°íßÖ]¯·8”~¢,:SaÃTú ¦‚©`*l4•L… SÁTØ0L… SaÃTSaÃTØ0•õMåÙóŒbk¶¶1•.L…Ël×7‰gP¦"'ÎÿþNo—W×}1ÐJµJž¥¯¿‡c*û=pl1ަT«M˜ÏoÞ¾Óò·»`*˜ õ7g C3•¢(4 ijç/tr%¦T«MØL4}L…ú›3…¡™Š>þMSªÕÞÞ±˜ õ7g C+÷Ñô1 Z­Ë|¾ØL4}L…ú›3…¡™J0êü¯hú˜ P­®Y wWc*Ôßœ) ÍTDM‚¾uL¨V›ßNrÊòJ–õ7¦‚©´û[Zº¦£éc*@µJ–õ7g [4•L¨VÉR þæLSªU KSLSªU²¨¿9SÀT¨VÉR þæL1L¨VûÊãããû¥ð•WyO–õ7¦póÄb±ØÓÙd4})útYDý L¨V›à—}öüY ÔߘJ )OœŸŸc*µøôù‹ÿyÄT€jµ¿w,¦BýÍ™Öb2™œDŒF#Ñ…ÛÛ[Le'Ê=¢é¦Òq4}L0•>œ©xÉA9ò×õÅtm*›ïZÚV4}L¨Vâ{ä=Y ˜J¯Låôôôü'£ÑÈdåøøx3µ{§¦²•›­ôþˆµÐûT« ±1_rSi¼g²0•þ˜ŠÔåþóÙl&Žbƒ©ôÙT„étæç+`*@µJ–¦²ç¦âkwS鹩`*@µJ–¦²ÿ¦"•ýõööv<koÑd2™ÍfUtAÓËãẙX,:Ê$Þ§?Œ‹‹‹¸£JL¾(i¬}èæ'UŽSªUªU²0•þšJò¯â%f0‘€²-" ‡‡‡ñp]ï+e¦"iì»>½¼O¾ž2,ÂT¨VÉRÀTvØTl¨Š)ÂÙÙ™l+_·4Âh4Šw"|ÅDGÞäMÅkŠRòsS-f»õküèà€äÑb*T«d)`*»a*³ÙLÿ$NàÓ‹ˆ4øæù®yCÐ¥rqqa;ñ?!ïU,2¦"?aòá5E>ן“× ɬȧ_2NSªÕ*0÷0•Ý2kðM""É.1M<“­2:¨pø™¿–}×~+ÞçòçØùî Låë·ï:÷g>_`*@µÚ⩦²+¦rqqaŸV-«€µÊø^ž*{ÈhÊÒóMîÇšU‚Vâ©P­æ Fín!¥¹†TÇT†c*Iâî•Z¦b]?U®%¿kÎI¶ÄäÇÃZ‹KÐٴǦBŒZÀTvéŽíy–3*»ž$¹~í…© ÖTŽŽŽ´m#ù-¹tÅ!ÎÎÎl© kñ*`ÒPÅL&¬É$é">ÊKžá˜ ëþ¦Ò_KY Êd• ÏŽ=ôLe€¦"]%Ј¸‹Ÿþ“Ÿ$ÜÌTdÿ™®ŸŠ¦tZ §÷‡µ”SiNQï?|ôñž‡“¥Ö®¨>ƒÚ\‰®ƒ:`*˜J•ÿëä,åë—ÑF“ ­¶¾©è2ÎÉ*>Y­gïGÔê|…7oßéO`*€©¥õ°27(%5‚¦‚©ìŠ©ˆ4”YBRä o6NÅWîß Uk”îpL%SL…,mÒ Rf$ôþ`*»b*ÖF"Wu0³©;÷G?±Y<è”ÅnÁT0ÀTÈÒ&ÔjÇT0•0•8q²÷géfÇr£ '™ðs•}˜3˜³³³º¦²™u¡1 Z…4•ºE¤â~®¼bjòÄ)Õš@ž2å‡,‚§¼ÏNY¿ƒìâ·Q,æñ+ªûXvz ¬P€©„|ýö]NY^‡–¥¾\N¶—iŠÙúØ'¯öl„ 7S±oÉS¦úòCöI\"ûGUm‘¯Ä³O1L%¾>ãùöºŠ²^Q±©”Ml®¸–òÒ-‹èÛr¬U& &¾Ñä·‚% ÷i…»û)ýdÓ™•˜ `*ë–ÂÝÁýÌR?åÒÌ ,qÙú)Kׯ™(k>ñ ¼•-äÖÁ¡b*{¦â¬:͸î%­1 åÕúPäCÙO>Xœ6ÃÈ›dÏ‹Ire^äGm·:k:ß§#·ž…^,;Ô3_îýíïÿ(ŠSL¥6q4}]Fk8Y?Ïé‚´qJ…²¹I½°}ƽ_¼yûNÎQ^;êúÙ­,µ~¯þÃ<ë˜ÊÊ/˜ g ;d*ºV‰<^^]ßÝ?,‡9¢Vø ¹L…,­(+6*Å>)kS±Á€˜ ¦˜JC4•²¸„j0CDl9‹ÞõˆnL…,më h#i汩Ðûƒ©¦Ò°ºÕ6£Ñ¨'5n™©X—yr Ãæ±±Še¡<©V1•¾˜J³ubQ‹©¦²QSñAuzRã–™JYøäN[M2óõ7<˜ Yº’L££ß´`n¾•¥úƒJ]SñÁÈã_±¯`*œéúhü’]yzÄT*áKº«ZoÞTF£Q¼šC§š’¹´-ª‡‹Ób*ƒÍRuˆøñV|«¤É–”¹¼ƒÏ뚊" ¦C!ö©¿9ÓŠÈ5©±à‚K½Wís˜J;ØìA•€d(§þ˜Ê†ÙʲݘJ‹h¼gšžAªíÆ—}³Ô*~ñÑk7xßǹ—¯H™pó[‘'¸˜Šo8Ñ… “$ RNýݸ°jp ÷íL­Í¯ ßRb•WìSÙŒ©Ìç ]«D@tk*úŒ¥GÝU­1L¥Ïøÿò>“R«êfƒŸz˜¥ÉUË"¿ÅÍ-+kˆf¦²ŒÖx³jFc“c*k604è¾ïÛ™®œ3Ÿ¦\6e¦b+ïʈ>›Êt:³Ÿ¸¼ºî6š¾±Ö2Z‹ªÀOƒ›!0w}êÒUÊþûƒ¥üZ ee¦¢ Fdlmˆ¸I0؃¤w¨?dÎ.O~7ådº~ä¬mчü!ÅyëW‹ÀTø~­húúLß`Py?³TÆåt‚ö•ü(7…_V€ÕՃIJgÕœ¼¹t„~òº•]YëŽì_IoI¬©˜Jc4c%«wF÷ÖTÊŠ\{ÊiêuøY™©ôäx?Le£Ñôm¹Hýï·–Uþ¹Gþ³ƒ‡°²§Rsáäú–É'¼²K*Ó¬Or}Kè´rÍdšÌCj²ÜO.æ)¹”,ßóy+ÿl¥WŽhúeø–äZ¦ ýÙ?vÔT¬ÉJ ™*ÎÝ[SY§SÙ·húúðdܬ‰¥l¿ö˜Õ rYøj5–ëTò+Â{ð±½›™ŠoUÖCòksÛ†ïÕdþ0ä; ß©ßÀTd?þä}Kq—å­À]¿Ñr8ÕjQÞúWFÓ‡–WlÊÂT0•þà{ýʤÄT0•Öï‘£é[µíKd«M“•¢ï =md\\¡•º_°Ûî«àº¬e*~< ?$m…ö;ÑÕÚäÕ·ŽÊ{³ŠàÆ^9N¥ÌTLtdÏþ·$«íPƒß òÖþS¼ô¬?\fhÑôuéy­M?ÖÓ². LSé'VWô•6]€3EW:$ÀÏ>³ù¸­ÚzNõQVž±Ë øb•ŽN{£öÒTäÁO/yœNÿªãº2½¦ƒQ)e+¶Çµiül×Arð]¼p¼W²Ã¨b*ÖÉ’l ò#—`²×#8Âf¦bMS¾‘&nþ &„Û)KVykûµbsU¬Voà'ÉÞ:+ëË´0•½7•ÿþ÷ÿoW®á²©:¥+¾†wÚTV·O>-g†è.ËG£çG)ÄŽ¸÷¦’¨ »0 ÷ÔîùJÑþc’ÎhupðÝLµí[q¼”Ô2ÛÃ:AM’GÞÌT¬Â+3k»¼‘ä1¹žm+¦rÕÐ៘ÊMå?ýçÿ²O×°/'b*Ú@b¥¨vÇÇã¾ýc¤ÎÕ—¿ú‰ôe£¬Ì·_ÁTÚ1û/‰Ûµ¬¯!®øóý|¾-¡JIÐäîkèê¦bÊ•™²´aS± ,[ë”3ŠÖŠ©œÀOüТø‘4è4ÄT†c*ÿëÿö¿ïÁ5,%¤T¨Á5<S©RÆÚSzl$V]–RÚ·tßLEÿ/“µ»M‚ˆ›VŽHª»@|Ù¥YÝT¬Zb3‡ýÞ¢©¬l¬uÊ]› ÕRÜV½›Ÿ,‚©ìÐ8•dÄšL÷eoM%¹²wP%µk*Öv’ü«*`Y—}Féî•©˜<ê“b€ý‡Å‘õ÷ÀTäZÏÅÂTK<3¼J,8²4n[íÕ‚çƒ2• Meå5¼s‘ߺ3­ÊÚæí1Æ·—´2S SÉÕÖû5ƒâf×MÅÇÌÐÝ6 SÙKæó…ܨU&þøXïµâÕ-K¥L°îK©}Hë Эd㮬‡µ¦âgÚW Ô[S±(žŠ3˜ŠïúIUN¶¸ôpÚó&MåññQvnûoßT,íÊÎÎ ²~Åq*Áª"Ç©ø3ÕMÅ®°*ãTÌšã†ÐMÅ ë•ãTª_ô˜J>}þb§)ïó‰­$’k¾VÜš­g©eö¬³’Q^SÊs}°€¾5‰ÄT|€×Š×0ãTâ•<˜Šrwÿà£é·o*+ûeªÿüü{>®ÎËVi¶Ò-ÙªVeîOr™Ë:¹°Q‹¦’0›<`_š`*­û~p¦òIÞb›Þn–æKضBÇס. !W¸Ýì¦zz³hlþ]`eGMEþ»ë®3©»Ê<Àk£NõB{¿MECUÙ6ÎZ6••!ó«4uÄ1?|[BPCg³® .RÅTüÓp|"rüzme´Ãì­ÌTʬ.ù-RqIí¯®Þ^…©tzÇjÛ¬Z퉩%l 6Ï.h4U´!vÍ)x˜Ê: É˜˜J-S©³jȦÒy4ýü¸¡¸ºõ•t&Žjf®/4½“ÊÿîÊ€­ULÅ?V1jÕ·t'VÎá#¸Lýh¿ÂbÞT¼´îGÕ„©Ô%ˆ¦/[&šþ:Ã)zb*ñ¥ëG“´û[ÉZ¤­à„˜J3š]ØJ\Ôc* ®œ»û‡6MÅ"ègº~‚ÿ9ßIá§•­M·µXJ«¿ƒyÿ™Ep*®ûãç—êå߿’%Ó‘€_Y[þ“å¬ý¢îVÏweéì °uŽü'qǦÒ:ÓéLeÅâ=wA?M%¸…[ÙŠ©ì˜JüxY½ÃtȦbk•Ø(À6MÅZ>ªügXb«VýLu¸¬SÜG#Ž×(NvÙ4XK99ý8^g.¹˜³MgH:G0yÕ÷ÚdJgmŒMN„N^Ö˜JGT™ø³—¦â/›VJÒVLEç0 Õû)$冧=c*ûm*ÉYQþ9SiPº¶i*òhUýž×—)ÁŒšÓ¹™ÿªà¿_WuÒé ezöÓµŽßBºiŒ ²d²;lUPö\v<¶[õâ‹ÝÌ«ÿ­Ì!eN¹Á¦²IvÑT¬2yE©µûfðZK‹'»bu1¹àYBg„–€>øPfñÂa˜ ¦RÝT,½<|j¡í U¹Díú”+Ó?âJ]}°ÖàÂ!˜Ê/}G+®sÕúÙ§faªUØSÉt½×mÀK¶®Äê¿xÜ9÷„êÏÉkquce¦²—¦²Œ¢;ß zíW†ÅT0ÀTÈÒµL%˜6¼Ž©h“žC‡ÆŠgYë£yI0àÝ>OvòZ¥¢M¶6SÙï3ͬåcs6ƒÄq£ ÿJ ÝÁð¹hÏÎÎ’ ‚Å—«Ú&1L0²ôS Vưj¾Ö8°ü#i³q*e«»-]{{Шîë’êÁ‚1δ"jÞùZÌÒìVôBLS¡ZÝ=4Þs>æÛ~˜JÙò¹òØ]ݤ©XÃIr­€IÀÊ)ŠÔßœ)lËTŠ¢ðk•ôÈTš5v%›SéšétæC(î·©‘ß||¼Šý&M%,®ìHì¶âSL%Ьzÿác¿L0•jM©M×M%Vù“µgÔ Ø¢©¬ RnF•4êoÎúi*GÓL…húC0•å¯/};èæM¥Ö²í˜ g =/÷:¦˜Ê@¢é¼ME±@~~ÄæMÅÏ Š z“1Îzn*—W×´©¦ÒÓ!S)3•d€Š™ŠÍ#-§’SáL¡ç¦Â8ÀTZc8sÊLÅ–H¶©$T·Ñ-ë›ÊÒ-PZ}x,¦23• i[Ó»6ƒÜtžJÂÜÀT`ÇLEä ¹îš­TG[ñË}·b*6¹J,/Le8g:›Í¶8]±U¨<¦Ô>¤Š'¹L,™¢Á„6ˆx*@µ »d*>lðW­$âÅϽ¦´e*~úO² GŸc*C8SmØÛnt×ü*e‹HxY‘[)N&÷—ÎzÃT0L%œìÖ‰ã©,ÝH[ùâÙÙ™”Îúùg2Ûrµ”í·ô`,®üNM ¾…© áLžØîé›y$ÛTô:}/kSM± x2™¨sëÕn÷ˆþ3³Š-¦€© ÅTÊ‚29@į< Ú(–¶LeéFÆäŸb1•œ©µN.õ°yS)û«^‡™€ìš îÀº}BßkOkõ®OL`ˆ¦Òõºõ,ÕòÿtX†<ŽF#]ÎÞzÓUVâ²Uö¦{N¶Øëa$ûwìë’FŠukø‘ôò£ñã¦ì$y˜ÊÞœ©ö3ÆW‹\ ¢/zyÈ›•°^T–^¯ÏêWÎ:¦¢²utt”?H]Ò|c@6•ù|Q¦˜Js¦Ó™Î£“WyO–¦Ò“3ÕÆ@yE\¬3Åú"3Y¬]P^ý`‘ä*Ê­›Š6Vi:|bÏLEÅÂÔ~úüSªÕ&ˆéÛtÝÌýÉRÀT¶{¦jvhnj¢g2­ê Ö‚¢µ&U1•`œJ [™FÄ`?›YÄjc¦\9w÷˜ P­ö÷ŽÅT¨¿9ӺĊ ýAAÇα/Žªÿ¤Êa”-ìœûc²²rKŶ™-÷ˆ¦˜J d…BÀTöÃT´‰âôôÔµRS)«ãõ+Þc$}­~ÐíÉÏý þ:XSa…BÀTÚáÓç/všÚ“J–¦ÒOS) ^’‰¹¢ÓjŽåx€ŠK­ùDëŒSÑŸ«Õm/MåîþÁº×/¯®—ŒSªÕÆÌç ï™,L¥Ï¦R«:×q*…Y5¥ÖpuLE»«ª ‹ÙKSYþ\«Äö©Õ*Y ˜Êþœi< D¨ª55]ƒ.Ÿ"ýT™“ß®©X ç•av뎞ÙS 3SªU²0•½9Sí:ñ qãð$Á2:RëÛt+äèV!ßÕ°=]˜ŠÙÕññq¸VGÛø_Ù¿Yʘ P­’¥€©ìó™&gúhày“ù§ˆ‹Ÿ¥¬ê` "¢ñW<~ž³veæ9W1 Öâ±1¼>гªýÓtJ›^6¶¢2¦T«@–b*œi ”Õß“ÉÄ×ý"+™6]7ʈ7HùÄ–ZÙ¦2z¢ì¯òÅ“Á¬i=*Ì>Á†—SÆT€jÈRL…3m1’uúDÊVÚä *ÄS©1ªÕañžåµ»é?˜ õ7gÚ€²¥*¢ãZÄKü¸û°'Ù«‹þl¬ëg“¦R…^<ûû?ÔI0 Zmr#ùhúòžhú€©ôçL5|þ:µ¸õi§ŒNWÎ/´aÔÆ63?yæ\9ò+˜ P­ö÷ŽÅT¨¿9Ófè¢Ùëìa2™ØêÜ"=b›Y^§úámxIp¢é¦²KÄÑô;êÂT¨¿9SÚZMÿë·ï˜ P­6áý‡všòž,êoLSi…étf?qyu]¦T« ¹»S–W²¨¿1L¥Eæó…”~¢,:SªU²¨¿9Sè‘©`*@µJ–õ7g ˜ P­Y ˜ `*˜ P­’¥@ýÍ™¦@µJ–õ7gŠ©`*@µÚWæóÅ›·ï´ü%š>Pc*˜J[E¡a ž=¡“+1 ZmÑôú›3ÅTºø!}$š>`*»qÇb*Ôßœ) ­Ü#š>`*-0Ÿ/ˆ¦Ôßœ)¦ÒÅùët4ý÷>Êo³±5Ø|€ù½¯V})Ü]› —Ù®o—W×Ã49qþ÷wzcØŒ©øÒ¾õÐTØØZÜöû Cn'1 yÝd3ÛžmÃ1¶ýÛº›û#{ÖÒ5MŸ Sés‡¦‚©°í½©`*l˜ ¦Â†©`*l˜ ¦˜ ¦Â†©`*l˜ ¦Â†©`*l{e*ŒgfënC5Öäññ‘«h¿·îo9µAýWþÇû·÷ïÿÃÕÕÿÎ)wñ2a*TÍâý‡òÔ(¯òž 8'''777dÅšˆýöûRºÊ¦O¼˜ @|TŒgÏ_!˜ ¦Ò Ÿ>ñ}Lò(ˆ©Ôf‹kЦ²ß$¢é“)u‰£éc*˜ ¦Ò A4}L !¾÷GÞ“!˜ ¦Ò ¾÷G¬…Þ€†Ø˜/¹©660•!0Îü|LSé/˜ ¦‚©`*€©`*˜ `*˜ @_aî`*ñõÛwû£ ca*M ž `*]@<€ F-`*AŒZ€`ÝÀT:‚uÚµ”SéÖRh‡¢(Þøèã=¦‚©´‚ÎWxóö6Wc*˜JÁT0LSLSÁTSÁTzÌ×oßûýy%+Si‘»û)]eÓ™•˜ @^¾zmÓýå=€©`*­ ‚â£éE©Ô&ަ¯Ëh¦BV¬IMÿë·ï˜ @mˆ¦0dDGf³YSY,¸K]ˆ¦ÐÏž¿Ú'É€ þqüÄx<–÷IS™L&£Ñèðððöö–«Åûƒç@L  óùâÍÛwrÉ+]?CCüC,äà 1ñ5•óóóÓÓSû“|N^ÕE×*‘'ÀË«ë»û‡%#j0²ˆÁK­€©4AÛQ’ZÇ`*[`6›•™Êx<&0€-svvkÊññ19ƒ©lŸÅbaãg f&c*½@ã=ëÐt,ççç^SNNNÈ“5™ÏºV‰€ÀTšàgüË{2`°Í*4¨¬Ét:³Òõòêšhú }Ÿhú`X³ #TÖ‡hú-@4}ðØ$ B½­ÑôZ ( oýDÓ MK>¬_©žhú͙Ϻô¼Òõ777gggäC+‚*+ò8ΖŒ¨h‚Òv¦˜ ¦˜ ¦ÐcæóÅv:ñç·ßÿB °±±±õdë®è{||”ÒÕâTa*Møôù‹Ý®òSaccÃTZáîþÁGÓÇTú~pÇÊ'˜ ¦²>ªÊ¶ét†©ÔfcÑô166¶¡™ ÑôZ ˆ¦/[GÑô166¶¡™JMÿîþShÂt:SY±xÏ›1ù„mó[ Ý™Š­Ub£1€µî¨N÷› y[aÃÅ‘/]1Š€þG˜ E¦˜ Pa* ˜ ü3ÞsG1ß0¬©Eá×*ÁTš0Î|EL0•VA±€Uï?|ÄTš°Åhúd>ì·©M ¶MŸÌ€ý6¢é´@Q´©¦Ò—W×´©´À”q*Ð6'OÌf³žþh4’s???çJ¸©0N 5˜ûÕ¹½½ÇçOL&“¤‹œžž<1Àü‘ ÑsÇT0•%s(`c, ©zŽŽ"NNND_|âããcù\^˜Qš-ò†Ë†â(S h€NI:ŠqxxèWôÃÓÓÓæ•øœžþ0{¾(Ž0ŠØB#ˆˆÖ¾â+¾ÇG Æ:znnnôCy3äî““U7®Š#L€¢:G\Ä4¥¬DTF Æþ9wš]â+\<G˜ @›tFSÙutÄI­®keY,CË.†Ób*1óù¢( L 9ÓéLçÑÉ«¼ÇTÀ°!µÆÆªÜh÷G0w4co=“ÉD»N´›I+{©ûuÒo0ìC'BŸÙwMªâ¾¿âMÚ—ä‹rx±Š1œS ÅÂÔ~úüSh‚˜¾M÷×ÍÜS8R [Å\«ǤD¾e{({k¿eŽâQ!H¶ÐÈþM}L8ò¿Òà+ªJqb;¼à¨N‹©x^¾zíåîþS¨ Ñô!ÓŠ`í Õ¿eÃi­HÞ\}þb7’ö¤b*°üÙ‰S7ά56$ûŒâÚÝú˜âV“¥ö4]XËM²!$é ¾b Ÿˆœõ eÚP¸©ÜÝ?X÷úåÕõ’q*™Ï>Þ3¦V¯×m°ž”¸¶NVäÖ3™Lâ½YTà ™¡¾eºÐà+ªPÉôvš¾Áɼ*y.0@SYþ\«ÄzÕ1ŠhS„ºÍÖn‘™ã+r?Q(ÓB“N›lí0Ç t¡îWìhu­AEvâG×–ÉP~ ¹8ÂT( ¬Ò­Ä§ÍìÓ*òdŠGÇ©” §Mv•uÙÔýŠïÆŠÑ™MÁ~l\0×Ŧ@Ñ}4•|WNP„ÌpüpÚê_ñ§#™ _¼yBNMŽS°º Á`M…hú-GÓï¨SÙE’+ùù`ù^J2ÃiËÆ‚ø& $3½ØÊpÚ@b’ç^]†`°¦DÓÿúí;¦Є÷>Ú$ïw½h€Ö‘ ^êlk_‘ª]L"®õµ­%9WH*ò“'’=)ÖŽ¢M)j?ö•À{ä¯ò¡$KN/’Ï寱ÜÔýŠ?6߀$箉ƒ]åOk*ÓéÌ~âòêº( L !w÷rëÊë ý)Žæó…üœ(‹ÄT(ú[a* ˜ `*@q„©P4`*€©Å¦ÐWæóÅ›·ïä^}ùê5ÑôSi‹¢(4 ijç/tr%¦Ð¢é¦ÒúH4}€µ š>`*ýÑôZ`>_M0•.ð-ÖDÓhŽ_íSÞïzÑГâHÔ$è[ÇTšßNrëÊë ý)Ž~üøSKW¢éP4ô½8ÂT(0h£hOØØØØ6¿ùÁy˜ ”š 6L0666LShÄãããû_¾z-¯òSaccÃTZ¡( íl’Mƒjb*M¸¼º¶ÛõÙó˜ ¦Ò Ÿ>ñ¿"‚˜ @m¶MŸm¿M…hú-GÓïÈTÚâæææàààä䄬€žDÓÇTâ{ä=˜ @+øÞ±zbc¾ä¦ÒxϘ @+L§3?_SÀTú ¦€©`*€©`*€©¦˜ ¦ÐW˜û˜ @G|ýö]çþÌç L !ÄSL  ˆ§ÐĨL #ˆQ Ð[÷S¡Áº?í°™µ”0¬¥ ÐEQ¼ÿðÑÇ{ÀTZAç+¼yûN›«1L ¿`*˜ ¦˜ ¦˜ `*€©`*=æë·ï¿ýþ‡¼’€©´ÈÝýƒ”®²éÌJL  /_½¶éþòž L DP|4ý¢(0€ÚÄÑôu-L`M‚hú_¿}ÇTjC4}ÀT:‚húíðìù‹ }’<L`}Þø<b*M˜ÏoÞ¾“»H^éúL -t­y¼¼º¾»X2¢Sè3˜ ¦€©¦€©¦˜ üÅo¿ÿñòÕkÙtØWÌ×oß5AY([ù¢&]5K c{%Áû“SV&x||”?I‚²ÑÁ:À-ŸàÓç/zœe¶É«íæU`*ä×Õå¦Ð\Sò!U¦Ó™O—òI6 Hn‘’(.à|”¤8Z®Üê>ÁåÕu|"ò¡%xöüEœÀäMNÉfjlòªWyåM…¼âºÚ­¼ÂTâCª$ï[_¸$oKçœI.rÇÏ@A”¤|Ù‡Ó­Å.H %Z>AœäÕÖóÊ› yÅuµ[y…©4Ä?U¹óå>Ï?5¸óƒÇ *w~ð°¯ Р” ¢_ÇYA^m=¯j™ ××U¯ò Shˆ>{2î<KãÒ'¸-ãæÖ ±4.\‚ÆÒ¸¹5(¡’‹)ú*Ù4-»õMÓq˳O Ç·<“W[Ï+o*ä×Õnå¦Ð¹WåÉC5ÊÂÔʽ­ ÊÆ¸ÉçòWIS6ÆMö¬ â§(+t$]Ù 8j' ’CØäCj'¯eËèP;ÙOY9>>ø•£££ÓÓSŸLösPI†©¦•4ÅDÌCTÀþé}b6›eÌãââB“É׫˜Êd2ÁTS€ÕœžžŠšm¨—ÈçòjŸÈ_U2¼âˆphKŒÈMrçgggm馘 ÀàۨأÝ:±‘¨‹xƒñhKƒ¾L0øgõ/¬E«Îqzzš4˜2S ú†0ÀT ‰©¬8¢½<ãñ8ø\µŒF£ø+···ºsß‹„©¦5P‘×`¦Ç†ÓM/".™&±Ýs‹R…©¦0,Ô'ò£Ul8íÍOä[6ͧ¬=¦Åá´˜ `*ƒ– ¦rtt7”EI‘Ä™1(-§ÅTS4:-9ŽâC£­Öa”ÝÒâpZL0øËÔBDJüçñpZÓš²Ñ²í§ÅTS€¿°ŽëJ§],ª/gggÉý´;œSLþ%ÞKâè´Êh4ʨC»Ãi1ÀTà/lâñb±ÐOÊ¢ÓêçA?‘ÑîpZL0€aqqq ¹½½Õ>/eÑiËÚZþY4·:œSL`X¨‘ŒF£Éd¢!RÎÎÎlR—˜²è´ÖO”4žv‡Ób*€© ?'Ù#*àõ¢,:í?Ëß’Èo­§ÅTSb!ççç'?‘÷±Žhš¸AE‘Ïå¯q~ùD>o±ëSLz ¦˜ `*˜ `*€©¦€©¦€©`*˜ `*€©`*€©¦%hÐ6[†SÀTú‚ÅÈoq]LSh]¹Ýuy0L ÎÏÏ[¬õu±åxõLShÂh4’Z_|¥-º>fL0€¡ptt$µ~+kÇcÙÕññ1¦€©À^1›Í¤Þ=;;ÓjXÛ9´2Nö¤È‡’F»ZD5Ô3䓸ò^,çççbºCy#>áÿ§•uWr$^ìyïLŽ\Ò«ôHš“Ÿø<==Õz^븦˜ ÀFÑÖ©Ë¥Ê?ø©øƒY9“Éä B¿´gˆL˜xLVâþùŠêˆx€Í[–3Ñ‘ÏÍWLVRØÁØ>å`äëªMëx¦˜ ÀF1ÏðÍRk[ˆ=¨ÁX2ýÐ7èç²[kË‘åUÿ §5¥ðûQM‘˜—Èžå+z¨¿ÁOø–EmÉïSŽì›¥´¶I/ɬKÅïVµ#ˆw"ââ? Ú]lúùFyI ©èñ”²$L0€>b-%ê%:ËÆÂ«$ç k2Áb«ÄÕ¶õæÈç:mG[Pò-4ú»²Û ¿IƒßëPÙU¶ß~îì'ËŸS‡doròEõ¤²YÖ˜ `*}Dëx©Âƒ8úÉ92áÍÒHõ¯=/Á`X #ëñóŠ“Ó˜mZ²µyhLÛxúOЯ4›Í|2=˜ä1øX·˜ `*}ÇÏC–ú^AÞ‹”µ:ÈçAy•š;™^œ@T$q0mGãÙÇsyä+ÁÞä½|]÷#?]ö[þàƒêçB[Áß0ÀT6„öà¬9kwh`*€©l‹›‰S˜ `*Û¬qW†!L0€-P= `*€©l Kצ˜ `*˜ `*˜ ¦‚©¦˜ ¦˜ `*€©`*€©`*˜ ¦˜ ¦˜ `*€©`*€©`*˜ ¦˜ ¦˜ `*€©`*»Æããã×oß§ÓYQÉò¹$-“@¾. dW™¿ýþGYáîþAÌç‹Æ äO’@’5N ‡' äPÉ«~æU`*ä×Õå¦Ð?þüÛßÿñ_ÿÛßd{öüE|oËf äM|ëÊ'>A|ëÊ>/¯®5lÉBÊ'HRï?|´Ÿ>‰ȇ–@Ç d·–àå«×ùrâÒÇ'-(¡¤ô ÄP@м ˜O@äUõ¼òEy‹yåM…¼âºÚ­¼ÂT ñTÏñ“PP4Ä dŸÁ}[7”hù2ne‚¸””ò(Ÿ Î òjëyåM…¼âºÚ­¼ÂTâ[)“1Ám Á#Hüswÿˆ A⇘àq-~ˆ¬EWÄÍÂA7 e\\Î’W[Ï+o*ä×Õnå¦Ð)M¬tHö=K{ Iö=ûUÙUrÛÊRâh1'·}rÚÊò¡–ƒ’,ÙµìÄœ&жeIpäUò*QK^q]íP^a*û³”awÁT0L0L0ÀT†Žs|ùêõo¿ÿQÏShÀt:{óöݧÏ_t–¦ÐFIÞ“!€©´‚Ÿ/­Ñr1€ÚÄãè˜ @|tì‚©Ô¦JLIL q¼]L  >ddiShFN—Þ€†ÈÍóæí»—¯^Ëkf}sL EQ|úüEJWÙ4b/¦€©ôLSÀT`SL&“‹‹‹•¦²X,$åíí-9˜ lQÃ'ÎÎÎTDSAFòÉéé)Ù˜ lUåèèHŒÄÞˆÁèçÇÇÇâ4ä`*ûÉt:ûí÷?䕬€~rvvv…~è'?~ü)¥ë×oßu­L  oÞ¾³éþ/_½&C ‡,‹£££2M!‹ Ÿ>š¾È ¦P›8š¾|B¶@¹¸¸HjŠ ý>ÐO‚hú_¿}ÇTjC4}Ø!NNNbS™L&ä ô¢é´oýÚ>Iž@?ñCk­A…lÞâûÖõ9ShÂ|¾xùêµÜE—W×tý@ÏÑ É4¨ÀN ~*+Ïž¿˜NgKFÔì=ÁŒe2v L`ÿ±Ñ*4¨¦½C'’€©@9:::??'S€>2g³ù˜ ÀPÐxÏw÷d@‹<>>êZ%DÓhÎûmº¿¼'CZaê¢é_^]M ¡ïM  ž=A4}€uÙX4}y¶xùê5[·Ž X¢é´@Q›‰¦/·hpÓ²±±±õd먀ÕðßDÓX—ù|¡M”òÚÝ ZL…mh¦òøø¨²"R.Q Ðg066¶¡™J ¦€©°±±±a*І©0ˆm+[0%S€´©'0´âS hÀTöŽù|ñãÇŸÆ|ÃT`€¦R…”®§ SX÷¦ýôù ¦˜J+ÜÝ?XÀ*/‡©Ô&ަ/Ÿ`*€©¬O0tw:a*µÙX4}L†f*DÓh š>m*€©´ÅåÕ5m*- 7ÊŠ¼Êû]/zRÙZ%6 ShNG>˜ ÖT¬tµ¶jL€¢ ¿Å¦@ѽ`<<1›Íxú‡‡‡rî§§§\ G˜ El1±Ñhtò„TÆ“Éd±cJ5•fÑíí­žûùù9 Ŧ@Ñ›«€EMRНøÄGGGò¹¤`FIVh¶ÜÜÜpÙPa*í ñž;šŸŒ©ìSí«ˆˆh›Švs(¢2šx±Xè'gggÌ+9k=ý¸© †i*Œ¨X—étæ§ûc*p~~n:rzz =‰Q_1S¹¹¹ÑÄãñx€Ù¥-O"s\9˜Ê|¾°€Uï?|ÄTš@4}Èsqqaštñâ.~ô¨™Í0»?ôÜG£¦Bä7€ š>dX,Ö¿S¦)1ñpZQ™›'ª|]S6˜4$G«ß­ÞíR÷+···ùô §ÅT÷Ã\|ÛŒ §M(Ž0ŠhS„ZÍù–˜dEî' eZh‚Ï­W(ní°¶“@ê~ÅŽÖò1yå}ðf]Ã\—‘âS h€Ía-µLÅZb’qôãŠÜ3ÊíÚ0‘ŠŸû#†×ÔýŠIRŒ(‹˜\î~Ùœ üpm; t¡ÁWêjÃi1•˜—¯^À1€ÚM’X?ŽÔâÕƒ˜eØ}Ež1«øã~œÌp›I´4øJ]Sa8-¦C4}€ š>”a£G3R+[Ý_6Y&#%öaÐj"»²h.eÃi“ÃA¬;)Øaƒ¯Ô¤ÍpZL%&ˆ¦ÿõÛwL  Ÿ>±I×%ÇT`ùk 7©†ƒ–©àµú·d«‰‘œÅ“œ -5½jJ²S&ßÚQ6œ¶ÁWì€ã14:Q90†Ób*1w÷ö—W×EQ`* ™Ï"ûïSÅ7lت7ÁŠƒùV“ …¦lj„ìÁ”÷ú¦l8mÒ‡òÃi«eùk¤89ìó'ä`,}u‚ÁšÊò©ÝZJWQ–^'ä>E´Èb±°N˜ÑhäÛ2ÃiËÆ‚Bà—5¶¯le8­bÊcåb8-ÅQE0Šh‘©³ED´AE×"NÆO“z:^»GGÛ$’c8äCm«Ð8%šÆ¾x8|˜ g¢n! u|Å›üÕ"¿éÑñBHùŠ#L€¢SL(Ž0ŠLþÉ|¾ÐIÿòÚÝôL†f*EQh˜Z[«Shr#M0•. š>@ M0•Ž~ˆhú-GÓï¨S€¡™Šo±&š>@sÞøH4}ÀTZg:M µÛIn]y݃¢ ?ÅÑÊÏ}ýö]‡b* ý-Ž0ŠL0 8ÂT(0ÀT€âSè+óùâÍÛwr¯Êë&£éÿøñ'Ûæ7š¡ëhúò[Ïž¿¸¼º¾»ÀT¢‹þX4ý™ ¶ŽÊ½@‰äQS¨Í£é³±±±í·©M ®ŸmEÓgcccÛoS!š>@;øÕ>å=½?lll˜Jëåž®T©4An1}ïSaccÃTZaú´V‰lKFÔô¹K™qÀƶÛü·ûwÿþ?\]ýáœòÆJBL`]NNNnnnÈ LSÁTSÁT0ÀTZFã=¿|õZ^ut:`*˜J+üöûRºÚZ%˜ @.¯®mªž¼'C0L¥>}þâã©È£ ¦P›EÓLehM âhú˜ ¦‚©´BMSh½?€©t½?íP…ÜN/_½–×î¢é¦2@¾~û®óQ €©ôLSÁT0ÀT0L0L ¯E¡£Óûýæþ¦Ò"ÓéìÍÛwŸ>ѵJ0€&O0•.Ç?⩬ 1jSébÔ´ëþ¦Ò¬ûÐÏž¿°IÞ“!˜ ¦Ò ï?|ô¦Bï@CäæyóöÝËW¯åUÇ|¦‚©¬­U"ÛÝýÃ’µ˜JŸÁT0LSLSÁTÊíííùùùl6Ë›Êb±˜>êZ%DÓhÎûmº¿¼'CÎl6»¸¸ Ögê¢é_^]M ¡ïM  ž=A4}€u!š>@GM Š¢ š>@høo¢é¬Ë|¾Ð&JyeP-@[<>>ª¬ÈCào¿ÿ±dD-ô™ƒ Cˆ­ÅlMäy‚«h¿7yvÜ׫7hÃgÛ¿mcãó06LSaÃT06L… SL… SaÃT06LSaÃT06L… SùkúܨÆ|ÃT0L…m€¦R…ìÜöß6R8²±5Øâ‚i ­Ÿ>Ù˜©p™íúÄߎ©È‰ó¿¿Ó›_B¤SS¹»°€UzƒÄå MîÐVµº¯gGÓ—OÈRhPÇTöøLÂÆbs6?Î0 Zíï‹©Ps¦0´r/MSªÕºÑôiSêoLSi‹Ë«kÚTSi¹yTVäUÞ“¥@ý©`*­`k•Ø(@L¨V׺uÉR þÆT0•.~ÎÚª1 Z%Kú›3…~™ŠSªU²¨¿9SÀT€jÈRÀTSÁT€j•,êoÎ0ªÕÆ{îh~2¦BýÍ™ÂM…µ@µº.ÓéÌO÷'KúSÁTZa>_XÀª÷>b*@µÚ¢éõ7gº’›'‹Å~œ‘ßSáŽÅT0Ît¯4åà‰óósʽZM0ÚT€ú{oÏt2™œDŒF#Ñ…ÛÛ[Le'L… Si‡)ãTSéߙЗ”#ÝX_Lצ²ù®¥­ŒSÑËSªÕæ-+ÌýL¥Ÿ¦rzzzþ“Ñhd²r||¼™Ú½SSÙJƒÍ&çþèÌJQý'¦T«d)`*ûf*R—ûÏg³™8ŠI ¦ÒsS ÀT€j•,LeÏMÅ×©Õ*Õ*Y ˜J¿LE8::*ûëíííx<ÖÞ¢Éd2›Íªè‚¦—/ÆÃu32±X,t”I¼OqG•˜|QÒXûÐÍOª3¦T«T«d)`*ý5•ä_ÅKÌ`<"e#ZDãáºÞWÊLEÒØw}zyŸ|=3dXþ„©P­’¥€©ì°©ØPS„³³3?ØV¾ni„ÑhïD &øŠ‰Ž¼É›Š×1¤äç:§ZÌvëÖøÑÁÉ£ÅT†[­N§3G'¯òž,L¥ç¦2›ÍôOâ>½Hƒo>‘ïš7]*¶ÿò^Å"c*ò&^Säsý9y z‘ÌŠ|úå¾S™Ï¦öÓç/˜ P­6¡( ›î¯²"Ÿ¥€©ôÙT¬5Â7Qˆˆ$»xÄ4ñx`*-@4}ÀTzn*Iâî•Z¦b]?UF„ø=XsN²%&?ÖZ\‚Φ=6• šþ×oß1 Zm§Ï_ì4u]ò]ÌRñ°áåQÖAŽVvIŸ¤þÆTŒ££#mÛ(»Ä!ÎÎÎl© kñ*`ÒPÅL&¬É$é">ÊKžá˜ÊÝýƒýÄåÕuQ˜ `* ™Ï"ûïy³4žiiž§§§]‡gh±Zêz~&¦²s¦2«wñÓò“„›™Šì?ÓõSÑT‚N«½üöøø(¥«(‹þSLe¸Yº²|LñëÖ¨ÞõüLLeçL¥b3›]BÚèbrc¡ÕÖ7]Æ9CÅ'«eÛĨÅTSœ©… ß‡n 96æÙÿæL¥‡¦"ÒPf Iqo6NÅW”ÈwBÕ¥‹©`*€©`*‰“EáÀTúc*ÖFrqqQEÌlêÎýÑOlO :e±[0L0²4g*ö¸CC@¨¿9Óf¦'Nöþ,ÝŒâXnô®ÉÈ„Ÿ«ìô˜ÁœÕ5•ͬ ©Õ*ôÑTòÝçRæJùkÅ®<Êcb²ìö…¸¤±ÇGÙmÙ y²”ò×G(—&g$iœ ÁFå¨äŸ™ÆyùQýJãÅTöÆTÊf›¦Ä¦brǨÕÛ!ßì!—™ÝöuëRY &(ÉE¨]œÁ h‡aéY¡Sù…ù|¡“þåµ»é?}hS‰ÚüðÀxQ·¤Ö”Í "oúß­2¼7~&¶ÇÓ2mRññÔëþ(¦²7¦²t1gå Å„-ë^ñ Ib¿î¿®Ê:h|<~“Œà ´ÉÒÞìËÎTÒè1ìÍ …EQèÅck•`*€©4¹‘ö#š~™©øfê Ê7M‘W[-E>´A¸q ¶©ºÀŠ”à²OSŠ`U[«6ìwåMÙÁĦ’o·qþ ëþ(¦²O¦’Ôh]EY¯óØTÊ&6W\Kyé–Eôm9Ö*“ _„ò[ÁÐ{³B!ÑôSÙ¥;v+¦â+鸾·b=î1Yñ­ÐeS–?׳ÄV7Ä)mi·`$crœßq˜/«!ìàü(¦Òç3µ`†µ¾%×Ãx<–ËI^í‚”e?ù`qÚ #o’=/z$É=”z‘µÝê¬é|ŸŽÜ­šRÞ”êΕ{DÓL¥âhúum7žJÜ:bmÉ {ˆô£3qÄlp@2¥I‰7¤¤©˜Ž”-°âÍ£Áb*œé0Ù˜©øk¢é¦Òœ÷>îA4ýŒ¦$»<ò‹¾Ú}+tf(LYkMÙãi¬AIS±ñ‰Ac¸i–ßCƒ¥þæL1•NMe:M0•Ön'9eyÝÝ,õKŠ(6|$¹–›5H²óÁè?“ƒxälŒ LîÙ,34hí·Þ+ßnm-^Jü(õ7gŠ©t=÷Gö,¥ß×oßu ¦˜Êp³´lœJÙÈ’üBµ~haÐ&QeVBÅuÚª˜J²OG¥$p¦?JýÍ™b*ÌRÞõø#ªUòdMeéæú5´™JY›Šb1ÍLå<‹¿mËL%î2ý ú­ü(õ7gŠ©`*SVºÉ?uìwO FŽDcXíÊš&˜ÊÞ˜JÙZ$u§€š©Téý©>¢eå½¼tUz;›{wwƒ¥þæL1•}3›£ØŸ¥ÎÊJ7ë±nqô\+µÈ®,¼‚©ì©øšÞ­­µ–l]¨«AyS±’Gog½»ã€ ~”ú›3ÅTöÍT|hšê‹lÅT6lÜ7•ôÍœ0•A™J2и}X¥D©?ÍF¼V/(2¦bR4ž[Ù14øQêï!ŸéÊå0•Ý3ùGî¡©l¸·eåb˜}ëÂT<óùâÍÛwrŽòº—Ñô—%K¼Ú³GÙu˜· nÇçJJÑKïW?)kávž1•¥ëñц“dùÓàG©¿w÷Lo²øëS. © äÊ ­Sé4šþûŸ=qyu}wÿй©èóŠöàÒn\êvMeó÷Ìnuî`*]ôÇ¢é數Ømë/QkŸ°Uµ”—Ï%½¶ ø;Ë"½ª¬H¹Ÿ‰¦ïƒ”KI¦;—_±å«ßËþP3«ÎÖýQLeGÏÔ¯;X}ZYð¿©lÀT|´*«Ù­©è3™Üíöà²É%ª1•3É“žtöäŽÝ®©”«•*¼l…ÂdD×x¿BaÐÐ’\Q¥¬]vå½ì3Ó4RëG·^KÛúº‰C0/I|ó‰iMð¿_f*ñ²Þ˜Jc6M?èNÆb Š3íì°RR{ñ›²’Hk°2Hƒô[eý&e¥›. P6(DŽGþ¤í‚¤L^‘ò‹þ$}|úC¾i:3=2Ó ¥O±¶êffÈKœ·öEyÓÖ­µfµªÿ/}hu[‰8þ~DÓ×ë9Óé®×IœÀnÏ`QÙÌ}!²B@JÙgYu+—«Ü~µ6y_v+å«ù +CòYQýG·XË‘èHçèî­©Ty8”ÖÊ%¨&ÊL¥'Àûa*¦¯5± i~} ÿ?]&¿É²Æ®<íóކâÊ»ì’Ê4ë•ÉxÐ É’ÏšºêlüCµ‚h¥jÙª›’>.ÅVæm+h«U;žŒdª[ wW3Hyÿh\‹–•Í`ÂTZ7•20• ·%ëJõ]™ŠT–A]n-Ée1 ìÚ4º”ý39>ߦ'Ø‚¨Úa­Íñì躦tcË×åÛ¿™9hq±’‡¡£¦ú0S–]ò‹æCò]9Í%û0nQòVRêzgZ¿e¥Aµ¬Ÿ¾]?ŠÜÞ3¦Õßroú[u'Ú1Le¦Ok•Èöøø¸ìnD­3ðMV7'+Eÿ¬ïgø½Åß-%U`ÙÊõµLÅF“ÄÍ3ºo*ò‹™TA¿ÒÊq*e¦Rv^’3ö§`ŸAÞú<ô^¸ÉjU»‚&¨v'Yì˜ÊÀMEnUÿ¨¶»íŽ;g*j‡Bà…q5¡]¨öP*¬=mÆN)ž=Wkbyv—U~ò§]¹zÿïÿçÿ‚©èÀÛä’XÞ?¬è–ÿAý\§_hy˜¥`ýÚË¿Å'º}3±‘Y5¾ú@×À~‚Ö‚üØ»8|+NuS)›¥¹ES1)VRë”3ŠÖº©¬ f1ÿÓÿü¿`*ƒ2•Œ£ìÿé?ÿ—!˜J•2Öö÷ÝX|Ô²Q ɶpL¥SÑÚ4Y»[P¹`l­’öM%íÑŒ™ØuS±×!ß¾±n+¦Ò·ÞŸ9ÂÝûsyum§)ï1•V. ?þÌOÜé™bµêï`?sZ1•d°ÍŠs3˜Š=½[€ä‚Þ=œö¼1Sùôù‹§"‚í›J×$¦lZlÅ~¾dÍ]fµ®€ø´!À+kñü`áM%9F¸ñ)o×T–?çíÜóâÞDÓ¯ÒL·ŒÊ“ë­·HËôøkÒn½#âÕ…öÕT¬}Å·×îD(ýå`æþ¬,cýÕ›SQ:¦oeM&„—­Æ^6y8y•-Ξ™Øì=£ñ,å8Â[­ËºuSÉÏš¶S®µ<ÊMÅ âÌj·=$ަ¿—¦²²„-[z¢Á´5¹*¬J¶zÚ.àÝ]]¥qý-ç®YDŒÚ5•Ì|ܨ3dS ¢é·o*VæëõäT[ÿl<4x¹ þÛ2££}[_•+ y ú>dì–˜v-ÆŠf­Öe¦RVîä¿•fÕ¬½j릢èä ]‰¿9„ÞŸ²ÖGwhKVlž]òvÈ ÒŽ©h™£÷8ëþ좩TŸ™±¤÷§»ÞŸ|H²éÅÿÏüXÉÿOC2µž ú‚ÿéZ¦"Ç`’̃×ÒÓvbÓâÍÒ‚Ãȼ=Dúzºì[vœv¾Ë§®P{îŒ[˜vÂT–?W‰Û‰ú¦( ¹¤ð•×}¦Ÿ)aýÄ„v«z«äMÅ~ÚmÍÂT:5•e…ñ…˜JÀ×oßu¾Bû#j­ñ Êu`Õ¿_ Ç%9‘59§Ü¯zšl Žûtë®ûåó£Ûìø}2 ¨¬gqüD²œç"ú4™¶œ²å ë.ÊØ7S]1 ·2²SÙ?0ÃJìêmÆ7•°hÑTlõ»*]Èñ,ÿ#e_’DvžlðôEX£Ý79xÊÓ©ë$ŸããÑEþ‚3•Ÿó“o%’5ØÆÍÚö§ä}™uSµµ&˜èk?Wý”Wž8¦‚©”)Brì¶^œ:Í8¹O¹eô>±KT{°Oâë\ö©s˜…`ò]|vçJJ"°±/˜Ê^šŠ]ÀÉ1šö+…vòJ“˵âJº˜J›ëþ¬sùAs™"¯ìa«,ÃúXœˆ¬rØÉÝ6è{¶ßêÃŒMLeP¦b‰É•;«7à%Ã$®Ä×ñƒD¦BÒ”zFþ9ac 6˜Ê^šŠ$&^"_WcŽïI 6cÎ-Š£kVO‰©ôÎT*À5 S­Â®˜Jf¡‰º¦Ò,~¼ ÔÂ$“_—jÀšãaOf*As&¦‚©¬c*Içöß•Ѽ”Íç˜ ¦˜ YÚÜTDS2ýîÍE5§b³ãáYekyÚ|x}Õƒ”ïnlÒ¦²-ä¿XUù¿ÖÐ qb ÔV¥S^’ÅMݲ7ù®ï¸WiŽÇSÚÑögB>¦‚©P­îEQèèt9뽟û¬Œa&KüMšJ>â¸ÖÁBëþ¹v+}¦˜ ì(›4•étöæí»OŸ¿èZ%˜ `*MT<•L{u[Íš™Š=›&ßšUü0;€mMôÅTS©^îuM¿1Í»4Úw•~GÀTÚbh1jƒÈob64$îvÙ˜©X§•Ë~ù#ikN>õ7gŠ©ìjŒZÀT†`*Y÷'3¢ÖO°ÕnÌTš-§‚©p¦Ðór¯óuSHïϳç/ì4åý^féÊ(àÁJ=ý4• ¦™BÏMåý‡þWúÕû˜Ê!7Ï›·ï¤ð•Wó5@S±U±¼^lÞTj SÃT8S蹩ØZ%²ÝÝ?,{5¢0Ø-SI¨Ø˜©dbº`*˜ 쮩$JL0²´™©äEg/‹²Ù:Íæþġa*˜Š]‡»²Üi34¸-¦€©`*¥¦âƒÃzQ°oÅõ„ ÛŠ©Ø“‹­`*ƒ5moÛ®©È ¯`eæm!”€ä~tA+yõóìt-¡d$!LSº©Hi+å£iJ` 7Ö£òa5¿S± =Œ`å,)ÖuZ5¦2´3Õ†½í®†–_¯ª,²¾—“$8>>¶óŠW ÂT0•šJ†äc«uiÁj¥­¼·A¸­˜ÊÒ­Rki³¥à[˜ÊÎôè‰íž¾™G²ME¯Ãñx\Ö¦"š¢×°¤Ô%/..´ÑîýçÆÂíc*@µº{L§39eyÝ×,µåa“ˆvdZžÍH‚6“˜¶LE¿këHu„üÇTöþLõº­Þ!Ø©©”ýU¯ÃL@vM? Ü>¡ïåÚÞd´åMšŠìYJ¿¯ß¾ëZ%˜ `*MxóöÝŠà­gi²¯½âzò¨gýëöØ'²"_O6ËñaäŸ%˜“Ò-K¯ã¶Ø/€©t\oɵ3å¿^ôE›Üâ¾ÂI »²ôz]UׂuLEeK<;¦yc@3yüóÑôEV0ÀTjGÓ—OÈRÀTúp¦:"*QëLѸx!ˆ@4™ï¾ÔomÀT´é±J³Ðá{f*A4ý¯ß¾c*@µÚß;S¡þæL뢆h‡Öè¦&:¼)Ó¡®`-(ÚÈQk2QS Æ©²•\«<¹Ÿ•íC»UîM0•ö­_Û'ÉRÀTúp¦±"hPÐq£¡}ʺÕòŸT9ŒS¥äÜ“••£X*¶Í쨩ø¾uýL¨V›0Ÿ/´ ¾¼ºî¨ëS¡þæL[1m¢8==õK‚«©”Õñúï1’¾V?‹÷äçþ¬©ÈƒŸÊʳç/¦Ó¿þ 0 Z%KSÙgS) ^’‰¹¢ÓjŽåx€ŠK­ùDëŒSÑŸ«Õm/M%‘™˜ P­’¥€©ì½©ÔªÎuœŠ-þ -Ø©hwU•a1˜ Õ*Y ˜ÊŽi< D£ûÔŠ;rxx¨-(™ÉóÝ™Š_^9¾îèL¨V©VÉRÀT¶|¦Úuâ%@ã ÆáI‚et¤Ö·é6:WȇQ ¬B¾+é“ ×7³«ããã p­Ž¶ñ¿²³”1 Z%KSÙç3MÎôÑÀó&òO?KYÕÁ$À/À郩˜Üh÷PfžsSÑ`-#Ñ"/ë¡Ú?M§´éec+*c*@µº{h¼ç»û²0•þœiYý=™L|Ý/²’iS‘¯{/±U9m9¡•m*£'Êþ*_‘²•ƒ69(¤ ñTêý0•©‹¦yuM4} ZmèûDÓL¥·gZ¶ôOEt\‹x‰×bö${uÑŸuýlÒTž=A4}ÀTvæŽÅT¨¿9Ófµøááá:µ¸õi§ŒNWÎ/´aÔÆ63?yÃåÑôSi¢(ˆ¦˜JŸÏôââ"3ˆ¤ “ÉäôôTME¤GÌ`3ËëT?¼Zó®wÈT‚+‡hú@µÚù|¡M”òÚÝ ZL…ú›3…ž°1Sy||Ô‹GÕI0 Z%Kú›3…¾˜J ¦T«d)Ps¦€©Õ*¥€©¦‚©Õ*Y Ôßœ)`*T«d)Ps¦˜ ¦T«=f>_ÈÚQÌ7L…ú›3…ÁšJQ²sÛ?¦T«ëžì§Ï_ÈR þÆT0•V¸»°€UzÙ`*@µZ›8š¾|B–õ7¦‚©¬OM:a*@µÚß;S¡þæLahåÑôSi š>m*@ý©`*mqyuM› `*- 7ÊŠ¼Ê{²¨¿1L¥l­ˆ©ÕêZ·.Y Ôߘ ¦ÒÅÏY[uh*r1IáÈÆÖ` &ªÕ.L…Ël×·`´àpLENœÿýÞÞøØ—x*ll-n¨Fë¦Â¶gÛpL…mÿ6L… SLSÁTØ0L… SÁTØ0L… SaÃTö÷ÜÑüdLSÁT؆l*¹µll˜J¦Ó™Ÿî©°a*˜ ¦Ò óùÂV½ÿðñ/S¡ .‹¦04‘ßȀ͒Ûê¯ØoÑôÉ€ºÐ¦Ð‰ É€L72N`høq*: ShÈããc×sˆÎ¬eÑb*Ð_0ÀT0ÀT0€¾2Ît¼Ê{2 æó……©ýôù ¦Є¢(lº¿ÊŠ|B¶¬O°bÔb*µ!š>@GM ˆ¦ÐA4ý¯ß¾c*Møôù‹ÝHº.9¬ÏÝýƒ•®—W×EQ`* ™Ï"ûïZáññQJWQý'¦ýSLSLSè+óùB'ýË+ÓÚ¢( Skk•`*Mn$¢étÑôZ€húA4}€ˆ£éÓÐ ¾ÅšhúÍyÿá#ÑôZg:M µÛé·ßÿW² E~üøSJׯ߾ë@Lú ¦˜ ÀÞ1}êýùúíûÊes˜ïî$le ´ T¶ÇÇÇf äsMP6;ie969…L?×ÊäyE^‘Wëä¦Ð?ã_ÞÇ >}þâ…Å änÌGd™ºae’ ¾·ýdiIÏ?’Oü(z[BÝ— >A\ÌÉQÉÁûé‚ñ‰ørÖäyE^‘Wíæ¦P¹£ò³”å6^p%ž‰$и™*ˆOAò”’%”/}4Þn@ŽÊ'cΔ>ºEyE^‘WäÕšy…©Ôfeä·øÎŸ¥âèFù¢aå‚oÞ¾«UJÆ…`@¶•Y‚äyÕ“¼Šk_òjWò ShÂÊhú¾=6ÙÜ·q[¨oM–A·ûöØdÙañSNã..\‚…’íÆäyµ£y<å“WÛÊ+L aÞºò P+·º>"H('……ÞÛ’ ~2¶Rj<{þ"¾«­ÖÑÉÞk-䯒&Ù{­¥ƒ&ßJ”“cÓvݲrúZÌ•eyE^‘WäÕ:yõÿ+ÑŽ¼NeûIEND®B`‚srt-1.4.4/docs/features/images/rebuild-missing-sequence.png000066400000000000000000000626161412557703600240010ustar00rootroot00000000000000‰PNG  IHDRK±Ô+ùT iCCPICC ProfileH‰•–PTWÇï{Ûm—¥ÃÒ;Ò«ÀÂÒ‹)‚e—+,UÄŒ@D%”PŒ†"± ¢ °g‘  |  ¢ò=$‰ùf¾Ì7ß™9ïýæÜ{Ï=÷ž7óþ"Ùññ±°q¼$¾¯³=cSP0÷àq€bsãí¼½=À?Úâ€VßwtWsýó¼ÿj¢Ü°D7Â;¸‰œ8„»vàÄó“€Ñ+§&ů²Â4>R ÂëW9bW×ÒBטûeŽŸ/ á4ðd6›1‰3R8Hb ÂúÏÃgá‹ñøËøü4~™ BP%X¼\ÂNB¡†ÐI¸M˜",E‰êDk¢1š˜I,!6¯ßH$%’ɇEÚG*!%õ‘&HïÉbd-2‹¼…œL>D®#w‘ï“ßP(5 “LI¢¢4P®QžPÞ Q…ô„\…¸B{…ʄڄF„^ „U…í„· §  Ÿ¾-<'BQa‰°Eöˆ”‰\Y¥Šˆz‰Æ‰æ‹6ŠÞÉ©‰9ŠqŲŪىMRQTe*‹Ê¡î§ÖP¯S§hXš:Í•MË£¡ ÒæÅÅÄÅÄÓÄËÄ/‰ è(ºÝ•K/ Ÿ£Ñ?HÈIØI„I”h–‘X’”‘dJ†IæJ¶HŽJ~bH9JÅH‘j—z,–Ö’ö‘N•>%}]zN†&c%Ñɕ9'ó@–Õ’õ•Ý%[-; » '/ç,/wBîšÜœ<]ž)-_$Y~Vª`£¥P¤pEá9CœaLje”0z󊲊.ŠÉŠ•ŠƒŠËJêJþJYJ-J•‰ÊæÊáÊEÊÝÊó* *ž**M*T ªæª‘ªÇU{U—ÔÔÕÕ¨µ«Í¨Kª»ª§«7©?Ò hØj$hTiÜÕÄjškÆhžÔÒ‚µL´"µÊ´nkÃÚ¦ÚQÚ'µ‡u0::<*q]²®nŠn“î„]ÏC/K¯]ïå:•uÁ뎬ë]÷YßD?V¿Fÿ¡˜›A–A§ÁkC-CŽa™á]#Š‘“Ñ^££WÆÚÆaƧŒï™PMÚ º·¡Ý x¹zõzì­îàý³ÖÇÛ§Ì癯o†oïFêÆí7.úÙûø=ô×ðOöïØÐ°èX(Ø´nÓîMýAÒAQAÁ¸à€àÚà…ÍŽ›mžÚb²%gËØVõ­i[on“Þ»íÒváíìíçC0!!!Ù^ì*öB¨khyè<‡Å9ÎyÁer‹¸³aÖa…aÓáÖá…á3ÖG#f#m#‹#ç¢XQ¥Q¯¢]¢+¢—b¼bêbVbc[âðq!qxb¼^Ïùi;†ãµãsâ – ÇæùîüÚD(qkbG ùù$k$“<‘b“R–ò.5 õ|šh/m`§Ö΃;§ÓÒØ…ÞÅÙÕ¡˜‘™1±ÛnwåhOèžî½Ê{³÷NísÞWŸIÌŒÉü%K?«0ëíþÀýÙrÙû²'¿qþ¦)G(‡Ÿ3~Àê@Å·èo£¾ 587 177 Ö~kè@IDATxí]|Å÷„@è½÷.H&M:üÞ¥)EPø+H( ¨HQªADTz“Šô&=b€Ðz Õÿ|ç²ÇÞ%¹är;{—ä½Ïçngggwß÷»»³ogÞ¼IòŸba˜f€`˜f€ˆ’¯(s9“`˜f€`˜f@2ÀÆßÌ3À 0Ì3À 8`€%äð&f€`˜f€`ØXâ{€`˜f€`˜ $s°–ÿ¶RnΘ9»£bñz[È¿§)I¢B¹sÇkŽ”ºpÈË‹1:")lK ×ñìÅy¯æÎ[8\‘¸©ÈuNÜx󴽂.\÷j’^¯&|Œ×ÂÂÈËÛ›š6mêðKâh4Œ¥°ÐkÀ™f€`˜f€ˆÏ ôìÙÓ¡ú[–Тc©\­NŸ7>¾°‡Ný{ŽÚ”+Ÿa8ÔýDX(­KŒÑ!M¿11\ÇwÃ)èŸ\çxüÝèXÁÄp¯ž-Ç.'èz51`<õà>={Öñ -¶²ÏRŒqøÀ@}©,Ì3à ðóè—•06– $“Å 0Ì3À 0 6–Þ5„ˆ¿ò"Q/3ø:ÆËËÆJ3Ì@`€¥p3À 0Ì3À ¨c€%uÜzÌ‘ÿ#žþÏc.† Šðut<Þ•`˜p8..ǽ~5„ž?iפI“Q¶¶±Œnß ¥GïG*‹ ”Å>ö‚ò§O¡$"nPÑâ¯Q*ß4öE”¯_¾~ž=é<É’&¥\Ù²Ùä‡Þ¾M=²ÉÓVPûD%×BC)äÚ5*œ7/¥M“°0>§cbôÁ­»w(g–¬£OÊ”QÑ 4OåuÄ5ÿûÜ9ñ.eË”‰ŠæËG¾©R)ÅÕÁUbÔŸï•+tÿáÊ”>ƒÄ«ß¦:­ªÎ‰Ký¤ «×ñÆ­[ôoHݹw²fÌH% ¦äÉ"×Áñ #ž¿{8T9[æÌ”RÄÙ1CT^Ç/_Rðåºpù !]PÄ,à†ø*1¾¸.‰÷âÙà`Jå“’Šå/@Ó¥3ãÒ‘¡OÂþ¿¶QßÎ £U|ãî‹”>cf¹=<üÕ¯bk<éwüfÚRªU¯™5ëÑ£4ì£N´sÛ:kš·£ÏÇ΢dÉ’Ûä«ZÙ±?5íÛ'ÚÃmÜH™Å £ `ýzÑ–]üÍ7Ô´Ö[6ÛŸ¿xAs~YJŸNœ(ó§ F[üϦŒ³+Îúº¨Â>ÆÌœAÓl ¤FÄŒþÔ¼N›|•+ª0>zü˜ü§M×ðõñ»Ï†QË·ß¶ÉwfÅS®£½Î›v練úËìÌ2PІöE”­«ªsœ­Ÿ”Vu¯j:ãÅó¿¥Í{öhYrY(O:¸ü7›-)—'DKć_Œ¡C'OÚ䛽¢ ãÚ;¤¡áYíÚ„Ö¤¥k×Ê–·Nÿ7”v/ù™J*d \U7îÚ% ¥|9sÒ;õêSßTôÓªUtñ"u>Œ^+Z”ŠˆV&3DF½î¸Ç?þú+}–©iUuŽ3õ“jÀ*¯#ZßêÜI>ƒ¥DKR#QOå/V´Š;sF54ëñUa|»Z5Jï—Öz}b]à¹jVë™*Œh|§ß‡òV}ýuj">À½Ddñ)‹IC»—ÿZ=k¶º²´*ŒçDœÀºÝºJ½V¯AuªT¦“ÿüC?._N_ÌšI9²f¡öM"ÛF5ÔXÒkÕ¾7õøà3mÕáòõ oÒ„¶_áö;ìݵYJ0Âæý²ƒÒgÈ$‹T«Õˆº¶®AëV.¡þCÆR¦Ì¶]`öÇ1rýýV­hh÷cuHÜÀ¾±lÕvme™¾mÛÑ™à#}éÅxƒ ±@î\„¯€ÚµM¨>RÛA»PɦMdzõ¶­¦KUFcD·éÔaé¯u±ölý•ý_ º)*µö›f,©Â¨ËYK–È/=¼”6ìÜ©ßdjÚè:GS>6õ“VVõÒè{úû¥|ɾ[¿>Í9м“›ÓBWFcÔÅò‚µ?>L5c©aȲöå\7ã¶}{å5Ì‘% ­š)zY"\;êV©JZ¾KÊíøH5KŒÆøÕÜï¥êï5lD³GS”%‘ë¹…+˨éÓéë¹s©]ã&Ö|8ã…ƒ÷š?~’Ø;öl5”QªLE*S®ŠÜ¶}ó*¹ŒÏ=Z¶¢ÝKhÜ A¢+/}|†¥î¯/!›K5C …à·…/È¥«Wå2>ÿÁÏ£S‹Ö XPIá«r÷~Ô>zrc<ûƒoºF0KübÝo[÷î•JOþô3·Jf²7kéÏòtíÅG ü³â³Ü»%Õãµ26õŽOŠVXè}‰Ï‚.8H÷–-m ¢NÍ[È|tÍ]º¦öý/Œ¥Cû,Í¥øÊ³ÍXºd¿)Þ­;t¨tªŒwŠ»¨ð]n’1ˆÀ'Sía/V  ²„ ›èûï8tˆèGö"4–.R„þW·ži_rqrnFT\»–TU+W^£Ì´¥ Œ¨¨BïÜ¡ð'átêŸóôåìY²¼¢ðqCwU\Å“®ãNѴʹ\‰ÔV4_½q#®° ÙÏÈ:G¯PLõ“¾¬ê´Ñ÷ê‰ Ë&FÛÎX _´abô.œ«•¯ ®kc›– Õøp|£1F¥óüß-N먳ËDQ•S•g4ÆjÂjÕ -[¿žêwïFƒ»v>gge·8|k§·³ÅhŒ0ˆ`0á[4~8mŒm1z´Ù)+†K)RXüP.Ÿ#üôR¥z=á›´Œ¼½-Mƒ0¤ °eÃú¢4{ÊšúÃ*Ê· Ý¿׺ÍÇ'rŸkÊ”–¼‡­ÖŠ>),CÜá°‹Ÿ^êT©BK„oRŠl)"úÿ1Œ|å–-ú¢4völú}ê4S†v:;ŠÊLŒ_ ç<FÞÔªTɆ#•+*1nص“Úˆ®T½ÀŸ`þØq.½|<å:"lÆà§î C†RRñà.QQçKlë'3p«ºWÄA™ºxq$?‹Ë7n ß¦L•y‘ œ¡ £½š‘«áý°}{ûÍJ×Ub„¿YîlÙiâüyôí¼Wƒ¶ÌŸOy²çPŠKpU ¿2ôµé›Ê‡J*L×ÅDÆ«¶n±ŽvD¸•bh-W¡r-Z³=ˆ¶¸FGÃè?OÒ€!ã¤þ{7ѼY_[±øøøÒú]Á´yßÚyô­ ^ÌvìV‰-½‹VZºñá©ÉÛ=zœÙÍU?êÒEú}b >DK5kJuºv¡É Z¡y{«œàe=“ t3¡«,uš´„ óïÐ} õè/¾ýÏU6gɘ)+ù¥MO)RúPæ,ÙeH€ñßYbð?²îÞ¹E¾©_…x keÒ„x( ™²hYJ—Àˆ }üúŽæÁþ:Z\×lßfs~8¦÷ó#8ÛeÁÏ0œ|Áøñ² üXàÏâibFtM" dìGƒè×^3••î›O†ˆQ£å—9Œd8xãAïòé§²kÙ °*0"ÈêèÓ-NÝâ™çF£ëM÷ØÔOZY•K×ú¦ŒpF7*ZuÑ"ŽaôsÇgˆYaLTa” "þÐ=þÝ"ËË#޵Qcú2*Óª08@Í?è+ë—þþtà×åtlÅJ‚¯>Òê÷èNpw0CTaÌ"¶.X(`8õnÓ†ÆL[æ/°žÉ’!£Rˆ†KÑiZºŒ¥{årHptE¬ùÅK—³¦Ãn^“•!Z› ¡bÝ^B.ž—Y¨ØÜ)ðGhŽhŽt)W¼„u3šã‹…ñü¥KôNÿ~6Âàç)bF=Éã –Y¸?̪¸ô:èÓ®`D€?|™ãWH 7O[±‚üiá`"ÏQàV½.ªÒ®Ô9Žt²¯Ÿ•U½Í•ëÝÒ‰8ˆÖ'W"þòdÏ.SˆÊîNq£^wOÕ\':4k¦ßäÖ´«G‰‚k"Ö†ÕçÍ‘ƒ–‹.Ôb Ègu麵ñ#”‡0F~ð!}5øcêÓ¦-ø R¢`A¹Tõg¨ÏRtJ^¹|An*X¸xtE¬ù×®\²¦³å´4'+YŽvïØ@û÷lµ† Ð íܶ^&µQqZ¾ÙË‹"¸¤x,.µk’G8|Ç1#†›7îÝK~!fƘ= ¾£¤Ÿê$Y²¤Q1-ÏŒˆPŽn{yú왵ÒÂö9sÙ1uÝÕ:':e£ªŸ¢+«:ß•ëÝð¢ì:tP.õV ±Ÿ¾I_ÆŒ´«õ:N °„ Aˆ´ö{ЏŠQi[ºHQHh)Dàdt™»»ÃUŒ6Àt+KÖ¬‘kˆT®zÄŸ¡-K[צ%ÑËÕ+iæ$K7\¥ªµ­›ŽÜM˜ÓI/EðIã†Ê¬Òe+QªT–ᬠšZ†w.ûié+«UËJGr´ô•Å¥®0ªÅ°–¥§b˜ô Þ-¥¾Õk7¦¬YsRXèuÚºÉâtV PqêÒë+ži†Ë¨ÜåߨAù £âÐöÍ«­ëŽ7ÇZ¶f&„ýÏ¢÷š”'¬ÃŸ ­MOFLŽrÒ]ë J`jm”)"Äú é‘¿Už7í`]ÄØ‘"`º-Ðw\8_>ad=ðÛd³(vÀ|hzé'¦:Ѻåv:$7Í’×lß.Ó£úõ·~ ê÷32­ã$12Ck?)†-×îÒ9’êßihh¤B.f¨Ä‡X8úÃ(ª.^FhE‚£, DˆYÃxUbt‘~ÃvWYç8S?(Š©¼ŽhYjúÖ[bDÑV9•$ॴ\Ìo Aü%’U-*1jºkA(1à“¯š-*1"VÔGãÇ ï•ÒðEøj#°áWk?© ü*1Ž™1ƒÖ¬¦úo¾)ý%aÜkþt;u¢zU«ª€dsLÃŒ%o1¤¾óûƒiYÀ ÜbiÙRù¦¡Öí{QG±Mæ|LO€ØH÷î?äAê6x‡ú-ÃXr,ǘóÓŸ4êÓžòؘÞR¬dY1­Ê0i³­ª àO{ö2%è¨JŒˆ‡ç|í:Fý ëÝ›ZÔ©«ÁUºL"š¶þ‹î Û÷‹à’‡©\­Nщ”ÃÁûþÝÛbj’Ì”>Â9;RÁˆŒ{¢Z RŠJhòЍt£+ÿâÅsºLÅÞFÕ+Ò,ÿ‘”3kV›|U+*1î?~œz|>\„—mÔÞ»}Ò½»MžŠC¥ðÇ¥Ž„ATºle}“ ËÝ7ŸMÞ•`êܲºüz+\´4Õ¬Ó„²fÏEçÎüMgN±–}öì)õïÞŒŽÚC9sç§zZНº¤´dÁtZ»"€þûï?ýÍÖò*ª0BçsgŽÓÃúÐÉãUBˆñØÃË2xñU*]Ú¦¼wroòóMm“‡V…·:w’½J‰–¤F5kR.ñp¢e娙3Ö²kwì† ‡fµkËÖ¤¥k×Êý:ýßPÚ½äg*Y¨µ¼Ê„*Œwí’†R¾œ9ézõ)o*úiÕ* ºx‘ºF¯-JED+“¢ £^÷ð§Oé㯿Òg™šVõ<>}òÄŠ£f¦Ö´–È[ ˆ–T¾T…ŠÇ¶V RÕ½úvµj”Þ/m”ê¯ Ü!óí[¾£,l@¦*Œhͧ߇²­úúëÔ¤Ö[äå•„¦,ZD0^zù Õ³f€ æC¨Âxîªۭ«T aõT§Je:ùÏ?ôãòåôŬ™”#kjß$òs³Æ±/a¨±¤¶UûÞÔãƒÏ´Õh—cG|( ¥ú[ÑȯæRrñ"ŽJöîÚ, %aó~ÙAé3d’ŪÕjD][× u+—Pÿ!c)“è¾3KŒÆ½Û5«$ÕoÛ¹Ÿ?©µÍ,lÚyÞoÕŠ†öx_[v9`ì—òA}·~}š5ry'Ü}Š äÎEø ø ];Ñ„ê#7¨s*Ù´‰L¯Þ¶Õ4cIžPü]ŠS‡ §vM›º,!=[¿GeÿׂnŠJmÇý¦KòäâÏhŒÚq±œµd‰üÒÃKiÃÎúM¦¦U<ðz…7i ÛVBSéN¦clë`J“Fß«ƒºX^°öJŸrš±Ô°FMûÍJׯ¸mß^YÿæÈ’…Vͽ,õNÝ*U©BËw)ðàA¹©f‰Ñ¿šû½Tý½†hö¨Q¤Í.’[¸„Œš>¾ž;—Ú5nbÍWÓmÞhú…ùtÔÔh %l_óÇOXPǃ­†ÖK•©HeÊUA’¶o^%—žôç Fèݲ]/ X¹}öµÀiÛ]éI¸ôº ûmëÞ½2kò§ŸEk(¡ÀëÅKÈæRÍPBüŸð¥¹tõª\zÚŸ3á£Õ©E k…,¨¤Ðâ¹{?j¹ÑÎ`ÔÔ„oºF°§‹³Ï£§ã‰J?g0:S6ªs¹+/.÷ª½®³–þ,³Ú‹øVzš8ƒñFØ-©þ¯•±©w|R¤°ÂBï‹§‰3ÑéÞ²¥AÔ©y ™®¹K×Ô¾?Üf,Þoù mÞ² ¥Nu3©dAüÚgi.ÅWž½hÆÒ¥à ûMn_w#”ê?Y8¶Ûv{¹D ì>|H–èÔ¼9ù¥¶íž‹aWëæ»,DÆôé­yž”p#œLµ‡½X‚žͪK\0›µ3)g…ì—{U¯ÆµÐPZ¸b…Ìê%Z}=QœÁX¶X1 á÷?7Iß%¬ÀzÊâE2¿zùòÂÀW¦=éÏŒšŸRº4il dï ¸<@‚.\´ÙfôŠ’n¸u+sÊ#ø)ÁQMåjumt:{B®§ýÉKL¥c‡þ¢Û·C)k¶\TþÔ¸E{9Úñ­°›²l–lRôÒFØa䊙b$F3õvæ\¿¬[GÇŨ5t«Ê“—*—)Cµ+Ûú¢²©iÅMü™vlYMI°!/^¼Ð’Æ-õ'ˆÅQU`ŒÅiM-R£B:µf-]Úº®¬ 1HàÛyó¬ú„GŒB Êé3èÆ®Ýºç/ þL• _kŸá?2R<¦¨ö‰./‰Í]©Wù*1Í—_8u·¦6‰QŸº+¢å¥–1xøÔÉWJ(N©Âˆ(¢Ü¥Åÿ¨|É’ŠQ8>¼ªçÑÇÇ—Öï ¦Íû®ÐΣ·hmày;iaÉåKÿ’ÿnŽ3p«*Œ±­ƒ „í¡TÝ«ö'Dë b áãÅlÇn•ÑÒ»h¥¥kžš¼Ý£Á™Ý,Q…ñ£.]¤ß'ÉàC´T³¦T§kš¼p¡š·wÔ‹¬\Lx¹¸¿ÍîøAW|Ðâƒaþº¤Þýe¹í®²–O„Ým•ªÖ–-NB‰ðMßé(Ëa½oêWáÜ¿kÝ_K  $C¦,Z–Ò¥ ŒJŽÃÁ• |,M ý;t´:ñ®Ù¾ÍzÔ”N„èŠAËZœ0·±çIÈ¡“‘ „@ ÈØÑ¯½&Ófý©Äˆ@pß|2DŒÚ-[ÕN¯]g}л|ú© uaNCEëèÓ-NÝbq™çPù¾æà·„¨Ö0Àôrñ¼\EÅæNq£;õvæÜð¹hÎvH§ó³³Zwò4É“=»L"²³^Î_ºDïôï'³F?O£0êñd‚ÇLMúô–Ü¡âBž»ÄŒð‡/sH!*Â^ðå—¶bB0¼U3,]¬öeÌXWõ</]Ϊ~˜¨Ò¦Ë`]7;á*ÆØÖÁfãÒŸÏ•{U¤ÓZ¬£›-`õj$ ýØ›·oQ3Ñuƒ¦aDðF“±»œƒUaœûë2ù‹&Mžˆ(דÌ—«èÞ´Ÿ*F+gôRFø(¡9Üþ·tâ$©>Fß`ÛwÆ 'Êã©zÜM˜M/Å “Æ •Y¥ËV¢T©â*CÌØ¤Uat¦Žž®”Qq¯êõA}„hÖ÷[·Òo2-­ c±ˆÖÅ>K ŒûmÓ&¹jÖ¬ª0bP‰}¬¨mûDL¯ÆK|pQ-†µ,=äõn)õ­^»1eÍš“ÂB¯ÓÖM§³…ŠS—^ŸXñ(\‚Þª×\nïö^M9ÇÛóçÏä(8Bü¥â¥,MÞðcÂþçƒNÑ{MÊK¿&ø  µ òɈɦLj©#p`ªp9‡jÉ‚i"à¦Åèè÷ñ¢E.òW½ÜÁ ?L_¡äB°H„‘¿!Gl•gÀƒ9X-KMßzKŒJØ*ÃÑé7ör1ñ—Œ2IŒÌКÁOе»t–ùú¿ïG‰44T¿Ýˆ´JŒpZ‡<Œ¢êÂD+œÜa BÌÆ«£×Àˆc¨|§M.g @“|ŠŠQqäs¨M¬;bÜ# Äx •©ƒcTÔ…fÜ«ZJ ¸Àä«f‹JŒˆõÑøqÂÁ{¥ühExø®Ü²E„ÏiS1ŠjQ‰qÌŒ°f5ÕóMé/‰sÍv`§NT¯jUÕð(éH!Ñ%øâñò¾FÙ󕉮ˆ5?©pêŃyÝþ9û7üû ˜²ã ¥òMCí»ö§ácg“¯Hë¥jú„øH˜ó¢ýsî¤ôOêÙïsêû‘i1$9¹7€)QþÇñƒÄ9.]ø‡Š•,Kÿ'¢×®ß\X§Òï_§»·ÜJùy_:sê¨h- "̇?XǯAÓ6Ö®I¹1–Ï¸?·¨T,†"T~øÓ'²Kåä?AtX8g#Ò*¢4 †ÚNþy¤æÎzUß”çQñõ†þãSbδ0|Ú³'èûˆQcr¸ãàë0O4 _¾;ö¿M›fwVnоëëwï¸#âQÁ0:zú4:'ã‚ÜøPv]ÍßöqªœÁyóñ#º!xs÷uŒJ燂ÿi?ýDçÊì+OžÓ­Û7Ý^çÀ©qˆNŸ8L§þ>$ë<“u¼#GìæÍ_8*b•÷øžçÔ9±­ƒcLWÈSꨄ–ÝNC--‚˜e@ b¨S7NIO©s^ÝÞ9ÅÇÙÑ3§EëkbÄí):óï¿>^§‹IʳŠXDqOÁˆ÷Åa,!ÎÒÁ'èêÍ›2ÜÌ·âºö~¯MTogq†ŠûaÊ ‡xG’D4mý]mûEpÉÃT®V§èŠDÊÇáàˆ}ÿîm9eGzÑE“<·^»z‰àpSù/žËÉ3ŠyàŒh»|Œ.œ;âQcâËÙí/ì¡Sÿž£6N}Äu„#ò1=G¦ôébÕuôX„k:¿4±*ï,GåOŠ›ýØ…`Á.¨#3gÌH)#âö8ÂÓ¶a¡t\L(é©×1l0ÁutsÆ„ÛÜ § NxÌóxOÔchíM)b¼¡µÜ+bÞ­Ø`‰®LXˆ¨s‚<«Îq¦Ž—>ßÓê<ý z}ã’ö´:îÜ»'_üiRûF‡ióÄÅöñ$ŒÏEè ¼_`übN8ý´YqŇýN‰dG…ÖS|Ü;úᴓ`‚; ³Å/¶‚¯¸¼ù‹ÄªxÒ¤ÉD0¸B±*«ªjŒªôvæ¸Àg^gFm¡"ÂD²ñETb!ànQ‰ÑÂL¸CT>€Ÿ»E%F`s¦VÅ…Ê{ÕH#Éü*1B/ŒNÖF(»¢§+ûªÂÃϨVÁ¸à3ÔÁ;. ð>Ì3À 0Ì3À x2l,yòÕaݘf€`˜fÀí °±äöKÀ 0Ì3À 0Ì€'3ÀÆ’'_Ö`˜f€`ÜÎKn¿¬3À 0Ì3À x2l,yòÕaݘf€`˜fÀí :ÀíˆXDÇ@Ò^ŸU,E¯‰)F¼œIø“^bNK̹™1&M“ZÎ ÓÏ£ábbˆ·3Ì3À 0Ì@¢f€¥D}ù<3À 0Ì3À ÄÄK11ÄÛ™f€`˜f Q3ÀÆR¢¾ü ž`˜f€`bb€¥˜âíÌ3À 0Ì3¨p8..Ì\¿"‡þÚï›4i2Ê–#·5ûî[ôàþ]ëzT‰ÌY²“wŠ”6›=¼O§O¡$^^T´øk”Ê7Ív3VTc†Ð›×èÚ•K”7aJã—Î X|f€`˜f€ˆ‚C¥ým£¾FqKÖÆÝ)}ÆÌrepŸ–tôОhËbÓ¨UûÞ²Ì#sØGhç¶u6û4jÞŽ>;‹’%Kn“¯jE%FèüâÅsúeñLš8vˆ„0lÌtjѺ›*8|\f€`˜f€ˆC¥ðÇåé2ƒ¨tÙÊ6§N.â5ø¦ñ³æU«ÕüÒe°®ë[ÖÈUÍzöì)õïÞLW9sç§zZRÒ¤IiÉ‚é´vEý÷ß4ú›õ‡P–V… Ÿ;sœ¾Ö‡N?¨L>03À 0Ì3À 8Ç€¡Æ’vj´õøà3m5Êe—^ŸD™ƒA3–jÔi"ËìݵYJ0Âæý²ƒÒgÈ$ó«ÕjD][× u+—Pÿ!c)SælQSE¦Ñ¡c»f•¤ªm;÷£àó§iOà&ªó1™f€`˜fÀ <ÎÁ{éÂRý¦ït¤Œ™²Êôš?~’ËŽ=[ %d”*S‘Ê”«"·mß¼J.ãÃ_T¡wËv½(`å>ôÙ×§¥»2>àa™f€`˜„Ì€GKpj^ñë|Éwë}­¼Ú·C¦_¯ð¦5OKhÆÒ¥à -Ë£—Ña„ÒCý'Sᢥ=ZVŽ`˜f€Hl (é†[·òg1§Ü1‚ŸRž|…eëOåjucäö÷¥?È20€Š•,+ÓðGºvS¦³dËéÚ»Û·B#mS™a$F•zò±™f€`˜fÀ5 5–R¤ð‘Ú\ >Gøé¥Jõz4aÆ2òöN¡Ï¶¦ÃÃÑâ¿“ëí»°æß×…ðñIeÍ×)SZò>¸¯e)]ªÀ¨Ta>83À 0Ì3À ¸Ä€¡Ýp*×¢5Ûƒhëkx4Œþøó$ 2N*gåy³¾ŽVÙõ«–b(¡õ¨FÆÖrI¬) «¡[3(©?A,©c,NËE˜f€ˆ? 8Y¯Æ`¬ibeÀPcÉKŠ„±“:MZB‹†ùwè>zô—ünÿ3j'ì—/_Ò¢¹“dŒCKM|S¿ 7UK´HA2dÊ¢í¢t©£R…ùàÌ3À 0Ì3à†KÑiRºŒeHüåà(‹ìÞ±ÁÚm×ìÝN6e`œ dÎÑörñ¼ÌÒFÎÙo7kÝŒféÈça˜f€`˜ç0ÅXºrù‚Ô¬`áâQj0oŠÌÇÐy¿´é#•)V²œÌÛ¿gk¤m;·­—yÚ¨¸HLÊp£Ijòi˜f€`˜fÀI 5–·®!LK¢—«W.ÒÌI–n¸JUkë7Éô™“G Sˆ@Z·ï%—ö š¾'³–ý4KΗ¦m_µ|¡l‘BËSÅ*µ´l¥KU•*Íg˜f€`˜83ðÊ9(·°ìøôI8 êÝR®T¯Ý˜²fÍIa¡×ië¦2¯@¡âUÔ¦Ëí-—_”‰JjŠHÞØÿ|Ð)z¯IyÂ:&âE÷ä““müœ¢:†y*1B?LuÎ ‡öï”Ë% ¦ÑöÍ«eºßÇ_PÂ%dšÿ˜f€`˜fÀ 3–¼S¤¤Îï¦es¬Ó•B*ß4²Å¨£Ø¦ ó× =}ú„Vý¶H®vè6PËŽ´Ä1æüô'ú´§<6¦7 S†Iã)ÒN 2Tb„ºhµÒbJiêÃ@Ä•±©•ã%3À 0Ì3À ¨aÀ0c ê}(Z>>Â9;*õsiçÑ[rSŠ”–MQ•C^Z1éîÄ™¿ŠðÏéŠpÏ(æK•*utÅ•å«Ä¸a÷Eezó™f€`˜f n j,A…$I’Pæ,Ùå/6*Åd$Ùarç-dŸmêºjŒ¦‚á“1Ì3À 0Ì€C uðvx&ÞÈ 0Ì3À 0Ì@3À 0Ì3À ÄCØXЇUf˜f€`˜ó0cg˜f€`˜`c)FЏ3À 0Ì3À $fØXJÌWŸ±3Ì3À 0Ì@Œ °±#E\€`˜f€`3GÃ%fba¿~5Do¶/“4i2Ê–#·}6½|ñ‚.‡üK—/ÓË—/(wÞBâW0R9}FèÍktíÊ%Ê›¿0¥ñK§ßÄif Ö \¾~] ý}©|²¤I)W¶l‘ò_¼|IÁ—CèÂå+„tÁܹ©€øÅ$®\¡ûP¦ô([¦L17t»ªçñö­Pzôð~”ºâ9Çón–¨Â¨×ÿVè ¹tžîݽM3g£ÂEKQ²dÉõE”¦U`¼{ç=¸סޙ³d'ï)–1j£ ŒšnqyÏhû¹TYç¼uÒ¥k×èlp0¥òIIÅò ŒéÌy?š÷´y5Üx¬ým£¾F«ÁÆÝ)}ÆÌÖíÇÿE_ïKçƒNYó¨X¹ùÿž²fÏe“ÿâÅsúeñLš8vˆÌ6f:µhÝͦ ¯0±a`ÇþýÔ´oŸh‹mÜH™…q£ÉÞcǨÿ—_Ðéóçµ,¹¬Q±"ÍòI9³fµÉ×V6íÞM-ô—«™3d   µMÊ—ªžÇððGT¿JôFâ7Ó–R­zÍ”ãà TaÔ”>FÔ7ŸÐžÀMZ–\æÉW˜–o8f“§jEÆÁ}ZÒÑC{ª=dÄ$jÕ¾·Ã2FlT…º9ûž1OTÇPYçì?~œz|>\|Ì]¶9õðÞ}è“îÝmòT¬°±ä$«áÊ=2ƒ¨tÙÊ6{'÷ö&ß4~Ö<|©õëÞL~¾^áMQ¹6'///ZôÃ$YúíN³n°–?wæ8}1¬<~ÐšÇ f ® < ,w…S©ti›Ãx'÷&?ßÔÖ¼·nÑ;ý>”ÁÙª¾þ:5©õ–¸W“ДE‹`/ÿ´zÖlky-þô)}üõWÚªéKUÏãÓ'O¬XjÖijMk‰¼ŠhIåKU¡ø•`êܲº¬£ -M5ë4‘pçÎüMgNQŽM;*ŒÕj5$¿t¯>´óa¸e\5«õLFgß3zŒN«ªsÎ]¸@u»u•ê6¬^ƒêT©L'ÿù‡~\¾œ¾˜5“rdÍBí›D~NÄÇÆRÙÄ—H>s¸÷¾=[d%”%[Nš¹p½µÙ¾JõzÔ²A:¸w‡ÜžÊ7XHÑOÍײ]/ X¹}ö5¥Ïðª+O+ËKf@7ÂnÉC¿ñZ«¡„ Ÿ)¬§Ô߫Ȅo‚ÿ´© Máž.Î>žŽ'*ýœÁˆî·½»6ËÃ|:jªÛ ¥¨ðD•ç Æ¨öGÞÒ…3䦦ït¤Œ™¢î^Žn_3òÁèLY3tí9œ©sÐéÞ²¥AÔ©y ™®¹K׮ʴª?6–T1+Ž[¬dYyô?×-—}ÊXƒÚâ'ËüòoÔ ßÔ¯ºí†úON•¶Ý%² ÿ1Š([¬˜<Ãïn"ø.Ap¯NY¼H¦«—/Oi|}eZû6ÙrõñÇ”!mZ-Ûc—Î> ÄbÎ`<¼ßÒؼeJÆó¯ŸÛŒÚ>ú%Ϭøu¾ÌjÝ¡¯~“ǤÁèLY(q¦ÎÑü”Ò¥±ôÂh82¥OOùr攫A.jÙJ–Ü GZ×­üYÌ›wŒà§GÈ2åªPåjumŽV¾R jÐô=Z¿j)uoóuí=„Î>N;·­#ø< cùº±Ù‰W˜ƒøeÝ::.æ>òNžœ åÉK•Ë”¡Ú•mýíª c¨Uƒ´lýzªß½ îÚþ>wVv­Áçiʰá6ZíÝr0¬Ê•(AmEó÷Õ–VT›B&®¨zψg|hÿ¶ä•ÄKŽt-R¼ Õmø®[ZaŒÆtö„¼BiüÒÒ’S騡¿èöíPÊš-áC®q‹ö6-âf\N£1F¥óïKÙ¨³5C#ªrªòŒÆè‰ï£ëD0˜PÍŸßæÒ`ô-¶Ý{ðÀ&ßè6–œdTëB»|ŽðÓ |‘&ÌXFÞÞ¯º.à-Gš?ûš7ëkkñùË){μÖuN0F3à1:èâEÂO/uªT¡%¾¥ÂØ×dÖÈQ”;[vš8};ïG-›¶ÌŸOy²ç°®#Áà§î C†RR1hÁ]¢êyÄG¡¶løÃÞì)chê«b ÿa³“ +ª0Þ†dñßEÒníŠÚ¸fM™»RJ‰TÀà UíÕÄ(G oû®æNº­£§¼gTÕ9jÔ¤Kèÿ&~K¾©|¨T¡Ât=,ŒVmÝB=*/3B¨÷Õr*Q9sì$Î&ª †ü¯ÙD[\£À£aôÇŸ'iÀqò z«7ˆ 'î•M¾hÒ¤G»:'Cf@5*T SkÖÒ¥­ÛèZàN:úÇ 3Àò‚ؼg0ˆæÙœ­E‹V®y…òä±n{»G:qîÕ‡F  ¼@—ÿ£ò%KZ˹#¡êyôññ¥õ»‚ió¾+´óè-ZxžÆNZ$[„/_ú—ü‡t3 ®*ŒOÂÃ%†¥ËÓôykh×ñ;´çÄ=ùÁ‡ ðgÚ±euÜpzh½ŠV~ÀtS£Nã¸a‹ã^ª®#Ôñ”÷Œª:ç£.]¨TáÂtSŒÚm3h•jÖ”êtíB“.´^ oïäÖ´Š„—Šƒ&äcbè?4ôñ§L™ŠræÎOº¤Þý%ìí®²Â?°w;}е1Ý »IþãæÐ¯ëҊͧd÷k—©GÛ:„þsf@¸W1ºÍ/ujòI™Röí÷ïÐÑꌽfû6ëi æô••Ñ :ð«ð³[±’à«tEt±ÕïÑ®…†RèíÛ4zÆt‹S·ƒNÖ+N¨|áøë—6=¥HéC\X¯QKÿ]€DtüÈ>BÀC3DÆ➀ »­RÕÚ²EÃè>ŽÏ³Â˜¨Â(ADüÁoÑÜIr #ŽÍ *Š“ªÂèIï`4ºÎwY„+ÀÖ iÞØ±éw›64~ð`Ñê½@Q–2±P&l,Dmé2–aÿ—C‚­Gœ1q„L#èYQù`¸cŽ\ùdÓvBÅåκK¬å9Á ˜Á@ň˜KšÓ$Î9J@ C†Èx%¸WóæÈA˧L¥b ȰK×­•MÞ=’ë…ê×§´+È_ɦ–á×øòCž£`˜òDŠÿT=ÅK—³jææW1úù¥—XîܳbÒš‹ÀÃ÷µ,·,]ŨWz÷Ž V׉fïöÜâÿwIDATvÒorkÚUŒñá=ãJ£]ø\¾S¯>üàCújðÇÔ§M[*.ꦿ#Z½K,¨U²dcÉ Z¯\¾ T°pqëñõ S¨^àÓTºì2ëîÈ•¾,§™£¸(¦&×U.ÚÐÜÒEŠÚœ>MZ@Ë[wïRöÌ™¥S7»õ?4‘k‚ü9m#ÓkÛÌZªz1‘&Ùr¾êªÔòÌ\ºŠ±@D]uh`$µ¬’-{n¹tן«õzÌ›"W¢-†ž"®bŒïWêG×iÉš5r3f°­ëh¿¸lccÉIÖ·®¡Gl½î¯^¹H3'YºáМ­ Z +—/вäsmá ù ؾœd&ÿ10°.p=­@z¹tõ*ži…Y«’¥5ÛÑzYá³$WÄF˜ü¶i“\-’/ŸôQBs¸ýoéDK÷FÎaÛwÆi‡PºTõ<9¸›0—^‹èý“Æ •Y¥ËV¢T©^E@×—3:­ #FQAàp±~æäQZýûb$…fM¹Tý§ £¦70aºHëö½äÒì?U=é=£¢ÎÁu û8oÛö‰˜„_——GÃ9ÁðÓ'á4¨wK¹GõÚ)kÖœz¶n²8Åâ¦ÕGŒmݱ÷ï/¼È ¡nߺi]'ýüR˜êǃŠˆ²dÁ4Ú¾ÙâdÙïã/¨@ár;ÿ1ŽÀ4$p„„`zLpCŽÙ*ó` îÒU¦ñ׫õ{ôÑøqÂÁ{¥ŒÖÐðOZ¹e‹,ƒ¡»MÅ(ž$*ŸÇi†Ë9Å0„4Ä$Áxµ‰uGD3D%FÔ%o‰)˜Pu{¯&5jÞNNŽQpÄ_*^êU·£*¼*1j:/]défƈåü±Ú63–*1:ûžQ…We3fÆ X³šê¿ù¦ô—DKø¡“'%”:Q½ªUUÁ²—%+1'03uç÷Ó²€9ÖPùØ Ó•àk¥£Ø§oMÞmó¾t"ü~Ú—„4Ë—|¯m’SÏ~Ãm‚ÁáËÎàzÁ¼Ú$¼zCL_†ÓÌ€=)E÷ÙÀÎiî²e„¯=MmûýÖ­i`ÇNÒé[Ëïöî»2r÷¸ïçȘ%ñ¦I›FèÓž¢ëB8ŠG'I#¦GIžÌ¼*Eåóˆ))nß •9hyѤnƒw¨ï Ñ¦… P‰˜üÇÏ‘]RÒˆpÄ€ëØ}µëÒO®«þSñéÓ'´ê·EF‡nUÉòø*1:ûž‰RA2UÖ9eDÐÜï-¤ŸV½@…»Ãz÷¦uê }̇H"š¶þ‹®Ø¶Àý"ðâa*WËsœá¢Ó5®ùa—Ñ…sGœÂÊ0ŠíþÝÛrZ’ô¢r‰IîÝ»CpM-"vgÈ”ÅÔ‘a!cscÂãIÛKµt!A§gÎ{’j†é¢Çølÿ« #¦à^Å(¶;÷ïS¦ôé(sú 1íBwîÝ“1LÒ¤ö£P2ÚLâhçÇb(:Œ&8bÆUÜ § NxÌóxO<ãhíMé“J¶${E…qŇýâò<ª®sžˆI—¯]½DpúŽM}~O«W‚‘F‰§a.£ß3/ì¡Sÿž£6åÊÇš6UuÎó/(äÚ5z"ZÍ1'\*c®å)1ˆá¨vÙ³gO‡Íû t¨FüÚˆ‘BJŒ_lÅÏ/¨ˆÒŶ8—c a÷*œ²ñ‹­¤óó#üœ„'p‡¨|áì ÎÀ*1âšÁˆÈ›¿ˆ;.Ÿõœ*1i$YŽCB%F¨ã ï`TQç`romj“8Pïò.^.À 0Ì3À 0Ì@f€¥|q3À 0Ì3À ¸ÎK®sÈG`˜f€`˜ÌK øâ24f€`˜f€p6–\çÀ 0Ì3À 0 ˜6–ðÅehÌ3À 0Ì3à::ÀuùÄ@–©ßy6¬ 3À 0Ì@B`€¥„pƒ•ÌÓØX²’Á f€`˜CpÁ{Ñâzl7i¬!gåƒ02б×Hæù‡Z4;qáõü+Â2Ì@|gÀ¥ÞˆÄ Éž¯L|ç!Zýo^>EÏŸ=MØCÆç ãÃ7*Ó³'ĬÔbvêu¢½Öñyƒï¾½VŒü<Æç+It3?¸:‰¢^M ïŽD€ñîÍóÖ ²Õ,»árå)$ç†KÈ•³wò$rn8Æèè6ñìmÁ (.ó4y6*[í0½²cþxáçÑöºÇ×5¾ŽñõÊÙê®c/içØ"¼Æ£á"s’ðr¢*9áAMЈø:&ŒËË×1a\GF‘¨`c)Q]nË 0Ì3À 0Î2ÀÆ’³Œqyf€`˜f€HT °±”¨.7ƒe˜f€`˜g`cÉYƸ<3À 0Ì3À $*ކ‹ ׯ†ˆaêÏ"íš4i2Ê–#w¤ü—/_Òµ+)øüòññ¥ü…ŠSºô#•Ó2=¼O§O¡$^^T´øk”Ê7¶É´¥jŒzóšàååÍ_˜Òø¥3 ›v"•ÃÉÑÇèî0Ê’5å-P˜R¦L¥Ú´¥JŒ¸OÏù[bÌ”9;å+X”R¥Jm6íD*1jçÀòJH0=|pÒgÌB™2gÓoRžV…ñö­Ðh‡£.Cf–¨Â¨×ÿVè ¹tžîݽMÅ5,\´%K–\_DiZÆ»wnуûwê9KvòN‘Òa£6ªÀ¨éöòÅ ºò/]¾L/_¾ Üy ‰_Am³iK¥´ŒmèÓ¾ÿ¯mÔ·sÃhõÛ¸û¢¨L3[·?²>ÿ¸‹¸¸ÿZóè=Пº÷ù?›¼G"8æ°:ÑÎmëlò5oGŸeÚC­#€½xñœ~Y<“&Ž"q3Z´îfƒYõŠ*Œ0’fNIó§Ú@€Á;bÜlªóöÿlòU®¨ÂøøñCš6ásy õúãg£§ÑÛMZ볕¦Ua´Wz÷Ž 4àý2;ƒx¾7ˆçÜ,Q…÷jý*‘?î4\ßL[Jµê5ÓV•.UaԔƇêıŸÐžÀMZ–\æÉW˜–o8f“§jEÆÁ}ZÒÑC{ª=dÄ$jÕ¾·Ã2FlT…º;ü}9¼/:e£jÅʵÈü÷”5{.›|U+*1:c/¨Àg¨±.^T˜¥ËV¶Ñ7¹·7ù¦ñ³æ]ø÷,u{¯¦\¯^»1U©V—þ9w’–/ùžfMEY³æ¤&ït”ÛŸ‰ ‘ý»7“7}ÎÜù©^£–â«.)-Y0Ö®Áÿ£Ñßüh=¶Ê„*ŒÐùÜ™ãôŰ>tòøA•b<¶*Œ;6¯‘† ‡Úõ›ËÖ¤µ+—ȯ÷ÿëߎ–¬ÚO…Š”ŠQ?# ¨Â¸kûi(i÷©¯Àºê·Et1ø ܙЖ(Cù 5BŒÇP…Qâ§OÂéëÑé³LM«ÂøôÉ+ŽšušZÓZ"o"ZRùRF(ŽÁÎ-«Ëg°pÑÒT³NùbE«è™“G”cÓN  cµZ É/]í46ËÀ-käºY­gª0¢E°Ÿx?¢5ûõ o #¾9y‰ž—E?L"/þC»Ó¬…l°«ZQ…Ñ{A6C%MIXé=>øL[r9wúX™ß°Y[õõ¤E Ï–=7MŸ8‚æÎGÿ×AæïݵYJ0Âæý²ƒÒgÈ$÷­V«um]ƒÖ‰nÿ!cMmþ7#µkVIâjÛ¹Ÿè–<éKOn4ñÏhŒhF«a».ýd—+ tîù15}ËòâÙ¶i¥iÆ’F£ÑÑm:ì‹ÔTúZ7Më½éõJÒ­°›t@T^fKª0jÇÅrÉÂé²e/%ûV_}9Õi£¯£¦/^>fü¢­ºu©ãØÊ—lýÆ­häWs)yrï…±K¯O¢ÄƒSÍXª! D3Åèë¸oÏy ³dËI3®·Ö;Uª×£– ÊÐÁ½;äv3]VŒÆèŒ½ êZºÍÁMj–ízZ %¬7oÕ Y× ²æŸä²cÁVC ¥ÊT¤2åªÈmÛ7¯’KOús#ônÙ®¬ÜGƒ>ûZà|Õ]éI˜ìuqcñRåd÷*|Ó4ïZ!W#®·¶ÍS–Î`Ä×y qk†0 ’*\ì5 çþ=ÇþîÂì FMGø&L›0\âë=À_ËöØe\0z,˜hs#ºßð! ùtÔT·JÑ@Š”í ÆH;Gd,]8C¦ðQ“1SÖ芹-ߌa¢e òÚë•mê)|¬ú£÷ÅÓČΔU…ÓmÆ’æ§d,V#ta@.ü{N.íÛ!—øÊ³ÍXºd¿ÉíëÎ`„²Cý' §ÊÒn×ÛœÅձܻ#³Ó§·´FUÆy®b„“©ö°(\ÂP¢=w\0NþÊâWøñ° ”6š®ŽhOè† qÁè5]:¥3ïß)ÏÕ¼eJ&­Kç5sgg0F¥Ϭøu¾ÜÔºCߍЏ=ÏŒÅJ–•úþ¹n¹ô] N-þq²Ì/ÿF òMýÊFfzÀŸ3)« š’n¸u+–£à§'A4•…O’^`ŒŠÊ_°˜~“ìNöbt ,bt_@ÐÌh/Ú;Œ\1SŒÄh¦ÞÎœË Œ¨¸Ø%Õ*W©º3êRVFTTwÄýøäÉcé‡7{ÊhÙ ^ºl%ªV«!z;sŠTÎ%J—§Æ-ÚÓëWœQÉð²*0BÉ3¢~Ú¿-y%ñ’£y‹/Cu¾ë–V£1=!¯C¿´Âÿs*;ôݾJY³å"¼`q]õ-¤†_´(h4Æ(NA¿/ýAf㽤Q•S•g4Æò•jPƒ¦ïÑúUK©{›·¨kï!tîôqÙ-וac,­hªðDu\£1ÆÖ^ˆJ£ò 5–´f?8³â§ôŸN˜±Œ¼½SÈìµ›ÈtâØE3¾/-*a7¯ÓÖXG/`(ä}ݰOŸTúCÊ´6äüáƒû‘¶©ÈPQ…ž®ÓLŒpæ‡À¨®Tµ¶+j;µ¯JŒ»¶¯£A½[ÚèCì¤Å¦¾|TaDh¯G ”ø†Œ˜L^b°…»DF|èAà4»eÃ6ðfOCSXeÚ°lUïòøÇïlðag6®YFS殔Α œ¡ £½šå¨ámßu€ýf¥ë*1Âß,[Ž<4ö74oÖ×Vó—Röœy­ëªª0ÆÖ^P‰ÏPc©‚¦¸f{0~RS²äÉ#ŒŸôÝןJge\Ä^ý?—xºÇ^8»ÂÑÎþÅ¢†a•D[ËÂxr·¨ÀènLöç7 ãŽ-«­Íáþãç˜þxUbÌW µîÐGÆw¹}림÷o\»,F½I“çüAðÝ2CTaĈU QFH‹’¯U0J´çP…~uëwˤ"Ï=ÑU|D´€Nøbl÷Ò~\º=Z½ŒÜ  ã“ðp©&Z?4šÊ 7Œ¢ÂˆÎû¶’þLxFkÕmf$œ(¥ £ýÉÐú/5êX|%í˨ZW‰NÜ+#ºñá©5VôhWGÔ9¿›æÞ¡ clíU×Ç5ÔXƒ¦ï*CÓY‡îéÉÓp`ûŸ«¬ÆR†LYhÁò„PgO¥ððÇÒW©tÙ7d¼Q(£ïkEp1{ß|)@PÖ QÑ ½9‡Ïœ#䘘a,©Â¨¿è_4w’̈c³»Ua<°w;}ÐÕbøù›#G‘_½|FÖKŽ„ëÑ¶ŽŒ—eF°XUck/请ÑiC¥è”+]¦’ÜtYÄôÐ †©"f~š ¨ %HAá òÑï ¿%ø·hÎßZù‹çeR_±iÛÌ\º‚ÑL=]9—Q/]ø‡ú÷°Ä®Aüµ~ä ['gžöEL]w£#E1‘&ÙrF6µmf,]ÅX ¢>>´?0’ºV AÜ;wŠ«õºÌ›"W¢Å/­ÅPÔowWÚUŒÚH[ >Ð ÜXÐSƒ.sL-åNqctºGe/DWÖÕ|/W ß?pë´$zA윙“,1XôM½pµý°o÷új¤ÅéÝwšÀÓ²ì§Yr¾4-Õò…òK-O«ÔÒ²•.UaTª´“W…ñ¶huèÛ¥Á§öÛ-Dþ9nsV…ñ×€ÙÒ°G“¿&OŸ>¡ßO«è¦ÖOù£•Q±T>J ~Ýé7qÖr Ï"¶cš3DFè}äàn‚a¯´zO7Tfad£ÞÖ—3:­ #FQAàp±Ž.òÕ¿/FRøöÕ”KÕª0jzZE!­Û÷’K³ÿTa, æS…¬\¾ÀÜV6‰«³áªÂ茽`C‚+†µ,aÚÍQA1]IX¨ݶi…TTMu†0 ÖˆòÍš ¤ÿÃñ#{­Ó|tê1ˆªÖxÛ }èØò{MÊËüð@kä1"ÇŒþg•S€3È¡ˆ(KL£í›W˼~Aªãô¨Ä8Î7Öfð 3'¨K«ê—þoô7ó"…’Ðo7"­ã1…æØ‚Q„—Q²dÉh¯ø€1ˈP‰Ñˆk`Ä1TbD°MÌ)†!ôxчZäáb†¨Äˆºä-15êhL=…y6ñRÂ(8â/™1A%Fí-]d1Þ1*“µ›-*1¶î؇Æû÷Þ ¤á‹0=X¢â„ëŠóªÄ茽 êÚf,aÖæÎï¦es¬aä¡4¢Ã’ï(¶iÃü‘_¬ÄëÒÙN„šÀ‹¿÷€T§Á;Z–\âs~ú“F}ÚSÓ›@#£Çäñ$3ÿ©ÄÕa•k1¥4(0ñƒèMm»ÑK•õS)h¾öúÃÑ_µ¨ÄØ«ÿá¼í% yí>tM}(Œ]t/›!*1F¥RaB’™8]†JŒ˜®ÎìhuÁO“º¢nê+FŽ™5›»JŒÀ„Ö]tI!H#Â@Ð:رû Ó| UcDË®öžéÐíU…kÒŸJŒï¶y_6|?íKé‰ÑªšÀîÙo¸)AGUbtÆ^а½L"ºÂ¢ƒ¾-p¿©v˜ÊÕêëóâpp`½÷¶œ²ÃQ—ËÏe·nf—ÔOƒÝ ±úÓ3ŠQ)F4ƒ‡]>FÎñ(ŒÑak~XˆÀ”À1zØu|"Œ>jDŒ³*WÅÓ¯#F¦¢…Wo;‹ÙÓžÇ{¢CkoJã ­åFÄ”òÄëˆûõÚÕK§oGuvl¯§§]Gàƒ¤£OÃ\q©EÄnŒ sµÇÅ“0ÆÅ^ˆÍµ~pý¸´szöìé°¸a-KÚY0!.FúÄf´.¤ýè6í8Ñ-±Oî¼…¢ÛlJ¾jŒ¦€ˆá$ŒÑ– gïUTÊfµ>Øjj»fæuÔ·Ûj¡vM%F´ºx‚3°JŒ¸:¸_óæ/¢öBÅpt•4’b€áp³JŒ8±Ÿ_:ùs¨„âª0:[ ÓPo£•ãã1Ì3À 0Ì3ànØXr÷àó3Ì3À 0Ì€G3ÀÆ’G_VŽ`˜f€`ÜÍKî¾|~f€`˜f€ðhØXòèËÃÊ1Ì3À 0Ì€»`cÉÝW€ÏÏ 0Ì3À 0Í€ÃÐa7¯Jå‡ ¡Êµ „ñr"ÀÈ×1A<¢ü<&ˆËH‰â:&†:'`Ô윘ž<‡A)W­ZEW¯Z ¦˜ÄÛ™f€`˜f€ˆ Ĕҡ±³ÎÌ3À 0Ì3À Éû,É&‹`˜f€`l,%¸KÊ€˜f€`˜fÀHØX2’M>3À 0Ì3À $8ØXJp—”1Ì3À 0Ì€‘ °±d$›|,f€`˜f€Hp °±”à.)b˜f€`˜#`cÉH6ùXÌ3À 0Ì3àøit÷óH ¯AIEND®B`‚srt-1.4.4/docs/features/images/srt-encryption-1.png000066400000000000000000002534561412557703600222400ustar00rootroot00000000000000‰PNG  IHDR„ÙÞÁõ;gAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ< pHYs%%IR$ðYiTXtXML:com.adobe.xmp 1 LÂ'Y@IDATxì]€TEÒ.ØÌ’‘œ–œƒ`@Ô3çŒ"˜0ç»3þžÙó‚gÎwæœsÎ ³"‚€HT$I†ÍaþúêM ½ÃÌìÌîÌîÎL5̾~ÝÕÕÝ_w×ëêØÄdžÌ†€!`†€!`†€!`¤MÓ.Ç–aCÀ0 CÀ0 CÀ0 AÀB«†€!`†€!`†€!`¤)¦¦iÁ[¶ CÀ0 CÀ0 CÀ0…Ðê€!`†€!`†€!`iŠ€)„iZð–mCÀ0 CÀ0 CÀ0L!´:`†€!`†€!`†@š"` aš¼eÛ0 CÀ0 CÀ0 S­†€!`†€!`†€!¦˜B˜¦oÙ6 CÀ0 CÀ0 CÀB«†€!`†€!`†€!`¤)¦¦iÁ[¶ CÀ0 CÀ0 CÀ0…Ðê€!`†€!`†€!`iŠ€)„iZð–mCÀ0 CÀ0 CÀ0L!´:`†€!`†€!`†@š"` aš¼eÛ0 CÀ0 CÀ0 S­†€!`†€!`†€!¦˜B˜¦oÙ6 CÀ0 CÀ0 CÀB«†€!`†€!`†€!`¤)¦¦iÁ[¶ CÀ0 CÀ0 CÀ0…Ðê€!`†€!`†€!`iŠ€)„iZð–mCÀ0 CÀ0 CÀ0L!´:`†€!`†€!`†@š"` aš¼eÛ0 CÀ0 CÀ0 S­†€!`†€!`†€!¦˜B˜¦oÙ6 CÀ0 CÀ0 CÀB«†€!`†€!`†€!`¤)¦¦iÁ[¶ CÀ0 CÀ0 CÀ0…Ðê€!`†€!`†€!`iŠ€)„iZð–mCÀ0 CÀ0 CÀ0L!´:`†€!`†€!`†@š"` aš¼eÛ0 CÀ0 CÀ0 S­†€!`†€!`†€!¦d¦i¾-Û†@Ú!àóùÒ.Ï–áÔB I“& ’¡xµ†JC€/Ì"ígj"ÐPí¯!ÚBCå55kNzäª WTë%¦GY[. CÀ0b@ŸÇøv¬ð¹m¥6†l©!`ÄøËØÕÐñÇ–Z£nhl†°¡KÀâ7ˆ€~*++éÕW_¥E‹EÕÁ­ªª¢ÜÜ\ÊÍkF›ŠJ$…èë躴j÷’¿¥£«~MØÉÇ/Úý•¡'}÷“k(÷éÂáñò|=»ç«vÊf{ ûådfPye1©„ÙŠžÓ‡ô li5ú&L‚$zŽqåpzJ8=H'%À¿¤¼‚òóò(3;‡6nÜ pE|"ï-9ß•\’E%å’&Xå?¸ß®NxŠQ¼ÙuÂOa¼Lïá¾ys! ÐŽw¸ú$ü©mgõê5ôÀ#Qvv¶ÄÉI Tj¯N±Òè¯åê¥ù€?ÚMyY 1œöÞgÎR¼•Ì„Cs¯½ö-X° *yãcú–7͸n–n–v“Íí|Pµmä²[Ú· ´Øjuý*™åLŸÆÇ¦mƒé´]V0=êd¦¿í)ÓdÆD_Éôžü¿–Õøs¼\²ró$ ›6m9 /aþ Ž@&µlÙ’ŠJË%ÝîÀê—Ô=¿ÅËÝVÍ+4w' ¬È?Ú¡Ø•oèžk€ž-,àÃ…•ôùùÀì!+7lÜ)†€phÁTVTPeY)UpehÊ¢x‚Ë27”ÍDyQ÷^½éØcŽ ðN´EÛùÚuëè‡çïGSi .ÎHƒ¾ÃœÔxîøëI—Niôé†Cyä1Æ'N¤6mÚ¤…¼QìY7L!¬~ÕBCÀ¸‚»½ÄÅŒ ÷ðð§[n¹…=ôP2d‡í” –——KÓgÌ¢óÏ?Ÿž¼ñþÐz§ð1$°€²{ÿ7RÇ]N¢‰ã`¯4lý}ff&¡#wâ‰'Òq—^KûÄŠr1­).£þ][Ñ´ekhD6ôÛúBB§²]^Íýcm[Жf®XG½Ú7—NâÚ¢Rê×µ%Ó¯¥‘=ÚÒ/ë6S3þx·Í˦ylúË×Q¦/b…n=+ž}Úµ é~ú%ë6Qóì,j™“Eó×l¤‘ÝÚèûvhA›¹ã¸‘½ýôÛölK?¯Ù$´ÍA¿zmß¿3½öù7ôØ­×Ó /¼Pc¡!ïP„þ}Óm4¢Õ*ÚgŸC¸î”H§¬ÆÀq"hE¼d-µ¬h'ޱ±ÉðUP§’y”ékædL¢5è æ7˧7ßø­Zy‰(„цMF:ÔÈa<ÿûßÿÒ{ìAǯQÞä²"øÓ,OÞ\zû½4’ÛÍn?m›åH{ZÌídx·6ÒvlEëŠK¥=uo/õTÏvR¿·aúl®/¿¬ßLØílp§V´¦°DÉn­˜žÛãh¦GûìÐýÐÒiïÒqGãweè¡Ìh¿mÂTZ×UJüDµ êMF³<úcÍ:ê°ítÈ!‡Q„~­àQêçŸyš;ò(:r»Á´¾´’;‚%4¨]>,[O;viM?o(‘ÑÙͲhúŠ ´wFû­ÚDÛ·`¯ŠV1ýÀ¶Íô‹6‹‚׎;:3Vl¤ÑZRß•i0wd6—VÑjžЦõòó_¸¾˜ZæfQ›ÜLšÅt£˜®?‡òsCI¥t”û;ô ÖSkV6[åfЬåëitçÖÔd›.”½q5uÔQ¡3ÂuÞ¢%4¤Å:èàý¨‚;¥iÔYË:$QŒŽˆ«’{Ôymc X7rÍJkîäŸxìDÌ‘g‘CÅ…0“ëZÛ6­é£E¥¡HRÒ ß¨öíÛ‹¼)((ˆ*ƒ ×¾›Iã&CÛs;˜»¶¶<îä7eåmäA^ܦ¶å6µª¨œŠyÀ¤w«\êãosÖRç–y}úH„?þø#ÝqÇ2Í{~~¾ü°T Bt| .½ôRêÑ£G€wt©M”2¤éÜsÏ ì³I\lÕ9/Y²„®¼òJ:þøãM!¬MÈ7UñT{HBvD§ß0t€ª`á_â>gáRwnz<ÙãíçP%Ïpxh§EÂ0M€ obÁ{™`—=MÀÔï&ø Ot =¬~‹ÝëXzaáîÅx"}Â× #ñI\[Ü=~H‡?~¤Kãòó“w¸ùݦðòûË;ÛÙ@Þ™FâA8 >L8^µwg…ÐíyÔ-$òÜb6Uþrfüýº@Ì,’9€¶)i\§#ÔQ®d^]e»Wç¹îj»Q?¼£møë´×öø]Ú‹‡7ìÒ^ýt^»àïµ´+—'âAûðè½tz¼xð”0’šIƒ×Þ´Í¡­Ò*ñ Í0^(꥗ë±€hä x¹mx Iiª€±—¯-yvä+{TæxeááêÕ`‚ròc%¸ûË î(+ÄËî e;”?'"¡IÀwMê1ò›à¸šcÞ`$¶†6X¶±Ž®Ïçå Pè ^xá…tçwŠRˆ}îæño¿ý–þÃká!¸ºuë&Jß©§žJ}ûö¥?þX …¯¯so×®uíÚU¹ì²ËhÍš5òŽ0øc¶&” Eº Hñƒ]*\% ÿK£v—NúaÔîú5oÞœ²²²¶JòTZ<]þÊ+V:¥GxìK€®®M¨¸\³Ç†@èÏvl°|lˆšá~3’ø8'4îž[(ŸàÈ£¡ S‹ Aìµ¶ħÑÖ6ö§R}(íGÕW¢LÖÔÒñ§ú:»øòNYnZÙ¡8íºë®ôÆoÐG}$ù…2ˆÆú(=ôÐC´Ë.»È!1Xr«tP´o¼ñFá‡ýŠtxàôùçŸ# ¢p≸±ŒÊ5ÊF±—û5éð=­}ƒC¿æPnQÃo„)€WÐ9S™¿e”'ëUÆ%ú™r°nm*ThÏ-”OpŒ†&(L-‚q°WC fpÕI6ß5 ƒÉ…DË–`þ1'Ø4 l†°–Å Ë=öXºë®»hß}÷•e¢Gy¤ì D#  Œ>ׯ_/3k˜Áš"ýÀÆíìàà(›f CÀ¨+˜è©mܼ߳|zš/jïÕµ•±ìÕïA]Ó)⎔.ó3 Ä"мU{ºÿ¨sDzêª>Ú<âÀj/¬N;š¯ÑÉ‘úˆ;±h¦wSkYÖPÜðqÅòF,Ÿ|ðÁå¤MÌ^A:ýôÓEÉs•BDÕ¿ÿj1¾óÎ;„=„ñ0hx_~ù%]tÑE„ÃhÔ`Æ ³u˜aÃÅÚ8½' âçMf-¡Äá°œý÷ß_E<ѹ€"ùä“OÒYg%yGx`£Š,–tÞzë­ôÔSOÑÈ‘#Å0[ˆ@¦ƒôá@ž={Ê/@ÄÌrÂ` !øâ„VÌf~øá2ÒŽÑvÌzB¡?MÊËR1K¨{:…™ý©Õ×;Õ¥S\‹Èë%HõÆe4!£¡‰%N£MF “23³iñô/é´Ó¿l,`°ò7ùºµ)/tÝxÄT€qŒ ¬j;(Sš;q1mìY5} MVv.]}å± =V{a2AWÃÅ…©1©¼ž|½D•z‘@Ã'âê(0˜QÃLÖÉ'Ÿ,3tª¨hî¡N™2Efᦠc<>ÔHfõÌÔ©’ï—_~M âÄ5Ó¦M“%¯o½õ–xéÞEìÓƒ²…¥œº´O,ÅDƒ×‘ŸàOÑ„Œ†&qLÕ|%²,´«–È8¶æ­û¿õP³­)’Í¥nuÏ ]71!ǨâÈ*¦,4:b¢Æ"ii£çI 4¨ÆôAãDÀfëP.P6`  @)‚"†½{˜y;ꨣd^ð’ÆwÜQ•Á‡i,Åœ3gŽœ0ª|ê$ ª «¨aV¹Â@qćH/fÝÚ·oPÀT¹få`pXM¯^½è믿–wŒ6‡3ªüi'Äå§{%U¡CZqR+.–DzQ(x3’0ªÔ¶nÝZÞÁOó†% Áå‚0.]0½×Œ€nJWJÔöTû×þÃ2ø¨…rS4kx ~<˜pË{py†eQ£x6Κ 2YP¹Ê þáÜá?ãÝ}WR\H“N;‹®½üY2?þ[sB¾0p†UX-ãæuëÉä²¥>×&Õ^èê<‚ݪûº±„÷U5_}ѧ˦–v°jœ­.š Õ ˆj¡«½Dˆ;Ü"€è¶“Pò±…rW·©©/ÍZѦu4ùÓϨgîr8a¢Ò¼p–&Äy0XEf&90…°–円à62( PrÐ8öØcáºhÑ¢­¸ë)£ð˜8q¢|°1ã†+'\~[ŒÁAïæSE ŠÕü!p2( –}BÄI¢Pî÷ÚµkÅ]øòˆûÿò—¿ÈõP±³ ¸+ü¡˜¹BaU‘Ó½|šÐê¾>UP?üðCQ?øàÁ a¡äá°,˜ñɡ˖-“¤!>(à§yÿäEÓ\N.ÙkB@?/]õ·šÂ&‡bóTî1ÕúTü·¼ó&êXí™*m$T>"¹¹ò6]5êðÜ+Ê˨G÷nTЫW8ÅTWOÄ*¨½v ú‚÷ºmÄ‚F5ú‚§F ³”£U@j—±j¡Ý—Hغtm(7¿_(É­¾ä‡&=–gYÉ&Ì3uíÛ{§ßǶ6´-[¶ sq 8š%)°%£µ(&PîôäN('PaôT΂‚yÇm$ ƒ’„ý‡W]u•ª… 4ª øûï¿/áƒÓ<Á'8mBl¢D Ò×6JiC¡§ƒ¨CÕHXû²J…6¢2Kà±|ËÏa7È ,{â‰'d Ió;}útÜÂ@ÜT†D(®Z{ádѲ2oär5‘?]ajõD­3ÐhÖØ¢Niü8Ee“-½uÊl´ãŠÊÈ‚«¯¾ZʑȴUœ‡€•L¤Vù+°Ð'ÌI´üˆ¥kÒ”û}%Þ–ȃDÉ•5º½Gã·gr"` a-ÊM;Xj‰ƒRÐY°`ÌlA‚Ñ2aGƒ„Ѱú<ôÐCÅ Z° ìüžPÆp'N 9s¦\qçwÊROÌô©b†« ä°Ÿ á„2Ø ˆ`pz)ò4dÈjdš”Pöþõ¯ÉA6Ï<óŒÌ +œ: wp€F]oE )fU/¸àñÓ´âeܸqâ†e®Ø£ˆe¶§vš\“LHÃìÙ³å@Ä£é“û5áf”¢fÒ„Á=‘Ú)dµ •ÒÀÖ*s*+!pr³^GfÞîI“&‰ŒÑpUÐ?þñ‘9pK¨œà‚nʲ Z‰þ!•ǰ›Ùêm¯úÛÖÔpÙÒæ£¡ÞÂcK¸-n±Ûb‹3vþéBÛ>£ûç?ÿ)ƒêжÙüñrg´®z‚d ú3óæÍRå¡áôÉ•E' )oO«Aól‘×[2Z õCÛ£GzüñÇ¥£á²ÁÌfÔ:wî,Š ‹ž¸¤³`ÊcðàÁr$ø /¼ û ¡içF"}ºq„²ƒ':9¸K —^÷Z(‰{ò,„è°·‚ {Õ`~ºTvÐãaœž  Ë\±ìî*@ã¾ãTSì!Ô=”nªà ǡC¨ê¬#Ü0›‰%¥î¨Ò‰ë9pJ*Fð`î»ï>:óÌ3åNDÅæ9rh&^4t'+¶®blÔñÂhk>%[R¦²Ëß1¸óÎ;<±œé½÷Þ“Á0•]Y‡v˜¬ÒÐ.•c€q¶h]SgöÆ.Fª×â-o[láFC³%´–ü—´¶Å^½@¥mç?`;Û/B_[[Ð/Ã5^jÐg­ž§<Ô?ž&ËR«”M!¬Ey¢á£!@ÉìÙG!ËD1’Ô¦Méd@Aq•&2¢K6Ñ?öÆAÄòFUjT°àRx̆éõ Úé —d ‡4àtPÒ²nÝ:QL•Â"^¤³m_|±,ÃÄ~Ú‚Ž”ƒø ÁW—ê!3nÞpýfðòòò$iðCÇ `dmåÊ•ÒÃ~D¨ AœÊŠÑ{\f%ºcÇŽÕD 4¸ïü~ðƒò‡ ÍnþpÚ–q3áÐ2P ­×ú.‡8}Þûšc @¼ ·Õ“iÊ_äžß…G <,ï­ÂÃA½üôÂO Ý×®þAO/~Fø½vábàÚ«³à@þÌà¡?ÌÀXìjó»y õ‚Á–0ž#xÂà)VÀïwÀûëÑø¹ ”Gçqå¿þ¦~6ü.áÜ5VˆÆ­ °#ZM?!uzOáž«¦+6 s`€Ü!Ç 7‚ N"ÆFiƒiÜ÷@ì‹ëÞî‘1€“¾F묖€]Ö_­¯R‰<}ŽbçrU;,¨ÖÕ~ ßxV¿ƒ 'éŽ//Ä*?‡©äEXz~~¸)­ÿ)q½¦ i rØâUÍæÅàO‚ãè,üíN¼ÙÞ„EÇÝcX—‡ZÕß&àÇ–@\¡„0€CìqP´.è»òÇS0ÐHÜ'Û]?Áƒÿ‰ÿéÙ]n[ÛµNb ú0HÜñÃÀµkà×»woù¹´.k×ô03ÏY¼Ïšßê¹+~ú„+Ûk*Ïêlýù÷8Ú_C jL!Œªê„*@àŠ=nXRé(R® ?×(`ÅEÝ¡˜Aé‰Å@©Ô<1ƒ‰Œ YðÖwÌ<öêÕKÞÕ *µã©JásÏ='÷%j>]>zú§ä?š~<¡Üá§&T:@…S•NÐ(&°»üpÅ~0ðC~±Ó5Hs·nÝÄIûþéjŽøÁ¸võ‡[Ó&M {Ÿ”Öõƒ¿pòØáµFãZè4Ø7u©CæXý‰ñ‡­æ±5?®6^Þ€GS¸ òêçæ3ØzТŽÁOýÁ˜ai ¸©]ž~:¶  çÑcå°Þ»Æuó—…Ÿ^âzþþŸ?ÞH‡ƒñòþš÷Ѐ©¿„A<ü“ôùŸ®~#” ?Õ*Öùò¿%½áØ!}hïjÜô«{°›Ê×]Ãëy‘åžÌ_L ™«ªRÌ€ý–´yŒRû/0U\];r­î®Ýk;h?^]•öhSp÷×sÔØùéÑxôh'^;´þ:‹ºZÞµá…ñÚÈðS^[ø²Ís¤Ç Çlqk{Cx»KzüôÂm® «iðCýôÊ\Û¢÷ú¯b&y•ünÁSC -dw_4,Õx… SÿP|«¹U{ñUU)–þ2ñ3sëFì†wÁeÈv•Z–^¹ƒ/êü—WŽ^òÂ(ÿpOðV97ê®nJü/¤‡«gŒku?×Á5l„§‡ppâŠ@o^†€‹€)„.1ÚU8@(¨À ¸7Hõ×0•  ìŽpÁ¼”&ø Z̘é:wØÁ_.½Æë¦KÝ@»ÎbOÞÃ?,'âàWÙ £qÀüƒãP<5|0û®txºü¸i| Ñ´»{¾éùʳ.§ÅSñ FØa° ² õOïŒTéÏ¢¾TTVy#ËÜYƒ©¬¬‹è» Þ¬ïc\*˜â{•ßÌØ*îUBÇúÕÏyŠ[…¾²² UòÁ'Êo ½çVÁtpÓxª$îŒß*Î ÒðˆSžâæñdzîÈ«´ î^€'Ÿü‚æþK]¨dÌ|>oOt0}BÞY–ø8?™þ²ŠG¸öÊn¡Üƒù£†T#ôøc4U š´~¢#›Nòu ÏhäM&c$uØ_ç®nxòOë·¸«?·´Gnäɹ У€ÚÛµ-i{‘t±$pù)¯*®o “¶HßËõFéÝôp«wM?hÖル@á Ú†ÃCÛœ¤ "\ÿþhê«?ŠFõ¨Ö?” dª~§C%VqÀt^™yuÁ•‡(_kÈÀ-uÅ+#¿\äøÄ "4Ãpø†r‡[(÷༠J”Ä‹¸µÖñµ« ¾%WÓLï€Ã:²´ài†€)„q(ðhC$ÁÎ/¾nò±„õî»ï–{áŽN8ÞðÅߥW;ö^wÝueê0Áïž«÷~‘ü5|0Mð»ò Å/m¸­òI·'>ªØSyÀȲ`ìÏ …pmvv-Xø3}?u*­ßpŽ(ìñcš4˜rF²²sé•Ïæò)/Ò;o¾*'@FÂ, ÿhò§ôÑÙgÑÁý{R1ã¹¾´œz´Ê§9«7Ò€v-hÕæÊÎhJ-r²hñúBq[¸v3um‘GeÜ)ÙÀôÝú•›J('³)5g¼—lðè0}7¦/eúM¥Ôµe3š»f# dþË7S^V&ågeÐ/Ѝÿ6-iûugšâòJ*,¯ .-”¾%-ÛTÄ´™&ƒ~eþƒ:¶¥7¦ýH% ¢B®5Ô…,xޝ›iËÄO¿ø /oçºÃ²¥^ êbçñ“™4é_×ÐÅWlÙóœèøÑ‰ÅÈþʵ›èèý¦¶½¼“‘cQ†¡l·hÕžž}*]É'¦‹ÑNüž¼o[j’7YYÙ´dñÏ4ƒ9;û”"i7¿q}mÅí(›ÛÞï\ïû¶mAó¸®÷jÏí¢œÛSuhžËn›h·ƒ_¹½µÉͦ̌&´œÛaŸ6͹Ýl¢ÞL¿±¤œÊ¹,:ä3=—§Ò·ÍËöʸ°„zƒ~õ&—OëKʨ’Ë›f¹´€é2ÿ_˜» z´û¾m›Óº¢2éç·k–#ôà¿xÝfjÏñ‹Í>úú½7¥ø±Ï²8¢¼áöUÂÛH&ôöÛ´M‡þ¬Ìò»\“Ñ Íd³Ìý…Ï-˜¶’¨tózÉ?C6K"{¸OƒÕIØJÒ}Ø(Ú&‡å0+€uYF¢|¶kI¿o,b™›I9\W–r]éDzr—MA«fÄç‚Òû?ΧëϘDûÛßê >QÐ8î56Ó±¢¼–Ü'ã3"|~å4Þ)a03X,)ÞD_ü°<’Þï˜_Š Ð„]‚Æ+R¡4Ϫ‡ûá ~Osx’&û(7ì«„R}+6MyF ³i©h?î­Ék·Ž‡Ê«Ö{Ìœ¢“{tÍÐ?ÃÄ(Ú]ƒPU7}Šÿa²€Ÿôïø½¾è‘îLî4A¡Ó¥å¡òëº!ïP ‘;„¯oƒ²Áh~~3¾h¹G½E¯e^\RBK–üRMÆ”©$UÔ²e‹­–·ÇÄ'JbM7Ãþn\tðÁËLK} –! ±Ê¤Kf¸¬¥-¡ÁÄÐ60‹¶m[ŠH/Õã§í7"=@jÛÎÏŠšr»ƒA»‹EÞx3òÈUò`‚e±È‘¹\°P*Q¾‚gÖå({bÖ677zõêUo j;„b¿xÉN·Ö„Ä&X`©lÝåžfMG"bÅÌ-Ú-N‘×íM8xWt`ËS"ãND~ҧͦX @„©éƒm¶ÁGyÆ“o´ñ]|@9ê~Òøp4.†@j# ²/;6ƒˆKfÓ¥ƒdò&.ÕŘ$1*?rsrhЀøÈXáHy+.FSCã’´®Bñ6‰àï4¿šÀhžC ™¨Ù-íÔ¹ƒb®,vðJ'Yjò&–Úa´õ@ƒÈÎV•Ô·I7ySßø¦b|¦¦b©Zž Ô÷Ç0DÌÉH:¬cU»"3yS;Ü,Tj!€!úzÛsZÐYnêz: žseц€!`†€!`†€!`Ôˆ€)„5Bd†€!`†@r å­É‘SK¥!`†@¼0…0^HCÀ0 C H§}Š µEo†@Ê ` aÊ¥eÄ0 CÀ0 CÀ0 Ø0…06¼ŒÚ0 CÀ0 CÀ0 ”AÀ”)Jˈ!`†€!`†€!`±!` alxµ!`†€!`†€!`)ƒ€)„)S”–CÀ0 CÀ0 CÀ0bCÀÂØð2jCÀ0 CÀ0 CÀ0RSS¦(-#†€!`†€!`†€!`Ć€)„±áeÔ†€!`†€!`†€!`¤ ¦&qQú|¾$N½%Ý0 CÀ0 CÀ0̆N@*Ŭ 5iÒ$¡ÙK4ÿp‰G>ñCü •†pi3wCÀ0 CÀ0 CÀˆS£ÇªFÊPÊQUU5mš±ªäÕ”QWŒ6LM<Íß0 CÀ0 CÀ0êäÐTê—¸Å¨Ê §x›{î¼óÎÛºÆJ¡ 0w,´råJ*--Ë ¡¦ûí·ß¦ë®»Nø":uw¢6«!`†€!`†@Š#€> õë¯M!¬#Ö˜„ùùçŸéì³Ï¦3Î8ƒ&NœHÇs ]sÍ54eÊñ‡²ïŠýÙgŸÑ«¯¾Jš‰¨ÊËË#¦QãùüóÏ©S§NôÁHlê^‡¨%èÌ™3éŠ+® ¤ÃŒ!n@>DÓ–¢¥K7ü,¿†€!*K¢é›@6EC[ ŒÚ€®F³º£xú˜B'4ýõWº÷Þ{©}ûötàÒŸþô'úôÓOiÇw¤§žzJbÑ8ÂZÉñÄ/”Q?7ŒÒm³Í6ÔµkW}•§Ò»ŽO×wñâÅ´Ç{ÐO?ý$A+++]b×ôwîÜ™Ž<òHêÖ­[À]ùÂ<ݸ„(èÒëÞ-Z´*UÕ Jãò„]ë¯nx†Ã ëÒ›Ýh(P'Ѿ‚ë~pz‚é¬.#d! ®,ì 'KÔ²)]4q!-¨wèWc‚@ëžö-£åat±!` alx…¥ÎÊÊ¿c=–&Mš$³…˜AÃ,áñÇO ,Thíøi%Ç?¼ „?õsÃ#þƒ¥›îlšK¯4pÓ°xºvø©øå—_øedd¨Wà‰°3pà@zñÅiäÈ‘4+_Mƒ—˜á]éñÔ¸õKZ7øóÒ¸ðTì\žš.¥Óxìi4Z‡ûí7úî»ïƒ4ÁéQºµk×ÒW_}X²mu9){7 H¨,Y¿~½|÷‹‹‹å›,K”®¤¤Ddd¾¡Át‘â2?C ´n¡ža[Ô 'œ@Ó¦M#ôQѧƒ»)†± =­*=V)µCpÂ@Qƒ’ˆ™´k¯½––.]Jýúõ“ Š=kÖ,™Û°aµk׎¶ß~{êÕ«W@ТÒÃ,\¸¾ÿþ{Ú¼y³Ìî´ÓNÔªU+ñÒÁeúôé¢|pÀ2ãÿ¢¢"úæ›ohþüù”M#FŒ Q£F¢ eæ“O>‘´‚3†Èƒ~ô¹nÝ:úøãiìØ±„ÙBÌ0Î;—/ìXÎ úÑ£GÓ°aÄ”ÇÆåóË/¿PÇŽ%\^^^€Ni‘§M›6Iú-ZD¹¹¹¢ˆBÕ¼Ë9sæÐþûïO-[¶ap"¿ÿþ;í·ß~’oÄýÇ~999´T‹Ô^ zF@ÛÄ»ï¾+KÎÑÖóó󷪟J7{ölY³ôê^Ïɶè C IP™>Á.»ì"ßì‚‚‚­d‰Ò­^½švÞygù®ïºë®[Ñ%) –ìFŽV=÷ÜsòûË_þ"“-Ûn»m`A'X´/ØÈ³Óè“g3„q."P˜pOÄrÁ*õ›o¾)Já„ ¨wïÞôã?Š’£aqÈ ”HìI„¢e«uëÖ´f͉3j Uz(}PôÀ  Aø½öÚ‹–,Y"}»í¶£»îºK'4$tD‘¦6mÚÐÓO?MW^y%=óÌ3TVV&<ôÆ¥ê¨£Ž¢yóæ©¡“Š¥²h¼È ž'Ÿ|2 >\ÒB(ˆéÇKäç£>¢#Ž8BÒ8uêTɯÆ¥ŠôÞ{ïMû,xÿý÷»Gy$o` ‘f„[¾|¹(£X åv(ŸØw™îC˜Ú¯þ0ÐzíÖ=w¤"Ú)ëçÚuF]ù€o(ÞVÆõW¶ ÕŽB•»Ö‹šž •f‹·þêe¨úáÊ|+]z ãÚ•FeüÀ'ب»•qý•q}c­õ#¸ì£y¦~€FãÐ:†~$ú¬˜8¹è¢‹dâ~¨¿èS*]4i0š0ðfê€+9šZ&èãƒQªq»é¦›ÄG⼰°Ð÷úë¯ûxtßÇ•XÜV¬X!4—^zi ,/1·o¼ÑÇ ¸óòß<àc%IÞO;í4+fbÿᇄþ¶Ûn ð@ÚÎ;ïûhY!õñ\w’}«V­ „íµ×„–gn>ø ¸ñ,¥¸]xá…>V¦}À[ Ïž Í£>*NŠ¿ú§Ë3]óÝÊW±×§›&Ðúªí¹th75<0ã:›Ý´ni}B]yã7ÄOe¸A•>h}•côWP?Љdx†Pèô›Š6R<¡èÍ-yвVyÂ+±¤~ .ñ„}:¥ ¶‹g®¿þzá‹>d—.]¤/‰8ðãC¯nóijGÉH[2Ê5+†kŒ°Á²F,[Ä,f±\‡Í`¦¦Y³ftÈ!‡ˆ]ÿ`ÖêÖ[o•õÒW]u•Ð`I& ÖOc† ü±T”•@ &##:tå’XByà 7ÐùçŸðÇ’²;î¸Cfáti' K°šæ½÷Þ“åçSÝ»w—ï*¾¯XU„e{Xqƒƒò°ÍÛPÐÁ-sÈ.œªŽíàÍÊ€„±?©€Ö)¬6Ãa†¨SÑ­ÑÖÔ%ôQaPç°ßFRÄŒ!~è;bÅpÔo†—ö'jL!ŒªÈ„Ú8°üQ ñ+¯¼P`´QÀŸg¼ûçÐaCÇBŠŠòÁL„oÛ¶­²?ø£a ӈåP‚Ð  hÁ ƒ !ýs0“'O–ýƒzZÜÐІ P–Tx»OUA_“ÑtC „Á‡éÔ}ÊW—»bÙªÒቸÐÐñ¡Q…+¤ùO {aÀÊæ×_-(ÜÍÅRãÐ| ¬ ðHg¡òÁÞL`§x ˜ö'î ÞkÔ_Å9ìou ê¤+C0˜Ò£G@ÑA"ÐãÇÁ"5ÿþ÷¿E!ÔwÄý̈Ê&Ê9ë»â’*O”%ʃ([•‹pTÎZ÷Pßžº*„à ‹ñ]÷Ç5ð”&—¶®öúŒ+Ú´Ö%MVŸÑÄ Z”Êûê]ƒm $~á…ÎØ^râ‰'Þ¯¾újQ±eåòË/¸ƒNûpoðA?DÓ‡F¦‡hô7êRîÊ3€ ‹ëïڃȢz­)|MþÁ‘„¢å.Ö÷šxÖäK|hãè¯î°Ã ¼c5è“~øá‡rmY¸úá¦}d ~©Á`®=Ãw 2Ï` ¶Ká»h¦öT—ܵç“ö!UðAyAÅD‡Ê>¶0n%ë­·èàƒ…OGØp —QƒðeÇ^ âp!ÂcV—†Ê£$jÀ{ìðáG'<а1ƒ6f̱ƒFÓ;Lð»çþ¯Òk§WßÝô"´¾#o0 SZ׸釢覟—ÆÊŒ è•§^ƒ7^b€G ³;è´cìâ ¿t1À8`øâ‹/N—l7š|{ @§ rÔs œ<öØctæ™gÊhCù¹n®a`4>×ϵ»¼];h‚ßÝpáìцQ:}Ö&> [S#¥5œ_$wÄ ãÆ[×´DŠÏüâ‹”8Uä\Y‚ï­~sQžî»n³=dS°|Šo ›!à! ²}jôm±â¥€Áÿüç?2(ª“(JçÊ$Ã0v¶L?ÅÖB„@  FýC}$±·'gº ¦Æ1åûëpÔ³žä E#2˜²G£€AxÐ@póæ[qãƒidÆ/:c†½F5áÓY:M›6´PáC5¾`7}×'0–Âbÿ òŠx7ìQx衇dÖRãìÅ×pÀà$ÒpFiq/ÛÍ7ßL×]w]`Y À#Á4oˆB£›H† Ç;•Ý‘àâ>a÷SÚPþ®ŸkE[“Âë¯&Úhü5=útøñ„òwi£µ»|Bñ®S¨ƒ:Žz‰•!¨¯ZG]:ÌÂ(Êš`ÞH³›žšò m44¡âq±NW8žê®O—o(·`ÿšhjJG4áÝ8ƒùû¹ïÊ[Ÿ±„U>6¸Ü£}W>±>¯Æ­aõõTHG´öp´O—Æuewi#Ù]?å7}Ç3øÝõ gwè]ŸáÂDr×°ÁO„Q7µë»xýʬ‚ѧÊ”«K§²&˜.ˆµ¼"\m~Ê+\Xøû‡ EFß]Úpv¥­éYSøpþ®;âÐw}ª›û*-êü E«ù¤œ)“óÑ—Ä7?•CÂÈþÔ›!¬5tÕ¢RÂhåWAq ®MÀ½*À?þ8ñi¤r8€òÙ—qÀ`f{‡0óˆ%bãÆ“ë »öíÛ /(eÄþ&Ì´aöyB~Ôh5­pW»>•6ø3𨇀<Ìh ýPØà.yÅr8ìÄÌ)fG°LJ1–æbŠÁ^ ŽsÅWöQaï&âÀ]Š»í¶›$‡OT“µæÀþ¤“NÒ$ÚÓh0ЮЙ†Ár\Õ¢û \âÚÑ>Ñvu4_;ã – ‹Øð#à~'àä¾×dçï‚ëÒ¸î¡ì.m8;¹~.Ÿ`÷àw—6œÝ £v}† É]Ã?FÝ4|ð»ë®~X•€Z]eàÊШl‚¬ÁRQìÕ‡ŸòP¾ú ç®þ5=#…ç纻öXârùöšx¨¿Ƶ×äL«ïú ^ÝܧÒ?]×®t®›ÚÕOŸê^×§Ëϵƒ¯¾kÿn8ô æÙgŸ•«Ótõö+µ~ ‘ý©; ¾™: À „Æu \+Ô=kÞTëãÊ/ôóßÿþ×Ç÷öùø^>Ï‚à Þ íóÏ?ïãY<¡á=`¾sÏ=·ÚѾà:½>‚Gô|ܘªñ€?/#õñ:lá£i}çwt¬l®làÆWáÜ8àɧ ŠÏÀ ­¦‘gîÄ/«wüáÑFïaÄ^ë0X™ Ä Zž ñášи?î8ËÕ áS\ÅG«A¸SN9Å·çž{úxŸ–8ëÛÀFó%/öÇh`x`Gê{MÉ@½Õc½k¢5ÿôC@åš];‘~ekŽc‘%9Z·bÇèSí;FsíD¬ ®ñA>^= 7«ƒ8âniŽÜÑ6SG°ÔD`ô^—[Fb‰ã1Ó…™/Ìäa–KAu­>Š#&à‹å”ðÇ ‚î#oăQ;¥Ç'yºû@ÞX®ÊI» · '8á§q)_ðPÃB@6ø"^=4KI'¤£6KM7NÅ잺ƒ—.E\©-f=ƒ1-òªËm‘v—›Á[q=°ÃÉTÊOã6ˆ { Ü´ ŒCÀ0’•kUÇéÓ0¸Ê{×!ÿmT=ÙKØÒo4Tž`u—žàŽ1;Ò@IDATåœX……Ye•EµI­ñÀ˜ìª ’ч±%£Ñc‘Š‘npHÈž¨èX:‰ Þ¡Dª’7(4hà‹cçÕ€þº|Cßµé´:Ü•J~®QzuÃ;Ò éöW:(WÁy ÞhŽôÁ`ù›K wMAü`WÐêr9¸¹´È«›_økúB¥~îJ«Øè;ø˜1 ÑÖËhéK¾,†€!Ð8ˆV–DK×8si©J6ÜþŸ)‚õSz¦Æçh¦VtÐÃèŒìðS£îJ?×ßuwúîJ·`wõÓøð®t°û+ž.¯PïJLwåëÆ¥yuý]»Òª››¾pq¸înœÊO3†@cB@멦Ië}ð1˜Néíi¤ *¿‘×îæOÛ>]?³G‡@(ìtæÇåŠÎõ7»!o¬ÎÅÑÈüL!ŒŒOL¾±T^кô®Ý4˜.ØÏ}W{(^‘øh8#¥+\¸Pî¡Ü4{FF@•¥â*Ì5}Ûú©Ì¶¦6—`€·aŒŠ½§2®ŒqA¢‘Ûº¬ÌÚLÝkˆ‹}ݹCÀHL!L†R²4 €@pç,TG JaMÆ:j5!´µ¿*ƒ¸bB/ä¦S¼5's17*gT¾¸õûÕ±W²@Ž€4PZ°§?Ø]%&˜gãF q¥˜c?N×3W -5†€!L!LªÆÓHb´3…–Û9CÇ ÷^®_¿Ž¯YN› 7I'MÎeŰi¯ƒÖ¹sÙ‡ŠýšØ'ëvÔЩӎ_C”𤣠€ŸÎKçœs}ðÁ²ÏXÝž‹ÀH0¡ä ¿¥K—òUD ùz¥Ù4oþ<š?>}:ùó©Ù{Ÿ=©oß¾4`À@¾Çw0õc{—.]å@ •]&sBB·•£Ê7bÄÂ5M¸îJÝ· `†€!R˜B˜RÅi™1j@¨:g³gÿHßO›J_~ù%½ýÖ»!"À=zÞ)`®ç~ûïCcwK£GoGÆ §îÝ»”C뤹H…·£S‹ƒQ6f TAí_œ`_¸p!}õÕ—ôÚ¯Ò«/¿îÏf?Ò¼?uê׋Û}egåp˜ ñ«¬¬à¸ùëU…4ùã_éÞÊ÷Ù}‰øM˜8ž9øP;vgêÕ«W5™ãN ±ýÙ ÈÈj“9[Ac†@J#` aJ¯eΈ·ƒV¸¹¦~?•Þzë ºé¦[ü ¶¥Ý‡ ¦ñûþ•òr›É5M°L‹;¼€‹>âÛ*‰/ ¢ÊªJ*))¦Õ+ÖÒ5×¼Í~× ¿ž÷gé¨í°ÃŽcM1hÂþAç WÏ'3†@²# J3\¥4mÚ÷ôäSOÐ]wþW²6vÀx:æÀ y†¯™¬8À8HUeUµå¢BÈÊa³¼æ¼a*èÓ‹vn²ËŸJ^ZZH‹¿ù•Žö!û¿K.¦c&K#GŽÅPã×ÙÃdÇ3Q釼Q™Ì ¯D!m| ƃ€)„§,,%†@½# $tÐpoãäÉŸÐÍ·ÜHô)5§14nÏs©Uk¾[²IUVTR%:gü«äÎŒ†‡]; |\ådåSž-èTî¬ù|ûцèÅ;fÐwÜM;î´]zÉ´÷Þ{®^AçÃFïâÖ÷sº8oMa.†@ãGÀmã³fÍ¢;ïº|àajKciÂþPóü,[|TQ^Ae%å[Õy{ò>!·2iî‰!É<äOvf 9„†B7m ›nüˆ7Ó…žOgœq/- ´nZÄÁþTCʺɜjØ‹!ò`­—CÀHCÐ)B' ¿iÓ§Ññ'CxýÖŽN:â2:zܾÜIk%³â¢b‚bRUU½£ áñTÃó„Búâ¢*åÎ]~^ ÚÜîtò¸Ë){So:âˆ#èˆq‡Ò·ß~+Á  "=fª#\1Ch³ê¸Ø[r!  XaaÝwß½4|øpzñé„Cÿ·7+r¹ž¬(-•™>äΕ-ž|Qƒ§'·\„©âÕ ¥Ì£¸¨”ò²›Ó©GBÇ|Ýzë{4pà@zâ‰'DŽ™¼Zá –1yó1RSS±T-O†@ h ‡8ÜsÏÿhô¨Ñ4÷“"Q Bå¥UTR\B¼WF;^èˆEg¶tØ@>àWV\I}úôÅpÝBžƒ3†n¾ù&Ú¸q£ÌZ'dkt=Eܔ孑1—ÆŽ2|>oöÑ¢Ÿéè ã謳Φ£÷ÿ+9nòUdˆ\€"»Œ •û-r§‚¯0 ÕÄ—ÍŠáx·÷94iÒ$:ãÌÓè÷ß7y >¿ä° BEȼ DÀÂ,TË’! UÑ)¬à9çœK8Æì¶+‚>aGxé E­FŠQÆó…ØAÁ)+©¤#·¥c¾þïÿ.¡C=€OÔ\b´ QÀË:gAÀØkÒ Ð„Oþî»ïø4Ð>4å­õtÊ‘WRnfs^¢^‚µž\ˆ“œqAaÕPxûxo!Ãæymèä#/§Çý’ºuëF?þø£É0Çn3„f5ÒSÓ¤ -›†PepáÂE´íö}è•—çÐ)G]AÙù|L S$þ(90ˆ/×C§uÍù´‚z÷îÅ'šÎ¶Nš ³å)„[°0[ò €A ´õ/¾ø‚vØaÚmÛñtøQûSiQ¹¬ðä@´+ê’oO1¬¨(— §u<î} Ÿ|<Œ~øá“7AТ\l†0{5ÒSÓ -‹†Pe÷zõë×—:øveeì*-¬àÃb*ü#õõ‡:8‘´¸°Œ=r3èP:t(͘1Ã:iþbFØe3„õW/-¦º#Yƒº;eÊÚu×]iÿO Þ©hs1/Hü S¨ =ð**,áÕ ÛÑŸFIÛn»­ B… 凟CÀHì”Ñô)kËi# ÊàÒ¥¿ËI{C:îEcvËÊX)wܸƒ–€%[ÑÀ­4,é$GÄc³OŸÞ%6^©BåO@<1“Š%\0x÷0K•ÜZ>R •5?ÿü3í¸ãŽ´ÛÈ£©K‡^R›4­ÁHˆb¶W'ðrÕ~½‡Ð¦õE2…åó]ºtIKy£h©lÁeèÊ¥±§!`¤.6C˜ºek93|àqªöì\xÑ_Ù­?í´ëÎÞ ¬ FPL¢õ–t•°R8tÈHêÆÇПvÆIiwÐŒvÄ ðedxpçææŠ=;;[À‡:k 5c46TÖà°ª‹.>Ÿ:ÒŽÔ·Ï QÀÓ@Òy8z»í®tí?®‘{!'Ó­mAž¨ú™ƒŸÊ•E&w[k³ôñEÀÂøâiÜ F‹À£>L/<ÿ2êp´fImèû-paöÇÅïwÔ^4ùãÏéÿó.«F'-•«j^qêêüA˜eYºt)¸³Xî‰DG4¦¦rHþ¼=ñÄãôê+oÐAGîCèáseá¦Äò¦ŒO<žD÷ß÷ ½òÊË.‰LäŽ*ð*w6mÚ$2gÁ‚´hÑ"Z½z5J2Lî$²4Œ·!Ðð4B1Ýð X TA£ºP¦OŸî&zàyTÁWJà®ÀƤ nÁÛÇ?TÐñ‡^D—]v¹H?ä#–:[ëÖ­£·Þz‹Î=÷\2duèÐþþ÷¿Ó¡‡*`ÐÙgŸM¯¿þ:­]»¶šb˜ŠØXž’ ­ËsæÌ‘«%Æï÷çF.kš°Bijc|õÅ!»FG=~ùå—À`Kr¡[jµ¬ðmX±b=óÌ3t 'P»víDöàžØþóŸÔ¾}{9èâ‹/¦É“'f~]Å0¶XÚ03¦6æÒ±´u@@GqJå-·ÞD½›îFù¹­œþêÀÍWB#‘¹¦ ËÌ`w;WÁ¬à:,×*`eðž{î¡óÏ?Ÿ.½ôÒÀ¡1J£<}êÒP¼ãn«®ºJö>ðÀÔ·o_9Bi‚ãµwC Q ¾¢N¢N?ùäã´ïö¸í&ßæª íÔ÷(>\æ%i‹Úž…[]ùª¬A:?äÜBUØpX ”A,]¹r¥,UyƒpÊË•9pS³fÍ™!¼þúëeÆð¸ãŽã%·å.\ÜÖž†€!иØÒÒw:-u†€!P ¦|÷­„ÊÏoá¿W*I4B>ý´Š÷æääqú[Ò—_})ù@G¥1t‚¦3fÈ©|°k*X1TZ¤ÿ†n=9guýûßÿ–+6p:Õ8æ=8,ÂÀ ~˜MDG '‚Ïi§&ËG¯½öZYçLP°?õ…€*˜¥žúÝtêØ¹Ëš iuINÖæîÙC·}Q^˜¶.ñ`–° _OzíÕ7é×_vš·ºðNDX•%³gϦO?ýTAUæ 'ÜtÃ?ìU>þøãéᇦ‰'Òý÷ß/ƒQX²Ž¼ƒ&œÜßo¿ýFÛl³ m·Ýv¼´öYêŽpO<ñD"²h< C ž0…°ž·è D# €òò î,L¦am&_eè‘ãXÒ’™•AÙ9™ÒIkšÑ”²s3)#“EˆËšíM¹sÒ/–Èü´UœîîO~ø¾œp‡ÎŸÛÙ©Ë„Áª3Î8ƒn¹å¹"‘ £4M÷¬Y³#ì—]v™øcÙç£>*—x·iÓ†°¯0¸spè>öí`yèóÏ?O-Z´]tÝ}÷Ýôý÷ßË»Æ%/öÇH h“0?þ8KžÙY9! ñŒáOEy%óñT”Áî-tY n¸Ã¿nÆã“—×LØÌõ¯H¨Ïć0`€:uÌ1ÇHÛ×Ù:År@eÁ| Xá4QÈ,\oƒ}Ÿ“&M’ë%\¹ƒp*w@ƒ}•>ø aà üA‹%§§žzªÈ,¸i\‰Ï¹Å`ñFÀÂx#jü F@?Êk×­¡W_{•zëÆ#Àu[Â…>ߊEhÁg«„ׯÕEôÓ++hÃ|h{B)ä_¶–ÓÜwWÒÚe›·øÕµ¯¤.=:ñ²Ñ×å^>°ÑüÕ†e¼Ã „}š9¿ýöÛ Ç³÷îÝ[fî°< 'ЩyñÅ…aÜN:Zÿý北†Ùíœi§lÉ’%Ô©S'ºùæ›…ff¦tØàߺukºï¾ûä´RSã³§!H´nÏ›?:Ñnq‰ª’åUVV&ùª<…&'7‹í|"(/#—ø k¸MὪ‚þu5h‹hsDCd¶ü‡+o`o ?¤ r—ÇCn`?1fî°üüÛo¿ Ìø!ýø²«\¹%„Å@Ô¨Q£ËÎA<¦L™"‡Y½öÚk¢üÁ2KÕÏ9ç°ûPÞ]¬ÄÁþ†@Ò ` aÒ•%Ôˆ \*¼zÕZjÞ²À+½ŽTl,a×NžÚiÃS;rx‚N;hÚaØ•Ÿvô”†‡?:fè´¡C‡”U¸Á ½X>ŠeYÇoôèÑtâ‰'ÊÏBäüAGKyïÉÐOŸ>]î  –mát@¤tÁ2Ðà®BÜ׈ô›1ê_%ï+ÎÊ´ËZÅÏÍ=#Ÿe ï„©båî“ßVÐÌishÏýÆÒÀ¡}©Yûl–E<SÙ„†Žè/tS¾žI–SÇ^-©´ËJÅ9æ?U•¬¶mIßL{O,,¯¬¨(gY²Eñse‰Ê}†òƒ›ë®ïÁOmûx†ú¹™ÑöEò°}ûöýÄóæÍ“ÕmÛ¶•ÓDq¢(Œ.…]ÃȲ©]»v¢Tâ³…¸ãùÓ9¸Ã(,W2dˆÐx>ö×0’S“±Ô,͆@èGÞûpoQ¬¢º5‰<77›>ùßZzÉrêÞ³3Úq}ñ_ Á°]öJÛ´oK+–ÿAS'ϧ¶]ò¥3µ5Ãè\ÐOÁ; ;ˆOÓ<þ|ÎÔ¬y¶t~po~è¨@©ƒ‡Ž ò<£3‡åP¹¹¹òËËË“{ûô‰Ã\p·hñCǼðCXürrr„|ô7ð„¿vÐ`YçÇ,2Ì ÂtéÒEd@zÿñH1H»v¼0ª%^H×?þ( !ìèH"®`ƒ¼C9 åLkï†@¼"D½Œ5&ÛýÕÁÔœ¦ç)„={u¥a»÷ ùÓ§{t¢>ýz°ÂVIS¿ú‰: iΊ$ÚF¬1:ô¸’¯ŸèÜ©›ÌŽa%¸½iûŠæéÒÛC½‡sƒ{¸ddö~òÉ'¢ÌaVpñâÅbÇ@V `¥A8¹>LÂŒà.»ìB¸bÊ%BÈ$•«Á< wlv0{7’S“¯Ì,ņ@thÇHõ%º ‘¨p²ètÚH³gÎ…pÈð~Ô¢c.­[\,Ê!Âþ8c­˜±‘úïÙ‘ÊJùÔ‡:,9ýzÖOtÇ·ÓÑã'ÈÌ*lPèTBá:MpG‡?Øe´ã…xTÄ ¢X®…Q|Ìât  º,t<Ïýë_tÍ5×Èé}È_ÇŽ ‡Ô@™DÞCØã: ð_3†@}#Ð$ƒOûäÙ´x¶¯*VTºlAS?ŸOË–®ä i»Ó­÷O¥ûNK-Z6§ùs~¦>^Lúµòfëqœ\¸©ˆºöèB|p\óR‡dÕ÷ÍÞzë­Be+°+ 0SøÃ?ÈÉÆ8À–+wT–`õÅI'$Já{ï½'³ƒƒ ¢GyDÜQ®*£‘òÀ^õ¥K— ßj†€!Ðh0…°Ñ%̨M›x{̪|µ_.*XÆ5Œòéûo¢}Ú•zt¡þÛv¦i+—ÐÀÁ}%ÈÔ¯gSë¾¼\”•›ºtB<ým1uïÖ]NÙ¬ ¿à°èÔDc"ÑÁO9œø7mÚ4a‰ûÃ;LN Õ8pe}XÂ{ ;wî¬ÎŽ:lçwžðÀ=:s0¸Ÿpüøñ²'§‰b–S;rÚ1Ãò.tà°—&žsah 0`ТsçŽT\TLMÚÅqÀ…›gn‹Lúãç4göBQ‡òsE#F ÔüÀËIK7ó¡M|Ž,% “ÆhœÑfŠø€œ<“¥˜\A;kŒm í¸c¹úŸÿügÉÞÀe iß}÷ Èx`…ÂÌ™3e@ 3~*3T†üñÇ4tèPÂõ7Ýt“¬Š/Ì2öâì0Ð…=†*çðTƒ+)påŽ^Ó±Ò´ÚÓ0Â#°¥U‡§1CÀHB°·&ÞÚÐq/ãú|ýºdùžCCGõ¥¡»w“Ûòe«d¹hëNÜ™ªãu’~¿RÙÜçòƒM]~ ÿAç%š:@á~þÍ7ߤ{î¹GFêqi*¥¯©Oo¥CcUp´}cvr« T.À?Wv DN?ýtŒÂO”ü1kXÀJö=CáCyËÈš/¾ø‚~ç“W!»ôð,ðüôÓOe¿ôŸþô'¼Jx±ØCÀH:L!Lº"³‘ÀG¦M›¶´ç^»Ó+×HÀ¯DÊ×ßÁÓNw7dæ¯u×fôÝW³¥óÑ`oÚuí%4ÜÚôÉåŽEÝ´AćŽËúuë©ÿÀ>²hþB%µ¾Ý4-˜!¹üòËåþ@¤*ø¡Ó¥vøAQòÉ'iøðá2âŽÙDÌ$â1,9EžµSæ†UžØôÐCɵ8ô§Ž7NI]æÆ«<ìi$• Caö˨’oŠ×vQ«øüò&–Û ö¯üa/]$ÎcwóîÓ›1}Ua†ÕMÜH[-+/þú Dß-Š/Ú;=DYÀOý‘vµãj Ì^yå•’%Ì&âJ¬lÐëlBÉÌb¶ô‰'že¥Xy?ìQ„RêðÆˆ›¥É0Â#`{Ãcc>†@R"€:X^ˆö•W¼D§öé…m7µ3èhq ‡™À@a)çNX§^y4õÃE´ôWœ6Ú…ZµnAË~_Iß}<ŸïòâÓEý×TH ZþÉÈÌ ¾\@/>ŒÚµm'\´ƒSK–q¬1»£ªšÒˆe]¸x#î8Ùïõ×_§C9DÊ üB)tpS¥—Hc_ÐÙgŸ-'­îºë®2ú÷ÌCC Jú÷÷öômà¥Ï-š·¦ŠJV !8b1L^U¼¥þ£ýW–c€…ÛØ <úö‹™´×~;óÚ\>Y¸Œ¾ž<‹: má-M1*7Yhs™Y™´rÅr\BÑ{ø«âºàü<\Hf±'û Ñ1C‡MFÏž†@¢P%³Ü—]þ7zû“ï¹.òàQ-fìpÅL—[Šâ÷æ+Ñ{o|N­»æQyIµîÐŒf~»˜¹÷zëµùù"ý:å·Ì‰Ë^åLV>ßýö]:ù¤Sd€G•¨DãWþšF-ƒp¼Tî`v綾^¢/¿üR®Î0ÀSâ5|°¼ÑwÈ+5¸X“&M¢óÏ?_œ5JcOCÀH>l†0ùÊÌRlÔˆ€* Æ çƒ^ÚÓòeËùôÉ®T·¨_L˜À¯WRnË,ºñªGø™,Z¿ºÚvΧâÍeÔ}dzñáOè“w¾—¹€Õ+6R÷mÛP9_5ÁºM­ :Y™Y¼,iµðÀÙÄ‚):_èhaïÏÝwßM;ì°õèÑC–ŒbÆ3ÚA •ç+VÈ38ÙËLO=õT¹ÿ<#… ÅËÜ x  uï ¦ÿ\wUTñŽœr›VˆdšµÈ¦¹Ó–Ò‡·Ï¦ö£ó© ˸CÀde7¥§ný˜6-(£ÖCr©_DC®êf¼ÉÒ²"f³ŒöÞ{_a— JNmäöâ®AÌöa ŒÙgŸ}D ŽÄ'ŽbÙ(®ÃÑ *p’µ?†€!›!Œyɉ>ìøPcoÉ—_Cï}ûÏÚÕ¾¹cÎæ %´ê÷ Ò1sQÉäNÚª¥ëi%ÿê‡ËvìzíÓgx&í:Ùw‡ü¤ŠÂ£J!–ábù‡˜3gŽì%<í´Ód!NÅ…ôØ'ˆý>8øáÌ3Ï,pµî9Ä’ÑììlQ0S›àz`ïU$0psð!ÐŒ)?RvN–È ˜R%£¬üaÖoȸÎÔ®ksòù•AðÁU÷!můs¿ÖqP=žÙ9ÙôñßЩ§,ûä—æ öT1*wpEî42eŠÌâ;Cepú1fqHdî5}øá‡i„ ²Œ2§’by;Œ)ƒ©R3,†Ë}:}ûí·¢ðá^·ß~›ÆŽ+ûAqáô¨Q£¤+÷Ê!‡¸²­«_"r­¼1±Ç{ФÃ/¡ª2>H‰°v¼Ë‘X?O•Ñ„÷È5)¥§ß¼Uöõ¢i^uƒ²†ÜP™ƒ„,X°@î>ÅžB N½õÖ[TPP@;í´“ìÄ2QÈöíÛKºMî4hñ5šÈµà”l,‡ÁþøGyD–$׳F“pKHHlÉhHXÌÑH~0 Œ“èxà~>î :õ¨+y¿Mí:gàÅÿCšx(‚.ãìÜ zâͻ馛o”“;á—Š3`Ú)Ók·nÝ?/**¢#Ž8‚î½÷^êÕ«— QÊwZ«eÖ^¢F@ëTÔâD¨ñî²Ë®tö9gÑ=ÿ{N=r_VkoâI¼Ù°<Ën–E½øúûµ×”A˜T”5.t(+Ož{«.0à†fqj1Á’t(ö®qøîf7 äG ökÈ’?ï–C m?þhÚu·hÚ”ir‘¼ÏWÇ_„Ò…½u Ì¥NÝZФN’˜ÐIe£P(†úC~õ Í;:kðFýìi4$P2P7q8Ë%ÿ÷7NÊ–GC Ý0…0ÝJÜò›Vh' 'QÞ}ç½4ý×·iýæU¬hdKG 1ŽIff6•m Ïf¼@¯¼ð>uèÐ^:#ÈG:tÂðÓüjG Š L°:`byLP?Qg äÂò÷¾~œŠË7J»Fûn, *dàúÂUôåì—èÓ÷§°¬é iGÒɨ’§'‰¢œ´ƒú«LJ'l,¯†@:!^’/JÖòjøÀÇy\„ŽC^þà^ª¤Ę̂š BéËhÊ+Ø3Ë鹷龜y˜ÆŒ“ö3aè„t²ÍÉ€€*XnøÐÃѳoÝAUMYÞð©Áa¦ÐSs¨´r½üþ½ôâ‹/öÈ©"” '2(?½s6‘ñoCÀh\˜BظÊÃRc$ñãÇÓwÜNO¾~ Ue”6ŠNš(ƒ|ˆLÓìJzü•ùô»kéÄI'&‡dbn a2•Vçõ¥!b 'Ÿx2ÝvÛ­ôäk·P¹¯ˆ²³rDñj˜ôy¸ggçò*„ÿgï*¬*¾þí"–%–Ú¤KB”VB0HÄ$L±EÁÀúô¯‚¨`J Ò¨”(bÐݵÄvÁ7¿y{—ÇÛ÷Þ.¯ß¸;÷Ý;qæ7÷ž{ÎÌ™3gµ¢:mÚ4êÛ·¯gÈñ²ZÏ Ïz‰BŽ ¸q*ãBp¥hAÀ[`! ñÈ£4Y£G?LwôMá¡1”¥÷'ÄewšfšœÔ`Û„ì‹éôÅœÉôÒË/Ò“cÆšM–Í|ËÚóA×÷X»/×#xÇ=ÌüFmWóðö9¸çžáÔ«Ãp*U"ž2Ó3•ÏË=]º’f¼C )"2ŒÛG?ý:]í¿÷5ÝvÛíºZ¾ïJ¼½lãsƒB£‚èí´ }‚€ põˆBxõJ ‚€O €>L¡d5š¢•6lè0ê|ý`ªT¡ efdéûFÁÀU ƒ°QTm\JÇO¢ù«?£wߢöÕ{H940­C De¸ðQzô~³Bˆs\C¹£Ÿ\ÕÿR®ÿ#`ä7Æݣ¶,(C½zõ¢Qí©u×V”›}‘²³³5®z–ñ¾¨7EÍL†R‘ ôý¬…tŽ~×ûëuìØQ×­Óxí©'íÇÁ|›©sŒûÂw%‰ÿC@LFý¯O¥E‚@¾?ôCïJ?/ý™–¬û‚>õ5…\Ô‘Ù$ \ò-°@7òU,fƒÃŠÐ׳heðǤ#F´2(!p¡Ød ^ñÞFp¿Y€6 r:ü¼<«üŒöìÙSov~M»PúlÖËtæÜI=cgZ[ÈJ‰3xª˜¼ô†©A§c§ÑÔY¨ÇÀj´k×.‚2ºpð»äE°¹n?ú‰Taþ`¶ï+îO·) ‚€[B·À,•Þƒ iø¸ßØñF:pàMšü:Myg"]_«Õ®S[i„A”•m%6  Æ B—©í¨74T)6Ah÷®Ê“è7tÏð¡4nì·TµjUÂÒjزe 9sÆŒEFF:uŠ–-[F‡¦ôôt-af¥S§Nï±,Ô*fÒnïFÀ¨DÔ­[—¾ûv6Íšõ= É['Ü«ýý oÂD9Ù9æÙp´Ê1¾s‰Ç M‚C‚©HÑ‹tâÄ1š¿æ\¦ï¾ûVíïÙK):¡š¯2ŸÌ7öìÙ£y ›‰">zô¨VÞccc {¡S,+À^…¥J•2çE9AÀ?…Ð?úQZ!Ò `ãú7'¿M=oîM¯¼ú"M=®-wÕmZ›"¢"”i—i*6_´¬B›Öù,÷QGÑ¢AZ˜ .JYÙôï–hÞ9Ô¸i}Z°`¾Rj:›÷ÚCz€-Ôýû÷fR,èQ¦uŸ|}ذazÃzþ-± àÍð{ç<22Rm|>„ÚµkOsæÎ¦Ñ£Ö¤7©¨ÌIëW£È¨h"¥ææª=9q`ÏTÅ[”ÚwY¹Lð¼;à1H•’šB›ÛA'-Ðé?úèÿ”©jo*[¶¬þÍïÚe…àV;F­Zµº3f\q«®¸!AÀç…Ðç»P VÀ afTÍ›7§Õ«WѧS?¡¯¾CM7ÔëIå+•£ðˆH½îr„5.@R3 jE‚LæŽEÕ@­$*A3\‡¢å›—©²ŽP·îiî›s´0ˆÙ-ÐL¦¢À¢K—.t÷ÝwÓÔ©S©~ýú”’’¢gp!ÌÂŒ+::šþúë/zä‘GtŸ v@M‚¯ Å |ƒ¢FM·ôéK¿ü²†fÏùžf~û¦nJqjMMU¥Ø¸’jF/\Íø±©´Éz[GàÙÇþœié©túÄZµe§Ò#×ëüƒ úÌ¢-ZR¹råô5¤Ï ô™A~VX¡nÙ²%=þøã4iÒ$jذ!%''ëÂbÅŠi~S¢D Ú´i-Y²„p.<‡”Xð/Š(Æ|ù°›µOZ# ÀB K™™™´}ûvZ½fÍŸ?ÿôs^)UéªNê—¢ÈèÂ7(“Ð`R”|.…Žn9CÛi·J¿Cçi×¾­¡oÛæª][Í:*Å4LôÅþÂÖ/¿üBmÚ´¡ÄÄDÚ·oŸ‘ *h³Ñ±cÇÒ„ ´p‹~üÌɉB€Ÿ‰¯¿þšú÷ï¯1™7oÝtÓM^%Ì[¾ÿà9xÞÿùçoúsó´nÝ:Z¶t…C}Úµ[g=˜Õ¸QªS§.%$$˜×ÝZò6‡ DÌs0Ètíµ×RÅŠéÀã“Ѥ¤$mž>gÎŠŠŠ2?_œFâÀE€ŸŸ#G޾OwÜq‡ЄlÀ¼(pò­–Ë ¡oõ—P+¸(8Xx ÓÙc3û»ïª…„={wÓömÛèࡃZhÛ{à >u–Î&%Svn•-«fµP·‡ëÓðJ¨VÍZjm`5-dÀí<Yùäë³b׬Y34h}ùå—T¾|yÂÇÖˆ„|¤åq ã&í÷Mø™ÆsŒžS³fM}ôíÛΟ?¯Ó§Oi¥$'7Wó'¤Åó¢ÖºÅªõl¥bKéµ´˜=7.õð»e¼/ç—,ÀçÇŒC¯¿þº^BpðàA Ö B!„É:”Aá9òÔþ‹€(„þÛ·Ò2A À@pÂÁŠ!Î!° Ö­kw%¨u:)Ézfð»ï¾Ó&ލhÒo6¾R›Ì]V·±</K ?ÌJ<°Þwß}Z!äÙTŒ¾BHƒiW½zõ4Z"äÊCã0?`6áLq`ÖÊÑÀJ Þ .×Ѽ˜8±’‡A((„0Ãv0ݹs§^_‹á9ø”H›Àuç(=,í Tˆ!¨AhÀó¢j³ijPy ªÀ9Fù± ecy… '`²°À…µœ÷Þ{/íÞ½›âãã5öaàÀZXC_pÚ€Gê×0`EÎÈC˜ÿä#-òâwÃñG…±ÂšågŸ}V¯„μýÉ'ŸÔçÂsÇTR ¾ˆ€(„¾ØkB³ àFŒ‚Î!|ñFéMæÀç¸o™‡ÓHlàÁ û“(̶°åĈ#´/®!AÀŸ0òVôò‹å}(ü“À<%Ü~ûíº l=±wï^½–¹mÛ¶úš`¬a?‚€ß" ¡ßv­4Lp  X8à5Ï]S³ï– %Ùò@kŒ×¸uŒcãÆéÐûáLº CGäå`,ÇÚ9§“Xk|Àx (ðoæ9uêÔ¡§žzа7!¼c¦g9=î9rŽtAÀ7…Ð7úI¨|.|†`7 L,Tk¼Ædà„-ŒÒc-!ÂèÑ£©iÓ¦ú^]Žƒ±kçœNbA@ ¬ñã5 `ü žƒpçwêë±¢8½£ç:“üŸ@@œÊøD7 ‘‚€ï Á‚ƒÀƒGØ ‚ 0„™¡C‡Ò-·Ü¢ø°Ã‡‚”ƒúY‰,H>I+¾‰øMaø0òÔ¨QC;¯Âž„X+Î Aå@‰”ï@AP“´‚€g…гøKí‚€ à§°@vâÄ zúé§)55U{ð36i¬ Nêà¤gõêÕz á‡~¨ósZ.ßXÎב¶zõê4nÜ8ÙCÌ(ù-øüî¯X±‚Þ~ûm­ÐaXæh.§á¦ƒç`ÿ¸µk×Òš5khÑ¢E„ý!y†Ð˜–ó[ưlÀèW^yE{ˆE™œß2­üïA@Bïé ¡Dð ŒÂ‡_4¨`á);;›>ù䂆í#  Ù8âü¹çžÓ3Œ¬òÖóp>¤Å–›6m¢Ûn»à)P‚ ø7Ìs°wiJJ Mž >A¬ˆd ›?ÇÆÆº¡ÆKU@ùLHH°©€^J-g‚€ à`À [×ÀüÓÝ¡|ùòzCæ{î®_êÂ! aáp“\‚€ ôôt¾0kr T‘JŒÑz˜n¡N˜|AI— Þ÷´´4ÝXðwÔ‰™EÔ{öìYwT)u‚€…Љ`JQ‚€ pÉ„H°¸6Eì®ÑsÔ#Êàåý ¿@@ÀÈoÜÑ^æ3\¯;ê”:AÀyȶÎÃRJ… †uë¸ÈUA@A@ð ¢zw©UA@A@# ¡Ç»@A@AÀ5ˆU‚kp•RB@BêMi‹ ‚€ ܵfÙP¥œ ‚€! Ne|¬Ã„\A@ð<q·7êÎ÷Ýåå/?TP¿‘{Â!îÛK“_]r]\ƒ€#|„ßsŽ]C‰c¥2½ …ÏóË)<'?däº à>D!tÖR“ øp "À`ƒxOÐY¦L½I=h€[xG·Ñ‘´’F\@A¼w‚çuÜý>‡……Qdd¤ExŽëŸ ©Ap¢:E)C¬vìØA{öìÑ ®Y ¸J‡Ö·óKg-¯³®adþßÿ¥… RTTaÄüH¤¯W¯U¨PAÏ*…ÊüòÉuA@p Ìo²³³iÆ ”ššj³"¤‡2¸dɳB† î|Aö>ýé§Ÿ¨jÕªú<¿úq<)!!j×®-<ÇfïÊMAÀµˆBèZ|¥tA@ðC–.]J=ô 4ˆ’““­ \v …„„Ј#´r(òŽ\Stt4Ý~ûí4sæL½I=f@“µ…ñ«¯¾Ò‚œ(„Ö’k‚€g€‚Õ¾}{jРU«V-_% ¼3rHß·o_L=ö˜„‚²Ç|Ð’𡘘úòË/éµ×^Ó ¡eù-îC@B÷a-5 ‚€Ÿ €™4(O Ð#Üö”<?l:e/­3 bó²k®¹FÓi¯N¦¯fÍš6gA›”!CïoÛ¶miÚ´iT±bE»<ï³½w¾`ØOÍõ•(Q‚&Nœh·~æ9ýúõ£­[·Ú¯@R‚€K…Ð¥ðJá‚€ àðÈ7ÚÆŠž7¶BZA郠&A¼ ŒŒ ó»\ÐwÚÝ-)}àQ¶LÙÝM»Ô'*¢jÏK»Aàª`ÏyˆytÜVޤ±•¿°÷Qð³ŠÜ¦ÂÖ%ùAÀuðûé<m€âèr¢R² 0¢2 ‚€Sð”âãâ Yˆ7·Ù›i+$Ü’Mx¼ù½v„6GÒ|' ‚€éݶT%2âë¼^–‚§óð”’AÀ6Âslã#wE@BíYi— ø4Ì0Š.#é>ÝB¼ àÕX81ϱ¼îÕâAઅðª!”AÀˆ€(0F4;ÇzÀ³Óš7Ò¼yót!¼~?†þ­ÉA@ì ÞÁüüšÏÁw° ÄÎ;õ@_GqF~c§x¹->ˆ€(„>ØiB² àÍ@pà8À ] ˜á1{éÛ¾};}þùçº0ÞJ?†þ-¸k˜ä Ø@€yŒ‘Ÿð9øÎàÁƒéСCº\g¾bä76Š—[‚€ ࣈBè£'d ‚€ï#ÀÂÙ¶mÛèå—_ÖÝO™2…NŸ>M‹-ÒÊ`ZZ½ð ôÍ7ߘŒæ1Š?}útjÑ¢9rD+ˆÆ}sb9A@!Àü&''Go?jÔ(ºóÎ;iÅŠtêÔ)½A<ö.5k–æ9ÿý·æ+Ôügß¾}4pà@š†”7ŒºC¬]»6•.]šžxâ :yò$-]º” ˜%$$PHH5lØPŸ£‰È·xñbªQ£>|XçA9ëòG+|úé§Ô£GêÝ»7Ý}÷Ý4uêT:zô(5mÚ”víÚE+V¤&MšP±bÅtn JÝqÇ4|øpºñÆ5?’½­+—F@¶ðáÎÒAÀw`3-gãÆ£ûî»O7æÚk¯¥””ŠŽŽ¦îÝ»Óòå˵àÆ-E¾àà`­"ýçß ‚€ À0À€Óý÷ßOþù'× ´jÕJ›©×¯_Ÿš7oNmÛ¶¥–-[rV ×çÏ=÷µnÝÚ|]NAÀBÿéKi‰ øÎÒÓÓ髯¾¢:èßÙÙÙ:†2ˆ€û|Íhš””D;vÔiø¾Ìj8ä XA€yÎôÝÄÄDÃ|VhÊÌÌÔ¦ à;¸gŒëÕ«§Ëì †Aþ~…€(„~ÕÒAÀóˆbR°>€0ÆNd€7Vþ ¤ñL¢±T¤gEóï˹ ¶`¾Â<iÁKpäÇÙçp^[åË=A@ð-D!ô­þj¯G€G¢½žPÈWDDuëÖ6mÚ¤©)(î±àÅë qÓ(„ñˆ¾› U ‚€"¯©ÆšAVy ëÁ_À˜_q¬/ÊA@ð+D!ô«î”Æ‚€¯ ÀØ€ôzÀ… ÒÞ½{ k Ù£hÉ’%éÃ?Ô ãŽ;Ì3‡ðö—••å+M:AÀð2W¾|yzøá‡iذa/¢8{ì1Ú³gVË•+G ,ÐήഠMGe°ÏÃ(Õ .D@B‚+E ‚€ <ãØbâ»ï¾£wÞyG b¸†Ð¦MíâýÍ7ߤիW›g ûõëGÜ$‚€ àPY¡ƒs˜ž={Ò|@ï¿ÿ>am <‹"¼ñÆzíò„ èØ±cú,ÆŽK¡¡¡ú·üÿC@¼Œú_ŸJ‹AÀG€€A­k×®ú0’{111ôè£/ësã5V,¯H$A@0 ÀJa‰%h̘1†;¦SðxÅÀ\«T©½úê«|ÉlBj¾ '‚€ àóˆBèó]( _E€4Ðs`ÖîéòGB"`¯XòæE¨Âx^È*%› ø¢ú@' ‰‚€/!(„³ÚiYŽñ·ñÜÏ€»ësG›¤AÀ_à÷“ã¶Ë2¿ñ·ñ¼°åK>A@ð=D!ô½>НF£Í T°SŽíu 0q7.è ý·EÒÀÛ £m±U–Ü× Àûÿ!vÄTÜ‘4® Ô>‚4 Ï‘´® QÊË…ðr<ä— pU8"|_U’Ùk€£vÍîax>Ü¥r]©ÛäH[$ ¸𙨨(]aAxò{e@IDATŽû(¼T“#Š(§‰ŒŒ¼”QÎAÀcˆBè1è¥bD  ·/·ŸÛɱ/·¥0´c6mÕªU”˜˜H©©©vGëáΠp±¢V˜zÍÃuœ9s†þûï? ×ò ¸N+W®¤ ä—L® ‚€ÀûyêÔ)Z±b…öŠ-glñ^¤/]º4ÕªUËmÔ¢NД™™I[¶lѱ­Ê13|´X±b¶’Ê=A@p¢ºd©"0À^M¼_“¿·˜ˆ@Ý .Ú?þøc½ED~£°°0Ú¿?aÁC‡Q… ܪb¿ÂÖ­[Ó 7Ü`×< Jë¹sç(66Öß_iŸ àS`6 žˆáýƒQà-Ö®Ã31ö„'âÉ“'ëd¸nK´VVA¯qÉÉÉÔ¬Y3­6jÔˆ222ò­<çèÑ£4~üø‚V'éAÀɈBèd@¥¸ÀC€×Blܸ‘FŽIuêÔ¡ììì|?‚þ€>þL°‘zåÊ•éÀþÐ,»m`¡ªGÔ­[7›3ƒx. B)«]»¶Í´v+.d’5kÖ¤¹sçêQx Xp¬ šÑ¯lÒe-\×#Àï*, ^yåýîò5kµ3Ï™>}ºž¥ã4¶òpš«¹ÄñññÚÚ Fz¦Ð/áï'êç2®–É/G@‚c&9«¤¥¥Ñ¦M›ôa5Ÿ^,[¶¬n”Ä@ !!!7£à%÷D@Ÿœ={Vo( ¡ ´HßBŠ— XcˆAIOÌú1¯)Ýž¢WêåA@ç À3+żxñâ~íA m„ЧÇ/sç îÙRì)À¸% #àžLb{£ðöî{º-R¿ ˆØã7ÀÄÈs<ý³GTGxh÷4½ A‚ ȈBȽ/mw X/öí·ßê5ééén5½ƒ@`ïÃêHš‚ƒ>{À³eTÐr½9½#8ƒ~{éÜÙFÐâMô¸³íR— àË8òÞ:¢4º #½ÆswÕ/õ‚@Á…°`xIjAÀ!à˜‹ûqHA@A@¼Q½µg„.ŸF€½:º°O7Ö@¼ŒÀSA@¼WX…xA³„A@p"¢:L)J`X1BÌç|ObA@AÀ]È7È]HK=‚€ï"PÔwIÊA@A@l!àŽµ…î¨ÃVåž \¢^~’[›xZP’Ù›Ý#7AÀ ŸqˆR„ àAD!ô øRµ ø?,(ao0wxaå:°_"1 þ¶´ÐÓÒžGï?ïjÜñþsàs¥J•ò<B YCX ¸$± C€÷|ä‘G¨L™2”••eW@ƒp…|ØÔûY" 6™gÁËP ÂÃÃióæÍ´sçNâ=Álå‘{þ€#χ´TZ‘ào¼ñ†~ïÁ7 °Ÿn‰%ôÀøÆ™3gôþ†öÊÏÁxÔéÓ§Êc¯L¹/îC@B÷a-5 ‚@!À‚9FËÿýwÊÎÎvx“zWP¡Ì 0@£6xð`1b±[G ìׯ½ù曡“3MŽä•4‚€ à[ðûݾ}{Z»v­VÊ1F:Ìî;wŽºté¢^«V-ú裴¢çh9˜|衇¨lÙ²º ¦É·jÀC@ÂÀësi± ¸B¢"##©I“&…ª1>>Þœ¯qãÆÔ¬Y3óo9AÀˆóœ *ŽÂ \A!\¼x1A±lÛ¶maŠÑyÀÿD!,4|’Qp+¢ºn©L Ð]gl`ò…Ñú´´4¾¤M±PNAËB¼¦Ð\˜œ‚€_"PXž¾sÑÔÔT}🌌 m~óÑ‚(wH[ô~ÙÒ(AÀ‡…Ї:KHßCàj#hx-”;o"l12 ‚€ÂðhaÞbä3Æëƺä\üñ2êý(­?G ! m~ÞTiž x°Tžã!$n@@B7€,U‚€ Px¤y333E8+ ˆ’G …€(ƒ…‚M2 >‰€(„>ÙmB´ Ƶ˜!Ĉ½A@\‰DÉ ¡+Q–²ïB@Bïê¡F3,˜á¼ÿɈ½9# üÆÅKñ‚€! ¡u†"‚€ã !B ‚€ ¸ ™!tÒR àyD!ô|‚€ XEÀR!”{«0ÉEA@p¢ºT)RðRD!ôÒŽ²A@€BX¶lY DNNŽ˜ŒÊ#!NGM<ØÄ1*1*„Æ4N'@ # ¡Ç»@AàF! k‹/®obchÜC0¦Ñä …D|†­‚ƒƒÍû¢8Þ ÷kš Y•d/E@6¦÷ÒŽ²A 0`Á ­ŠŠ2+„øÈ,¤éòGB"€Á¥ŒŒ Ê`JJ að ž“““µ"È×ÂÃÃ…ÿhtä à_ˆBè_ý)­ñ FS’!Uû xv0úlÛ¶móPŒÌ'%%Qzzºnщ'hõêÕ©¯á~óæÍ Bç÷Á¦ É‚€ à˜g@!œ8q¢æ-åÊ•£ÔÔTú믿4E¿ýöÝu×]šç8p€nºé&?~¼¾Çù=@ºT).@@B€*E&bN˜ýîŒVó³J³gϦ^xá²b13á¬]»væë|ðµjÕJ”A3"r"Ž"žeJ:t _|ñ²¬°TÀ€Ô¢E‹Ì×§L™¢ªÏhÉ`N '‚€ à³ÈBŸí:!\ü YÆ ÓqåÊ•©R¥JZðÂ,!·jÕªQ™2e(!!n½õV#õA@((<…¥x@g¯R¥ŠŽ™U­ZUÿ~øá‡©Y³fúœóéòGüQý¢¥‚€ àë`ÄÊ”Àwß}—`¢¦Gñ!€ÁŒ1LGŸzê)Š‹‹Óée¤Þ×{^è<ƒø Ï2DÁŠ +}¼vpÀ€zp ÷ùžg¨–ZAÀˆBè T¥LA@ ÏöõéÓGçÞµkÁ‰®ÃœôàÁƒúz·nÝtÌé Q•dAÀ¬Ü5mÚ”î¿ÿ~Ú¿?U¨PAóÄø}ï½÷R£F4Z¢ ÊC#ø'¢úg¿J«AÀàYBbÓ¦MÓ-(_¾¼9ÎÌÌÔ³‡˜E„2(³ƒ>ØÉB² àEg ‡ª) Ñ1,p&ë2;¨á?‚€_" ¡_v«4J|žõëÞ½;U¬X‘öìÙC¥J•¢S§Né&õèÑCÇœÎWÛ)t ‚€w À³~7Ö³ûöí£ZµjiÞƒ5͸ŽÀ鼃j¡Bœ‰€(„ÎDSÊAà*À¬FâK—.M“&MÒ¥Á‰ ö{ýõ× Ndvð*A–ì‚€ `F€g ±• ;µ‚©:~cÆPfÍpɉ à—ȶ~Ù­Ò(A@°‡”*oecº:wîLÕ«W§?þøC7§gÏž:†£o5õVºì=rß½ð3αµÚyFŠcki|é”*oè ¬1b½÷Þ{z¶°I“&f’½‘~<þò|˜–AÀˆBèÐ¥JAÀ›°%Nüآ; H”,Y’>úè#½OØ[o½E5kÖÔÖóH| ¼sÌkŒïž#ü…óóùRÛ™V_,Ál +„ˆáÌ Á›ig~Î8K,G@¤Š‚c&9ŸE€+4€…+{™µ<> @áh3Öä9sÆk›Ü!œEGGkãããõVØ“Ð[…3pˆ# «×>Vn'Œg•ðÌy f¹ÓÒÒèܹszK<ïD8Šèç;""‚Š+®¼ì†™y•‰ø‹Ê|Ñ7tŒ`£½‡&8†òÖ€>€ÙhVV ãRµ ¡ô£´B¸|Àq:%G¥ýöÓŽÛéð¡C´cçv:tè°úðç˜èUIAÁE©DÉT·v]ª <]V«v U­RU{½Ä‡˜0cWàå0 þÙgŸÑÝwß­GÄYpõF²Ycܽ‰FÐ á­·ÞêM¤ -@€ŸU~ŸÀoÖ¯_G?Îû¦~öyE•¨^±TNñ•[:4¤5 ¬æÕFé/誌´ :õß9Z3w%‘ɹR­:Õé®;‡RÇ7R½zõô(ÐCÐKŸ~ú)%&&jEËßgЉ€~ôVúÐß„êÝ»·Þ'ÑD±ü«A@«AOò ^ˆ>ä8XÄ/Få×­_K‹/¢o¿™•GuÅRSª^µ Å”¨F1¡a*OÊ{²3³èüîtú~Þ_´‡¾Wé÷é Ø’¥)GñšÌŒ¼5ÊJ³ƒY¨½w iòôCs3¡(ÂÚ×´5lÚ4­G®ßLmÚ´¡_zzp¤2Ë,©'P‡½zÌ…»éDxŽs€f½­Ó:)Eð ²1½gp—Z§!ÀÊ„²ÐãONÏ>óµïØ’þù糂ʊC«‘ä‚€ ¢\—Kƒý £ÙÑ’%K(!!Þœ<Ÿ†ôCõê7PÓEeꙩfìrM™¶Í2™fÓˆ;„2xŸ1)‡YOCû>ME“¨e‹–4eÊ;Ú<TQ ޲ä|VSRRiÌ“«MÌï£;º¦êÕj+E0˼U‰_8»E—fÿ Rnˆœ*z:‘êׯO+V¬0ÏŠRèlì¥R¢ ?àwÓxÝ™çÌ{  >òè(úäÊ[oßq”›YD[!˜ê/ìÀSÁ(E]°^HOË¥&×5£r•ËP‡d:uÒæëîÀ¤`TKjA@¼ ™!ô®þj‡` ÊØ«¯NÐÊà€›¡r¥+k“N˜T¹Z(dBu=JöþZÁÃih¿ñôÕ—?Q¿Û{Ó±cÇÌŽ8½Ä‚€ à:\ýÞC¹Â¶°yjüX­ âÏN'µ¶8Çm|Lj ÚŒæìñ¥¨W‡{¨sçδfÍ}4{CpußxC…A@ðMD!ôÍ~ª½w}ìY ƒËö×&¾FãÇ?Mwö£”±mÆi¢Ã=£óÆn@½¹j¤>SÔë7’V.?K7÷êFǥДœ >Š€q¦íý÷ߣw§¼§fŸ¢¬´ jÿ@“Yº'›ÏÈPTc‹ÅS§æ©mÛ¶j¿Õ^üE1õdIÝ‚€ àˆBèý"T v˜:í3åHáY¥ >I²ƒ=6:o$J!f'3R3éž~·ÐïŽÑƒݯ¶ÀHó¡ÌH¯œ ‚€ã°Bƒ5z=ö8 ìù˜ò"ªNy2È­ºÂÊ«Qµ¡‘£Ô[ð`V“éç´¾;›n”ÇGa1àüΦ­°ôH>A@¸:D!¼:ü$· àV`* gõêÕtϰ{莣ébN°ÚÜ3¦ZÖ¯•Â"•c‰L5SxÍž5—>øð}Ô—…24ÂúÀ–ÄiÎò°–ϘÆÚ}k[^³¬“˱¼n¬‹Ï9­eû¬Ý·¬W~xðþbÆ¿{ŸÔñºR$Rñžl˜‰ÚB3…i™Ô±ï ´ä§eôÕW_ÚJîµ÷øÝ4 ´åoöÊé,by¿Ñ(\³LgYŽñw~e \GÊ2¦1žs~”ey].AÀeˆBè2h¥`AÀ¹°@väȺᆴ@\LyóóBL¹—ǾaYé¹Ú%ü˜'ÆÐÊ•+5 ¾ú¡Ý¢ ç' Ó åaÌÇ8Óï;òô  ku¢üŒå[;GZc9¸Æi¹,¦×º$ÿ!€çaÚçS)ãQbåk”‰:Wy§þ““yúu¡ÖXßOÛ¶mÓï-+.ÞÞCü>‚N~—ó{ù¶l“ñ:—‡~ÄyjjªRæ/yŸÎ¯lc™L? éééz_HæHk¬Ó˜×xnLc<çü(סÉX®œ ‚@á/£…ÇNr nCFþòéÿt½U®¡4µ· åÕmDÙ©taMaXHµmx=ðÐPúuÍïjãèX³òa§¯¹ üÑ@¤R¥J郯ƒP>‡s¤¤$sq# L+VŒ¢££µ€Çý Ç;(¿u¹œÇVŒò¸ŒS§N rrr¨\¹r¯ï%''Nø™Áï   ]'âóçÏëô8çP¼xqŠˆˆÐ?¹|OâÀ@€Ÿ5¬Çûä8º­ë(ÊÊ4ígê­à‡“›âѱŠÄª4}ú4š0á5ý.xûsÌôAaûì³Ï´EÈã?N 64óÆi¿ýö[úóÏ?µ‚ÇJb¼ÇãÆ£¸¸8ý΃7-Z´ˆ¾ÿþ{š1cuïÞ D={ö¤¨¨¨+Êæ:ó3€5š0ž7o-_¾\ó¬Ž;RŸ>}ô¶Go¿ý6íÙ³‡"##uö’%KjºÎž=KÁÁÁtúôiêÚµ+Ýzë­„kŸ|ò‰ŽÁ[A3x+<›7oNaaa6i2Ò'ç‚€ puxçÐÞÕµIr ~‡>úØtù¹gŸWû}=L™éjÃg÷ûŽ)¶ʲ2³èškjжÿöÒ?þP üÞ–ø»ï¾£š5kªõSi¯ªh÷ Ç–*T¨ •2(f|àÚO?ý¤›---&Nœ¨ï7kÖŒš6mª·™3gÚm6 gçÎSû>N¡Ò¥Kka±I“&ºîh®÷´”/_žp0M8/[¶¬ÈPé›o¾I+V4ßGº-ZÐüùóíÎRØ%Zø,< `zC)&ª„Þ½ÇL=_`ÌÊÌV ìÍÊóDÚ¹s§NÊïj¾ù<|ƒéÛ¿¿Úßñ^úòË/é믿ÖT1Ïá4PòÞzë-ÍKÊ”)£ß]Âû˃;P¶Àon¹åÍ+æÎK]ºt!ð ,A@à2õÃ\Ç3nÔ¨QÔ­[7Í}ôQ]ÆW_}¥xü5ô÷ßk¥ (ꎉ‰¡§Ÿ~šÞ}÷]ÍgÀ§ÀXYÄ@ÖOçzÍÔ ×…6Nš4‰>þøc]ðàÊaĈj°ïG Õ3˜ýCÀ¶HLÿxòÉ'9¹9feuذadä-hïàÁƒõ>’˜}dÌåDœŽ€(„N‡T TðÑrE`l÷îÝz”ûÖÎyæZÞ¯ j8 ddgç¨ã ú÷o¿ýª>þ}\ö‘G}è ¬H_M¿°0 ‚=/˜ „ —¥³(„5jèQrkuC¹|衇´Â†Qz0»BÙ\&Ç|ßÃL Ê Ì¶Ú·oo¾•˜˜H?ÿü3>|X Š9T®\YŸÖ®]û ÓT0;hè’Üì Ô®Á@5Ðñ5 4D›m;'”ƒùŒ3C¦ ƒ5˜Ãì”B({›7oÖ ¡¡iúƒM°8€%Ë’Ї" R!deeiNô'׫þðu ,½òÊ+ôᇚ•A®<3Hu¢ ¸‚™)ßCŒ|¬ â7ÌE@”ÊV­Zéß0=…Bh‹ê„òG®1½j¥AÀµðÇpíÚßtE11ÅÕU9ðv{ÑË`QBÂ…"T·xwúqÞ\5ãÎ 0‹Â¬0ƒ@‚ò!|\M`aoñâÅtã7R›6m¦R˜!„b‡ºŒíàsCÖ@¸†40§‚†ûˆA?÷·¾hñùpkaJvß}÷ió*$C^®koªV­jn?î!°`Æ´q}SýAùHá’ËÂL!ÌY!Œ2­œ^bïCÀÖóSjùù_»n­ÊÞ„ŠQ3×ê_Ah .ª”%rX dÁ#hQu 6Ÿç¥QÙô=Ë<:=n: Ïy…JåiåŠ5´oß>‹ßSаš„ßE˜¡bPï°B[ñî¶|·rÁË®»î:JLLÔ|&ë(›y†öA‘€îAAƒÂeL“LX7ÀŠéqy¹-\ž1æçiãÆú24äC~ÜG~Ð…€ßœÇxn¼Ç÷uõt#€&„3gÎèØ¨¬ê òG\†€(„.ƒV 4,?rÎh?¨!¤/\4ŸšTê¥+“à^ÐòYز®PŽú†_vËÆ=Ë<œÞ˜ÎÖ9Òç*3³ºÍ«ÓÔϦÓÁCur~låuôÊ‚´aÃúâ‹/´Ðƒ>a¡ˆGËC:¦ Ö´ :T—סC] FÍéô¼?,Ü@8 | L8jyýõ×µùFÜ!±g¤iß;pà€®¦U(×Yäö"·×8æg±ñy@7”@¤Å³ƒ>ø VbQçÁ¹ÿEÏ>–/_Fץܜ‚ °„„)åá?N)g3”B©ÞÉ (&ž““•KY9”­œÔà€³š ðìBé¸Hj­ôEõA?¯JqÌH˦ì,GÚ(MÕ®Ëغm«Žõ&¨K—.¥Î;Ó¯¿þªgÅðÞ€V¼—Œ¡£ï §ÃfóªT©¢I…EÁ4efŽu…\.b¼¯{÷î%(m›6mÒǺuë´ð¤pàòÞ{ïig2Pb¡pâ>êD ™(}ÜŽ?þøC×ËJó\Dão°àø ƒTPT·lÙ¢Má±&‘ÍR“)IA €ˆBX@À$¹ àNðFÀú™3¾¥*µ*hý²U£ò!¡ÊŒH E¢0J‚Ù3“@†‘{ç„2ý±Ç B^yHW°`ò8£³íع£`Ù Bœ·À»Ì*1 Ç <(Î!ƒñ‡‚ÏwXg‡§-†8­¾ þ¼üòË4fÌ} >\Ìó=ÄÔë~`Ï«ðüÈXh4Þ°ÖάÎcí^~×@?Fêá`bÁ‚zÖøÝ~ûíÔ«W/=‰¼–í̯<¹îûp_cÀbÕÊ5Tªt¬žbv`¯…xlI¢ õ~Ôn\‰ªÖ)GIGÒ(õl¦š1T§2r)*&œbJDPxD(…GâÓ|Ê`hX0•Ž/®yê‚¢’žœEqêZLI“é²=pß4£ q§ íÞ½Kgm¶Únë@^ž%Ã{¾R™’·nÝšð®/[¶L›Aâ]fE ´;R'Ò@‡‰(<„²8žBÀ:ccY°6øá‡ˆSaFΠà u#`Í1x#”0Ósu(±(ÓŒ˜ù Ú‰ojq{t¦«üÃta  ¦èàÙ0Ç÷S ìù¢Gê«„D² C@Öz z©XpV¢•B•iÛ¬K…Âe/=9“ŽmI¦Òµ¢)<:”üy†"JS\¥h¥ æRÒÞd5¨Öù¥ÁtH OJv -BekÄPNÖ:w\9 /J1qáJ‘ Ò£üÉÇ3)4*˜Š— ¿L0ậÅ®‚ŠB¡I¤]ʪKç.漸a±£ÁZZ˜i›~°VB Ì0àk{àä#÷PÌ,éÁ54¨¶›@@;ùÚ„ ´Ã”G ˜€`Çîåa††Ãˆ 'hLŸþá„?ÀõA…G[8ÑÁÌÚº]§È–"\Œ¶FA §‹JQSO±þmëÌ:ÏO£‘¯ö¡öZ¨Yq5¤²9tœ>|k&-yóoºÿCµýÀÀîú:#ð|…‡‡Ñº_þ¤Îm'Ò¢S˶Mèû™‹è“WPhD0==å.º¾UcZµ|==?t*%4*¥ø”}'7(»‰zÿ·©BX\ØÛÒÀ¼ëtá)³AƒÚ#(¼‚ÂqËý÷߯‘ðþcæ4Ø ÌÙò<‹C•*UôšB8uÁ:cžñØÊáž{î¡7Þxã2žÊÊ$èDÙxw¡¢|¬‡Æ@”X luêÔI›¢ƒÿ ö*/b‡U'Oždrœ3Æ<»é¦›h×®]šŸB!f3R§T$…‚€]D!´ ‘$<Àû5P¨².:àÐAÉlÁJLV&Z•«Çј C¨jõÊZ(9“t–æÏYA‹fn r‰%èÉéwRé2Ø«K%\ÑÙ3ÉôÒcS& tï¨Ûéà#ôÖó3èô‘º¾s-ºë¾>j<“Þzi:>v^ j!f³.SAÖÿ¬vXmÚ¾c›V< ¼!°`À±õÜ—_µ•n×! A˜ÁZ¬·C€çMŒ>c¤Š•- ÷ @a?lío›p~€õu´ ü!`ÿ/S–Š"è°P.4=íÚµ£–-[ê™9”síµ×j‡0Øâµ×^£gŸ}V;¨Áp5Ò#ú¬\ C~…Ëüî[»A3 ˜E€ÀŒÙB˜ÂaíÒ< Ê 5ÐàZJjŠne°R²Ô€Í xÌDþ›DCÆt¢Þ·vÖN¥6®ÿK=ïÁT§~ ªY7^¦eô\ùÒ£¼Ù&§Òþ=‡´¥§ÇOS ¢â%bô3[œ~Þzˆf~>\+ƒ™™´jÉF*QQÍ晓ڤIÝDºâ¥bôûŒw!?‚ûxÿóaù›¯#FÀ`œ®À‹/xìY³ô‡O`£&æw:ƒ•?<اPÓ§O×¼ƒ4À œYmß¾]ó6.÷Á“Œ¤øž1fša²Ž½1SÅï8ÌGQ?f ‘ 'öGÃu8ɂŸöSE#~À¿×Œu[;ç´uêÔÑí¿†Ã+`“Q¬Ó– îA@B÷à,µ…B€?˜'NÂCÖÀصFE0éÌÉÉÕ¦¢<=„ªT¤óç’ÕHòJTç]onKï?ºD­éK ú k©™D5ó—lr$…ð‚šÀLd\™TN m¡¡jô>ç"……‡Ð÷ö¦Š•ãé ÿÐÁí§)¶|”ÃëŠ`¶W­¸^ÂZ` ÷ÂÌŠñëÆƒ\ƒRo{ȃY7(38`>g080ƒˆµ6Šì…ßÿ]'1ºg7æÁè:̯X˜ã{FÇ-|û“éå¶B؃ìEBŒµ@½‡ç>xÜÃ}´1YlÛ€õ}prÃå¡|>ç:™{1Ó†:   úÿ÷z¦çÒ€qA˶W·Ü÷n.Âl@‡ú,J™'so©¡ó­Y¾:vIí*]Gõ;TRü‰è:*£öR5yŸÜ±m/5iö"u¬G™§s(®NÕ¬VB+’( 55u¯G½oë¬Ë›ú³hñÿýE5:”¥lµþжˆuŒ1%¢é·ÍhEƒg$ùD ¾þ8çß|Žï6 !b”‡˜˜c?O(SPàpà}†¥¿g:¡á¿W0qÇzeðÐÃy€=‹.\¨j Šv!?hF@Ùülàº1Xþ6Þãs`Š€òÀ«1c> PcÃé%ç# ¡ó1•§#EŠ(ÑüQ·WAZ#xr×yêÒ¿©VÏž9OÞ>ö­9E¨G±¥‹QÕë”À'Jè(¢¼~¾8æÚ·í8E•WÊ_.Ü—¬>ú¦šf÷²ÓôÚOC©RByÚ§FóŸ{ð]*Ôáz”„{‰¸bôçæUzÔ¿1â ¡Š•8\c×à¶kë°Ž [)àš1`Ý À<£ävP&~#FÀÈ:”üÆŒßÔ©Sµ„m¬1pP×7ß|£M´0S‡ë|@aš={6=ÿüóÚt”·tàòX3 c¸‡ü–× Ìaö€=¢ )Ça l&&&j1(µñgº,Ë6æEšüßö,0¢,̨"`Öûáš„CÀüØ8Ð÷*-LÐsSðn›<í6h\‹¦¼1š~úêwúï·CT©^,…•RæãyÅÁ<±£ØÊQ”S.—Еޠs‡3ÌÏÙ<öâ@mNº|ñoôÅS+¨V÷²”™æ˜2ˆÞÂc s׸¸2ÔNÍÊc– Ï2Þ;(s ß~Æ¿ùœ,cŒòÁ{À+`ЉB(…‡"ð˜„c¯Ñ!C†è÷éó ¼^J!,Œï(Ƈ~X—Åï=h?B;X93æÅ@˜º[ÎR²y*ÊB@Ü~üf^Ìk_xáªW¯žyP iØÙ ”Jäá2PŸ›R^ù—éežƒ=TáǸ?á•9åŠ 8 Q…¤”#¸“S„ÐÕ€YBþÀFFEPç¾×ÑôKiÕÌÿ¨LÍhŠˆQÊœúøãcŒøÄá³ô×Ê£Añ×£ÐH“Tš¡Fòû¿ÔŠ:vi¥Ï?˜4“”÷yí"K¯i,i‹6ІÐÓGÔ¡3ShŒ3Tø aÒñáäÅ´ˆÑÄXÿ‚5<ØJ!„@˜aam!b˜IY (u¤jÆŒzÓe˜kZÐó¼R1ÛÈ ! ? iŒ÷aþ ¯€0'C{‘JØ¢E‹tR˜M!à—§/äýA;Þyç=ëðöÛoëµEPŒ ŒÁ!Fýá8qàskeâèÀ $Î9 ”Tl*õ’±‡PÉøp¹û7ü,\È›)´ÙZ¥xa6®tÓhZöÓ:jÔ´.Å)g4#¢-ÌYIkn¡ ÈKWjÔ®BóŽMTÏ_¥©ÙÀ)¿¤ç5¿;­Û5¥2åJÑ}‡iâ#3¨ÚqÚ! +”6éÉ»‰÷0UYAàyîׯßeï…#ùí¥Á Û¼yót2(uPŒ&Nœ¨güñÎð€—µrð>>¼ï_ýµö®Y¥J”ß5VÌ`â‰Y<¬ñ…õ®ã€)ΠäA@Œ%ðYоƒwh¨ ƒ<0‡G07åºtæ¼?\LØÙ‚3¡à?˜Â`ÌóáLÖh·±~ ¡.kÓ~~ÎÀÿÀaÉ5àIÖh³V¦\Â! aáp“\‚€[0}(Ï;¼ÿFÔaʹqÕ6:<ä˜Úƒ«œZ÷×—®os-ý¼àWZ6û ‰PJ–ò8ª]²G„Ñ“¯ÞEÙ/dkeÍŠôöÈ…Ú) S,І¨uƒÓ?™M¿ÿ´›)ó.åž?âú¦?.RÎ¥©õ6ÕôwAòÚ)úŠÛ©‡ ƒ€µ0ØÞ3‚P`ògÖÓó,˜j!`&$(WX7„‘u¬¯Ã:¸gSQ¬‚#‚Pú03 Ï¢pž! # øà6S¨N#X!dNWž÷‡…(Œ¢óvPJÇŽ«•b˜¸B±„{yËüP’¬ h(æi ƒë@:”qóÍ7kråÊ•zÍ‘gy Qxx„ninîåŽòk>6‚/Q&’~_¾“&]ü”nÜjÕ­FÕk&Ò¨1C(*ú[5À0Û¬ðÁ{1ÌR/^4m¡‚õ~˜eä™iZ+[²T jpCeÚõ÷Q*¡<.›§.9i¾1ÞéÔ”Tª\³„6ek~×óÍhãÞæX?¸víZ½q<α% oÑ€" 8!­µÀïxħŸ~ª×+ƒg°hÌÃS¿üò‹V13¯¢ØÆ†ù•1=E¬ÿƒiè3ÏVH-Ã2¿£sçÎÕïsv ñ5¤çºØÖæa=5LGá•Jè_âôÆúôEÃn+ølÁã(öDùøb¯§HÇ´‚b?Eæ'\ßÇ #–°¹ª‘G`ÙUÎÃeH,ÎE@Bçâ)¥ NE€?••‚ZƯþÚÄt*ÓÈöïwt¢ª×T¦»µ¦¥·¬§äséÏõéèðþ“Jé)YÔ¦Fi5»eò(xòDmþ}+õ¹½3Ý6¨ý¹n8t–"‹…9ìPTA(Ûºë8uÔÁiB7XA ‚b†õ7ðP“I¬DÀ}£°Âù,cŠ6Ìò¶ù7§ƒ({ò>…æÄ†¤A^N¡‡¥²É岚OqËr‰eàº1§áŒp@ƒÁò®ÝqLjÌëЇO …?KXÛôºFt6錚a׃*êQË?¨1l;B§ö§Pht~r*SN]¯š!Œ¤zeVá}x©u¿§"(+E™I«ñ¦"j/T<£!¡ÁôÞ„oèÙ5”—ÒêtÿK7Ñ{ÿ@×´,£7µwŒ%^¤úUñ“¹µç?ÿ]y‡ócV3ëP^ à 0ÝPbøœã+KºtŠ“± ÆÿR Ó[9àÊÅ@[é@”@VX9?âüêâ2ù>ø èäA2¾Ïíãt|`ñoc ~ STË€²ÅÂ2¯üÂ!`Ý~¡peI.A@påãMMmvdS»D²ôóYtbG …¡)Ï,¥ñ~ ½‰b0ášr”–¢¼»å)÷œ¤ß¿;@[=LIÇ’uA,óE¨½O&,PÎdR±âÑt÷ˆÞtdÃy¥àqŠKõÚ<+r‘ÎÑ/Údé \8;ìÞ½[Ïœa‹ VYY*Ì(3 :ùÑi¼a¿m,0&¤M¬ ò5N“_¸Îi8Êáúù§ár,iåë¶b.ÓV¹çŸðó³¿öí;Òæõ»µGb[­ÕyÔ{žz6‹žzMí÷åºiàõôÉÒjfN©6Ó5Ke“ŽU¦ o||ÝùXºÿÅžÔºK=:¿'ÓüŒ‡©½ ?ÿñ/Z²pÎߣw{jѯ9‘Jp e/àÝÈÊ6y²¬UÓ´>×ø.ØËoí>cƒµwx‚"‡2ñîáß·–×ÖµüÞ]cÔÃô£þm-æ|œåóô¥X"Ÿ± [å0ML‡eÌí5^7ÒÊeï˹ 8™!t>¦R¢ à4ðaDÀ(jbÕJ”t:‰JÇ)Wëj]ß³VÌElI¢áÏv#8lX4w %Ô,M-Û7Tû~Ejs®½ÛPT1åQŒú×èúêT©Zi WÊ_VFý:«YèPê‡Îóõç é‰g‡+Ó­ú4dBúúÍU”ØD9LÈPNìè†$²³Mk,½çYkCA¯±õwŒ Ú†zq6pYùå·¼où;¿|Läãkùå±vóËɆü®[+×x­°ùŒeȹo"ÀïPË-é š¤žÓ¶6‚çÛØ¤ŸOSköÒ¨A£ÚTÿÚZæ<»vì£/>šO©$eg™,ªT !Ã/yü… é£ã癕OÐЪd<}õþbª× U«‘ Ö4÷¤Q½ßÖf©æÂ­œ€ ¸?q”jÖ¾†ªT©¢S9ã™FÙ0‹D`œ®¶\~Ÿu¡ùü±¬Ãòw>Ù4q4m~eà:Êp´{éòkoAê°E«ÜÇ…Ð1œ$• àøcŠu ¢7^^FƒoéªB[!_n†R†”‰f“fõõaL?î2ÚðÝj5°)e)Dí38â‰Áæ$ÔÜó¢r.™l•©ZŒ~xy#5mQWô·¦A÷ô¦ÿ6北’”™©Ú§P¹tÏ/°PvòÄqªR­2U«vNÊíË/_A¯syÎpžŸ°QÐr]™ži¾Ú:œUÎÕÒ!ùýÞ‡îÌÙ$ŠŽ,N9p0“Ï(U•¬I/?ö)µR³}U®©¨¦CŽÑò9›)8¼(ÕìRš–.Ø@çÎ'«÷TÍr)^¬”É{ÑMM+ÓüY+hÛ¿»èuÛ¨b“ÊTõ½ûú jÙ®>>u–ÊT‹&8°±‚C‚è§u?+Ç+éíhÀœñ¾  .Ëx=œä¾ .¢nßKË}yî L¶^yùUʽx£f0ã•¿†ý+][’fO[EçΤP£ëjë=¼ÎM¦µ«7ÓòY[¨jÛRtdÿi‚rXºl)%°©Y>%à)%25%23²iß®£ôÛšMtìÈI5k˜M‰béËiáë€Â"óÁüI1£ ¡láÚ5ôÒË÷˜8C(3W`8á̆œ W¿£•*U¦±ãÆÐk¯.¢a}ûPŽ=•¾¥øˆRÃhÁ´ ”²ÿWMAT¥*[½ÍS¤N=OSŸûYí{ª2¨ÿ`k¹))¶N„ò’Më–l£y7QùÅ(2&ŒBÕý#{NÓ[ŸÏ¥˜kB©lµbJ!³Õ8“Ivfö#=H:uщY‰³•ÓÑ{Œ£é% Þˆ€(„ÞØ+B“ `@€ŽFSý†µéÐÁCT±Be³KCÒ+NC‚èûÿ[CŸ[F!Ń(ët.•jAec´3FïûR÷˜6{Ç=ÆÛÊ¥„–±thÏ)z¬Ý‡]C _j†´ÀÑÌ+þ¢Ìc¹T¹C ½Ÿ!Ï$^A€¾ „2%éeå`“êíÔ¥sW}•]ëyäª xxçù]í×÷V¥¾NY]Ô¬ž º¨Àä3KE 3j•¤"µMöäà79jFùEhx0U½¾´.Á¤×™fî°mœb•,Eq7Ç(Ï£¹:ªŽÂ£C¨vÏrÚ"ilÐJKfÿJwÝ=Dm_PO'gžj+¯ÜA …0z[Úê“°@ÇOŽOƒ¢¡ Ï(ð4GÉaj” ªaòF§7«W¦YÊPvå±T¤áå ¡f« çC©v/%|é<¹Ú< B^ÕqÈ­5˜zÙ &¡,„Ö,ø îO šöôó´Pæéúma&÷oB€ß J=öø#4y’š%¼¥¯Ú:F)d—³ŽËȆ˜­”¹üx¬¬Ô Ň1À4=K™«Ú àaE‹*¯ÃÙ©tˆÖЃLÖNlX¹µ—ßU÷‰ç X:G)E…÷´ ø ‚€ÛèڥŖ QûìíÖn¹í~•ì ”@(y¬ 2ñ¸—¥„2ãt(×#ñ¸†ûäìÕÏBYVníÌZ¦„²Šn5³·Æiðt zpØk§éô¦úà&Á{pÅ3 å ýŒÁ ûï{P5ö_:}ö¸ÞËÎõ9MÌF„ÐWó&«mhžÑûƒ¢\VnRG ÁV ÆMÚ|Çkq,`¸à0bÅ}hÄÒÙõJy‚@ ! a õ¸´×'€ †b©R±4í“Y´|Ó 5œ“£M1¡tym€PB_þ8Æ<ù5oÞB“ÊtOÓͼcÐe)|xšFo©ß`…ƒ7ÖŒ§·Ðˆt¸êÝb„­\f̘As—DEƒ•’¨ÌÁ½áYÅFé;vlÕÀý÷C‰5yu>º;¶nÝJû÷ï7+×À”ùè²|Çìç—·à›\pXb…-˜vîÜI99ög‹ý(i” àdÄdÔÉ€Jq‚€«`A¦{·î4jôHšòÎ4åÜa8¥§a/AWÕZør/^¼ „pÚ»§*ä8õˆú¨_ši(|ÉÎÉ %°ÿþz“m¹•+W¦¸¸8=ëÁXsML8°€Â¿ý1†P†À1·Ù—sçÎ6æÞ·o-]º”Ö¬Y£…7ÄDÚtiví¶Ûn£ ÖÑÛo¿JÃú=Cé©YtQí=˜ßzBwb‡g6$8„Χ¢5[¾£µk×R¹r0{7Ípº“c]86l˜k®g+Û´iC7¦Zµjé= áI<ÉòógÞÃü…ãüøLzz:øàúí·ß¨uëÖÔ¡CºöÚk©FT¡BÂÆÓ¶ ,¨±Ç±e:_ø ÌR(ÝÆ6eddh¡lÏž=ôÏ?ÿкuëèË/¿ÔøôéÓ‡Z¶l©±ä}ÙŒy}¡Ñ>èS̓”róÌ3ÏÓï›6ÐçßÏ¥»úõQ<(SÏz²ß5ß £ì‹©ôýâè믿¦ë¯¿^?ÛÖÞaû-¾úŒfÏŸ}öY9r$³£/¸éI( ¥ÌÜš9ÿm%”}¤„²{uíÞöq`%?*55•Ž;¦Í”¶lÙ¢•Ù³g›”p5šs¯ÚµkkSS*0÷² ,¤áz~õXæqæo´ãüh8þ¼në®]»BÌì D4kÖLÏZ@(«Y³¦6mC[­Í¶p[­Ýsf›¤,Ûð{æJ…0‚0?pð´bÙjºë–±t!;H­ëÊVÏ;R¹ÞjA·WÕFÇO¤«§Òt55X B!0ú‡‡ÿð{Èq~ïcJJŠŒÂlüßÿ­ßGX3@1ÂL¢‘÷ÀÔÎj,¿¸ŽzŒ±þáÄ?ÜŽókx*Ù`úÉVà3TêÔ©5mÚTóÔ„„m¾o­]¨Ã^=Nlšeæ¢Úɇn‰BèC%¤z'ÌÝ©*1G}Mú7jÁ½VÉÔúÆÖ”QzµÉ< î@ h¬‚Pv2é0Í[ù©%¼ë®»tõú~žPâz Z‡QÈ@^kJ Ò`ÔþðáÃÚ| ³e˜5ƒIÌLaîA­ZµjT¾|y*^¼¸U2XPãþáØjâ\}ç'”effÒ‰'ôŒÄÿý§Me¡<àú!CôÈ|½zõ¨J•*Tºti«ŽbŒxåWOH—¤ND}ƒ>qµB’™÷á½xé¥è­·Þ¡>ï§Øe(3ƒùR:_1äç<$$”‚B.ÒϳVª­çM³K°Î@`,ô/ýãÈ»„4˜IïÙ±cmÞ¼™V¬XA¿üò‹„e m0c™ ÚXã+Ì{Eaß[ÆãüÊ1Z]€Ïà;µhÑ"Íwúöí«MÌaePµjUmúeµ‡˜fnÇVËE·"Àï¿(„n…Ýe•]9¤í²ª¤`A@p0I4 d×]wVPúÝÖ‹>ùîEÔóq ‰PÛE(GêŸ+? ,+'Á¡Ehõ«iWöJúñÇéæ›oÖÍEWÒà LAŸ‘FnÇ|‚˜pÝzë­Z‰‚³6÷š3gŽz \¡ýp å*11Q+Wð|h©l¢ËziS~ùŒíÀ›I¬Á g X/ ¡k&±gìØ±z”ÞškYhc<¡SÒø/x–ñŒaÔ×_ŸD×5kNúP nHƒ{v¦ðPŇ”9¶¥P_xDò”‚ žZ”Ž?B ~ù”úÜÒ“~ž°]Íd×ÐÅã¹5¾ …¯Óµ9-ß%æó}`Œüä–[nѸÂÔëá½tÙ²e4~üxÓ·@½Ó0¹4*\ÑÑÑWð´Ì²o,1³|ÿù>Ç(ix°ŒV BaÅ@Ì>øàƒZaE;ŒùQÓ‚sÜÇaÉ/qO‚ 8Q©”(¸ È`ηjÅZzï½)jÏ­ç©E­>T»nºSDíA˜§‚,gŒÔ›„2”3ÉÐ :•tœ~Xñ1õìՃ潶U{ÎÃ} Ö>ü¸çÍiæ´²€Æ1îÁQDÅŠõg÷Þ{/ÁÜ ¦¦0¿„©)œFÀf1’³(˜~UªTI+—ð>h¬uY F¸†`¬y,óqÝð¾S3¬Ç©UÀÔ ³PáQŠ-oa*Ýôuثǘ^ÎæAÚkïý©Iã&ôî{ïÐ{ï¾AÕC:Ró®)"Ö×gÌøŠz÷î£×öâݱönè„>ð‡ßiŽA2cÆ1îÁœ–80ðk xãd“L¬Gœ>}:-X°€Š+¦€`¯¦‰yTà_– êàzpÏ–ÉÉÉšÇÁ¤&æXºÊøL¯^½ÌÞSCBB®@ÞX×aIË™ä‚ ¸ 1u´Rp  2ŒˆºÃ©Œ5\™Üèì“c£ß~Ý@74ìOU«UUj Zד£·Xà½I³&ˆYÖpIÄ ¢&¡¬¨NJJ:Es–¨3Lû|ÝÚïV‚Ó_Ê,Èï·5¡Æ2-°0:héFóaöÕ­[7=Kgô,訃6ÉâÙÉõë×Óüùóµ g"…q„Ú‚¨e[ä·÷#€g}è“Q#—øŠÉé† ëéãÿýMýlºJV‘º^ߙʖ+«gö.æ^T¼è‚V<ð~@éËSÿt‘¬ .TT \(/¿j[‹ÔÔÚúÇNúëø|îƒß§Þ½úèÙm\0òAÀÿ8Â{Ð|Xð¼™bk˜ŸþÙ<@å·¾°6HÄÞ…±µ L?Ág¾ûî;=ð5pà@ÍgêÖ­KUªTqØáè>|;ðû&&£¾ÝL½(„Œ„Ä‚@!`¦èI…¤38Çþp‹—,¦×&¾Lnú›ª’rDЩ.‹)¦>ÄE)7ç‚V‘G+™é>Ü&LÏd)¹,#3öîØG¿íøN'™4é e>y›žuÂ#\F Ä,sÌ‚­eû!daœ+°sS(ÓØÊÖÀ¹Öñaö£òðú‰Í˜!Øáyƒp×®];}°RÉ[eXÖ‰ßÜß,Œql-­\óMðì¡_Ý­2ZÆ÷ƒÿþûR@–Д÷Þ¦ÃëduBºSB“òS,FͲ‡(…/8o¦ ›³+Þ¤Ö@c N²’N$Ѫ±—éF·ÿ€Ûè–>ýÔ´Vzv Qg~ïšÎ˜çpŒ&[›qÃ,-¨àQæØúk¡y€ üJ"x,0ó%ƒWØZ†ù ÁX31G½Ìgpèý ü5ð». ¡ô°(„þÑÒ "ÀLÑÓ ! €0€ƒ(†ëׯ£¹?Ì¡?øH£TœZÒuMªS©Ò±Ê” kÚ”ÉbQµH)‰9ê@ÀòA!¦õAÙÙ™tîìyÚýûÚšñ“¾sÏîtûí¨m›¶ÚôY%CCdþøpÌ}cNwOŸðØsO(|Xç·páB½VÂ\±#`í{ãcÓOGM²òª’ÈÀsæI…ЂÆg{ËÁŒúï¿·¨µ¬›hÍ/k蟿ÿ³ÓE¨{.Ô\­Mlxm#ªS»Ž$aï“Öê±S`@Ý>ç§œñ, Ö#‚áá‡Öù`Ž …0Q™˜bÝŸ£&æ:³üñ{Xö…Ð?ºZÖúG?J+ðYPÂnçÎ]Ôú±N4扱´åï¿”WÌõÚ¬ô›Ÿ>¿ µRÔŒê6© ÷ÛûÇQ:LÌ÷¯©^•:ëLO]?]­i¨¼ÙUW³Y¦½ø¸.£ðgÎ('Z8 FE™1ã븇u>8°³„¹¹¹%ñ7Þ W_}U#‰õ:X§c¬•ÇÏ‚eZù-¸~öø¹oÀö8°Þ-7w¨~¶ñ|Ã1JV–ÚÔJ¤" F줂ét\\ÁŠ¥JžB¹Æ÷ÊÕíòµòŽA?p6ÆÀή°¦›0…½±¿9pâ7—‰˜Ï9Ä‚€ à{ˆBè{}& và4À±£¼8zÞÜK cðŽyæL’vežž‘®6ßH“'½¥Ë:ì.êÑ}ŒZRV e¥ÔQšJ”(©LƒÌu‹Pf†¢À'Ü?œÑRHÃ}ŒÆcTh8ðÌ̾ŒÂ°eyœ^bAÀ“ŸKæEÆgÏ7Ì¢ ÌoϾ„Â!ü8fÞ|ÁsÒÒÒ´s¤ÂŽß0eG:äá|¸/AüQý§/¥%‚Àðœ…1$€0ųQDÕÔðu:_|¹òf…°Cû•kó¾W”Çå \Ê®€§ÐXÈâA@ÆØÂ‚Cvv¶>ÅuÁŸQ‘Ø`^ZY áØýœOžw[(þóÆ1_ƒ•Ÿ¾É)¾€€(„¾ÐKB£ p•°PÅŰb‡ß8ÇÈ0@pàsX(°,‡ÓKì|Œ˜séŽМVbAÀ[°öl{+­L,xf6q¶ €€(„ÐËÒFAÀ£bÇ|ҔϣÅEÈO À}媥JA@˜ÿÃ"AxN€tº43àCü€A@ðfX8"œysO m‚€ À<3„b•à}*­ì! ¡=„ä¾ ^‚€g^ÒB† àǰB(3„~ÜÉÒ4AÀQ-‘Ÿ‚€ x+2Cè­=#t þ”Av0#3„þÑ§Ò AÀD!t%I#‚€ ¡t‚ ø9ÆB±JðóΖæ yˆB(‚ ^Œ g Q„3/î(!Mðx†&£Âsü¤S¥‚€D!´ÜAÀ[Boé ¡CðOŒ&£²†Ð?ûXZ%XC@Bk¨È5A@¼ã ¡(„^Ò)B† àÇ`_Z™!ôãN–¦ ˆBhˆüAÀ[ó-oí¡KðÄdÔúRZ"8Š€lLï(R’N($¾"Äû …ìŸÍfœ!”>òÙnŸ@ü†g³²²d ¡Oôš)\=¢^=†R‚ p,ÄóHë ¼è‚/ÑêE°y„1õìR© à×`  ¾ˆù»…ybÜç{~ ˆ4Î.üÌ !Î9X»Î2§‘Ø;…Ð;ûE¨òq°ö!33Óë? øÐ‡……éõ">»_ÏWÄ–RÎŒ]Ë4~‚4¢ÀX>7üñ¹<7†5 2€‡0 ¥Ýn|¿øœg i¤]ð¼ð3ÁÏ2aÀ€Ëà]½*(„^ÕBŒ? P¼xqzÿý÷iÑ¢E„}ùC[¶ApsW>ÔL{÷¸8:uêTAH•´NF€ûcô þÀò‡˜¯K,ðó±ñásŽ-A€À ÓùóçõO<)))æÁA(„‡Ö 8ÏÍÍ¥øøx=€Èù%< ×$''kgΜ1ƒ€ç$))‰"""ôó~ãõãæðÉ%I#€A¦ ÎDJÕÂ… Y¤ÛʪU«–(„nCÛzEø BHƒ¢ŽmFF†9!7ÜOKKÓ×0³i¾/'‹@zúÿ³wVI/v%gI$IP1‚HÅ€Šça8Ã)"fÏœ³g>õ=õÄó¿SÏÄ™³¢@‚ "P@Å@Ž _ÿj·Ãóí²áí¾÷v«aßÌt¬þÏLMWWuõªØ@¾F²bÅŠœcþgÏ3ø Ø<8L2=üðÃrê©§Ên»í¦ƒy& ð›^½z)ç{ì±2nÜ8M+餥öŸŒDÀî9ß§K.¹D'¾wÞygY²dI¬?Œ}ú÷ï/ 4÷Þ{Oîºë.3fŒ~Ïl²*–ÙOÒ *áo2þM+ÒœG 3€9òQ}ã7dï½÷–víÚÉW_}•Ä@åý÷ß/Çwœ3ñð)‹h{Ž>üðC=z´tîÜY…¿9sæÈôéÓµÉ=zHÓ¦MU`œ1c†¼óÎ;Ò±cG½6íaYÐæu¦/öÜL:UôC‡UÁpþüù2sæL%¼gÏž²õÖ[+Ÿš5k–<õÔSÒµkWnÒ÷¶– eöì|óÍ7Ò¶mÛÍÚdðnÃÃæÍ›Ë¢E‹äý÷ß—]vÙÅŸ›Íª\öÌðíéÛ·¯NZ2ÙDˆ>3† ÏVëÖ­ý™1@ÒøèÂ4¾9NZf"€0øàƒÊ AƒtF>Óê0|Ì^ >£W~Ï aÍ@½qãÆòÈ#ÄÇl”ÁÙ´iÓbq\p ƒDXÙX¢ŸTìÞóÜŒ1B&L˜ë»™òÉ'ÂaìØ±‚%ÁÊê…ÿT:ø6ÁWÚ´i£ÚžN:ÉìÙ³cÂ`«V­dÞ¼yr 'HïÞ½#n*Ý£ë°Ý{&Ž:ê(ù׿þ¥Ï‚ŸM À_˜xºõÖ[U$>ÓÆA±W¢×V¢›í]-lÆÌ4„´òꫯʀʦÁrª&nÌ¿œš¬ôÍØ³ôÖ[oÉ^{í%ݺuÓÁ™9)b-B&>ûì3Õ"Z™J^%Àž48˜ýuéÒE¾üòK5–:uꨆð‹/¾ÐI…vØÁgì+ñóíº=;¬lÙ²¥&Õ®][ÍÒMXd°ÿî»ïÊî»ïîÏM¼JznÏŒi Í*Šñ“P˜£cnŒé1“ –¿’•1Ýöé3æV9¡™„€ àY„3Ì´?Só´™n³¯˜ü5jÔ(FLÆ U¼ð U$ÁÊÄ2ùI¥CÀžwÜQÍ™,@Ëlçað¬³Î’îÝ»k´•±<~¬œ˜–p›m¶‘ûî»OAÀD”odѱVŒàÏÂP©ìèÓ§.-a‚Òž&o»í6yŽ\;˜‹ „™qŸœÊ CÀ&G˜a¦ýý{Æ“ îLà,æÄOÔþ$rþqØa‡iyý^eüm/uì¹ÁYÌ1Ç£õáÈ‚y=âˆ#ôyñçÆñ#0h'ì»ï¾zœ;w®ò 4…Ö“ólùs£pTúã7ðž ެ¸þñÇõúÀÔ£=[zá?i€ „i}{œ8GÀ¨l˜€‡–ðøã×zfê™Ág-Ï9çœãZžÊöP¡¿öÜì´ÓNrÒI'É·ß~«f¢8“AËsæ™g N‰–·Õz–J€€i á3<ð€ö¸C‡jvúè#AH\¿~½>sÝÁ-Ÿª÷Ü“œ•×A=kÍ }ùøãuËKKvÿ¼¾Š…šžZµj ÞŽÙkÎCf"À7?Ù|ÇÆñü™mm˜ˆúé§Ÿ¤I“&1ÀL³œl:b øIÒp aÒ ôŠôDÀ> ÎÓóþD•}p÷Øc9õ”S¥zNŽ ƒä·ýå *ëñ%GÀp/y ‰KÚ{˜8µt±ÑAš9aáÔSO•zõêÅö#­<h´Á¤Ø?ý ¦ÏÐç!C·ªjµªrèÈaïÊ)²pÑݾ¤¢|?Ðäò|VÏ©.û?@ÍaËòÝ´»nmà|âÿ&OœK”'óÝ@³L`NkøðáÒ Aƒ˜&2/¥t¿à´g2xe½òüùóÕÔøé§ŸÖµËÛm·°‡%ËåÁwJ×;/í¡?Ž@FÀ>Bì1Ó®>ôÉüU`èÒ¢k|Dk†™ú­[¶ì¬l™>}šîVVBK:tšçw‡zÄ„ €”5}h^§Ï˜®ଠÕ+m€îuëÖ« fTö>–¶ÞhùøAÚ¯¿þ*ß}÷¬Y³FðÉ€lΜ9ÂÖÌÜÛ³cå¢u•öÜúÇqܸ»å™gž Uv  Ÿ7J‹où”ç¹ÿMvØv <þŸqá|Vøc “äkÎC¥)( ¥•ì!C¦ay–þ·?&ÿ{mBhvÛð·,ü•ž×äÝŸZ¡®…áOT@K¦@åS¦L‘ñãÇ«ó˜~ýú©³*ÊÜyçÊk^{í5yä‘G¤k×®rÞy牭K….ãœ{H/œC'á~ð¢ðá´ðàâã“ФWá c¾|ð|ðÁE*ã™ÒJԹ瞗ŽÄ%¦¶Mv”i_½¡aÒ+OP¡½'k×®•1Çž#L{-A®’G=úè£røá‡'u0dß<´“&M’ÿûßòüC½ý±fpûí·— È AƒÔC-[Rà”­sÔQQY|£¶mÕR÷>V:lß^Ö¬]£kìJŽ —,oÖ­]/;í¼[0?f­Zy·^víeeU‘µkÖKƒ&µu’­ìZJ\s•*Y²SïÝ¥aN[©]¯†lÈMž Ç ÖÈCÿ½W²²“ç3Ò„AxÉÅ_¬kÚY×þMð,ŠcËć»îºKدµ¬/DsxÀèxØê‹/ãשEÀÂ$ào3®6¨ ÊèyšHJÐÄŸÑ›”J½’Œ@ '˜Çˆ4‘“G!«V­L²“1+™ÝÏ`"‰U‘õërµU«e‡c^\wª@Ò«„ÁÚš•ë¤YËú)áSYa°6 ß¾Ò¾y©U·ºä2ÙWÂ÷^[³V ¹ëß÷á+§À>—$!ú}a]éŸþô'Õj^{íµrá…JÓ¦Mu ‚ƒ¯›nºI/^¬k Ï=÷\™={¶¼üò˲Ûn»ióÑúJBO¢2©¿ý°BVµZ%«W0é$ÊêqiŠ÷°¢Â5«ÖKÚÙ“¦FÒE{¿béJɪºQr×'W \»näÊâ¼ÏDnž o¶FAïÖ[o•úõëoV;<Äï:éðþN9å9ꨣ‚ ò'rþùç«÷c«×Êø1õ¸@XÊ{€‰vÔ<ü˜çØKÁ ñË/¿¨ÙNóæÍSþ1´¾}”íº”Ý÷â‚@Þs¹8 ×…Ùѵj3äÖmF&æF9dmÄêUëT㕊~2¤ÁÄsõª5’æP6änä—Þ¹¼õ|ódÃÆä ú¢¼û‰'žC=Tž|òI6lXl½N”V&qÂzþØ|ü•W^‘Ýwß]~øa±Íê£õFË—ü«™P:üðݱoOÉëó’Ž@éˆ=‹úp–¾¾’Ô@ÓL4é¿$Òë[!Dåx-·<0¡Í„AªÅq ã]Ò¨Ë Ñú¬ ÆÇh1Å´ô²Ë.Ó1ñ•W^©¼1ù<§Ž{ÒHžNy‹MU¬ ¼ Öj°a4 è Øx1æÎ+7V›j{Qì%!çÑkâ’¢us l ½¼¤\GóDÛ.(ž<…¥µŽh¾‚ê,j;ñuùuá0¼ ƒG =¨¤Ï&üξ)…ÝòÙ÷aaðòË/—#F¨0o'ýQ—s$=''G×ô 5Ä|ôÞ{ïÕ& û&F“§9Ž@f à=·w½0žC „@&‘¦OŸ®¿ÿüç?𯄗ÕEÀêG¤<Ž­øûá‡Ô23R釀 „¥¼'6cχ–€36t:t¨àe鬳ÎÒx{i8ò¢mé¥$_|HÍcug/$qVîí·ßVç¼”‹×‹ükâ( 9šf×Ñ|ÄñG°:å#º¢y-ŸÅVÞòpôP<ôÎþþö¯Ïí”9•ç!…Áï|,^ãyL@b‚…¹(ëƒ/¸àŒäûcõåÈñ–~Ýu×Éc=¦&¤£G–ÿþ÷¿Ú¤Õ_Pûï8™‰€ñ…¥K—ªÃ™(ϱ±X´g¦ùcâèŠ+®J÷îÝåóÏ?—Ã;Lî¾ûnÍn|+Z–úˆÇ‘ÝV[m¥“VW]u•îq ß9ñÄÕù 4$j;Z—Ÿ—n2ZJ¬y £‘uh¿þúkÕÚËa/¤•‰^Û¹ÕEžDq–´¼ñu“4‚½àæ–Ü®51üT–xËk}±kk7Z?ç…å#:­=®­>KãHˆÆsmíEËïÁpLBÀxûv1ûÎ$"V%øg<$?ü¥ûì³0ÁÇž”>ø :Áäæ›oV-㿆‡]s¼ä’Kô…ƒˆ-Z¨PH= .–7X^+ëGGÀÈ|ŒßÀ?Ú¶m«|3ófÍšiçH7c<€qì7Þ¨š=2¡øÀQ^˱ŒûùçŸuÍ2|‰26.ã‡2˜Šâ§2Vž¶ÿö·¿©cÆÊVF3øOJp a’à·‡š‡ŸèÃ?”6mÚÄ>®Ñ—/püãu–åúë¯WOMVžÍ„Ï<óLu@/™fVn»í6})‰£N;’wõêÕ‚7;fŽ™ÁÁNu½ ©·Ür‹æg¦æä“O–ûî»O¯àh§¸ Æ/* Ÿ.f¢Ï9ç9äCäøãîÄŸÑu>Qa˜{ì±êÑ’=°Þ|óMmÃ~Ø«7ÄhQÁˆõ+8D€n<üðF5räH9î¸ã”+kB/ôTÄïÖW?:Ž@ÅFÀø7| >¸ÓN;©ÖŽ*ã­–ÏŽhóàßì3È÷2ï½÷žn ï_¾|¹–·oGêãû0fÌå»K–,QaoCïÞ½uP7anð7M Vlô½wŽ@ÅFÀÞøçÆ?lL‡Çánݺé:bƉě`gy™¨ºôÒKUh$ xžEÙÞæ…^N8!8ª[¥eÉC=ŒcŸ}öÙ˜0HõößÁJ1'ù­½Š}GÒ¿w.&éÕ©SG?´KO=õ”~Üí°—„™à=z¨ºÁŠ÷óÏ?¯³5_~ù¥RÂℾwß}W¯íEáåÃm9u3û±ÛŽx~5j”®'aÀ€K`̉ÈÏ Ì™öŒa_*²eËTˆE`Ä,€Ùg „ú³óÎ;ëÚ <ð@!¿ 6Èß²eKe¸8GȃÖþýûë`Åh$ß 7Ü ×\sôéÓGöÚk/¥ º”atº'¦mèAH%à=_|ñ…ÆãÓ ÿqG ÍØÜŽ$8ã‰v„_ÃãqöÂä_T0dÆ`м“É2 ð?¾=ôjøøÆ0sO~fô9Âwq&ä“y¬ç¡œµ@ÊD$ßâŒ×Z~tÌB fÍšJ0að³ «^ãawÐNb“H˜uþë_ÿÒkË ï€W0F#O x{⩘ñeß¾}cüŠñ"×lƒ3|øðXYøŠñödeB oÇѺõÂR†€›Œ–zFìî¸ãæ–œàÔ…Y3 0`€Ô­[Wgi.Î°Ñæ%züñÇuá¿½Ä¨ß K^NûpsDØÄÝ8t ÐöÞ{٠^BöŽaF‡Ye ÖÂØ§Ÿ~*]ºtÑY â-Z¤æý÷ß„ 2DJŽ83@¸„É  aƒ 0aÓ‰'ÆÜ›Û~5Ó¦M‹9ä1 qMüç?ÿY%ØÔýúë¯ ˜Á FE`&ºaHc2zá?Ž€#à¤ì=F0ƹñ-;Âó˜T›5k– †vx僯cMßòö¥¤,ðPx+“tl>ÏLòmÀÌ‹ÉJ„Iò!`Âßm߆c"Ö³gÏØ÷:=8Ž@† q«€@†IøºukÈÒwšq<„À’&ø ¼…fð4‚ŒAáGLRQG|€Ás:Ù†‹²ÓO?]ÇiLV͘1CµÆ_ŒÇY=Œ™Ôg|é!}p0I÷‚!sFL€Lð1ÁZÌÜyçšÏÌyé°§fFSJ^0^LL>/"Ži8À”“7!þ£Ì-Ѽ5jÔHóáQŽ—Ø4~hîF§ Rp]Ž0hùɃ@G@ëG ,³é1L„8¤hBø¡m7\Ó6×è ©4`/ƒ ³SÄ Œ²>†m=xÖC06Œã±ÐLþã8Ž@Š`sï®Ûì-ÿ|ð~ùdê4Y·~.0ð¼I=¾X0õDÄ¥;vLš|ðÁ1“Nx ¼˜IÄh°G6ŽÆúA’% |WXÀ‘·I@«ƒï³úLX"zpÌC ¬T¢·jÔ&fe•¨ŒÅ“a1Àòø k ™ÔÇó0ZA–5ÁOâyÕGã4x¼… ,”˜‘bŠjV Ññç6&¦=x‘‡ôAÀÂRÞ ^ Ú2^ „1Tñ¬£#ØÃg'Â#<¢Â/ i¼˜¨Ø ˜ñaÞu×]õ;lBì»Y÷Ç:;òG6{ÁžX[²Ë.»È¸qãÔÔ~ûí§ZA­,üØ‹iG£ÝÒmq1×F7.áŒ3ÎPa²VžxL“Ä!,b¢ŠkbÖ bòÄ:ELcã_|X]0‚6„ìoÂ,Œ‡`}7aP#ýÇp4D`ÆR¿I-yæé‰úW‰ðC¾ ¾L ÂÓÙ7Ù{&Á0ÕÇ‚0Æ7íH9x¦ñDZcÇê÷„M¡™ñ ƒÆ[)Cyãõ­[·Ö :â=8Ž@ÅEÀø†½ÿL¸3~kß¾½ò|? T˜9s¦Zl%B~Åx ÁÿXqa€…Úû￯cQx‹ñꈞãÔ e‚‡ôAÀÂ$Ý fwypыʜ7¦ñ³$x’c]/‘}˜O;í4uÍkZ=̆0ŸÄü‡µxfgm‚b<ÉöR#Œò’½ôÒKrL0%à¸:^Îh0¡‹8c–‡>±î„þX³Î,&ÀLÐú±> ˆÀ,˜q¦¯VF3‡«×®í/8Æç£ò~ñõZ=~tG ÕE`à™6 ÎÕæ<×è‹ò9Î1ã‚ßÚR¬C.¿ürå§8Ô²@^ãÑs¬2˜d"-!–L’7šz¬<š;·úýèTvâßÃ#Q|¢8Ë_>GxŒÈ¿ü¨–UX0þ‚.à0ýË_þ¢Ê,´°Ô"0V#à'‚¥;X±¶˜ÉýÎ;kšý0c‹‰+ò`!ö`ðvÌ$Žh«bÕ†¢„`UVÍääÉ“Õç…Åù1õ¸@˜¤{ÀËF`•7ÞxCí£Ñ®áÆ›€æÀâ]>î{qp:ƒy){¾0[úDf‹ ñí(ÂÞ!p„8\Œ£9ä…%ÁQ½ˆüDëµ:MHŤÚ @q†ƒS h Yx­ÛÒ·”º¢ô‰ê*ï8÷¼Û-v{<yßbõŽ€#PtªåT•É3§…ÁÙ}aMôÁ²råJ- ¯à›akz˜@#Àc1ÿꫯԥ‚†"µûh)·grÒÞ{û‹hqQ¾(.Z¦¬ÎóxÂiÒ´‰ tÌÆó‡2&ðÚ¶m«Í38bÅ€ýÁþ÷¿ÿéd.ÚÍ#4eYçƒÃ‚õ‹vøƒß" b²Å Žt©S§êÚCããäµõàÜ‰Çøuà¤yp*#¼;¼'L–pN°#çÄ“nsïY\*ŒÏŒÿ Ûƶ½|aðüóÏW?Xva–5_g½ò_ÿúWå7†c0|G þýï×t–ðÀO(Ë‘z§á=g‹Êϱ- ÃY3ùOJp°”ðÛKÆL ëvíÚéÇüž{îQ³c"u4}O<ñ„jÓpƒö—¿ÈÏ»lÌ~̶$ V7šDœ°—ƒÚæEÇC§9c158`棫Ãq&ˆÑöÄ„•™  h@a˜‹b¢J°™úȆÉÏ=÷œö‹ÙjÖZ†W<ã4æe C+ ?ض¬<çÜ0ª9sæp¹YšFøOáä}Û Ï㩎@#?`€bQR-Ž#ù Ë-W–çÆïàð8þŒÚ¤4A&ñþïÿþOÏáÝäµ~0€b= y¤ÑOŽÉ° Á 桤82±‡Ãöw5¾7˜`c²Ò&ôŒÿkþãT2Œgàð =ÎRö®1ά’Io ¬õ=à€tŒDu¤2T©’7¼òCè1^Á² ¡( L`ƒn;ŽLZáèåK”8d­á¿ÿýoÝ•xxŒñ ã+mڴщ&–D1v„ÏQ>tÁl¶?!uxH=n2ZÂ{`Œ!Œ)‚ÁâY+ˆM5ªs˜ rò`¿0ˆPÃoN¨ØX0fdö×¼¶‡ ½t–×ÚëNgôxa„¾˜ ™wPêDŤ¯8z>{Xa^jæ­~ŒÒ&™ÕAýÆqLZÍþœo¶¬`/,„B¼•¢ÝDsˆ£sƒõ˜cމm{aôC{'bÚ ÔƒõP ¶05¼XÑr~^›&5 ÉäIŽ@z"`| «j¬oéß¿Œ_±U ƒxkZhÔÀ 0ÁS¬Žòê¡ñ(ŽÆÃM`ƒ<‚2‡vÏ&ðHÏÏ5ü“>3‘ß%0i7zôh¹ç; Q†kœaª¯å›„‰4¶¥ N´ˆ„hYðG ’!`üuµLÊó¾°VÎÞ]&¼á'ŒMXïK@ÛÆD <ÆöîK'ØàÆ¢¬CŽ*âyM”nbÊÀcs1Ù„õ‚YÁÊp±r¦e$?Øá-3U”ŒáÛ›‰ÊZ~,ª„’ÚiŒòïs™·¤öòE‹Æ#Ð0;‹ cy-Ý>Ȭ¿cæÆÖåY|´NέçäAÓ¬¢é–ŸömƒRâ ñeÑäñòGË’‡`ý O´}«;¾.‹/ê1QùDqE­/Yùì¾ 9e`G°õ;––¬¶Š[µ?qâsáCµ¿üùÐË‚¾*,!t©°¸Xzþ²G +«Š¬Z±NZ´i(×ÝqfŽjmÆß Àži&ÔrðÀ‰F þCëîX÷ÂŒ½YE0PcÃ$8ãù†]¯ZµZ.9ëoòõg?HíúÁ£snÉ?ÔY«vM¹ïñ+ä‰'—ƒG£=Š$Ö%| Lx¥ôÅøi4¯ÑyóÍ7 ^¨™€ÃZƒw¬50A5|¢å8·x´8›` å ™pdâ±´Áè£?'#ŸL\"]wî$kVÿÞ±XiÛòòŽ@I€Ç¬Yµ^5¯#7Ü}vØV¡Nü€q“ú}hÅìùÆdón|+Ød7TLƳç'‚å5ízÍšµrù¹·Ëì¿—ºj†wßœNYÎ’›­]·F{ñ–Ø>¤öÞS«Ñ`-ÆkÈceqlH¿ô±W¯^šF}& ¿"Îfµ†Û®Ýzë­êkÁO•ócjp aà΋a/…½$4ù½€¼$ö¢Äçµ sR‚yòŒÖ¥ ù?ÖõPÖ„1k+ZÎ^pº¢¼ŒÕMÓñõ[ÝÑö­/äÒÁuqC|ù¢Ð_Ü6*C~+Ã]ÎÐ>áá4>€ƒ•æêƧà76h¼¢*3±ÆÊf1‘.èÀÃlm·ñÓ(Ï,ˆN¬)°ÈÀ)¦k¬Çú‚³ó‰‘ÎàË 4’”1bDÌ$,é<•±¡ƒé.DñÃ=¶1£Û‘<Ñs® ‰âòRÿFóGÏ£uoÁèá:>¿ÅqÜR>òDƒÕ=’­'š?ѹ•O‹ÆÇŸ“wKmÄ—!4.ÚžÅÇÉcqÑüññÑ<…S®0:âÛ(¬ø¼ùíR}ÞÓX0@ÆO£E½»ïa¢É&›h†÷†wÉ4ƒ[|BÓFy ÅÐúQ0¹±òÚ¯BòY¿Œ¾-ñÒáKL¼!bõüóÏë’YhÅÈÓ÷<øŒðcrˆ¥CL^±žÞE0,¯S€ „I¸‰ìèKmÂ^@eù Ѽ&Tá˜ý£0-Å,5QÑz©ƒ¿h½ÖV4q–'Ún4Oüy¢º‰‹¯ß®­~»Ž¯¯ ¾'¾ ¼ñmùõæ€tD€GSŸÏBžQãshÔð¨lÁø9ù³@}‡¸S'pmy-ÏæÇ¼w„|JËæ‰E¾*Jyë •Ä/£ ’ŸoB$ß4hüÐN0 5GañmSŽ?K'?ÎÅpXÃÊÚ·'Ú^iΫsÔzÍjKÍZ5$+Û·* –^6yT Âì¬õ’–§f)ÃûBའDßO‹³÷Š£½s§… øaB¾vÝšáÝ(B´z„Á¬¬l©Zµƒò„Äh†üó¢Ð-F¿é/o˜šâMžåEhû1ýdÙõZ€/a*ÊE„@Ll±Ú0 *°Šæ·r~L-.&ÿ’<Ø•±x>Ðì!X\¯oV¾°n%O¢òE-·¥|¥'¾ ¼‰èö¸ðmA‘–a ™·FÓñó§"à9Í’ªa°–S(y<¿ *,DŸçøøÂòZy;ò–T¯žkµÂ §zmjÃòõ5k±ÝP{ÉÊwð¨l”öDéñq6@ãñÀèúAÖr3@³í‰ÐÆ×Ë Ž‰Fg˜ÍÞxãê, ‹Ò¢Þø6Kr=þwòÊÔgÃ_œÊ/%©ÂË8e†@é%WÜœ§­*¬‘DïE¢8Þ·øw.Q½7n÷?zS^zóÉDÉI‹ÛdSTxÞëñ:Š_ ²í!^EùkРšÒ"âs‚õ”lņ[íÂ`ÒnqÒ+r0é–®B / ZAÛPÞ_ Òaê¥%xøZ`øNž}øuùV~ ç›fóG }à“ô‹ìÕ«{àƒ…¯­)hð•(>Q\¢>o¼wúô©2ñwC2{È®O”­ˆq¥4y¿ [äämÏSÄ‚[ÌfB!&Yw8 CÓ‡CÖJ²åƒ5phq²ƒ‡h„AÖ bnJ:!™Â áÌñì³ÿ¢G´’|Ã<8i@øôm =¼;99ÕË${7xνdŒœ|ÖQR5»lÞ ^7ÌÉ Öni;j<‡#B u𠊣*Ö/?^ÒZAÖkcžn&ìðhI=¥í—ÿ=îTæ÷˜¤E PýJ‹[R 6 JG§2ö ±üÙçž  õÔ¦¿ÀÎx‚#Bàu¬s«[oÄǪi¤=ÃeI–µkôñ=¨æN ‰/M`0´dÉR¶Ï0éÓ§OŒ§—¦ÎhY£Ï¾8º`­ ^žü]t‘šwáÃÖVÆ—Öë玀#ù_KvO wh ;ì0mÊ!Ò¾Ñ`<*Ù´x}ÉCÀ5„ÉÃ2©5ñòø ”TH˼2`•yCÅh€gºÌ¤£E=«#”´6ðú|⠣ˤß60JfåF·MJáè?6£ÇÔÖX²PÛ¶mcM[þXDœÐßtä‰eÐU¯2CHdúY]áý+ëo0þì¶ 7ÞoúÂÄ—íÅM[¶¯4é–7Ù4x}ÉGÀÂäcê5VRÊŠù–NèòYiQôòå@ªÞ§²zWʲ?6èbpý ÐØ~È‚ ÖðøGšå·ô²8Òß²ìsYÐìu:å@y¼åÑÞoëKô]·¸ò ÁÛH.&K¯ÉH[¢Ì:m‰tÂ4@ “ßb‰Zë“Óf'ÁpG À?­GÀpGÀpGÀpJˆ€ „•ð¦{—GÀpGÀpGÀý9pGÀpGÀp!5UwÓôA˜òB.¦ü8Ž€#à8Ž€#à8Ž€#à¤Sƒ»·ê8Ž€#à8Ž€#à8Ž@Êp0å·À pGÀpGÀpG 5¸@˜ܽUGÀpGÀpGÀpRŽ€ „)¿N@EA º¨º¢ôÉûá8Ž€#à8Ž@Qp§2EE*½ò¹@˜^÷Ã©É 7lØp#h Ë“A]uRGÀpGÀp*(U+h¿¼[Ž@™"€ Ç,Xt&¬Zµj±6«W¯®çÙÙÙ±8+‹ðGÀpGÀpG Ÿ@˜âàÍg&ØÍš5K¦M›&5kÖ„Á?þ8Ö™7ß|SV­Z%ëׯ×c·nݤk×®ªMŒ ‘±~â8Ž€#à8Ž€#à¤Sº7™Ù˜@ˆöïðÃ߬3uêÔÑë+®¸b³øÏ>ûL¯­ìf‰~á8Ž€#à8Ž€#à¤_C˜"à½ÙÌE ++K5}:t{ï½W;Ò«W/iÒ¤‰,_¾\ÿ7n,={öÔ´;î¸C:wî¬e(ëÁpGÀpŠˆ€[Aeæ]u afÞ7§:ئoذaJÉÔ©SÕtÔá²eËdöìÙš6|øp=Z™“îÍ;Ž€#à8Ž€#à81\]ƒÂO¢#`Z–-[Ê]wÝ¥·ÞzkÕ"øm³Í6ºvð¶Ûn“6mÚ¸v°èÐzNGÀpGÀprDÀÂrÛ›ªX ø8à=Î;WjÕª%5jÔ h܈#ôhyõÂGÀpGÀp4AÀÂ4¹NFæ!`ZB´wÞy§v yóæÒ¢E Y³fÜrË-ÒªU+×fÞ­uŠGÀpGÀ¨4øÂ4¿Õ¦Y²cQɵµlv,j¹tÍGÿ‹‹Ayôšð6ºß~ûÉ)§œ¢šA´„ÓæææJ::“áÙ¨(ÏGyÜkoÃpGÀp GÀÇ…ã“®©.¦Ù‰ >Ñ{I_°‚êK³no‘œ([Ìœ‚ ¬œ0a‚nC±zõj?~¼l·ÝvJIÕªéûšñ|”ôÙJÌÞ¤#à8Ž€#à8Ž@’Hß‘j’;šÎÕE…64IÑ:iK—.•å+VèqñâŲ:˜#ææn]Ê[ÃFß²C¹ê99Ò$lwP¿~}©S»¶Ô«WO5Sñõ™Og| í£>’yóæ)6ПNzröóçϑʼn' Âaºá =Õ«W—=öØC4h š×t£1¤Ÿ8Ž€#à8Ž€#à”).–)¼…WŽ Á_T\¿~½,Z´Hæ~ýµÌþâ ™>s¦Ìo½òÊï*«bª„¿%¿KÙ¥o_éöÁëÖ¥«lß©£´o×N×¶!ØàÆ zn× ªI›¨qãÆÉ?þ(”•+WÆú‚÷­aÆrÔQGɺuëTÿä“Oí`: °Çž|Rî¿ûn¥äˆð{qŸÞÁì³TÍN`×çʆ ÑÙ°!7¯üÚ`.ŠðG}Z"OKÈ)šªªá¯n0_¬ßr[iÕºôÉ®*G„üK—.‘o}TÎ ¿†¼‡}´üqäHé»ÇîA³ÕHkJgÁ­æ—„tuÒÂýe_B‚a©iò}hÑ`®&ÈGÀpGÀH&™`u–ÌþV”º\ ,§;‰€`¦¡Ë—/—Wƒ&ðº[o•Éo¼!‡.ï»»ÔÃ;eîzÉ öÜU+e}ÀÇBôùJÁßÁ€M¡† u´@„ăúK .‚Õ¬‡’Ã_§Þ½å²sÏ•}‚æ°a£M‚!´¦k€Ù¤#Ã&î!ÝñK×{ët9Ž€#à8Ž€#à”/.–Þ& ",¼Às/½T>~ç9q›¦rÈàR-į[³ZÖ,_–G O8+‰ÐóHëÞú ac¡Föv Âa¯¬lùöë¯dÔá‡K½¶íä‘;ïP“ÒªÁ,Õ4\%¡ÁÚLæìÒ…–Âú• 4F¿§9Ž€#à8Ž€#Pl2¼8eE@Ô¬™ Ο?_× ž»soÝl~cEš! Â_nÐŒ¼¥^zÑEòùçŸ+&D„MºÆOš4I5yÐ÷Æo(™6£e‚ÜSO=%‡óß÷‚O¾·ÞzK^f³gžyfLÐûöÛoe‡vPsáY³fÉÿþ÷??~¼|ÿý÷Z7?U«æmùiíG[ÖžÛ5é„-Åçåò_GÀpGÀpG døÆô%Ã-VÊøÏ¿ô’䄨æ…í%–ézÂX¦ ?Aù“™µëHŸpþÄÓOËÅ;«éhi»†ÀcVWQòVžvÐj® f¼÷ÜsÜxãz~å•Wª&°N:1áŒzL³×«W/Ùu×]Wµ n&Pþõ¯•V­Zi¾N:É€´=ëŸåVíSôÜÊ7_PÙh|qÎÑTœòž×pGÀpG óp a)î¡ ÜY‹vgØfbt‡6²q횒׈V1‘)&qQ#çñÖªÆÿþ¶VIT¯•)Ê1Ô›»fµ ع—\¼Žšö«¤ZBFx8ßR=ä±2E!7>•;w®jû8à6l˜ö­ÁòDÏWsY‚äúà-–¿(­¬1$XYò±n0''GªU«‹×L‘|§S§N•sÎ9GÐ.cvzÕUWé:T0±6^~ùe¹óÎ;c&ªÖÎóÏ?/÷†gÎÖ(Z¼µS”cAÂfQÊzGÀpGÀpŠÀï%‡ŠÑ¯ré… ÂgÍž-3>ø@¶nÕZ†´æ‚à#UòoK¸®R­z^ñ!TÁôtòê1+µ |Ä'0šÊV%&&`h%%ø¡V´„M6ÒÒÓ‚SÒn`„ „€Æ9ñ…ÑYb\¡VÖ4zmÛ¶•í¶ÛN»ðâ‹/ê1º.Òò#Øî0ûäÏh%¾Ot¦"·Ür‹:•!ÚEúaχfÈÿ±þ}òÉ'Ò»wou2³õÖ[«ÓšK/½TfΜ©9icÍš5ò·¿ýMN=õTùæ›o4ºpp³ï¾ûʧŸ~Ód½ùÍøÁpGÀprGÀÇ#åyRt“ÑRÀhý'Ó§K—PONÐ(Y|±ªýõ?ÿ(—ý"U6—ªõhñUŸNH¶doÕZª6h,k¾™#—/–«sÃ1¯DÅêÝ:JÕFMdÝÂy"AK™Õdk©Ì 7„íÖL$Ù ‡:¶•¬5óÌb—ßNزƒ°9 \~øñDzï>ûÄ„£’ô¹I“&rAÐ6"˜ýáfÍš)vRüE´+B]Ї‡Ð1cÆ¨Æ Á¿k¯½V.¼ðB9餓¤qãÆ1!η—‚)0÷Í …C9DÇP/Îiî¿ÿ~u4sûí·ËÃ?,C† ‘¦M›jv«ÇÊbšŠSLQi÷òË/W¡3SÂaba=öÐs¼–Nœ8QÏ?ûì3±<ß}÷ÆvØaz¤da¥ú#à8Ž€#à8Ž@¥A ¨–<”20Ù›´:=¨¤„[1dU¯.«Þ~O\s£œ9m®ô>i¬¬žý‰¬ž>I†Ü;AΞ½@_w“üøæ;Òÿ†Ûå”ßÉØÙßËé?,’Ó‚Ðpvðˆ¹Ã 'ËÒ·&ÉaþWÆNž.­–Õó¾’*7È^zOÎødŽt?ú8YÚÉÊ©Q’.k™¡¿êV‘‚@ˆ¦ª$á !¦fÍšò—¿üEN;í4Ùj«­ä¾ûî“ ¨ð†€Cp&”Dà4Ú¬s†3pà@K’¡C‡êùô Ô,¯^„¶‘À ŽeøÃ± š;‚å=î¸ãdòäÉrÌ1ÇÈ‘G©‚í /¼ yŒnË‹¤hJégýúõÕdô†nˆíWˆ°]»vºÆÁÔÌCÑ.:vì¸Yzá?Ž€#à8Ž€#à8Ž@1p°`%ÊŠ7ÊÉ~(MÚ·– -R¢¼Æá‡ÝêšnÛJj7h MÂñ§VÊ\/»xÔªW_¾›™g¢Ùrûí¥QóæR'U‚¬”%Ù¡<fÍÛ´•†[5—ú-¶–Ÿ¿^({±lœ¢`::ï½w%§W7Ù˜»IÛU M ‚ ºËsÿý¯®w#› ;‹ú‡ „€Ó²eKa=á„Nm¶ÙF[[,\¸0¯O!¡©¤Á´gi»ï¾»:1a«}ûöòÌ3ÏhõæLÆÊŒ=Z×ê¡ä’æy¬ï˜ŽþãÿP _¿~ýt}"æ©V †hB?ÂôرcU4!úöÚk/5;5 Bàé§Ÿ®ÚËqãÆÉÏ?ÿL6õ~Š©i!5ÒGÀpGÀp àa @‹a@ÿs^j…ýéò„ƒ<&šg‹ç,ù ™Öå;¤YL‡ëǯE_ÿ |tîùR·V°Í×Ê}øì3ò× ˜Ü{Ðþr{¿]dÚ÷H­.ÛÅÒ—ÿ¼Xºx¼ìÊA˜zêúkeÁ#Iõ&Ídcp|R¢ˆDª‘¿®ÎÕD…Ÿ¢þ™ðµO0=½ä’K”$öòcŸ?ÖÕ=øàƒÂ~€Öò™ð¥Eü±2Ôƒmß Aƒd·ÝvÓ?Ì;ÑÞvÛmê`Æ7+g‚¨]Ç7K<äc}áÎ;ï¬[NÁvm0Ù%˜à‡`GÀ! å âÌ$Í ùY?ˆii×®]IÖu„L@<òÈ#fÚ5š5ÂGÀpGÀpb Pµy=kT ûja_8äIväÛRt•|g2ë‚@xÐ+/K½°Æî³IïÊëÇ+öÝG–üï…Øà?7h#—† sÞŒ ©öÌ]¹"ÏÁL¸®„“áç] M¾9áQùìú¤áÐA’¶Ä•h|I~bª†òmBaL‡ÍÝq6SP½æaÏ´b˜c¶iÓF­{ì±J"š76u OE¥zigÚ´i*P!d¢á3A­z0×EI[}ô‘j(©Û褪 „ÖP" c–Ú9lëA@ƒÙ(le‚fÍ¢…=ÔHÙ.ýè8Ž@Ÿ*0ƒ'8Ž€#$l¼“¤ê¼šrBÀÂ$½>ëUSSºÊ²²òµ.}ûÉVAø9¬§{ú¤ã¥ön;ËÆõyZ=wëàZ!§F­ZòcpRòÜY§H•àÝÒ^ľGýIë˜5ù}yõˆ#¥Á ½%wEðBh-M þÜ å›PIß¾}¥E®¢kꌢRÑëøs„(BÛ¾¡^½z*T-Y²DÉ>|¸j k…~7Ж „Ø,ìˆ#Žˆmoõ±U[;°=Þ;Y×GYByë- £8›aëˆîÝ» Îb, qœ0a‚\}õÕºa=ñV¬XÃHÎiXˆpä¡¿#FŒÐõ•£Fïÿþ÷¿«gQÖ,šÙªÝoÍä?Ž€#à$@`Ù²0èÁpGÀ( ¦¨Ñõ‚GÉ•KK• ÉQ5a‰…®Ã:C„¦â„:&¼z²-ÒcÖI=&HÑ6 ^FYÃxöÙgK·nÝtßAÚzì±Ç3M4ŠÜk4~uëÖU éhîh1qŒÃ:?Ól"ÐY0$Â'5„lwѳgOÕŽ²v’°Ã;èç2wß}· ËDì¸ãŽ2eÊýƒ&„Gh7W ù#à8 øâ‹/Ô#r‚$rGÀpd“á`”4C»‡"?íMÛÖmÂ"¿+/q¨4Üï@Y·ä7©Ú´¹lX¼HÓøy÷ÑGdQGÊv={É^w?,/-aÃ!K§%DãV5[–|=[†…Ý1…$D,(â‚ ÎZÖ#GŽÔ­!vÙe—˜vx*â¸.Nøá‡G/ƒÖbQñu¡µc]¡­Yd Šk®¹F~úé'õ.JAÊ¡D;‡¦{ûøã«ð†À†¹èÍ7߬ýØvÛmcš?ÌAÑN"ÜçA8¨A0|ûí·c&en½õVaCzh¶€ãÖY²o#tzpG 0¢h>qTRžæ8Ž€#àa Ÿ 4N݃ÓûC=;¡t!½Z¨ä…®—“zXºì¾‡ô¹õfùø¢³%;X/] 6±vÕJù‰ÝÞ|N6üÌ.wÜ!,ac¾PùË÷ßËÛaíàðSO“Ý!sþ|œ,|e¢Ôl·½lX³ºÄ¦£˜¥ÎýeµìœÔŽt‰,M(äáau{'žx¢}ôÑrLضaÇÌ4ÉÎöWHu “l„víµ×^ÓöÈÉÃ}l6ªgíž•A`cŸÀÂåZ·n­ZE´™lÃù«Ño/uì½÷ÞêE”>HkÓ¦<{Î-mågœ!§œrÊf¦­`}ÅW¨€É3G£U û#à8¢fì‘h?uGÀp~‡@i%˜ßUX™"l ß3˜ù±3ÜÚ`ŠXªAzž\ ˜ŒÎ A§-¡ß¨#¤áÀ}9“MBÃæ-dçSN–®§],Ý.½DêµkÆü–/.ŠÔf¬oŸvºÌÿüs­oàØSeý7?A"h!K(¸"HcKy)´ß'YÚô¢ˆ?&Ì c#w´axÝDhB˜¢NËÞŠXýï²qOÄì~Åg  Ò´Šsÿ(g´¢-e/Aè§®x\Èk í[›F[4Žsú8'P'e,žsŽ€#àÄ#`|‡TGÀ(o||RÞˆ'§=K£=ô]‚GÈí‚c‘EA+W5íM1+ei_(’´Cê Ëþäõë®’ŸæÏ—Ͷ’ý.»BV†¸¬| S°uÂ1wü]F^z¹vÅ•ÒûðQ²lÞÏ’“ïÅ›Wî¾S·¢hÖª xø_²ìõ·K´1½Ò«_ò½ôL‚—K„¦+¯¼R=n¢Y3á Ú [ð°`».ÎѪ‚ÊÐ^Iê7Z)kÔeøGÛ‹¯ßÚŒÆGã8·PP¼¥—äm·$彌#à¤/Qþ‘¾T:eŽ€#à8退›Œ–â.Ø mNNIn ëÁÎlÕš C­›ó[jbãºõ’Ó±µLå%Yöë¯òEXSV·GY½hx¿l¼“Îÿï£òLؤ¾ý®»É¯¡\õö-cK·D×féAà©„Íwßš$]v™®•#=‘à³Y¹†ëê¦M+¬.Ê”f€S”²EÉ“ ;U”²‰ò5ŽFå-ˆž¢Ä'»¾¢´éyGÀpGÀpÒ Ky?в0°Þ/xÜ<-ÔõÓÒ¥Ò¨z5Y‡'Ë"ÖÍ–Õ[´”™·ÿU>Z°Djwn'Õ‚“˜* Ë÷¯¾(_Ü~§zÿ©ÛO™ûïÊÌ«¯ÕšÑÚÑÇZÚHÍ>}åí㎗õáºÎŽ=$;Hˆ5:t“Oï¼A>ºp©ÔlZ]jv [Xàé2¢} Ù Ô_5hí–­]'o†ó¿…mr… qš©€03 UIë( j."†³{6GÀpGÀp ˆ€ „¥¼©3 ¬Y¯qËßþ&g~ºÜ¼Oðf¹,l_T¡ á(lgP«S©Ý­šlX»F…¶A¨¬ÃCZ çÂÖ9mÚKÍ]XT–' *ýa-›–Y+uö×ýÉ‹ƒ„¿ZÛ0݃)kðÔ¹/¨E¥Ë° ë«Ö®'¿øŠ\pñźoI¥Õ0•¶¼‘çÇ’!àø— 7/å8Ž€#à8Ž@EBÀ×&ánš¦å(ök¹­Ìœ¹à¡­¨!i‚©iîÊ*ªÐ¦‚â:3Ï xäÉ ^FsWÚߦ2äÓ:‚0ha1wÅŠPñ4ƒ”§Õ«çÈWaë†éázLðªñ¡þò( cmÜGÀpGÀpÒò¦]ç3˜ “póÐb>Ù¤Iy'lõðϯ¿“_Ö‡­ ‚×Êb …I %™U „U }[åÜñÉ Ý"}òJc*ZRú`0´Kpá°¤(æ•GǰtziGÀpGÀp* .&éNšéè{ì!ãÆãW¿ùŽ¬Î©¡U&¾¡9!¬v¹ä•×å¦[n‘áÇ+Z©˜ýßZxÍ Á¶o@°±¿LÄX;SF?àÁ_»ol‘á{”•ð^­#à8Ž€#à8†€ „epÃŽ>ê(¹þ†䢗^•µµjKµà4“4…Њ™uëÉy/¼,gŸwžœ6^' d˜`QÐXåòåËå¹çž“¯¾úJ~ûí7͇hFSTª,BbA˜DñY³f|¶Fyë­·d„ ®%,ðióGÀpGÀp*îT&‰÷š8 ƒð³Ï`/©„Â5ù]Šê}4‰¤©*]ñ¾œ°âªjÕåâç_’±ÁIÎUa› 6D·¾©²$g:*ÙÍë®»î*¹ÁÁÎ }úô‘®]»JÛ¶m¥iÓ¦º)<ØGƒ KÄqLpŒæÉ´ó‚úí÷ê×°… àìÙ³eÊ”)*Nš4I,7Þx£š8.™†Óë8Ž€#à8Ž€#\ LޱZHŒ#@]|ÁÒ,*'G,§õî!mš4–u8w ¹£ƒ÷Xáž d ¨æ³Ì…ËWÈ o¼#×þõ¯rv«ço/l•'¹ƒ Ò ì—-[&‹-’/¿üRf̘!·SÖ§Ÿ~Zz÷î-ýû÷—vÚI:vì(¬ulܸ±š—ÆcÍý±@Z|º¥¥Ã‘ûB°£ÑO3Ô… ÊܹseæÌ™òî»ïÊSO=%={ö”½÷Þ[vÜqG9äCtÿHöÍäù´zãëÒÿqGÀpG ˜ø˜¢˜€¥IvËàF˜PÈqô 'HÛÖ­eèС²whkŸ!%'ì;¸6˜ï1ÔOõ‹c‚Fõ Ì ‚ßak‰§]O<ñ¤|pé÷L¼*°á­nݺúסC6l˜ ß¿üòKLöÑGÉ­·Þ*~ø¡bΚN„"ò·hÑBêׯŸpïDí~Ø1Yô¥»v„£ÃŽÔƒéçO?ý$ß~û­ “'O–‰' BáÁ¬ZÔ3ƒ†úoa4§5kÖü]óÑ6~—莀#à8Ž€#à8•ËèV# š3dÈÕh]~íµrÞÈÉÛÊvmÛ1²—uA8T%ƒÿ2¢%¾Z3 ¥½jAS”U£¦ÌÚ¥[§*C‚)挫¯–nÝ»k1¥Tjv"bìH´áÝ•¿=zÈa‡¦Ó?þ(ß|ó|öÙgª)C[¶zõjÕ’í¶ÛnjjÚ¦MiÖ¬YÊLMéG´/&ôÙ‘¾ƒ?¦Ÿß}÷|ñÅjúùÚk¯É| ”þA+:bÄ9çœsdë­·V×0‹ÓAàÒã玀#à8Ž€#P1°±LÅèMåìE•pólÒ*gÿ˼×ÀË‚˺°¦ðù^ÇŒ‘ Ò’3ºu––Ûl-YaSúuA8Œ9ž)á0O¤»–PµàircÐ . š¦[¦LS3ÖGƒ£‘ƒ@ˆFÉ‹¨pRæ`• £ÓŽÐ›ˆf35Å)Í'Ÿ|"ï¿ÿ~ÌÔ´_¿~jRÙ¹s¸-[JÔ¤2J’ UÄÔN4ôÜè³cAåNL?§M›&ï½÷žÒ‰),tb[–tFiösGÀÈ<lÇäáÑG•Ã?<¥ëÀ3I§Øp¶„cÆ3ãÇ—cŽ9F³c¡Å˜ÅxÑ–êðôô@À5„e|làÏ‹Q-8k9`ÿýeñî»ËÄ矗?{žÈÌÏåOÕD:öí'µ‚€¶1h ×ÁqÃ†Ü ”å^6BQ5ˆVÌ* [5d£ ÄaLØ#ñË™Så?,Ñzïºç9(Ð…I%!“^bð%Ø‘sT¼à55Å|—>ÆkÞ0±„‘¡yÛk¯½TãØ¾}{Ùf›mŠejjmÛÑžx:ÑXbúišLL?Ÿ|òIY&0ýd0wöÙgËí·ßÓdRG4Ä÷•´tÐèFiôsGÀpGÀ¨¸Øx‡FÇc·Ç¯g.–Ó=enƒwœ}ä‘a=áyóí·eü¿‘ñÿ}R:Zú7¨&-»í$µkÕ ûá,‚WMþ´|>½y¿|Ñ­X~Ñ>bN m™7ÔxÐMø#ÞžDh|9¿vGÀpGÀpŠŠ€¯!,*ReÁ¿è UÐäÍW"säÓYŸË”©SÃz·É2oî—›QÐ2\Õ È„ËÃßüð ·ÚJöGzöè)ݺt–í;u’VAø¨]‡Ry!Qû–V†}7,„´ß~ûM½š"ÄO ÷çÍ7ßö÷;ôÐCe÷`Œ(šEÒG%»ì²K̉í—_oQÚŽ/ã׎€#àÄ#â›âkã‘ñkGÀH6Œ]/=øàƒrì±ÇjõX>1ùm¼(Ùmz}eƒ€kË×"ÕjB‡ \ãÐ¥cØ"¿áûí«fŒ KƒÆ‰#Þ3W…µglÎnk ³²²ƒ·ÌÝ÷°aƒº5«5jlFK´-k³ •ø"°"Ø‘tZ8áóQ35åÞ°þïä“OÖ2ãÆ“£Ž:Jj…}ÓþQ'!¾íDe<ÎpGÀpGÀH6.&ÑÔL`£â1cÜ*hûø+iˆ ѶJZ_e)V;rn¡£÷ˆý-4oÞ\…Áèý$Íðj…­ŒGÀpGÀÈd¢c¦LîGe£ÝÂ4»ã&0Y&xØÑâ·t´’£ [B«èéQ\­”™E°a¼ÈXpü ?:Ž€#à8Ž€#à¤.¦Û‰£'‘—Å/ÓªÁ£«…¨phq~tGÀpGÀpÒ ¬t#Èéq2¨@ȃè3µON·#à8Ž€#à8Ž@ÅFÀŠ}½wåˆ@T t a9ïM9Ž€#à8Ž@Z àáiqŠM„ „Å†Ì 8‰¨^½z,a}Ø Òƒ#à8Ž€#à8Ž€#î¸@˜îwÈéË\ ̘[å„:Ž€#à8Ž€#àä#à¡? Ž@’ˆîùX\¯°I"Á«qGÀpGÀpŠ…€ „Å‚Ë3;#Àž‘ÕªUÓ ¹¹¹±Œ.Æ ðGÀpGÀp4CÀÂ4»!NNæ"ÀBêììlíÀ²eËÄ×fî½tÊGÀpG ø¸S™âc–%\ L‡»à4TjÖ¬©}Yºt© „æ®zGGÀpGÀ¨¸¸@Xqï­÷¬œ°Ù°¬¬,©S§Ž¶Š†0j6ZN¤x3Ž€#à8Ž€#à8Ž@±p°XpyfG `ëÕ«§–/_.6l(8³§8Ž€#à8Ž€#à8i€€ „ipœ„Š@¼@hBœÊ¸c™Šq½Ž€#à8Ž€#P0f5UpOIG\ LÇ»â4e z¦ D ¬[·®Ò¾råʘH¼3ÈŒ¹¥N¨#±D'ŸMBYz¢´Œí´î8åŽ@a¼Äø‹å)wâ¼Á!PµD¥¼#à(zæY”õƒ5ÒøuëÖÅ„@F#y=8Ž€#PDùQ”ר„”ñª²hÛëtʃ€ñ”ø#Ÿ±cåA%³{êafß?§>…`úúë¯ËâÅ‹UØC+øí·ß*E?üðƒÜvÛmRµjUùñÇUP<ï¼ó/¤&¦toÚp*LBÁ“à/ð™5kÖÄz¸víZ=_µj•ò*qì›êÁp’ ×à%ð Æ{V¯^›·=š-ÓÓïž8E€ŒÙ¯_~ùEþøÇ?nFq5T¼âŠ+bñãÇwa0††Ÿ8Ž@²Àmà¼yóäôÓO—­¶ÚJ¯¿ÿþûX÷Þ{¯¼ùæ›ÿÕW_Ée—]&}ûöõÉ©B~â8EAÀøÍ¤I“䪫®’Ž;êDÔgŸ}+~Í5×("/[p]ýõÒ¦M]^µ\ˆð“´@ JØæ‰øiAŽád¼6̲3v 'È¿þõ/éÚµ«Ìž=[÷$­M`€h YWólÚ´©À2ãö:•Ž@Æ `¼ˆÁ×gœ!wÜqGŒv›•ÎÞ÷ìÙS-4hàü(†”Ÿ8Ž@Q0~ƒecšh¨^½ºò”(¿¹à‚ ‘1‘•–ñóôAÀ4¥Ï½pJ2˜0L%ÆŒ£”#ü­_¿>ÆøˆÄt†èÂ`Ý\'ÕÈ Œ1ó>zôh¥¼S§Nº3þàSݺuÓ´ë®»Ná_”õà8Ž@Q0~Ó¤Iyâ‰'´X÷îÝaÓtøMãÆ¥]»všvì±Ç*Ÿq~ST„S—ÏÂÔaï-g86˜êÓ§ľþúkiÙ²¥Î‚a6jë ¬=eṽ#à8ÉFÀxQ—.]t KÛ•¶°R˜9s¦š‰î±ÇÚ¼•I6-^Ÿ#àTlŒwôïß_zôè!3fÌÆ<à7fšÞ¡CvSQC'}.¦ï½qÊÒ˜"³^¬%´™ycz-Z´Ð´›o¾Y¶Ýv[--Í»åä9Ž@†!`¼ˆã¨Q£”z¬ÌË_Æ 5îœsÎQáÐgë3ì;¹Ž@!`ü¯ê—\r‰R†Æ@š9•ùÃþ qðé€ „éœÂ4FæGèÕ«—\~ùåêØ¡MX;¸dÉßÿýõèÚA…ÁG Œ° 'LC¤-Z´H¶Ùf53gŽ 0@˜Ñ'ßÒ ÿqG ˜8p ìºë®ªÄ ð… Ê•W^);wÖZ-o1›ðì匀 „å ¸7W±€ÑÙì×G¡Ã‘ÞGÏ?ÿ|1s gˆë¾{otDÀxÑa‡¦ä¡%d=áÌ3Ïtí "á?Ž€#PZlìƒø—¿üE«kÖ¬™¬X±BÏGŽ©G·F(-ÒåWÞÂòúB¶TÍ—å±cA@¾¥ŸÕUP¼¥s,JËoy9Ú_|ZauFË[9;Æ×_O¢t+=Zñå]GË”­/>ôÚòEvÍWØy¢ü6ñ„ÇãsÏ=W,X sçÎfñûõë§ÕYžÂê.,vµM™Di–7QZaí¤CšÑ¥ÅâìM³sKãhç–ŒO¿ŽÏï׎@º `¼ë´ß|óüüóÏrõÕWËöÛo¯dšåBºÐìtŒ€o;Q06žâ>â0G¼Š6oÞ\N<ñD¹ë®»bkxŠ\‘gtG 0Å ìÓO?y}þùçeŸ}öQk •Ü PÔ´È +Þ…4@€±k•{ì19üðÕ¢Y³f žŽ±P¨(ü¦¢ô£°GÆ7¦/ OsŠˆ€Î”ÆÈ¦Ð÷Œ§.—a’&(±Ïæ8Ž@©° Z /¼P^|áŤiKE˜N ìùH bœˆ ƒÀ(-·ÙVÎ<ë éXÕª.bdÒ v a&Ý­r¦Õ„™Ÿ~úI/_¾<¶^®œIIØô±¿V½úõeÙªUù¦9©ÙW+(öâù>,¨Î ÇÆÁ×:ö% ÿR  ¡t鯿Ê_Î:Kߘö ôx››#`÷bÊ”)rã7J­ZµtFuó\©¹b‚#77Wê׫/ÕsªÉŠU˃œ…™$óœ–^P_JZ® úÅÇ¿ƒQ:KÚ~|¹øk舋¿Ž§•tB”¾¼˜MuTGˆ<1Ü(Ù´„ðÆ>z_¦~ö±Œ>z¬zþ«4‡Z7ÕkSñuid‚öãóAS|\ü5yâÃïóÀyÞXw}ã y^™í݈/]V׎ÿ§<÷Ü3Ò¢ÅÖºZ6ÑÖŒnŽ„D÷'/%ñ¯•'5zž(w©øö/¿ü*'ž0ZÙ·8QîâÄ¡±ùî»ït¿8êL§À3€±aC®>Æ¿¿åLmì)A»¥)[‚æRV$¿Ÿ7äÅî¹wœôØ¡‡ì¶¶a_f|ˆb=OÑÅl8¼'ô£U«Ö‚ˆŠ\|¯Èw·”}³.„ï¼óN™4i’Ô©S'¶ùz)«/Uqç~õ•qËM’^ÖÜ0ÊÀà<¢q;'Íòlù—7¾lÜuþ@,»SØw'0‘Üõ¹:ÐÙ¼£ÉÚæ:®Ë;Fé+Êy^AZ¨…cO:Y~úÓŸbµùIz!€™ñ²eËäÒK/¹ëN5…¼_5kÖ”§ŸyJž|è9ãìÓó }T£Ïkª)-mû¼%©?›ð`¶kÁ:˜Y½zM>/Ú”žš³ÄxÖ)«W­•Õk)YjqQ޶jÞQï>RjÕ©hL]°wïú+n–ùÃæ)!ö-.)UVžAzÛ¶me·ÝvÓ=sÙH¼¼±Žï´1q |ùå—¥mónÒ Q½0ÎàûŸÛ¯Ó*²~ÝzÙ®SkùðÝérùy7JNÍêù“ôéIñ© l »Z˜Pÿy…´é´<ñô#R?(ì]Úbù ÌàaÞ´TC‡Éu—߬k«¦vsaµ £ñÑó-!Ìô-µ»¥t£%Q>â…qÕørñ×y5lú-,ÝÒ츩TÞYAñXIåùRØ$È”7>>z=§Åèuô<ž®-]S–Ç‚êÍs˜%5jo²zòj«H¿å"e3/þæ/ÅÙf옉=Õ0³‹h1%Kõ½â™â#M+—ü&+W¯Vº>kp'äõœ;ˆ)pgbåC=Ê7òËZ¼µ¸f(¨¢üø¸¶)O0ó®"¿‘: jk³øMEÁce0õ-¸îMyý,u¬ &Ï„tx· ÃÞ¯Õ®ÕáÝ‚¾U«W…Ç?ÿY%“G IÀ¾Ö0!ÿNR…U³fíY¹j…T[_5°Ê8þ\XÁ2H³öù¶•EÀlÔÖv¥“ 4Áÿ²³²ƒ ŒéhžpQxeƒ@PôV¸;ÈÎÞôå«È_Àr‹ºˆ9“¦ÂÞžìH^újŒ¿¨øÖFY§A»ÑÉ1Ú—²n»°ú–¬|úø”'|aqXÐÓȵÅvܬ|¤¬ÅÛQ눤'¬³€ôÍêˆ/)³Y¾‚âóž±è}‹¯Ñ¯Ó{ŸìyNÊÐ,C›>Ga ¡Ñ™.ô9¬ÀÊRõ|ÙóˆØ¸1ÂSS/ãåáÝ+‹{ €§ së›õZô/ôŸcÞøˆqR^NèÌ‹³’áÈ3þi|ô<’Eùî¬Ög·8Äó9µqi›µɧU†k ù( Ñ|Za$5¿L^Ýy%ôœÓ¸>YÛÑ*8'(ÝáK#>¿-g½­ÊÅúŸ^Ú#e—"6á™ß„uGiQÄ5Áê±êb´Y[áhýŽÒQáÎæWéä†Jb÷YÛ£* !Ú€5ªñšš×òƧ…8ê¥"£#¯D~9k3®=£eÃÆMï¢U-_QÎË\ d6Š}I,ÄntˆÈ»Ay³ÑlnÉšÒ-ÞÊdêÑúòÖ[oÉSO=%˜…|ðÁÚ?úXQú™V÷ǸOZåÄ8Ž€#à8Å@ÀÁÅ(Rܬé4±ñcr;gd¯ãøüŽmŠß¼§Ñøèy,— ôµîX,Õk°dkëwuX+®ã£VdùÃÑê&jóóÍk²¶£y~_†˜"EÓòâòŽVO^\^’kŽHY+j¹¢e—7RI4ͪ+ˆîßÅÓ¨ uFÓ£í[sFÛæc±±“X=‘º7µ³ùó+N6k3Z–´hÆ |^f!3<Ìr±RÏž=·!nkM`´›MEg•Z^»¶<O~þ¸¶¸h^Ë_P‹·|V6ZW|Vˆ›nºIž}öY¹õÖ[uá4f—xì\¼QÖ¨QCmùÁ*ÝB´éF›Óã8Ž€#à8Ž€#à”Y8,3ÐnZ?17mÚT£f̘!_~ù¥4 Î@ðt…pôþûï«÷2˜ÐfeT(AȲûLN<ñDÙk¯½ä ƒÒ&<²Ø 7Ü ƒ Ò:Þ0ïDËGøä“OT˜{æ™gä›o¾‘sÎ9Gúõë'ûüç?ÿÑ<ü±œp šoäÈ‘ªýÓ„üêÛo¿ýäÞ{ïUÁ‘ã5×\£©ô™~@+ÁÚÖ‹þøÇ"…à{ÓŽ€#à8Ž€#à8Ž@9 Pæ&£ôÁ4e&ì‡æÑ4p\ß}÷ݪ›2eмýöÛ Á_|¡Ç /¼P~øaîN9å™:uª<ôÐCú‡Ù)ûäÜHCèúßÿþ§qüÌš5K^}õU1WE+xê©§ª`wÈ!‡È £Ž3Fó"¨"0bÞÉú¿7Þx#V'ï¼óŽœ{î¹*PZ´Nœ8QæÎ+íÚµÓhLdÙpmà‘G©íS–ô¯ÂÆêlúN8þøãõè?Ž€#à8Ž€#P‰([å`%Ö»î8‰( !‚`"m“ÅE;í´“Ò‰0ˆpÈúº?üᇇN)´|˜›>÷ÜsªY#‘|L ‰0xÁÈ÷ß/hñºwï®YL£ˆ0†–õŒh#8)ƒWP«£zØØ›€08jÔ(à¦OŸ.h'[·n­ÂàÅ_,‹-’W^yEóò3mڴع™Ã¢żõ®»îR!–öwÝuWùüóϵ|ïÞ½µŒ™±Æ*Hщ ò)jÞ›uGÀp*'e»|°rbê½v(°ÀÖã¢È•W^)£GVÓÏN:©–qĈºuküÖ®]+lUÑ¥K­…õ{Qí#‘û쳜þù²õÖ[ ÂÖ1Ç£y ¬M$°¾/§ß}÷®ÿCsÇzÇhÀÄcÛ¶mU°<úè£åÛo¿•£Ž:JÛÀ´SÒãŽ;N‹E·Ú0ºÐ„rŽ€MúÒªU+55!TÒàÇõ4 ÅIpGÀp*®!¬<÷Ú{šñ ¿ØXß:cqQÙ†4òÅçµ2©<¦•@&ˆõèÑCµŠ˜‘ÂÉ’%Kä‘GÑõz¤#„ÝvÛmŠ¢d0ÑDಛ&À5ù·Ýv[ùûßÿ®qcÇŽÕëK/½TæÍ›s6£‰áíbÔ! Îlv8Æ¡N„Fê$¬\¹Rñ?FNhômÍš5±s=I“Ã-MÈÙ2>£ºeŒ<‡#à8Œß¥øo×|KÅÇÇep×tGÀHCà1Œí£Ö}Ñ8Ò¢|ˆ|ѼéÒ¥´ ˜ 8„&Ì?8â¹ï¾ût-ZDÖK&L’Ç<{FóâpæÍ7ßÔµ„ä¹êª«„õ„x%Pž@]Vžk»¹–Ä› gù£m?>X~`;Ïã×ÅDÀgT‹ ˜gwG ³à{ýfƼ¢½±¼ö½Ž¦¥ý¹Op¦ý-rã?ì+ÎR1”Xx;àÏ‹A®ácÄ|£¤‚i+Ã7‹uv¬)$p~ÑE©I)fšÓ,êEþ} ¢qœÛMá­u°†pÒ¤Iªuüè£ÔËi|^£‰x Ei#Ú^´ç8¶Bd¢¼šè?Ž€#à8Ž@%Gà·ß~ÓeÀÀ÷’o0³¬ãNڒζUË–-Ûl0F¼‡<|¼áO‚#P<¢zã?Ôpã7JÏž=å믿ŽUˆ0ÈrµÇ{LãàUX¶oß^÷ ‡—Òå=,wÐ7Û;PÑÈÿ,[Gg6âVŽs¤nÛ4°D|°²æÆÒ­ê$ §.¬ä£Á‡“R3 5!ÍÚ·øøº,ŸÅÛµµE¼Gûlë?üðCõ|úé§Ÿ*´—.‡õÉŽéJ—Ñ€‹úÉ&–MXø™#àdöíáÛ/€¡C‡Æ=zóüóÏ«oœÇYX°`4kÖLnºé&²o¹¥ûÑ÷>ögÀ(.ÑQf”§àh²OŸ>1‚zñsBhÓ¦ùAF`k=v80y'–˜â“rÙv"ÚG“ˆÆŒÉ[:Ú@´sö$Œ†:¨·Q$í^½zÉ(O?ýt, B´n$õhÑxÒ)ƒ£ûb:J`K sVc¦«lkaÚJò0ëHøñÇõh?8¨!,]ºÔ¢byÍ™ 8§±Àþˆ¶Ä`DhŽ>h–/]ŽQLSE“Ñ=Ú‹jq©¢-]ÚUù¤~cqìfãŠáÇ«×oÉYhÙ²¥n9eÛ>Y^KyÞœU­–­uFÓÊûÜøñ„d·o}¥^{Ï“ÝFqê³{K«~ÃÑúniÅ©Ïó:ÉD€y‰ìlÆOyc¨‚FR¼KQaÐh0þÃ5Ï3ï7¼ÆB:=ãUq[ž’3ÊSx„LŽÙ@Nfž%+š/_Ø9³dÌ"„l@VP=ÅV¢´h=8Ì1Õq4>Q¹òŒ³ÙC¶ãÀ{*ë6y¸Í‰N*hµ6¡-'¼Tómxнï>zÿˆ.ʦ´j‹—™Þç½Ô¥fäožðòÒË/ËàAƒTËmÏxZ^ ‰±÷‹}U1y»úê«c‡Ã>Æ÷¢Ù{=Zº•³ë¢ãËPoÍšµäÉÿ>.CÆž|ªNä%¢¥(õ{G PÓZ·6WvÛ{iØh“ Wh™b$Ú{RP‘)“?—…óKNj²qCj‡Dyï^M¹÷ãdØÁýdÔ(5ß¶þc¹Ô¿yøá‡™±‡}ÈCˆçѸÂÒ´p?V.Ê?Œ&ø äŸ~úQî=Dn¾îî°|§±¬c²hþªöhG ì| ~]5[öÐSjỔMBÅ7}¢iöœÿ{g$gÑ­áæ'üÈ%h(‚wHàÜànàînA R¸;§`A w—$¸[] ©ûß¹ýî;|™Ììn„oóvÕn_û¼³ýnŸÓ§OצñÞjßÿ¸@(pAâY銋ù¤bñ½X^ÏŠÕ†Þ‹1Ï"DÊ…@‘¥øb»È §ž={¶Üߨè#R4ÿ‡ Ž­0[Ǽ¤U;,ÿó?ÿI¡Ñl©ß*yM@`‚MºMØ-=ÿÂsq®¿W¯^£ýo,v£ÿAúÿJiüVL¯ý^lGõ(?hР8׳ì2˦ÿÎs°³•Š“wŸ<=ùä°4dÈÄ‘qEíø;ú.l8êÒ¿ÿðpˆ•’<"v´qY®Rùßìpã×ôï‰&å;—}ºm#Ђ?ÿôsšm¶YÓ-·Þ’¦œrÊ*Ï´U¯¬yÿ¸@P"§F u$Ÿº"þzåë¥Q§^:i jSïÄõêŒiz[m¨¯z}+¯³âFãî¬ñ¸ß±GÀßåØc7.k¢hÁó˜æ?ß÷Fߙҋ±Æ¨zjƒ¸ØfñgB±Nñ]cŠBþeÆ!üvïÞ½j.8»­i%¬„Zéïý¥ø³?Ï>1ïêñ†òÄ-¼Ã!EQžÒõÞV{j×±0F@ˆ;ôއˊ{TOyJ×{[í©®c#0¦Ô¿ôgL[qy#ÐDn¸á†´ÿþûW=.ržð裦e–Y&î©ätÊî¹çžiðàÁU·øf1è×þÜ5yî¹ç†Ê(¯XÞÏFÀ”æ/‹%Húi4§kËRO¡˜§tµ«2Ž€0í!PäsR{h9¿•ða+}ãñXD¢@pøá‡§SN9%}ÿý÷iê©§TÞzë­´À ¤ýöÛ/|òÉá} ό뭷^zä‘G¢Ìã?îû¥AÓógŸ}÷.EÁüëóÏ?O\bL~q¨w‡¾Ù‚êIDAT<žõ®XuÔõ*O\,£6T^qm9¥;6F m4ñùÀ$®ÇYj©¥Ò¼óÎ[¯jAeq³ÿðÃÇ…å}úôI½{÷®ºôÇËäSO=•Ýý?×,¸à‚©_¿~iá|eKq.«MÇFÀ"⸈ûe‰ûöí›æ›o¾q|/iÝ'=ýôÓÁI~øa¬à¤EYÄœTÞÏMAÀ;„MÑ4É'Ÿ<šƒ\ _}õUá–[nYIGèû¯|ñúÀÓæ›o»…Ô!]uµc¸ÄK¤³Î:‹ªU‘g eªË;mK@Ô»bµO}ÕSžbòä«MòŠ?¤Ûú³†#К7#GŽL›m¶YÚqÇÓ6Ûl–µóJeŸ}öÙ´ÖZk¥]vÙ%]sÍ5Ñó‘…׉'ž˜V^yåôõ×_'„A„ÃE]4½ñÆQ. )ô¬t·Qz½üÚº”)Ö'_?¤;#кhŽ9‰{ qÒsÏ=W夫¯¾:>œ„ò å÷J+­”¾üòËà$îƒFh|ýõ×£œøƒ˜P䥭ô¨TSOm*¯¶¾øHq±œŸË@·rߣÜ&šh¢X ±+H8çœsbgs„N8a¤±óÇ"nÕUWMë®»n:øàƒS¯|±±Èf'q‡vH×^{mÜsEEÒ ï‹/¾Ü£u{饗»‘ÓM7]ìPBÌÏ<óL>|xšuÖYÓÒK/þ/ê¦Úáçƒ>H/¿ür,FgŸ}ö¨;餓VË0ÎçŸ>ÆÂ…ÀOeŒ€(ÌcB‘“–_~ùêü'N"f]²óÎ;‡ÐȺ@ÀzáØcM÷Þ{oZ{íµ£=ò^{í5sR á_ÍFÀ;„ÍFÔíým$B˜_|qìüIH#tVüñGZrÉ%#é±Ç‹˜tÈ“ðÊ+¯DŒàX/°SpÞyç¥K.¹$-¶Øb¯±Æi¯½öJ#FŒH‡rH˜¢^pÁi¥¬­c—!Q‹º'žx"Í5×\é¶Ûn S³W\1Ý|óÍÑeØ•˜i¦™ÒE] LÚ¦õ×_?„Äzcrš0C@|°ì²Ë¦m·Ý¶:÷”.@iŸ°‹È%ÿþúkucŽEÀ<” KÊÙ!œa†‚Kh3ö÷ß?ê¡õGÄgò™ïß}÷]Ìë7ß|³Úcà‡|øêí·ß%±ú’@IÌX_xá…(ÇØÞyçôÉ'ŸT9-é_FÀ´$â| Œ¾ñÆcœJ'1·YÀ[SM5U‡8 Å5Ç]h£–“~ùå—±æ$8Å7œGÁUmqһロ>þøcsRKþŽå ò•ƒèt2ñTÇpÜqÇUò9 J^¼!ÑU† yyg0b•Í ºJ^V².Ò³ÉW”ÿé§ŸF)—ÍÃ*yW Ò²š(“ÍPã_ÙÉL¤í¶Ûn•lšéy§ Òèÿ„N¨ä_…~/¿üòHÏ&Q.peÚi§­äÝËxçW^4V2QÆûÏ?ÿåóîCÔ'1/(#-Ÿ1ˆ2ú<ñâ_FÀtñA¼b>eªÂœbÎæÝühƒ2š_gœqF%›Wà‡ 6Ø rÀTó†õòbµoêeÅO”Q_wÞyg”ËJžÊÜsÏÏô—-‚;à*ÞõsÄGT²…BµÍlŽZɦBÃ_ã?üPÉŠ¨H_e•UªíÐ^d+YxŒ¶T¾Ú°Œ€ètÄYs7ïòU|ðÁxþè£b|ENÊÊåJð*Ùb¡²É&›Tòñ—*dßQ磌®ª~®zœt÷ÝwG98)[Å3|°žÉg¢«i¤vØa£pÒ7ß|SÙzë­£Œ8)ûq¨Žƒ± 0 òk9i«­¶ªdá1ÆgNª~M¥}@ºw0Ž@‘L$Øe'2AB_|ñEŒOd«²„Ù £’wð"?kë£|v.Sý;Òà4Æ–MÉ+ù¬bUÐS>¼E¿ÄÙ<>øå’¸Oí96F uOH ÌgþBA / L&¨Œ¸*[E:k™ì]=žù…"iûí·¾@IÞˆ“zè¡(CwÝuWpRöÈiâ¤ì˜&Ò³S¤KM?RˆgG{ÁIÄENR>Šlø‡qä£4ÖMæ$ì:á/Û»ü×ä`ZL¨88­ÖY£& ˜Gä©7Úužï‚8ƒ¸é¦›ÂÔ‚œ "`fAÉF¼üÿ/Ì?ñNˆƒÌ%8/8Ï<ó„éeœ $dá1â)¦˜"LE9»ÈõœGÄÜBA^R13#Ð?uó"1Í8㌑Vï3E†#Ð.š?¿ýö[šfšiâÊ®—Á|JÜ€3‚Î¥þd“M–²0–²5@:úè£g{³’'¼ÃêCüÁ8œY†30EÏ»aŽÙ\‚c,ûá¢BV…ÌP€YY·nÝÂ\, ©é²Ë.‹þé“qœyæ™) ²ÑWq,jÓ±0­…€øNb pà¦vÚ©jZÎh9JBàl¡ó›@}Öƒ ŠsÑÇsLpÁi§’‹< NÂÛ:^×ᤕò‘¸îÁ¿¤ãP‹ c4<ÃIm´Q˜¡ÂI˜£9 g[§™sÎ9c\øHÈj¬³àÍâXhÏ¡¼X ,ïw×eGÎâÇ\3§¿¬íO×]w]ÝÏ qŠ!Ðl¢•.¼ð M„ûì³O–Ý»wú"Übc¤Q6k¿ªÉ¤ÑÞï¿ÿiêC± ÒÿÆoãÌÚ²´øâ‹§lFh(ƒ†oºé¦qW"*g n¸aÄþeŒÀßG kÞ£æޤ´èa#T!èM?ýô¡°Áyƒs¹ŸM¿Ã  $AœîÙ³gâœKqŠ"‚E¢,²i¤Ã ô‘M±‚ ŠcË;‡Ñ6J"¥S 'Z8ÇÊ»™Ñ†¸&›tÅBL}E¦#вh¡ù 'Šœ„°‡Ó”C(‘娎râ$x…5 œtÒI'¥C=4üpÆXœ×êq‚]‘“èc»í¶“† ùèGc¦MÊãŸÁœ]?X ìúßqi?!N 4¼‹¢]C£&¢,~(‘/i8– pw¤ ,ÔŠe•F¬ÅW1Å_½ôb=3N¼…!Èâ\†÷^ýõq¨φ·ß~{Úb‹-B`•³ŠFcRÛŽ€hÍ#¬pò$GìÚál Å ¡¨DR«Ô•°G]aìÎ?ÿüªà§Å—¬ÄÔG©¤tÞÉcAÅKcc7‘vñÈBm÷ÝwGTôƒ"ŒwÑ|Ö9ËäóCáz{SÕŒñé¥%xódA…@†ÙÏ3Ï^ YÄ¡qÃ\äÓO? í&h÷œöÞ{調hmŸíõç|#`ê# ¹„5@>Ÿ›ð Ìu/˜g£åærh‚Ê[!æ³Z(’˜«ù M˜zQ^œ$A°^Å4•/öI»xÍçšÃ<”]Á|9ªaâÅ ¡ÁóÑ;î¸#­¶Új‘_l§ØŸ€h=ƳÁ£(k„AÖ cÊIoûî»o(©ò9åøÀõ8FHˆ×ôNLùZ¡]îÙMºâŠ+«z‘“³o†à$ÌGYßd?ÅfýÜhÅÛ>¤?B¹ЂL¤™¢ÃÜR¤Š=<ÄÆ.¢L¤eÃ$[{‚vÈCx”'¥ õ!×béœõSyÞ *#­&hÌ0Ã`Ç d¹ at’I& »{ìó9ù#œb¶†Æ{î t0F`ìO[йœìY4:„*vصå™ÓìîÁ3äiÇnW=°ð‘‰imÝbŸõòHã‡>ôL¸óŒ†˜™gy9÷‘† ª3ËÔQ<;#P.˜ïÌ×™ÛUµ¼R䮲©å$ÖpRööÙf]ú)ò ï Å>HÓ;œ„…~¸~‹µ \ˆ©=Â"J5¬µÌIB²ëÅ»ÞwZÊO$Rbð{lÖ ç Ù›VhÊ9{‡ G„+ÊÔw‰qxšÃܘiƒ…ÅÝ?HMý©-LºT^mhGR¤ÈöÙcé(‚<Ì;´ÉN!NgT`¹§ -;†Ü¯ˆ (aVý86F cHI£Ò¼3Ÿqª€@Èüf‡¾ŠóŸEV¾š&œÃ¬¹æšÁ'¤!Dæk&"Ö|GÔö ÷p°6P^s›&ã+åBÎ&¢øBø#àœ C‘ÄvÈw—q/÷%Âq,Ðà@#`ZZ~'¡Ð–²º–“X[¨üƒù8Š-Ö2piðÂ]Z÷¨nÚõ ypk*bq;„XWÁI(©pýb%…Éš ®'áhÆ¡ë `°ë|—]æ“@„,ˆäF‹0L«Ð¤‹äÐhápF‹;ˆRD«†½;ï,äH“ ‡é)N8¬­€‰g‹ :Úæ, ÒDº" }pDY6õœ$aVåq qÿý÷'b ®ÔcÑÇ‚ B•'ÏÁŽ# …vóYìÀ 묳N„Ù=zBqC9Í3œ;`’I€OòÝ€u<]zé¥a®®º,Êê=ÌåÚ×h.ÉîÞSv% ¬|—ihá1ieEèÓ§O8Åšm¶Ùb¼ùNÓàÚÑΡÌÚ¾ünŒ@k!P“Ù%d΋Wà%΢È"ÀøÈ÷¥Žö0ÙÄñêÊ¢ª¶ Šhvö(§@?8ˆQ܇¢]œ„‚3{x ‹'œ„ù=œD€“àTŽïä«~R¾ÛµªôŠþUZ&Èýµ”öcxàFà/ø“Ö¯Ñó_¥ÿÞS±ýbKZ´É=$а¨€YfaäÆ$Må•ïØ¶ÐÜCÃÙ‹)´×JÇä“EfÚ(_”N«xøDéCž&â#FŒ… vä$¨i~2Y0á|Å”Ú$ m;VâÚÍ—Q‡&^‹)ÆÄXGŽc¥|=bù>Ÿ6G‹,‹…]ÿþýÃc1‹Ábû*ãØÎG@|Ј“P(áäŠÝ8Ö*ÏÈá8e–Yf©~”ÌÇï'Á%(ŸÔæØrŠ2hDŽ·ÈdTÃs3ÞÔáÛ|¿¢9IÀ”<¶@Xò/°+B#Ô[øÔæÕ¾ 1I“²´_¯¼4Œ™- ñ:І4¼rq $‹->¶ù˜–I\ãwlŒ@û0‹<¡wÅj¡ö]éŠÛÊWžbÕQÜ(]ùŠ•WpFš]M<ce €Ù¯_¿Ô·oߨYPºc#`ZÚy®wÅqí»Ò·•¯<Ū£¸Qºò7*'Nb§“U„KÝ›L][Ù8ÆáþV‡®€®ñ=úS´0xã HóLa9Ô»wï0emDÊ-ü‘<4#Ð204‡j…Ãzé \ ™£“¦vxV(擦2ôS¯¯Úòµý¨¾Ú§ Ò¨ÇN—>¯°Â iÀ€¡¥g‡‡ZC‡ÝNv´XSŽ€h-4ÏñDm:£g^צ«â§«å•iT·¶ü˜p½zõ g3 NÂû(Wñ`jÊn'Öæ¤â7TÞg „åýî<ò YCÔB{ùê9Ý®ƒ€xSœÙpýg!1!ô‡T˜™©\×ùäþ$FÀ´"âUâ$Úp m8IWzµµÆiÅÏæ1ÕGÀa}\œjš†€4rEÒÙÓšÖ¡2F tˆ ¼½üFõœnŒ€ÚãœöòǦO×é<,vöîÙ#`Œ@¶ÌÆjM¿ª•ü`Œ€G˜“ư-جÂüR<$#`Œ€¿°ö}üþþýé@«!`Njµo¤¹ã±@Ø\<Ýš0FÀ#`Œ€0F 4ü«4#õ@€0FÀ#`Œ€0F ©X l*œnÌ#`Œ€0FÀ#P,–ç»òH€0FÀ#`Œ€0MEÀaSátcFÀ#`Œ€0FÀò `°<ß•GjŒ€0FÀ#`Œ€h*› §3FÀ#`Œ€0FÀ” „åù® 1 LÂ'Y@IDATxí˜^U™Ç¤BOHè¡w”J@Z,%B/‚R–•¶èŠqAV\‘&-Q©Ò\–ªtB'€„! )ìæà?{rçÞo&3ßLî÷ÝßyžoN{Oû;÷Ü÷žrçùßY&` @€ @ ræ­\‹i0 @€ D(„\€ @€*J…°¢O³!@€  r @€ @¨(Šv<͆ @€ €BÈ5@€ @ ¢P+Úñ4€ @€ !× @€ Š@!¬hÇÓl@€ @(„\€ @€*J…°¢O³!@€  r @€ @¨(Šv<͆ @€ €BÈ5@€ @ ¢P+Úñ4€ @€ !× @€ Š@!¬hÇÓl@€ @(„\€ @€*J…°¢O³!@€  r @€ @¨(Šv<͆ @€ €BÈ5@€ @ ¢P+Úñ4€ @€ !× @€ Š@!¬hÇÓl@€ @(„\€ @€*J…°¢O³!@€ ôlŸþy#T“:B€ @ )Ì3Ï—³/í ¡ÂéÓ§‡áLJ;î¸#ìµ×^aÒ¤I rsù¢¡x@(—}úô =öXx饗¶ÛnæŸþ0cÆ ÆËòt5é ]ßR_yå•ð裆Í7ß< 0 èù%¥ƒPk$›wÞyƒ~7ÜpC¸õÖ[È#¢®0Ló(í ¡‘ëŸ}¾ùæ ãÆ ;ì°Cà¸(M€ªN@³š|øá‡ÃŽ;öÚ°à‚ ­°á¹êWGã·ß×÷øñãÃ*«¬þð‡?„Áƒó<Ø]+å[3±R¶GŽÉý£ —5ËÒ+„7uêÔ–‹Rƒ€ /ø%©ì‰'Æ:ÅèÁF'àëÛ׳mž»®gÅV+ 8Ô±ë—-ç†Ð®ô†Ó¥lÞx–í2¢>€ 0·h\ÔCr:Nª.Œ—s«G(·ž4k%¥ë»žTóó2k­.›çí|NÍÚ aö¢ämÆK‘6A€@GxL̳Ö‘|I2Ð3`jÒk:u§2¸;G®ã׈©Ù!Úˆ½F!@ÙçL4^@€@.Â\,B€‹oõ«¿¨- ²@!,KOP@€@'0CØ x$… Pa(„î|š@ÍC€ÂæéKZ@ ;  v'mÊ‚ @€ P"(„%ê ª@è(–Œv”é T› aµûŸÖC€ Pƒ€m”퀚$Š~n’ޤ"Ðß!ìPËHt˜@v`ÔÞ$ö'u' !Ð-ªþ?êûV½ïWUçÚ-o ¡ŸKÐ Ta®`†p®¡§`”“À矕¿yç7ø§Rá@ ¼ª<‹¥¶ë>¥{–ìz²x÷ÝwÃ|PÞŽ§fu!0qâÄðÏþ³.yéúÓ˜YÏë°.#@!,C0ªH@ƒ—¨¦NÆüñhO™2%†3¸Uñª Í(7Ý—¤¾ð áÖ[o Ÿ|òI§•Bßë>ûì³pä‘G†Ÿýìg-×€£a ¤}ùÃþ0|ç;ßiiKר‡ÒuÕˉv:D…°CØHæ#àALUûî»oXe•UÂzë­íƒ>8¼üòËqc¦°ùúž5=„VÍø¾¥v_uÕUa§v Ï?ÿ|ÄàzÛ ”;õGÁÌŸTF÷»W_}5|øá‡…é,Ÿ—o^˜ë‘)ï\$ ~š}zK?»_kUË2îgÙú?ÔKÕ×_=èeªüŽw^ö˶ÛqؘPçuÊ„@Éè¡GƒÖ{ï½ÖZk­ðôÓO‡'žx"úï»ï¾ð_ÿõ_á°Ã Ÿ~ú)3…%ë;ª*H•à½÷Þ;üéO +¯¼rDâ8?ëÁ[îÔŸ²óùâµRBFa‹,²HèÛ·o*Ú—æ©tÙf.+Mì4iî¹K@ýÔ»wïøSÿȤËæZÊï~TZ÷³û_/%–]vÙ8Ž*M6½åÖùbC`nàP™¹Ežr!P"”dî¼óΠ%R×^{mXc5bØÐ¡Cã2¬wÜ1<öØca³Í6‹áü PzàÖªýdü°îúù7cÆŒ¨´éá?•KååÖ Ï|óÍúôéï‰3gÎtV³¥“G³JR$+%Bée\¦ì4‡G!þ”†€úH}é— {ôèzöüâQÙ}h[—{Ú´i±ïÕ¯î[¥“qZ癦µ¬ÊѵSF£úÚ¤n‡5ªmöZÿ®¨73„]A•kã{¦í¼8Ëb×€tm‹øÿøpàÆåžz ¾ßk¯½bÿi|üÇ?þŽ>úèpÙe—E9Õ@ý©ÃؤTjEä´÷^ùHQLÍâ‹/½Ru*.g«¨>×/{M8}Þ5á¸zÚ*gÉ%—Œ/Qn¿ýö–ÃqêYFwç¥ÿ5=çH¹=ï¼óÂ2Ë,Óòÿ×Ýu)cy(„eìê¹@@€Œª<ð¸*zéªå´œT ¢Â–Zj©¸ìÅiüÝ®-·Â@]O Uº¾´ò•Ýç§êáOæ„NˆŠŸ–úi„–Ž3&./•’¨Y=Ý·4›(Å@rR4[ôóŸÿ¼ey§òÒ‰£ßþö·cz+ƒ W>R5ósÒI'ÅU[mµUT¿ûÝïÆ¼´\T³–RT_ÍÂè$J)–rë^ªr­(ê>ûÆo(û§t¾ÏÆ@þÔ…€þwÔ¯:¥V³wê ]ÚGªkàW¿úUü¬‰½‡z(–©¥¢2îÇ 6Ø úõÇÛ-ôòAFý©2ô󬛖’JT¦ý*­ØÑ!nCS£8-=Õ7•FõÑõ\ïkÂuÒ6‘fÜ*òãÿ8ÅŠ{B.Tœ@v Ñ $£Çƒ˜üo¿ýv6lXTü$óÓŸþ´e`›4iRÐÞÆ?øàƒáœsΠ묳NÐ.…ëäR?à(/  î  ûŽŒžet_“ñ!¾ß}ôÑG1|À€ѶœÒ+Ìù(RŠ£Œ–ê4I=üK^Šœ”G+§:ôŒ3Έ3‡÷ÜsOÜ;æû¡”å¥Ù É=÷Üsñ¾yÜqÇEEDu:ÿüó£bðÊ+¯Ä<´'±ÿþuWb¥+üGý'åM –g˜} øšÂ(ãÏšHY”‘¼ÆQÿÔ¯µŒM-)–Q:•'·–”jéW¿úÕ¸äXù{ü|ôÑGã^Õ5×\3*†:ýÛ§êÆLêôÇ<´…dË-·Œ/VÌ£NEt{6šýÕÁy2îãn¯D‰ D!,qçP5tßä}ƒÔ¡~ãéò5€éaEûeô`£7êZ%£7âúˆ½FÈ %7Z6£=9Ú?sÅWÄ7®ì\^LÈ@ nü`Y· › #ßoR….m–ã5K'c*£´i¸•==”k™ ß?5#¨YCÝ+óîwž²¼”=Ôk¯¢>û#åC{ ­4jvJåhI«^¸éPôœ—wZgÜsFÀ<Õ·v;÷•¯ù¥ÀÛo9ù•¶-ãtV %ï2õíBŽ´Ûn»Å1×ù©ÌwÞy§e|]mµÕâ8íëÚy¶Uv{ã•ß›o¾÷ÒjÖTuU˜~®k­¼\ï¶äÓ¼Ò4µòNãÒôi¸Ýi¼^¼X!tY–Ãf†kø’€Þ:K±Ó~=´xÙ”–£xÃû!C¢´ö@h“Þ`ë­¸fõ–òÙgŸûf|*©Þpêä=Í.j?Bzs< P_zøÂä(bãC-Ý“ùä“O¢­m)kº¿yÙiŒ˜õG‡lÉè>§‡å<ã{î¥ÿú¯ÿ•>\*åO÷E¹¥|(/ç'ÿ¶ÛnÛòâ-SýFÌóÊ#¬ót¨¿¬úðŒ°g ¥¼û•ª~Óu"“ôuçë! }ùÇqòºLÍyä‘qYªf S™ÿùŸÿ‰/ ¤ jÌÕ˜­òòòNËé¨ÛíWz·¯£y•!—¡.e¬§Œ–±W¨º‘€ (ºák_6Ìk¶OƧjã»Ì†nm=Ð|ó›ßŒûõ&{Ï=÷Œá•’¨‡¿µÔ²,í‰Iß„Æ ø@ ÎòøÒê´8‡ÛÖ +}Ø^ÆKLÇOžÔa!6RÎ9ä¨ÜéeWÖxPáÊï–[n Ç|Tèô±òïÿûq˜•‡Gy$~âB3:œDàÈè¾ì½hãÇ·Ýv[œ-Œ‘ü©+±Ö~½«®º*rV溤ìÝtÓM±¬å—_>ÚþÖåßþö·è·²¤½v/¿ür óØ©Y_õµ¯µ¼ëT2ªCº”ÙòŠ“‘zï½÷Æ—¬ºžÎ˜µY3Ç’SÚzåë±\¶ÊhÄŸ¹t#çÝ 6KF›¡i:I@7JÝüuJÞFm?=¡Qo+5ýà?W^yeTúT”6µ¿ð ᷿ým|£ª‡&)“ÌÒ‡!Éf5…a ú¨úgòü+Â˲ña—¦ÓE5;£å€zé%eNŸ’XuÕUãr=ßˤèP}ƒPË;õ9 )‰*K/Ïô’L £”8}ÞBû­­DèäIíËÒýT³…2z‰¦ÙF}÷P÷Ò7Þ8®¸2"¥DûõÙ -Ñ—Ò¢:)1Cþtˆ€gõ9)[úÉݳ¾c©þÓ©²Þó§1ò€»ì²K¼f4c¨ýòÚ.!%QŠ£eu˜‘”zÍèé[•êkï[õµçÊêúRŸ[yT¼®e÷µ–5¯»îºñº‘Œ0Ò§Sôr¶«Œ¯y基»ª¼zç›2nÄú×›G­ü˜!¬E‡8T„€ Ü»úê«GåP'Šz°“¬ô]X@]I@«tOòìŒÊÒÑòºGyÇåë]ŸœÐ²;=,êtÉ_ÿú×ñþ§½€z¿ë®»‚N$ÔC¿Ìõ©C³ôÍ9¸!¥OŸ”Ћ3Åm²É&1OíS}ü‰ ÅIÙ»ð Ã_ÿú׸gP‚+¬°B6kÿµî±rë$RÕY ‚öaKÁÔ÷ð0]C@}«Ï„h¿»öËk¦O/HõQz}h^},£qRã™NÓ?š©S¿è:Ñv -–Ñ~O-ñÔu"ÅQn_ƒzù Ólí ¾üãkL/ôKe¤™µmCã¬ö¨JÁô7/uý¦ÊOš'î/À§ö•0Ï,@õŸg®]f»bU-]àZj¦‹^ÿt:â7}[Ò®Œ‚ÚMÀÿwJ ýz ÒQê˜d¯$=HsÌ11ü‚ .ˆKúæ’–•êûLz‹ªYF-£ñÏNñ¨ ‹?üpÐÑ÷Ú§ÿϪý¿‰ƒžR…P •~i˜ geSVº÷)ÃÐKÞÓ‡ó´ã$¯•úd?Û£x…é!^锿ŒÜ'ÅBŸ›PIIÐ ”W)†úþî¿z9§Ó-õ‰ÕYùùp¯˜aþøúÖªíU×KFícwx½¨ŸÕ?b¬¼µÜS{éݧ¾Fl«\“šùS:™4^3Ñê_)qŽ/º&•VuPzÈ&åTŸ»Pùº~õrv÷Ýwû 5.küÕl¦ÂÒ2•OGóQµ?V3áúŽf½Yw´~sšÎíQ:gpòÉ'Ç,^|ñÅøò¥QÛ5§Ú#Ï’ÑöPB! ÁÐ7H 86 SœÞVëæª½0zØÑ §ÁI—Þ°ë ¸–²èm§¾Ç%“Þ˜6 zÐý&«øé!Üâi9YY+j’ÉÞûŠÁ¼{¥Ò;ÜûÁÒ{ŸÝ^5¡ïÍi¶I'“êð®a³f õ.#eQŠf+uB¢öhki¾feœWôð§ÓÜÏî?Pœe<¶5NZNét=úÅ‚û+ïšt™:Iô—¿üe|  ëPc¬^|å+_‰¸×RÖSN9¥e™±NÅ@ PëA‘< ÐDô ¤+f£p-·’B¨ÅéaÆTZ¥ý0Úì®pÍ.z tØ€@ý øA´þ97FŽé=Ë5Î KãRfvëá\F~ßí_þq\¶ ß/³áÎÃáòk)ê˜1câEÍFYyPÜÒK/—–zÖHa©rëú*Óyî—´_Å8³û8{­¤µP:å©_šËIóu~RBõ Bõ³âÕ÷~± ­Â‘¨¸TaMóJë€í%€BØ^RÈA B4¸ 0 ×€¦K{vl<èÉÖLav™å°!t¼{V^˜Ë.ŠóÃy[rŠWEù´7\³…Úëhã{©ê!eÀ ã±»†@Ú_µú5-={­¤qrçå“–“•×,eÑ’`_Z.kã0û±!ÐQ(„%G:T˜€4 Dúɤƒ^—†WM‡@·ðÿc·F!u#à{©ï—²mòú4·vó(ês÷»g%¥Œ:¬yZßu-Um¶(„µù Ð͵è[+® ;‚!N(úìd¶$ïbµî—ôiÃ/aömõy[³’%lR)ª”§h—¢b%©Äÿo *I…¨ @sN€ž9gF @…«€ ÐÚšYh‚&Ò@è(„]•,!@€ 4ÂFè%ê@hƒKFÛD4 äh…e0¹}G  @¨^|Tª»il7hˆSFõÏï@êî&F@(-® ý¶Ž F$àëØvÚ†¼°4÷œOMÂȆ휱kté†P{õêôáV} @€À<.êæƒ j/9žž+¤øúös m®ïú÷®WäõìÙ3ôïß¿þci ”^!ÔŠ©S§†sÎ9'<óÌ3aòäÉ›@i¯'*@ÝL@ªÖCò“O>Þ~ûípî¹çƇ¹3f~+´›«Hqè0]ßzÙñꫯÆ<Î?ÿü0`À€0}út®ïSÍO¨gn=c˾îºë˜/Ø€¡Vv°êÝRåyfuúÿvKIsXˆª¥ÎÓ€võÕW‡ñãÇÇJZÝ9l†úÐØ(¥°_¿~á£> 3gÎäa¹~xÉi.ÐõÝ»wï°À Äë›—]Û!š••"¾ûuÖY'*ˆ¨PY—-½(;ùä“#¸_|1¬°Â ±L2}q-•v†Ðž¦­GõEmù @€ t T©ê–)d®(­B˜ÒðÅ([FÊ¢Ãä·Ûñ ³Ieoe3›.+[äWÞÎÃåÈv~vË.’s\š&Mçø4Ìõq\š6u§iäÎ3®WšNn™´ûeÉ*NÆé¿ðýÛž–)™¬?æxçWd;ǧõw–Iã\^§0ù-—ÚÎ?k§éÓ´’KÓ[Îái>Ùt–qå#“úíŽÉË&A¹mR¼ëg·lç›æ£°ÔŸÊÕJ›M缕Æ&[‡¶Ò¸mÉ9ÿ<»¨Ì4O×5•U^.ßù:M*¯¸¬ßòí±‹òt¾®ƒËpži]‹Ü’Mã²þ4.ë–¬ë&wjî:)­Œý–Íæ™—ÎaN_”Æñ²-c[a2©_n—‘­Ÿãódçtv[>µ6•u¼ËËʤáEî´L¥·œó–†ÛµkÉ)Nù:M*«0›"Ë˶Lêž“:;ì´lùeÒü¿ùÿ0—“¶Ã2¶—g[&ÍÇa²]vŸu;_ÉÛmÛa²eÚ›ö éâ¿i½T–ó—?[‡4Îò©\šÂ-ßžðTVî´ì´¬´<‡[6µg;›gV6-_iRyÉ:,:fýqzûmç…gÓ§2u»Ü´¾ ËË×õpÛNk¿m‡Ë–ɦw¸ã²ñ1šŽ@C(„¾mû"uo8ܶÃm;Üv­pË´e;Ôv…¥îT&—•Ëúkɧ²©;›&[~êOÓå¹³7§Me³a¾ÙdÃÛë—\^þNŸµód³aò»-yqižŽÏÚ©Lê¶œÂì¶ Ë²q>–· Ïóge-“g[6kKÖaN—õçÉ´7¬#yeÓdýEõ,’³|ÖNåí¶m_^”OVF~_sÙ4mù—íT> KÝ–IòîôúKã”6õ¹³r.3 OÓ¦áy²EaivÛÎæ™ž†eå]¦ed§\RwV6›—ó°\Ö΋φÙo»­2ŠäÒ²ódf»V9–±•Mý’ñužÊ§2©;+£8ÇÙVxê¶œí¼8‡Ùno–·]«Œ4ÏT>ÏÝVX^9Nc;-ÏòE¶Óض\-—½þÓ¸¢¼j…§éí¶ít©³ßv*+·Ãm§aYwê/’OeäNMš¦(\2yÿiÚö¸¿em+¼Èí4¶S9‡Év¸í4.φãon ñÂæî‚r·®è†Q«ÖIS+¿zÅ•¡^e¨C½xv$Ÿªµ¿lí-[}t •¡NiRwG®ñ*¦©³zäQEöõn3ýÐy¢0ìCáô-‚r”ö;„i¨#1€ @Õ&À¬Nµû¿­ÖKgÐ5rã7†]wÝu6ñùçŸ?HLõŠ-·Ü2Ü|óÍñ—(„³áÂ@€ r¨òƒ{9{¤<µòµ!Åïˆ#ŽW\qEX{íµÃøñãÃÔ©ScE—\rÉ0hРðØc…ûî»/ :´ò©/ý’Qiñ/½ôR˜á„âÔïœ_¤€ @ho½õVÜÿåçÄfií¨+w›l²IØc=Â5×\–Zj©ðÆo„=zÄ%¥*íC‰Š£—™Ö¯—SiB£´¦¯ÎÜyç™)4l@€ Ð䤸iFðÙgŸ³9ì!lò¯Có¬;ôë×/zè¡Q!ôu³ÄK„×_=În°Á±4+u(ºa³(½B(²Z.ºð ‡>}úÄ_ÃҦ†@sL`ÑEã4$¨.+y›nºiØsÏ=Ãþð‡ ½ƒ6t³ƒ†1Ën…PZýôéÓcµµ„ÔZ~ÒŽgº„ uK@~_$Ñ3ëOQ¸ãS;›g—ºkåÙѸ4ÿ¬»V½ÒòjÉ)OÇ;ÂÌËq–Kãä–q:§) ‹Â]ü'­¯ëázÕŠËV++›/ò;m×A¶ëQ”¶(\yÉdÓ» ÛEéžÍ'M—sš¬Ý^¹4]ZN6\þl»––“¦OÝ’“IÃR÷±_üu¸l™¼2¿ìÜßzåïúÖªMVÆþl²þ4O§QX‘;•Ÿ·ó³¦MÃì¶]$— —?ÛÊÃá©;fþ¤å¥îŒX+ošoên%˜ÔÊ?+r+«4Î~!ÙxÇ¥²–SX–]V>/ÞeÈNópxšGÖmÛŠ/rgÓvÔ¯üeÚÛn—“¦sXj§ñ©Û2EayL¦+lÕ#[¦Ãl«ÜÔ]TÉÈdó+’Ï OËIÝ–MôœOËü¦M›æhl´I@×§®,zØa‡E…pàÀáïÿ{8à€†nóèÌuÜf%H !ÂôÆ Ž«Õyi\êVŸdýî§¢pǧv{ekÉu4.­GÖÝÞu[¶½a–ï*»V=Ò¸Ô]T—öÈ¥uxšGꮟ'gylÔ" åOÛÎôy 6£CgÚšdª•g³ÅÍÛl ¢=€ @€@5 hf0=(f§vZ„qÜqÇ…aÆE·fžõ¢ÁòÕ¤õE«b†°ÊDÛ!@€ Ú¤ØIÁK·–é{„Ÿ~úièß¿L¼Új«…‰'F÷b‹-zöìÙ"¯‰ú¥ék—Ø<±(„ÍÓ—´€ @•" %NÆŠÜ;ï¼üñpï½÷†±cdžGy¤…Çá‡ô“Ùn»íÂ6Ûl6Ûl³°ÖZkÍ$J¡t~UZ¦ŒBØr‰à€ @€…@º4TŸ“¸þúëñÇ«ÔQG…O<1ž.ªÙ@)|Rö>úè£ðÞ{ï…W^y%ÜvÛmᤓNŠòW^ye1bDXd‘E¢?Í»Qxt´ž(„%G:@€ @`®°Â6cÆŒx`Ìî»ï?SwÓM7…6Ú(,¾øâmÖKÌœ}öÙáÎ;ï ûî»oœe¼çž{ÂСC£Ûe´™Qƒ p¨Lƒw Õ‡ @€@•XQÓlß©§ž¤ j†OËEu¢¨•AÉý¼_p™e– ûï¿øøãÃw¿ûݸ„ôW¿úU¢©e¨Jßì…°Ù{˜öA€ @ Ä¤œIñòþ½ZU•œ5)pGqDø·û·°ôÒK‡'Ÿ|2 £´Ræ¬ðI6ï—=aôÃ?Œ{7Þxã˜ïOúÓX'¥mO½jÕ¹ìq(„eï!ê4<ì@g­†µG¦Vzâ @@@ãOµ’¦°<£p)hR Ï:ë¬ðûßÿ>¼ñÆñ-ý\xá…óÏ>Ou^yù(ÌŠ¥òûë_ÿ–]vٰ袋†[o½5<öØcáä“OŽù;}QßÈ6 a#÷u‡JO ;Ð¥þ¢Ê·G¦(-ဠF `kúôéAûþ^|ñÅXm)hVæ,“mƒ‘øü#3dÈðþûïÇeV_}õ¸'Pi¬É­³Ó|öÙg1Þ`b @ÍJ@ãâe—]VZi¥¨éôO™¬bèñQãç;ìÆŒÖ^{í¸œS žNÕòÑK/½4|ýë_ye÷*«ú(½öjð€ˆ§~ó›ß ½{÷ŽcµÊ׉£:hFå(ê#» a3ö*m‚JAÀÇ£>ß8úíçÏþó°õÖ[Ç·™ª¨•>Ék`”Ñ[J}iÒ¤IÑï¼¢‡?€   ±Ëã—Ý©­&È?dÖ ŸŒ”²å—_>hÿÞ /¼ìJé“ј*³ë®»F[é¥àYÑ;øàƒÃÝwßóúö·¿?1á™B±rk<ÖL¢Êyæ™gâx«Ì”‡>V/[rÇsLøÅ/~^~ùå–²¢£Éþ 6Y‡Ò@ |¦L™+5yòähëæÃ?žzê©Ù*«AMÆË^vÚi§°à‚ Î&ƒ€jð½TvR6šåWÄ×=$Ì/:í¶-…KF¶•½õÖ[/†üñaå•Wçw^xþùçc˜5}"B‡É,¹ä’Ñïü•û~Ë-·ŒéôruРAq_¡ËÓg%49|øðx*éj«­ûCi-3žõgÕUWNí)lfƒBØÌ½KÛ RЛKZú>’Œ5™t“ÿé§Ÿ–¤ÊèÁÁic T”€î‡2µß/e—ñ§{~³üŠø¶çò´ò&Y+bï¾ûnü€¼f eôÑx)e?üᣂ§-÷ߨ`ƒ b¼òp+@n…é:‘B©¬‡vXœ Ô¬áµ×^¤,^pÁAŸ–0`@”UùÙ|”_ÿþýãL£N0•q=£§‰þðaú&êL𔓀=6z[©ï&qÆáÈ# Œ˜š»îº+6d•UV)gƒ¨ ¹D _¿~±d¿h›KÕhQH¥xX‘u;,ÏVÝžuÛŸÚrË(fÔüS²n…µççòÝùS·ãe»l¹µ7¯è§º8În×OávËV^Ròî½÷Þ˜ÿÛo¿ýÚ'اOŸø9 Åÿûß¿c=6ÜqÇq rþh¬ÕOùk_¡‹ÑxºÕV[Eé›o¾¹å…«dŠ®%ÕMù¬¸âŠqÙiNQM„BØ4]IC ²°B(Åoûí·Gfkï‚BÕ_o3¿÷½ïÅ} -´Pl’Óy@¶?moQœÂ=¨å¥KóÀ @ ¬|ÿ7n\Ü“­%øR\òTéHÝ©"¢´ò§¶ÜÙŸîŸ Km¥“’¢Ÿ“=uêÔèÖÕM‡‡Mœ8±¬8kÖKK3õa÷X HïÛ·oüIA“»W¯^q¦L}¢ŸÆ4ýR·”,‡Û-[?åã0å¥t µ4T‡ºˆ§Œ>!¾&LˆþýöÛ/*u×\sMðVŒ‘óG}¦2d^™uH>+±É&›Äoþío ›nºiÌ_uô™ÍFõ’i«¬lºFô£6b¯Qg@ á ¬¹æš± ·ß~{Ø|óÍãÀ©}?IfذaÑÖ ¦Ëv Lþh ÓO2©Q˜Œ4jEƒ^š7 2¸ð £’"¥L÷=Ýßdçý¬€äÙRz.[?)"vײ³Êï±yvZ¿ZõtÝU¹óì´ Î7í'å/“g»nŽ·?µ§ÃìOíÔ «ãŸO?ý4<ðÀQI“Ò©ï úP™]vÙ%®¦Ùb‹-¢BzÕUWÅOAh[…ê›ÛÒñR3J§ÓBuú¨NÕŒßO~ò“˜‡Wâ¤iÒfé…‚±Ùl³Íbp¶¬T¶‘Ý(„Ü{ÔhHP´oAÕÕÒÑ£Ž:*nzWc´\Tû:ãð´ÌfpÏþJ¹ZD ”œ€ j2ú°®ŒÀï|ç;qýüóÏ$…k ÔRR} 'ÄåH×[rÍ2n·Ýv­N-Õ>‰o}ë[Qá”â©MyùÁJy` 4+š©ËÎ䥳hvëAß?ÝÿükO›u¯Lºw7Ó/mÛÜp{,TÙîWæ)ðÆo ZºãŽ;FeP²ïd÷¬b|›â”‡lõµfO<ñĨ êözéªü¯Ÿ–¢þìg? —\rI\‚ªÃe4h¥0½6t€ŒNofà a3÷.mƒJM@o'µ—âÖ[ooB½<ÆKS4pé¡F›ëõÑ^;ôÒKqàrÔV{/dü±{=ðh†”J̦= ûì³O¸å–[Âþûïï¤Ø€Ž€•Ýu¯ë ã|m»Œ¬ßáØ# ®zé)sýõ×ÇS@¥´É¨Ÿe¤¬Ié“ÑØyøá‡ÇOR\qÅqœ”œdô½ÀwÞ9¾ ÕÉ Þžáx¥—[/9ä¸|T/g¥\êà•+åPñz‰:räÈðë_ÿ:hfQ嫌f4ÍÙªfì)Ú4 ?TèИsÎ9'èCõï¼óNxðÁÃú믖ÿò¸mËéM¸–Æhé‹)‡kPÓñÙzª#¸=X*\oC5È-µÔRñ”µ=öØ#èô6  F&àûŸÚ wWü™O£Ö}Ô¨Qá7Þ»í¶[TÊÒ?+aêk+vZ-ó»ßý.. U›%£ï ®°Â á«_ýjS¥ *Ÿ¬"'Yå#£“Gu˜>C!¥ï¹çž‹ã¬âµ­CFJ¡ŒÇØèi²?Ì6Y‡Ò@ |üc[5ô æÙÀ›nº)nÇsLœ5ô&[{e´0{Ú™”JåõÁ´(ŠQxÖ½I× £Þvê­«På™Ö%ò€ Ð<ÉÞxãcÉVÔ¤´9>­’Â5†éÃòcÇŽ #FŒˆ{òuÊ«7½`Õì¡VßxœMÓÛí|”—öí륬NøÖGêµU¼þùá‰'žg•¦Y a³ö,í‚JC ]âäJy Óá1Ú#¡¥+2W^ye´­´I΃šÜú).5Š—â—†ËíÁK›ï—^zéxš©Ò¹ì4Ü€ ¹AÀãZ:nµ§Ç‡Ëh •¹îºëÂî»ïÝÞ˜Ž‹ûÒ0 KV½é{…Ë-·\6lXÌC§ž®µÖZqlõx#šðOóªºMØY4 hLžáÓÒO€:9퀈Á?øÁÂ!C¢ÛWô|ùG’JHæ¸ì€¥<î¼óÎ8;xÞyçµ(yy§ùᆠt'KÙ1¬¨|ÉZ©ûÆ7¾O •¬¢yøá‡ãÉÚÚ^¡ü$ëŸÒèg¿lÉHVŸ½ÐïuŠ­–‘>õÔSñ»….§¨.ÍŽBØ,=I; ÒÐ`#£ý ú@±–¥ÈxÐs¼¾±ôÖ[o…ã?>~ð×VNþh¦QËb<ãè(ùu{®¼z衸I^Kh¤xæÓî<°!@B@cœ•µa³fôôñyíÔ~ú½÷Þ;Œ7.¼þúë-¹W»”Æã®ü“'O/¾øbœeÜpà Ã6Ûl^»öÚkÃk¬Ñ’šFéšÑ°d´{•6A¥ àK¼ 80·NÐ4s8xðàŸ*ƒvkÐúÍo~OÕI¢ú4Åž{î—€êHí?ÿùÏñdµ‹/¾8nˆ×I¢*ûôÓO¶Þxj`Ô3Gydèß¿Ë[ÒÜJ@(9±ª¦–zêÓLÚ~¡ïjo¡ÌСCãKY0£qX/NõC£O6éänŸ;ì°CÌG~oÕ» fžY³oF)I«ý ¤u½Z#¬7çꨪuPIºƒj@  û™ïiyo‹âF÷AÍ *­”K,£?­oNš4)Ì7ß|-3€K,±D¬­ŽÌVZÍ Êhié Aƒâò˜À@%'àç>}–G«,tZòâ‹/Îó`Éû­»«çëÄåê 5ÍþéQ}~Â'†*^«r¾öµ¯ÅY@]S:Ù[/Je<{%O ¬Àf+ÐÉ4˜»¤Èå)‚®UQ¼Â58iÃ2Ë,cñ[qÚ¯_ž)š•Ì“% € Ш¤À¥ÊÜ"‹,•>)~2'5¹¤e¥ÿþïÿ†|¹_?m¯”J»USÅ…0½pC(+’¨Rc%Ò`çÁ,›F2ŽKåqC€@:.ª-ž5T¸÷ØkåŒÆM­ Ñ˜èq¶Êã# a¼$ø@ ÜŠ*€yµ/J“'K @ ¤ žÝVÕ>»5F2N~Ñãœ2Ú W>m€ @€ Ð(„€F@€ @Í@…°z‘6@€ @è†ØC¨õ¿^ï«6¦î´™$€ @ @€g¾è$ªØðB!ÔÍ@ßÏ’aógÃ_s4€ ´‹Ïí„:E !¾}û¶«,£vªÏI @€‚€>ЫW¯ø‰€†¨0•,%Ÿ6ZÊÊ• R¥W5;8uêÔ0bĈ0räÈ0iÒ¤–ï…”€U€ @è"zÔÄÀÓO?Kð÷亨8²mR,=®Ý±¥U­Ék6ðÌ3Ï Çw\l Z»C‰… @ÍF@σ½{÷ .¸`lšŸ›­´§kp½ÔæZZ…ÐÕÖÚñ7ÞØ^l@€ Ðäø¾:Ðt&”jC+½B¨ê«éÈÚI, @hvš)Dlö^¦}ÝM !BýãóÏßÝ—åA€ @ÍN€Ó7{Ó>@€ @P À @€ @ Ù  6{Ó>@€ @P À @€ @ Ù  6{Ó>@€ @P À @€ 4>¾VP»Qkó!€ @h`|ϼvç¡ÖæC, @€ ÐÀ˜!¬Ýy(„µù @€ @ i ôl„–1ÍÛ½D!@€š…@3ͪ¡KÔ¾*B!l¦ ²vw @€ @ û4„B8sæÌHŰû. J‚ @¨&ͨé¹{ÞyÙ]V…+ ´ ¡/Ä3f„ .¸ ÜsÏ=ñÂT8Ó¾U¸4i# @€@w¨gì>}ú„iÓ¦…SO=5l´ÑF1ŒI™îê…¹SNiBãÐ…9vìØ°öÚk‡áLJ)S¦DÅÐñØ€ @€@çè™»GA+óvÝu×pè¡‡Æ ŽBØ9¶eO]z…P`Ïž=Ã.»ì† VvžÔ€ @ M`äÈ‘( ݃sVùÒ+„jÎçŸgåÖ[ ÞRˆ€ @õ# =ƒÓ§O“&Mª_¦äTz ¡¦SÕRÙàZúëŠ B€ 4=oËè9»Ù&_š­=õ¾¬8:¨ÞDÉ€ @( +»¥©PÉ*‚BX²¡:€ @€º‹ aw‘¦@€ @ Û °d´6rÂÚ|ˆ… @€˜KFkw am>ÄB€ @hZ(„MÛµ4 € @€@m(„µù @€ 40öÖî<ÂÚ|ˆ… @€˜{kw am>ÄB€ @9¤h}þùç¡ì 3„9—¡&0pB€@m2ø×n±€ ÐYR¥hÍ;ï¼Ñ.³RXæºu¶ê‘…°É€@h@Mÿ 4™&B€@BÀ/$EPfüøñaâĉ¥W ceù“K…0 €ªKÀ¾Þþz)•A úW^yexíµ×" ™3g¶€r:Ø-@K@/}O÷KAµæå—_^xaXe•U›o¾XÖ{¾ê)&г8Š@¨)€^þ“¶}ÆŒ¡gÏžáÝwß ûí·_¸ÿþûòË.zôèÅô ×v+Òqi~¸!@ 1èÅŸîÿVª¤ÞrË-áè£ni€Ç!e1{4žÉ.S½ÊÂ'­ aJ7 ðà©Á„ áƒ> K-µT|˜:ujxÿý÷#¡?þ8|òÉ'aúôéa¡…ŠŠ¡ü’QɽòÊ+a…V /¼0Ja…¯+š4)V¢¤ Êèž~óÍ7Ϧ®¶ÚjáÙgŸ }úô‰2–ž’üQ[z÷î]’Ú”³(„åìj@ [ xïõ×_gŸ}v¸è¢‹ZÊ?ãŒ3±ÇÆŒN?ýô¸0<òÈ#ᬳΊ~ È.£%c€ PJÓ¦M óÍ7_¬ÛK/½g9昖º®´ÒJqÅÈ«¯¾Þ~úé8ƒ¨—‚Þ[Ø"<—~É)[/91ÅP‹Ù@ ¬¨Mš4)~øáaܸqáÞ{ïKBßxãpÔQG…í·ß>yä‘aÅW {íµWÜG¸ÁÄ7È .¸`ä$¥Ooõ»øâ‹ãÏo˜% r<@¬Ãì—í‡ Ç¥Ê¤Ü@è:º÷j¥‡^êé%áØR˜V}¼óÎ;á…^h ÓrÑÝvÛ­Å_FÇàÁƒÃ!Câ,'ãHëB!lÍ„@•" Á_äÃ?•Áûî»/ :42Ð>Aù¥Øõïß?¬¿þú1\Ë„t@júöí½—\rI8äCÒ¨VŠ #UnÞàì:9ζÀêK@÷ÙùçŸ?Ü}÷ÝáñÇ™Ké[yå•ÃÛo¿· ¤%j™hzÀXW÷?ÿùÏ–ª”½®-íF a7¦(@e$à9-ù‘YuÕU£­ƒdׯ_¿èן)S¦D·mËHî³Ï>‹q~S¬8=DXéÓɤz¸Øj«­Â ,в„TûOz表tnºé¦ñ¡Ci¤„>ðÀAË•´tiË-· ‹/¾xKºX @ ®t?×ÌàñÇ6Ûl³pýõׇýèGq¯  ’b¨}âï½÷^¼ÇëÞÚi§…å–[.î+÷ ¼ºVª™IÑÕ¸’®j©C¶M‘ aSt#€ Ð1žqÓ éOIø´8+sŠÓOo­<Ú¶¬JW^2>T@2~0øÓŸþ—Þxã±)„ŠÓ…>c±á†½ÁÕ¬£Àå—_>>„üå/‰³š½ÔþÅ .¸ *‡®w,?€ PWº륞^î»ï¾a»í¶‹ûÅ¿÷½ïÅïª0m!ÐÁb;FÖXcºÖ¡«3óøÔÕå4Bþ(„ÐKÔ€@ð€¨Á‰%–ˆ¥X±“­_ªØe«!EQñ©Q˜ŒóÑÛcDzî¹ç/+u¼ÞÔêÃ'Ài‰’f ¥^}õÕáÐCÊ¢4Î<óÌðãÿ…0Òå ®%à{¸–XjuÆ>ûì¶Ùf›¨ê^ìU%ª…ïûœÖ5ÔX“†¥þÔ-ùÔŸº³qÎ; Ïʧ2vK3;ÙGñÙãðA€@x _f™ebkýi šVöô¦85ÞƒáYÄ4΃­m){›l²I4hPÜÐoY=ôêÕ«EÔ©vúÎá"‹,Etˆ–*=óÌ3ñÓóÏ??>”(Òy;/l@¨/ßgekœÐ=ÛŠá]wÝ~ûÛ߆5×\3ê1Á5Pšô§ð"¿ã²¶äæ´Y6¼(ÞrÎ3fÌŸ(„-(p@¨6u×]7Ð ¡~øa¼õ½A-ÓÔ’M4 óÄOćƒÉ“'Ç%C ³ò(wjüVX ŸÀÔ(ÎwÜqG<Ùn½õÖ‹"Æ ‹Ÿ³ÐìàA¶Øb‹®õ”"n@]KU2cc IDATÀ+ERÅPKI¥^z饳­þèhM¸¯w”\çÓ¡vž!9@hh„uœ¸–ij&N³tÚ¢¥žúö”¾3(³ä’K†“N:)~ŠBß"Üc=fûX½d¬Êg<#©8Éj–ñÉ'Ÿ #FŒˆøÇÚ[¨£Ï?úè£0vìØXw®“¶ÊÈ+—0@è¬b¨{ôÁô]B™¢ƒ+•Ô]M€=„]M˜ü!”œ€B)X²÷ÜsÏx0Àÿ÷GEOßÔrO)„R䤬é49ÍÖi)§f]tÑØBí-¹å–[ZN%µ¢i[³‹:-Ô³ŒVu ÁÙgŸÝr˜Œ2ÓÒÑÑ£GÇ#εÏPÊçe—]{ì±x¸ê’hSrÄT€@S°â§{±ÜÜ»{Q»ÿ¨= ºH•µÖZ+è—)oôekÖpÇwŒ?Ë(|õÕW?‡YÔƒ?~|¸í¶Û‚NÕ©¢:QTKSuTùá‡&Nœžzê© ½ŒÊë¸ãŽ‹'Ž92<ÿüóAËW.ã¼]6 t?+†Ý_2%Ö“KFëI“¼ 40+…Ràl¤èÉoÌ2 —‘­_6ÜqQhÖŸW_}5*t7ÜpCÜs8a„¥¼÷»ß…m·Ý6<÷ÜsáÍ7ߌŸŸÐA4úÌ„f&u°Œ>E1f̘¨,ª<BL€ Ð9ÌvŽ©!4)vVîÔ0ûÓF*LÆŠ ãžõk)ÑN;íäà[éµÜtï½÷n ³Cq‹-¶X\6ê0ÙÙ2Ó8Ü€ Ì9Â9gF @MO «Üå5¸=2N'E.k”>/\riœÝ’ef0K? @ sP;ÇÔ€ ÐEÊcQ¸²tœgíoGqˆ@€ ÐNì!l'(Ä @`î@œ;Ü)€ªA…°ýL+!@€ ´"€BØ € @€ªA…°ýL+!@€ ´"ЇÊäíñ!j‘ݲÛc”ŸeS·Óæ…9.k[Ö¶âSwÖŸKã³qY¿dmg; —ÛísxÖV:™¶äÚ›.[¥Ë+#•k+Þe;å®z;Ürõ´óòvùf–õ»ü¼´Ž³m™4‡Y¦–]K6Í3/4mê–lêo+ŸlÞÙ´æ”Í7›.õ×Ê#•KÝsZÏ4­Ü.3kgåì·\šÖq“íö§ò©œe}-;M‘|žº³yæù³òYš¦=q’‘qÝÝÖl>–IÃSw­²R¹Ô¦IÝ©L-wš&uç¥Iã‹ÜyéfyÛyr޳'“†µW.MSowQ²á©ßnÛÙ:å…+L&{meóþöäm—›µŸÍ·HÆõs¼óvŽOÃSw½ÊQž.ËuÉÚ.×áöÙ–³-¹õ§yÊ-“—.•ËKï´iîêh…°èÂv7ù"¶íðZv*›º&/ÌqYÛ²¶Ÿº³þl\ŸËú%kã8Û—–ÆÛÝ^9ËÛÎK—&ùløœúkå‘ÍËõ«‡]”w6<ëÏ«o^}ÒtvÛΓφµ%[+>KÝyuÏÆgë‘úSÙÔ—oš.u§éRw*“çžÙlz§ÍÚY9û-'êv|6¼H¦H®H> OÝi¹Eî¬|ÖŸ¦koœål§yØ]+N2mÅ;ŸÔNÓ¤îT¦–;M“ºóÒ¤ñEî¼t ³¼í<9ÇÙΓIÃÚ+—¦©·»¨ÙðÔo·íl:^”^å´'Î2¶]¿Ôo·ílÞix^z‡åÙiZ»m·§œ"çaÛegýÏÚ–³ÝV9EéÓti^Yù9ñ§ù´ÇW‡4Ýœ”]$[”_žº‹òqx{e³rY[ùÉ;vóhˆ%£½{÷½zõнÁ7¨šÿ¢¤…€ @ÝKÀŠa=B¿~ýj¾LéÞšQZW(ý ¡f§L™:è 0zôè0yòd>LÜÕWE'óWŸù¦ÒɬfKÞÙ|;›~¶Êà@;t×5×]å´£É5E¥ž5Ad§ ä]ya.ˆ æ˜ý0ÇÈZ%hd†ª»'^®¿þúøìݪ4%yfu~û6Þusóý5sæÌ0nܸ0a„Xƒ’V·›éP @€êO@J¡ž··Új«°êª«FwW¼è¯Íɱ£J«v´A¤ƒ @€:OÀ4ωÊL ôKFYÁ2_BÔ € @  03ØŒ½ÚºM ¡r1¶î8B @€ t–@Cœ2ÚÙF’€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Z@!lÍ„@€ @• €BX‰n¦‘€ @€Zø?f>© IEND®B`‚srt-1.4.4/docs/features/images/staircase-pattern-5rx10c.png000066400000000000000000002320451412557703600235420ustar00rootroot00000000000000‰PNG  IHDRIÈZ„~Ô iCCPICC ProfileH‰•–PTWÇï{Ûm—¥ÃÒ;Ò«ÀÂÒ‹)‚e—+,UÄŒ@D%”PŒ†"± ¢ °g‘  |  ¢ò=$‰ùf¾Ì7ß™9ïýæÜ{Ï=÷ž7óþ"Ùññ±°q¼$¾¯³=cSP0÷àq€bsãí¼½=À?Úâ€VßwtWsýó¼ÿj¢Ü°D7Â;¸‰œ8„»vàÄó“€Ñ+§&ů²Â4>R ÂëW9bW×ÒBטûeŽŸ/ á4ðd6›1‰3R8Hb ÂúÏÃgá‹ñøËøü4~™ BP%X¼\ÂNB¡†ÐI¸M˜",E‰êDk¢1š˜I,!6¯ßH$%’ɇEÚG*!%õ‘&HïÉbd-2‹¼…œL>D®#w‘ï“ßP(5 “LI¢¢4P®QžPÞ Q…ô„\…¸B{…ʄڄF„^ „U…í„· §  Ÿ¾-<'BQa‰°Eöˆ”‰\Y¥Šˆz‰Æ‰æ‹6ŠÞÉ©‰9ŠqŲŪىMRQTe*‹Ê¡î§ÖP¯S§hXš:Í•MË£¡ ÒæÅÅÄÅÄÓÄËÄ/‰ è(ºÝ•K/ Ÿ£Ñ?HÈIØI„I”h–‘X’”‘dJ†IæJ¶HŽJ~bH9JÅH‘j—z,–Ö’ö‘N•>%}]zN†&c%Ñɕ9'ó@–Õ’õ•Ý%[-; » '/ç,/wBîšÜœ<]ž)-_$Y~Vª`£¥P¤pEá9CœaLje”0z󊲊.ŠÉŠ•ŠƒŠËJêJþJYJ-J•‰ÊæÊáÊEÊÝÊó* *ž**M*T ªæª‘ªÇU{U—ÔÔÕÕ¨µ«Í¨Kª»ª§«7©?Ò hØj$hTiÜÕÄjškÆhžÔÒ‚µL´"µÊ´nkÃÚ¦ÚQÚ'µ‡u0::<*q]²®nŠn“î„]ÏC/K¯]ïå:•uÁ뎬ë]÷YßD?V¿Fÿ¡˜›A–A§ÁkC-CŽa™á]#Š‘“Ñ^££WÆÚÆaƧŒï™PMÚ º·¡Ý x¹zõzì­îàý³ÖÇÛ§Ì癯o†oïFêÆí7.úÙûø=ô×ðOöïØÐ°èX(Ø´nÓîMýAÒAQAÁ¸à€àÚà…ÍŽ›mžÚb²%gËØVõ­i[on“Þ»íÒváíìíçC0!!!Ù^ì*öB¨khyè<‡Å9ÎyÁer‹¸³aÖa…aÓáÖá…á3ÖG#f#m#‹#ç¢XQ¥Q¯¢]¢+¢—b¼bêbVbc[âðq!qxb¼^Ïùi;†ãµãsâ – ÇæùîüÚD(qkbG ùù$k$“<‘b“R–ò.5 õ|šh/m`§Ö΃;§ÓÒØ…ÞÅÙÕ¡˜‘™1±ÛnwåhOèžî½Ê{³÷NísÞWŸIÌŒÉü%K?«0ëíþÀýÙrÙû²'¿qþ¦)G(‡Ÿ3~Àê@Å·èo£¾ 585 456 »çû @IDATxì]|EôRB¯¡#ˆTéÝ@.]:ˆR>:¥JGTPJèˆHÞEº@B „ÕoþsÙc÷r®ìì]ȼßïnwgggßÿÍîì›™÷Þ$ùI’ÐH ©æHH H H H H H H H p H%I>RRRRRRRV$ •$+B‘IRRRRRRRRI’Ï€”€”€”€”€”€”€”€ $·’fNÚ°v-ÝŒŒ4Ë)))))))7M½{÷¶ )^%)Iì%¥så¶zñ›xé^=zC…Jæ{àXÅ~%‚G?–­J'á$&†z¼z›¢ÇlsÎsiÓÄð¬JŒÖj>á¥=ŒzL·oFØd<^%)GÖ¬ÎF’JçÊe³€„~âyÆTx>„ ¾ÁJRò¤)è™KcXC=¦}šœÎ\ ‘mŽ|V=^‰á}L oGÅ«$I›$%ƒRRo–”1ú7 •D#%ð&J@*Iob­JLRR,¿×ƒ+G²&% ‘€T’Höê4O„<,Ùæ°,^J@7 H%I7QÊ‚¤¤¤ì‘€I²GJ2”€'H@*I$,Ox%RRRRRž&x½ÛœaöÚÍ›ôìùó8—&O–ŒòæÌ' =¢”4Iz§xqJŸ6­Õ|ŽæµYˆ‹'n\ §çV0&csçµî ý0šÎœ:GI“&¥’¥‹SºôéâåâöÍÛtýj8*R2fÊo^'Eb|Ì\¼ÏýHw£îRÎÜ9ÆB”&Mj0â-S$FÔ÷ù³At‡a̞Ë ó¦´él?×ñ2êÂI‘Õl]½r>xHY³e!/†×HÕæDܹÃÛ&kXЖ¡M3ŠŒ¨ÇˆÛ‘t%4Œîß½OÙ²g£¥ŠQòº"lŠLÆ»wîÒƒûmÞ'²çô¢T©RÅ›G¯“"0*¼½xñ’Â.‡ÞÅ—l¿€w~*P(¿rÚ°­HŒ/_¾dßÅtéB¥I›†Š÷¦ÌY2 Ŧë°çÈòí÷‰M†ƒ·n%¯ÌYÌ磙rÔmÄpÚ²oŸ9 ;í||höÿFQŠä¯Øs$¯¦°×8fphïaêÒ²§ÍRžÛÍ?J†GÑhP¯/i×¶=Jß6oëK~3ÆÅi„^<AK~ZN~#¿ãù&LMm;·Ö\+ú@F(G3üfÑ¢ù¿j @aüfæ8jìÛP“.ò@ÆGiòøé´ôG ûÀ8~ê(jÚÒG“.ò@FKž÷lßG=Û™Þ{(IxŒ"QmÎã˜*ܰMK'O&ßÚulžÿ„gµ9øàLù-íݱ_Ãv¡ÂiË¡š4Q¢žÕ¾б¿ÇËöèIécöñæÑã¤(ŒàíØ‘4òó1|þ¢†Õª5Þ£I³&P®<Ö(4™u8‰ñÄ?§hHßaL¼ªátÐןQ¿Á}4iz¼ÒBt(5:æ1/Å+Kª\¦Œ¦Ä”)RRÆtéÍiOŸ=£–úÓ¡“'©`ž<Ô²ACJ–,)Íó÷§ô›ûaÜ8žß‘¼æÚyÄ>‚ | ÊU*«¹KÊ”))CÆWŸ=}FÝÛöå/i¾yɧEcJ–4ýòÃRúmÕF†ñ?š<÷sy>h4>þ¯9Í;¢0îøs'W 04lZŸmXýaÔe@÷!ôÇžuT¬dQC ‹Â¸ë¯½\ARê;=úÎ…\ ¥Á}†Q©2%É»h¡QÍü“'Ohì°‰ê$÷›þÕæÄ<}jÆÑ¤V-ó¾²S¬@AeWøVÔ³ Æ1êЪa{þg#Gõ?¨Ã?¨¬-:{*P86å¢0ÖiX“ÞÊœI¹f»cË.~œÈ$(T«·,§,YMë4¬Emw (CG6OOøÖjÅËëÚ§3]¼p)NÏ.Þ› 8©7F ýBëïÖ· *˽ô ÚåL#H[7m7LIRÄ¥7Fï"iâô1ì…mNÉ’›¦c:±g¥~%ŠŒˆâ–QJ’(ŒJ¹Øþ²`ïÙÕnP3Îh©:Ÿè}½Û…ßjåÊÑò)S•C·nõ~VæCÆòk“Ðw³'RŠ”Æ( ¶©7Æ>­ú£Cª(Iõ×¶ÅŽt½1Ø}ˆ×!L–¬ÿÙÜîÔ¬[Uõ¥Ãûðó¯3ñЬÞgO™ÇÙû°MSúnŽ%a¦9 ˜·Lð=Í™2ŸZ|ÔÌœÎOêô—T§r.ÆÓ&~ÍÀÎ]Ì *–.MUÊšFhvïæyÉË/ð¿ßVþÎ9éÕ¿»YABBÙ e¨|årüÜ_›wò-þ:voGw¯¥á†j¦ìÌw¥;‚1’$ÊWzWÓî¤JÊÌ>f-<Áˆ©6Pûni!´µ LÁÁVI¹MIÚwì(ǃžš%)JÒŰ+ü”#y-Ëzý±¸‡<C –Tá=îË—.›Oþvá#›ÈQŒÖ°Ý¿wŸ'gÉfi³–Çi®b„ñ¨ò’-^ÄPlÞیߎžÂËé÷›ÒxËfÙŽ0õ»Æ¾ÜbÛûx0åòŒ6çÈASܦcK™€#HܑיgUÍ'œbV/[Ç“:÷ì >å1ûŽ`,õNIÎwÀ†-Ü6 0pþiîbžþÞû•(}†Wf <ÑþÁ¨Ø!eÊ”QÃ9fg`Ú‚Iƒ2ݶjóf:D)Ù\o‘üøÈPÝ*UÌüC«½ÅsgÏnNWv/8x—8’W¹ÞˆíÆ5›¸‡ì .@å™ÒS½v5ó­Á7¦W@µ¤Ü±†tQ‘w,Oy̱Ñ`ýsèÇ\¹Z%ñ‹Àˆ õú$æ ]`óæßOšÍ‡»ß­øaZÊh å2åJSó¶Íèæ›FÃÒÜOÏ6G]ð©óç©ó°¡ÜÆ#óh+S¬µ¨ß€·mê|Fìë]çÏq¶30ïÙÅ –Ðñ¿OðçF¾•Ù‡µ«WeÊØ|¸‡Þ­ñ½â×5<£ùoÇ*Öò‰JÓ#” ßV>´qmszêL}õ¢À3çùô7L=&°é£IoŒP„ (Á+žÂj‚7-ν΋Q}#ûº*IiR™Ü¸ƒ¯\!üÔT¯jUògsû©˜RqïÁ«)‡ti^M½(ùÓ¤6•ó :Ú¡¼ÊõŽm빦Žå Z«¥æZ£îû4oÉL‚âtÿÞ+Œi¬„4H;å·iO##1ÂèOšj5_)Ò¢e"#<ûvê¯EyÆÂ)†~tDa|þì¹ÙXžAp¸p‰hs€%U¬1/“ü¾c‡žß‚´~ÖlòΗO“nÿg´9w"ïr–žûKÖáX²iýfúiå|® ÆÉ s‚¨gÕ’MxØ*x»ÒÅò´Ðc‘¿›íG¹óå¦3~¤ù3šq¬ÙêOyXºQ$ c½Æu¸"?qÄ·<” ÂSܾI[7ýeö^|ùâ…˜º¶n5+V¤s›(lç. ß»Nþ¶ÆÈß~ð M]´ˆï+FW8xþ`Žäå…;üçØÐw••iÏÉ¿èèŃtòÊÚþÏf6f¿+\hçO7= j¾_¼ˆ7Êa6 ¼À(ŒÛ™·›2ìýͬñqÂ!ˆ„,#z:z¶g#,¾Å~ý&µjÐŽþ=qF$,MÙ¢0úÿ²Š»#4Å;åËhîéúc6Ò²Î[П[(tû ß·Ÿ6Ó"??‚£IèµkÔ{ô( :†QT=>aa@ \¼f!ý{í(½~œæý:“§Ã^iÇŸ»ø¾è?Q-ùþcí&³‘3>¼F’HŒÙ]³t‡ƒ§Bí›váñÚ”cÑ[Q{ìÁMQ0;ƒhíò¸óÓÂY?›!apBéª$!P"¦Ï2¦OO ‚kÿ€NidßO8ï›vïâÛ é^R¼ÿ0îH b”€¼²d%Gòò‹ÿ#FàêˆìñiW³áê¶€œƒô^a´6 £„ðÊ*˜cÇ‹7ãÙÓçè“Î8s_ÿ’:ΩóWˆÄ/¾Qß ç^è‰ï;½Ýü‚ìùŸBvžsû¯ÓˆÓ&ÎäÆÚŸ׎–ÙÏ™~9Qï6Gá.GÖ¬”9cFJà æòòâaJ~™4‰Ÿ>rú4EÝ»§dºQ`8Uì¨8¼‚ªÕªÂGÀ<² ƒfÐ)ƒÂ‘ˆÂÈAÄþaüÇÙ‹ù<ˆžJ…ñо¿©kë^ܼ1‘þ<ø;í8ú'a³vMºÌŒ Q³±ïäºm+Øhüdêæ.½:Òˆ‰Ãh óWìx³fó-ÕUI²U •bc&¡‚ Ñ#…GDð­ú/äêU~ˆFÊ‘¼ê2ŒÞ·‚É#qG@àóÁ kè•PF/Ù6¡^/‡\áq=€áðóÒ £OöœÙù 4Ì[{ÔùEﻂñØáã¼'Ž˜,UKÖ¢b^eøO 〞Ò>Ž'àªh|(ß•6'>þÊ—,e>}32Ò¼ïŽWêüfz+#gûŽ»HeŠæ¡•N¬‘X]ŨæAO‰V±J ú¼»ö]Å8mâ÷œuL}#Vf1òæÏC?­šÏ"Ræï+Â͸“\ÅÞá}éÓ¼1 9ˆà,òqïNT´DóHY1¶/‚ Q’®\7¹v—,\ØŒáÝ%øþî#›Ó”%·âåæH^¥ £·×ÂLÊQѯ0¾]ÖÔ Øs8;Jn|'Ò#ÜÆ;7ïÎ{=ˆy14vªÒSd FkXÒ©–$I¦Š$o-¯è4W0b LÏXþ”Þxǹ|µ×ѽ«mŽ-.ÂÂÃͧòÛXfÉœAðŽ+õÖŠ²(èïÿð­úOéìåÎc}™%u^‘û®bTó¶hÞ¯ü¡V2Ù0©ÎoÔ¾«ÏÙ’eLßT…oL?•«hê¼ÃÃÖä*F[¼¯ ³ƒÈâ¢<øtU’6ïÝCX>DMa7nиysyRíʕͧÚ4nÌ÷®^MWU ÏÒ¿s£oŒ4Õ¬dòvr$¯ù‚v€ K¨ ñ}0ªV³ªù”o«&|ÙOþškýã=Œ4U­þž9¿§ìˆÂˆQ†®­zñ!àF¾ hÒÌ n3ú…qÙÏ+=V í+ô”Eoþ!vîSµÊ£r^ÔVFØ ­eÆ –¿ËLøÀ†sXNÇÕæ ‚’ä…£E 5ËÖk`Ą́ ï¢Þšs¢Da„³ˆe¬§,ˆæ˜¡8˜¼ˆ¢äzŒ0þíæÅ}P£&åΑn±áè;wò´ÞÞ4¤k7óíšÔªMH ¼t‰Þû¨-á8êÞ]ÚvàÏ3åË¡æ$Ék¾Ý;ö{š` Åk ñÁ»}+‚¶±HÑ <¬}õ4ß!þ‘†õtšÔhÁCþcÁS|DA£X\$õ¼ø¶$ Ê)1L/XJJÀÉ/G}·yA"1þðýOæáî ³¨u£öqPL™7)Ž‹gœL.&ˆÄˆFüx6`€Q£,ò:l@¦á[Ñ"1ŠæÝÞòE¶9cfÏâ+Ô¨PŠ,È: 6•ðvÍåŠèm¦*°„Ú/,ùGƒgìc¯6â'!¬h2âYýuárŽŠR!—º|‘ëiô—ãiÍòõ\á­Î–ì€íà–Û8 °›màSOÍŽ}‘§ùÍäŠ{-B%]ú´\©W–ïB°æšõª Á„B“ad«ôk¡¡„y÷Ò¹^ïBˆ±cž>¡/\ ³ƒéøÙ³Ä®OÏz[ŸvìHsFþOc„J­6¤ PžÿLðºFeÙ4Üô¯¾¦fuëšÙr$¯ù";wŸÅP{  •|ýô@röÁÃx&ˆ‚Î] ÓÌS Qkqî¤X°Vm°áÎ&l½¶‹A!¤¨ÛîÇxxÿßt”Ù´€0ü{ûfDœ_«-øªÜ<“ž‚1³ÂYÌ 3§Îñ¹rÔçÃÑ| jÊ\?z_KËx<«§`´Æ÷c¦Dü̦3`çÒÍ×êè›è6{6ÜÝæ ´ÀÙà`:HÇÏ#´OXC²yýú<”I‘¬‰Á®4Ojsj±KTÄ>z„v q½08`h?ú|Ä@f[i¿B§ïIÏ*Frûw3uàÑîæ+h >¨æ×™}OÁX†)²è”a´ Q§áA{1è‡Å÷›™ãÙÒWÎÙ¾z F|/0µ†öôÔ±Ót+ü3æ»܈[íMîh]FG=æß¡ ¬Cd’°!¬ÿ¬@Úá]»è$ Ù®¼õ‹­]‡â`Œ}—ÅBÊÆ¢ðze6/[Ë«¤! Àåë×(gÖl¯Âv$¯R~|ÛÑw(ðü%ªÓ²Z|Ù4ç€Æ·÷îÞ§,¬A±gúäÅóvå*eg¯ÒªìS4 :¸z>œ.œ‘-Å+²cX IXLÁkx]¤bR®’§×#bР³äÊò·NÞ 3C<¦Í¹sÿ>OË<ÁrçÈÁ¨vÝBáDtksB<ªÍÁózƒ}`32%מöìuϲ§=«ÀJ­Zªãu^wÞÓ0‚_|“"Ølìsঞ©xkç= #¾¡×¯Ý §Ožò5ÛÔË[YãÝÞ´›ÁQ|1çÞ½{[½D·é6¥thtp—ÅÏ^BÃZ8_~»²;’׮ȌðXÂÏ^ÂÃZÐÛùÞ§½÷Ñ+ŸÄh]’öÖ#c„p7Y‰áÙæ ~î&ÑõˆçµP‘‚n…)£žÊ‘+B‰|a$WñZt…OW®…m¯²‰+ü9z­ëÝ"Gï(óK H H H H H H $ H%)T’dQJ@J@J@J@J@JÀx H%Éx™Ë;J H H H H H $ H%)T’dQJ@J@J@J@J@JÀx H%Éx™Ë;J H H H H H $ H%)T’dQJ@J@J@J@J@JÀx Ä øòÎÑYr´´Il.^ ã%#>‹qd,ÊË&Œˆyñ¦RHÐeMbLØ5*ÛA(Û½+Û½%êžò®†\÷Æñ“\¶d E³õŠ$I H H H H H H ¼©p*˜d‘|ùޏЈQ²S—CŠð›Ð0:á7¡at&2¬Äèyp&â¶ç¡ˆŸ#ÙæÄ/Ÿ„rV¶9 ¥¦âçS‰¸m+—´I²%™.% % % @ÿ‘±S_ È"¥¤’”hªZ•pDRIrDZ2¯”€”€”€”€”€”@¢‘€T’MUK RRRRRRŽH@*IŽHKæ•H4ˆ7N’3R¸vó&={þ<Υɓ%£¼9sÆIWÂ#"èjx8-P€2eÈ $ÇÙ>|ôˆNRÒ$IèâÅ)}Ú´qòˆNüÛ+QXo\ §çVê1«ÇÜysÙ¼íí›·éúÕp*T¤ eÌd½?Ž¡sÿÒݨ»”3w–·¥I“Úf™¢NˆÄý0šÎŸ ¢; cö^T¸˜7¥Mgü³*£º^®^¹F<¤¬Ù²Ãk$‰z#îÜ!´7ÖmÚ4g( %qø2QÕŒÜŠŠ¢«Wéîýû”#kVz»hQJ‘\÷O„ú–š}ÏêÝ;wéÁý‡šûXdÏéE©R¥²Lr,£Âè‹/)ŒÅÄûø’íðÎO åWN¶‰ñåË—ìûrƒ.]¡4iÓP‘âÞ”9Kf¡Øt}ö9B¾ý>±ÉpðÖ­ä•9‹æüó/è‡U+éëiÓxúÌ#èãæ-4ypÍ«n#†Ó–}û4çÚùøÐìÿ2ìe‰À앇F:Ú{˜º´ìi³Ôƒçvó¡:Ëç/hÉOËÉoäwS¦HIӥפ¹p>›0žŽ=«I·„þØ³ŽŠ•,jy™cQwýµ—+Hù ä%Ÿ)=úÎ…\ ¥Á}†Q©2%É»h!!˜, …Q}Ÿ'OžÐØaÕI†î‹zcž>5ãhR«–y_Ù)V  ²ëðÖÑ¢0‚ñËׯS»ð³ÒläȇaÍ›#ýËÚæSçÏ;ŒÍÙ D=«uÖ¤·2g²ÊÖŽ-»xzò)¬ž×;Qƈۑԣm_ÞŽV¬Rž4©GI“&¥Ÿæ,¦ƒ¬Ã;ôÓá´ä·Ÿõ†cµ´Ê“«‰º*I 3½Ú´¡a={)‡6·Õ:˜´÷~í;ÐùÐ8=å‡s Ê×öE‹)[fÓðZãê5¨^·®´rs0€rf˦\"|«7F0l¯<„ƒ‹½zWŸ}Ñ÷µ·ó­ÕŠçéÚ§3]¼p‰öîØoõ ýBëïÖ· *E¦ÞzPír¦¤­›¶¦$) êÑ»HAš8} {a›S²ä¦é˜NLŽõ+ùPdDo´ŒR’DaTÊÅö—ËxÏ®vƒš´kÛõ)C÷E¼P­\9Z>eª¡XlÝLÆ~¹‚ÔªaCš?f,¥4Ha°…Qï÷±Ï@ë#âèØ)JR½Æµm±#$]oŒvâ L–¬ÿÙÜîÔ¬[Uõ¥ÃûðóèœEzcœ=egýÃ6Mé»9~”„™Û€`ú1uÂ÷4gÊ|jñQ3s:?©Ó_RÊqª˜ž­ÛÐåþôÍàÁlÎö¼¢ÿ¦M¼ü»˜$$T,]šª”5tì6nˆß°öbD™Žäu„Ñy;voGw¯¥á†Æ™†Sß»ô»oóaQÌ%+„‡=Ðõ°ø×Ðá™Üôg/Æâ¥ŠQ›N­Ì ØEãT¢tqÎùƒûÜ„àõ·µ£Rl¦°©Eàƒò›ÈÞ1gl’‘­½1͆(hÆ×ÃÝ® 9‚ÑÑgÕ²ì_XÆ“Z¶oNÙ²×¹¶ä#¾c{1F²‘$PùJïjÚT©S™‹ÿÓ-HöbÄT¨}·4ŠÚZ¦à`«$‚„Œ$ÙËèÔaÃìʺïØQž½:K‚’„i¸‹aW,Oyı½Á¬#y=\,£¿á;÷ïÝç×gÉf[Qvé:\ì F*/yÑâEtàFLŽbüvôÎÈH¿¯Ø”Æ[b˜Ò¹Ô„úŽ9"{18~ŒÛåÃ)czíº#÷sG^GŸU5p.Y½ÌdkÕ¹gõ)Ú·c©wJr¾6l¡.}:qe Î?Í]ÌÓß{¿¥Ïà™õk/FÅ)S¦Œš:Ê’53Á´çaÒ'_nÍy=„(I«6o¦ÓAA¼gR$>ÚS·J§ø…|›y]€rgϧ Åcž(F’žäÛ‘{m\³‰{¡Á©PáTþ½rT½v5GŠxm^4Xÿ25Ö•«Uzm~½3ˆÀˆ**ò=‰yBؼù÷“fóáîw+¾C˜–2šD`Ä>å2åJSó¶Íèæ›FÃÒÜOÔû»œÎÆr|Ì£­L±bÔ¢~·ŒºèñLp0—!¼‰çú/§Ã§NQ$kGó0›¤ê*Rû&MœöàÓTŽ"žUËÛ¯øu O*_¹½«`Xæy¬7F(A¾­|hãÚjçÓ™úêEgÎóéoØ•N`ÓÿF“ÞE^ÑðV¼i¡$½Î‹Q}#ûº*IiR™Ü¸ƒ¯\!üÔT¯jUògsû©Ø×º÷àÕôDº4¯¦i”2Ò¤6ÝóAt´’$t+£P†(Êà‘yÐÑ×—ó÷ºð,vƒxMFϪå-1 þãìÅ<ž¸Š÷©e>QÇ¢0Ú÷7umÝ‹+ˆ‰ôçÁßiÇÑ? Óp蜵kÒ…`Ö`‰Â˜Í++­Û¶‚ÆO¦>LaêÒ«#˜8ŒÖlYÎÛV`Ëš]Œ`H×½RlÌ$ôÀ%½7Ø%! 5/5!B, š;ÉŒîäÛ‘{¿[ÁäIˆˆ®®Ðå+<®Ê@8ü<…ô¨Ɠ=gvþBwiуϣÁBš»ÈŒÇç=qð^µd­8ÐÓ+æU†Äî—u?Æ9oT‚¨÷±|ÉRf7##)K¦Læc£w\ÅøSAÊ´›šÿü¹rñÃÑq;¦ê|¢÷]yV-yCÐSÅ| ójórã´‰ßs(˜úVbåÍŸ‡~Z5Ÿš×mËG}®çgÝÜÙUŒ` ‚’ä…±HqS§uͲõXðö‚*È»¨I!Ödp #œE,ÍDsÌÐ ÌAD‘n#Iãߎ…}P£&åΑn±áè;wò´¬×2¤«v¸¯?[’CÖ ýÇŽñí\¶ÔȦØÀcû ôvšÔªM¸>ðÒ%zü8êÞ]>º„‹¦|9ÔWU‘Ã^y ¯(Â2Šg‚<â£~ûVmcѰAx!û ꩹ý¶$ ò€Ž<Ê·‹,¥¿6›êþËQŸSQ6úÃ÷?™‡»ƒÎ^ ÖÚó¼ê¿)ó&ÅqñTŸ×c_$F40n‡Ü`€Q£»rżO˜6F¯-C$Æ×ÞÜ  "ßÇ1³gñøk5*T ¢ ²ÎßcÖ.í2/x;w”1J HŒh[}ëÔámtýîݘû¸_œ|-[c„øIåTÓ‹¢ªÕˆgõ×…Ë9ûp¤P” Qx¬•+#b=þr<­Y¾žG׮Ζì€íà–Û8+°)màSÏ[º¦‰Ä8Ío&­_±j±*éÒ§åqç”e°zõïN5ëU׋º0Ý”¤ÔlhoÐÇÓl´=…Ò³ÞV¯¶mi‹–­¸ë+ç6ïÝkޤ¤AÂ48V©B›XHýÆŽåecPY6 ‡åO¬­­Ä3èü'#XµW:ÃÒ‡Õ°{èNË~^iÛ ˆªÜ©G;6¯Ýs«/‚b€Q"5a¡Ee±Å¾±JUŠ”¯7Å6@} öá¢+šDb8ìS£I°PSP_þoUaÃÂFHŒÖøO» ìŒ"‘ïc¯6m ±×0Ê‚ŸBÍë×§Ñý>5Ìý_$F`š7z 7Nÿuþ&Ò02? sgú´CG 'ÑÏ*Fr×ùÿÆqôè×U8k7‰±}×¶„÷oÖäyÜæë™)ÛþCûÅYoS9¯çV$Fijúqö"s=‚oxñ üêSjܬ¡ž0┕„ aý'56áð®]t’…lW¾‚­,qÒQ ¬ï²øFÙX^¯ÌúN- Àåë×(gÖlº wŸe#Y§.‡zÆ8Bu1áDtž¡:-«Ù]êÆ÷îÞ§,l Ũ)"»´Èxõ|8]8sÉc0ÆÄ<áá-¯ 4 ®’§a´Ä7y²dÜÀÒòœ½Ç·NÞ 3C<æ}¼sÿ>O˼us³ ‹É˜#‰«t&2‚N_¾ì1ç1A¾Êì­ÞʘA—6ÛÓÚ¼ Ôª¥:x‚ žø>¢½Ž`£ú°ÏG˜«|ž„ñÅótýÚ ‚ý#–³R/oåB5ÒÍà(:{*z÷îmµ˜W]{«§OD\#¸Ëâ'‚ÐΗ_DÑv—)£ÝŒÌŒðÀr§–@x¼h‘Ñ#€»I$FKláù>"~î&‘ aŠ(àV˜"ŸU=•#W„$#øÊôVFþs…GW¯… ¦&×»EFs,ï'% % % % % % %`€¤’d€å-¤¤¤¤¤¤ž¤’”ðêLr,% % % % % %`€¤’d€å-¤¤¤¤¤¤ž¤’”ðêLr,% % % % % %`€¤’d€å-¤¤¤¤¤¤ž¤’”ðêLr,% % % % % %`€â &¹lÉŠfëI’xS%àT0I…•ΕûM• ݾEOŸ?—x Ÿ¸EÏØ"ˆ…Jºwõy‘b¼|ãÍÇxa”ï£ÈÇȲC»*ÛC%á7¹Iѵ º«ooÄíÂyóòeIJçÊ¥¾æÚOÊ֜ò$c®ÖçSñ¥W ¾ÁJRò¤)øÒ+o2Æ´O“óeIäû˜°ßÇÄЮ>Ϙšµ9—èM~C›“&E:¾,‰­7.ÑÛ$ýG6—®³%³—ž0&¸J‘ 'Z $†÷11`L´p"žè•¤$dšR|“ë=1`|“ëOb{³$ÞÇÄ€ñÍz*%[HôJ’-ÁÈt))))))Ä-©$%‚ú—C߉ ’%Ä#ù>&˜ª’ŒJ T’äC % % % % % % %`Eñz·YÉÿÚ¤k7or^ˌɓ%£¼9sZ&Óã˜:DQ÷îRžì9¨h”&uê8ù”„‡щÀ@JʼS¼8¥O›V9eØV4F ˆ «áá\™2dp ›3ö"1:Zç.çâ×Âé9s7·¤dìYÍ7®GçãÇ1tîß@ºu—ræÎA…Š¢4i¬?«Ñ£éüÙ ºÃòfÏáE…‹ySÚtÆ?«"1ªåvõÊ5zøà!eÍ–…¼^#IÔ³q硽±FhËЦ9Cžö>*nEEQÈÕ«t÷þ}Ê‘5+½]´(¥H®û'B¹]œ­ˆzŒºwî?|ç^ꄜ^^”šy9A"ßÇ/^RØå0»ø’íðÎO å7–æ"1¾|ù’®_½A—.„Pš´i¨HqoÊœ%³æþzèúì9r„|û}b“Çà­[É+s~ÊñóæÒœåË5ù¡ôÌ5š>¬WO“Í«n#†Ó–}û4éí||höÿFö2‹Ä`Ï_¼ V­¤¯§Mã8gŽA7o¡Á,ú@FGë\$ÎC{S—–=mÞâà¹Ýüƒ PŽføÍ¢EóÕäO—>}3s5ömhNüè1M?–þèoNÃòŽŸ:Šš¶ôѤ‹<…Ñ’ç=Û÷QÏv¦÷Jdg‰|V 7l`ÆÒɓɷv›çõ=Ú°ú€,štBìYGÅJåeìúk/WòÈK>-SzvÍ:ÿ r1”÷F¥Ê”$4÷u £šß'OžÐØaÕI†î‹zVcž>5ãhR«–y_Ù)V  ²ëðÖSÞG0~ùúuªóqÞî–f#G> kÞ9è_Ö:ÞalÎ^ ªU¯N™3f²ÊÖæ½{xºQ£e¢ÞLjۑԣm_ÞU¬Rž4©GI“&¥Ÿæ,¦ƒ¬38ôÓá´ä·Ÿ­Ê@ïDQC‚C©í9»uÕ¦uªQP`0ù/^E3¾™ÍFösRËvê ‡—§«’¤pØ«MÖ³—rhuë//ìû }Ú¡¥M“†çüqWzÛ·)ßÿc×N³’´óða® AùÚ¾h1eËl^k\½ÕëÖ•Vn qPÎlÙ¬ÞKD¢ÞÁcµí9«ýÚw ó¡!qzv"pÄW¦Þ©óøøÒó\Çíé³/úÆ[$†¬Ñ[éÖ· âEæÞzPír¦¤­›¶›•$ï"iâô1ì…mNÉ’›¦c:±{Ô¯äC‘Q¼Ñ2JIR@éQ)Û_,ã=»Ú jÒ®m¦Žú¼Qûz?« ßÕÊ•£åS¦*‡nÝŠÀ8Ðo"WZ5lHóÇŒ¥”)R¼Qwíf:¤Š’ôA͸J°Õ‹tJÔû}<°ûW`°dýÏæv§fÝêÔ¨ª/Þ„ŸG'Ï(Òãì)ó8ë¶iJßÍñ#e5˜ELð=Í™2ŸZ|ÔÌœ®'ΤzæHYåJ–¢/{ô0+H¸óüԨɋ »qÃ\œÿ¦M|`ç.f K—¦*eM£»â73öšG0¢¨ž­ÛÐåþôÍàÁlZR¿yVgl ^Í|ÚŒŽä5ßÀvJ¿û6ΟBx9Ñ£]»Î·ø+^ªµéÔÊÜP! S‰ÒűKî?à[Oûs£Â;l¦°©Eàƒééä)ÏŸ§¼˜fC4ãëánWì}~ô¨Çù+WðÛuôõåöWöÞÛ¨|޼‘l$ T¾Ò»šv'UêTfvÿÃt‹‡‘#1Õjßí#"„¶„)8Ø*‰ ·)I¶ÀÜ{húˆdU) ûŽåÙÑ«³$EIºvÅò”Ç[Ãf§Æ%õfÜÑá}=îo £µ²Ékízw¥Ý¿wŸß:K¶øÚ»wî’ò’-nšBvÏŽÞ7>ŒßŽžÂ‹é÷½•ù-G‹ö˜üF?žò>8~Œ×A—?¤Œé_M/{LÅ8Ȉ½õ§˜_7là¥÷iû‘ƒwqovkïc©wJr¦6l!Ø&`àüÓÜÅ|ÿ½÷+Qú §~­aTì2eÊÈ1)Y²f&˜6€`Ò ‚„L·­Ú¼™N35 ÝÉ_€öÔ­Råµüãá=pü8ÏW½|¾…|›y]€rgÏηê?Åcž(F’žEóílÏÕŒÖê\´<Ôåo\³‰{¬Á©PáTþ½rT½v5u«û·oÞ¦™>2•«UÒäAy‡žÄ<¡ lÞüûI³ùp÷»ß!LKM"0br™r¥©yÛftóÆM£aiî'êY…]NçaC¹G>6Ò]¦X1jQ¿K£.žò>ž æ2„÷ì\ÿåtøÔ)Šdíhf“T½BEjߤ‰Ó|šÊqà@T=ªYX¼~?D»l‰êS†ìëý>B òmåC×P;ŸÎÔwP/ ,{ 5ê¾Oó–Ì$(N¶Æ‚ B… RµšÚlsúv꯹ö3NÑ ‡k28…ñù³çfcíÑ“†s' ìÛU¤¨g5U¬mBü¾c‡†¿ hý¬Ùä/Ÿ&ÝÞOy#ïÞå,ÏZº4ëpˆY»u ­›9‹+ˆq2èœ ª-Ù„‡­‚÷³Ž&C`Ë<¢ŽE½à÷»Ù~”;_nZ0ãGš?c¡š­þ”‡¥E¢0Ök\‡/XBG|ËC©”`f ·oEÒÖMѱ¿M+/™g¸Jªg¡5+V¤s›(lç. ß»Nþ¶ÆÈo÷Ò©‹Ù¼<Ÿ”!й£Ç˜]ú-\÷xw“ŒîÆdy£0ÚªsK~DW©Q™öœü‹Ž^íj6@Þ =PÊ?{ú}Òy?üzü—ÜXR9§lá 7ê›áÜ ã§•óißéíæ|`Ï/XÈ cŒ(E`Ä4â´‰3¹±öçõ£e ~#·"ŸUTÌœ1#¥I•Šr±€ƒ=òˤIÞ‘Ó§YÜ{†@…15Ãjߤ)¹Ç(?ÜáòÍ ½Ã‘ðB­ü‰Â¨¾¦Á¿_ò+O‚±³Á@Õe:²/â}Äýíû›º¶îŽgéσ¿ÓŽ£¦áÐ9kפ Á<À…1›WVZ·mŸL}˜ÂÔ¥WG1q­Ù²œ·­À–5»@Û$ÅJ±1“г¤Kaa<Ò?5Aèè½Á. ö+P¼Ô„± 4hî$W0º“oGî­Æ×Õ¹#<é÷Ý ey‘ˆZkI—C®ðx$HG8üì¡ì9³óºK‹|î ÒÜE®`ꋸn=?³Áð®b)R¦ ŸæùOáÁ{±²¨X‰"J²®[C”¤+,`¨dáÂæ†¾Iß>\úè657Hs^9x—×áß}äo³Ë¿rN‰À­x¹)éFo]Åh4¿ÎÜOŒöÖ¹3üéq͵0“rT´„öY…»{çæÝyo ±:†ÆNÍÙ{Ïtª%I’¸Ôƒ5þ\Á˜=§7Ö¶,÷é“§æÆ ÆÜù :g¯cY®³Çz<«ÖîÆ– R(¿•e–”sFl]ÅXÒÛ›³¹?Ö{XÍ3‚L‚Çõ9#÷]Ũæuöòeü¡V0Bè)äÊû ŠçlÉ2%40ýT®bY®$ÁÃÖä*F[¼¯_ù;?…N™(>]§Ûœ ˇ¨ ñŽÆ±åG@Š16öo߉¢fl “ë·nñˆÛsG¦dlÔȵiܘ'/\½š¯g¦äYºñwÞ3ÀHSÍJZ#%Þ[QõæÓ•òDat¤Î]áßžkwlÙE¢µÏ*âa* T­fUs1éÚªºnäÛ€&Íœ`ÓXyÙÏ+Ët`h_¡§,zó±sç˜âƒÇ‰$#lÖ2cPËß‚e&Cv`ù ÓF‘Ñæ@‘â¦Ýšeë5xàíTwQ“B¬É à@F8‹Xš) ˆæ˜¡8 ˜Jˆ"ÝF’Æ¿ ‚B@ÈÜ9²Ó-6½qçNžV‚õZ†¨¢ŸN_¼Ø<ôy–¹¢Öíú1ϧþ[8n</TˆÍ‘×&\xé½÷Q[~Œq1ºšòåPCæ—EbŽþlI áƒö;Æ·sÙÒ+›beŽí?€”Þ?)àO$FGê\4s‘XJCñ>C@H(.·oEÐ69„F§Ï žæü?|ÿ“Ù.èìjݨ½ùœ²3eÞ$F†ß(65:°û W°w´1Ê%B·"1 eÜÂE>«cfÏâQþkT¨@E d¿ÇܦRYðëKA"1¢-ñ­S‡·Ñõ»wcîã>|qòµlMâ'!p£h‰Qá]  /ë…ŒQ”{c+ò}ìܳþr<­Y¾žpWgKvÀvpËÆmœØ[6ð©§fGȾHŒÓüfÒú¨ ¡’.}Z>zvúø¿G¯þÝ©f½êB0¡PÝ”$¬¢<èãéG6Úƒ^BX›­WÛ¶4ˆEËVÜõq.…*ü½e¸åZÅ(elþa!õ;–—eH@ˆqåO¬­­¤”¡çV$Fð¹yï^sL(…o(†øl…ØWò걉ё:׋­2R1ƒÕÞºÓ²ŸW”…9ºSvlî¾;7æVÒS¤|õšX† Pò`\ÐÀaŸòˆ°M‚€B˜‚úòƒ¨ 6‚Db´ÆòØ%X`7`‰|V±à)b¯a”?…š×¯O£û}ê´û¿R޽[‘ÁüÑcøÔ<‹áöÂÈü€Î™}hG~,úO4Æ'¬¿lãFŽDî ‘ïcû®m ï߬Éó¸Í#Ö3S¶ý‡öÓ¬E©œÓ{+ãÛ,`æ³±u03³ /¾_}J›54§‰ØI†°þ³Uðá]»è$ Ù.6°£­|êtë»,¾Q6…×+³¾S pùú5Ê™5›.ÃÝgÙÈͩˡ…Q-O=öÏDFÐéË—ßhŒ'¢£(ð|ÕiYÍn‘áY…õ½»÷) ›&Òs,&æ °o!x] q•®ž§ g.y FK«0ðõ#_‘QgPŠ(àŽê3ßS$F=•#3ÃNìˆ~3½•‘ðs'‰Â…OY‚ÄH|I¼™¼—”€”€”€”€”€”€”@B‘€T’JMI>¥¤¤¤¤¤ •€T’ d“µ§@IDAT·¼™”€”€”€”€”€”@B‘€T’JMI>¥¤¤¤¤¤ •€T’ ·¼™”€”€”€”€”€”@B‘€T’JMI>¥¤¤¤¤¤ •@¼!‚Ylâz¼¢$l×fh¥WÙlî)×+[› 9»@®c|·VøV¶ñåõŒsÖ1ºÊ¿r½²u/Ö‹±õxóäu»Q¸V¶v]äæL!—Ã8ˆ]ò¦Rèå+âìE®´fÎð˜ÞGë‘–ç^£´9ˆíeû´&†6çjHüߌxƒI.[²„¢ÙzE’¤¤¤¤¤¤¤ÞT 8L²H¾|GÜNht&ú­ÄèyH õèLTqÏ«©ø9r&âvü%zÞÙÄð¬&ŒÎD÷¼§1~Žœ‰*‰žwV‰¸m‹³Do“”„0á")¡K@ÖcB¯AÉÿ›$ù>¾Iµ™¸±$z%)qW¿D/% % % % % %`KRI²%™7(]öêÞ Ê”P¼äû˜à«PHDJR"ªl UJ@J@J@J@J@JÀ~ H%É~Y%Øœÿ¹²!Á–ŒK x¤äûè‘Õ"™’°*xã$Y½â5‰×nÞ¤gÏŸÇÉ•%ùORÒ$IèâÅ)}×zcSÊ»q-œž[y“±÷1wÞ\J6ó6úa4?Dw¢îRö^T¸˜7¥Mgû}T.¼zå=|ð²fËB^ì:#IT=FܹC¨Gk„¶ mšQ$ £šÿ[QQÂâݽŸrdÍJo-J)’ëþ‰PßR³/#¾+÷>ÔÜÇò §—¥N™Ò2YȱŒ £/^¾¤ÐkWéòµë„ýÂÌ;Ý›ýŒ&‘mÎK†ëúÕtéB¥I›†Š÷¦ÌY2 …¨ë°çÈòí÷‰M†ƒ·n%¯ÌYøùG,þÒèÙ³è‡U«4ù¡ô|?|µnÔH“Í«n#†Ó–}û4éí||höÿFö2‹Ä`Ï_¼`2YI_O›ÆqÎ1‚>nÞBƒÙÑGm Da„r4~Þ\š³|¹ê|î¨Ñôa½zšt‘¢0:ú\‹ÄxhïaêÒ²§Í[<·›+5ÈðøÑcš<~:-ýÑ_“?]út4~ê(jÚÒG“®>س}õlgzï¡$¡\£HT=âY-ܰMK'O&ßÚulžï„§¼ A¡¡ôÕ´©´ýàA%‰o‹äÏOG×®Ó¤‰:Uí‡ ¦C'OÆËö”¡C©W›¶ñæÑã¤(Œàíð©S4`â ¼tIÃjÍJ•hþè1”'GMº¨‘mΉNѾÃ(ìòU ûƒ¾þŒú î£IÓó@W%):ÆxÒ+Kª\¦Œ†Ï”)RRÆtéÍi[÷ïç RÁíÐÒ¦IÃï=øã®ô¶oS¾ÿÇ®†)I p½1:ò\+<ˆÞvìÑž>û¢o¼·ñ.R&NÚæ”,¹i©»®~%ŠŒˆ¢ƒlTÊš’ôË‚e¼gW»AMÚµÍôщ÷F‚Nê] ›øè,Ÿ2U9tëVÆ~ùǵUÆ4ÌXJ™"Å…qp×nVñ Cª(IÔŒ«[½H§D½ëq×߇yæÎž6ΛoþžÖ¯Z*¶nE{åç4Mѻ͙=e—þ‡mšÒwsü( 3·Ád`ê„ïiΔùÔâ£fæt~R§¿¤:•ãp1˜ïîÒ¼¹¹BQ*½нøþ›6ñý»˜$$T,]šª”5õvïæy<éÏŒà»gë6t`¹?}3x0›–;Ϫ—œÁX®d)ú²G³‚`ÛñAšœ°7ôbK×rÁèH^]™t±°â¥ŠQ›N­Ì ŠÃT[‰ÒÅyÉî¿z•[Áö` ›¢C> y{:%ÔºqD®Ž`Ä4:  _w»‚d/NG0Ú*sþÊüTGÖI‡ý•§‘#oEFqöß{§¬æ{š&U*3¬ÿ0ÝâaäH›ƒ©6Pûni!´Y LÁÁVI¹MI²FvGNŸæ§Jx6gÙwì(ßG¯Î’%ébØËSyl #˜:l7–ôHÆ`*>ŒÖй÷ÐôΚ@C`p£#y­ÉÇ]iwïÜ%¥q*Z<î4è·£§pÖFú}Eoe~Ë]lºtß„Z7Ž€¶…ñÀñc¼˜.~HÓ¿šzu¤lOÉk £5þàóë† üT6{‘PÈÆwK”àÖÿµÛ&áÎ3—.áé5*T`æ,éø¾§ÿÙjs;¤L™2j dÉš™`"‚i€2ݶjóf:ͼ—0t[$>ÚS·J•8ü£"#îÞ¥˜'1tîâ%š¸`>¬Äì™0— ‚Œé7†-Iñ˜ƒ'Š‘¤'FÑ|;ërlF4XŽç"¨^¾‚hQÄ)_F{žë8ŒLظfû7`‡T¨p*ÿ^9ª^»Zœ;‚ï¨È;ô$æ ]`óýßOšMðv{·â;„é45Þ„6l¡2åJSó¶Íèæ›êӆG€€]NçaC™GRÊÇF=Ë+F-ê7piÔÅSÞÇ3ÁÁ¼žà=;×9ÿÀF²vF¾Õ+T¤öMšhF&Œ¨TQõ¨æ}ñúuüì²± †ú¼è}½1VgJP›ÆiõŸRÃÝiH·î̦,ˆ;9ÁDe&3k1šôns AQB;[5Á›çÜß‹Q}#ûº*IiR™Ü¸a|ŸšêU­Jþln?•ÊÕrËþ}ÔŽM-© ŠÐb¿oÌ/§zÚ-]¬‹:¿â:þ :Z,l_FaÌÆì¨7‘'Î7Í5Ó¦våÊ¢Ea._$F{žk3#wRdžU@˲—U£îû4oÉL®8),À¦¨o§þÊ!ßæÌƒf,œ¢™†{þì¹ÙX{ô¤á' w‘¨zLk›§‚ßwìÐÀó[°€ÖÏší´{µ§¼‘¬ƒ šµt©à³vëZ7sWãdÐ9AT=Z² ¯EïgM†À–yD‹Ä{²|9sÑ´Å‹hꢟÍv,^Lùså6‹ÞÕæÔk\‡/XBG|ËC’”`æ·oEÒÖMѱ¿Mì—Ì3\éÚºÕ¬X‘Îm  °»(|ï>:ùÛ?p çî¥S-Ò`(^°õnÛ–àÆ% týÖ-ªÍ¼-ŽŸ3yx)Z8÷xw“Œ¢19Ús5 #¼Ý”aï¹ÌMÕ(/È[$F{žkÑuŽò«Ô¨L{NþEG/¤“WŽÐö6Ó°1Cø­÷îØOó§/Ô°Z§žíÙÈ/A‰…_¿I­´£Oœ1çõÿeŸ¿Hm;·¦wÊk½XÍ™ ÚUp,ús …nßAáûöS`ÀfZäçG虇^»F½™Ç³ä)ïc óL•/UŠ6Ì™K·ö ˆƒ‡Ì†ê°WÂ;j‰ªGKÞ1ÚÅq£ ¶EbÜÇŒ³—ünšBD‡S¡F={r¯iåXôVT›Ó{`‚ œHБ«]¾=ë@ g½R1R.‚’°é,›]‡wí¢“lÚ¬‹Ó “ú‰&°eïg†É¶èÆíÛÔzàî~ WÿëãÓm™ß30@éBºš®^E_|÷umÑ’ÅW®>e×þÙÈH:u9Ô­ÕŠ Â4Ü6Ñ›Ó#NÒ™È:}ù²GaÄ4FN9\¿Ïs7»3[O­G`±ö\[«ó×á>EçC¨N˸Se¯»V}~î´ÜmÎÆÝkÕ§4û·ÂoQ>áÁ%1Üýבd²^Åx>#¼F“µË5äÇ®ÄIºuò¹âQÏ*û·ŸÙñøôîÍBþÚNY2eRŸ¶kßSÞÇî,îÜZ»nò—CygUÍ|¿qcyHLÝŒê×O}Ê®}O|1\©Mk>Ë1aà êß©“]XleòŒ{ÿù‡š~bò^;z4uhÒ”®0'˜OY³ Qˆw…`ÍŽÒ‰è;¬Í¹äÖ6Gi+Ÿ=}FÛ¶ó)·Çb(_Á¼T®bYñùÞF­XBå+½ë(DºEgORïØ÷Ú²]G’, WŽacB,>ÊÅ"ŸNlêé"/ìU` €ÞÇ–„± w{(¸‚Ñ“§ë…ñRX{œ€Ÿ§^Õx¬=×êóFï¿[¡,¿%¢dÇGÙsf§‡ñ,˜ó¿}ó6;|œÛ)ÁV©jÉZTÌ« ÿAA¡§‡´ã bÉ3 þQ`¹<óÐTè&ë`¹“\ÅøVF“¬2í¦Æ’?W.~ø ZŒ‡ú^ñí»ŠQ]ö¶Ìf š5SŸr뾫ÇÎÃùGPÌŽM}¹÷Wܹi-›*-áíÍGÎ&ÇäJ›£ð"e òiÞ˜†ŒDpù¸w'*Z¢W§ÛAºÚ$Ùbð X*Yø•Çš­¼ê%I’ÇÆkõ>ðÝGþ6»ü+×+¸/7%Ýè­«æ×™ûéaù›ôíÃñ?úÀ‡MÇr†a×èÑsÖžkkùŒH»fRŽŠ–°ã}T-I’Œ-Q‘=§7Ö¶äóé“§æÆ ÆÜù ¿‚š'QõÆ– R(¿•e–”sFl]ÅX’}@Aûc½‡Õ<#È$HqŒQŸ3rßUŒj^g/_Æj%s¬‚¨>ï®}W1*áeŠ×@€ý/‚:# 7<ãÜI®´9ññ½~åïütÕïQú b<4uIBp.,¢&ľ7o.ORæþ¸f5W|0ªÐåvú/‹ù!挕%L`¹Z¸z5_ÏŒ°¿¥ç=Œ4!üº$ £¼Û{Qo߉¢flÙØ!â6††“±‘Bw(ŒŽ<×¢qïØ²‹EkßÇëa×iÚÄ™üÖÕjšìq°ìç„åEÔïãSö>þ;çnL­ÁiíVÿ8¿Ëfó2‘ç'LÍEÿ‰ªÇƒ'Nz5aÉ™áÓ§ñ$ôþÕŠ¯:ŸÞû¢0ƒ „)åC‹cL…/ÿãìrÛ=¾#øOF…m`BôiP¯¶m”dC·¢0b´´4Ö&I…5ëÖmÛÆ•ÀÌÊ9Q[mx…³ˆ¥eÐ݇hÌÐ JO»ò­ˆ?ÝF’Æ_ñTCpÀÜ9²Ó-6½qçNÎ7*rˆ*ú) aÌ e¨{Y1jCA|@Aj·Å&µjóaChÄï}Ô–pŒÅQ1ºšÂæÔXlR$FàèÏ–$Q†ð÷;†$æšëO›beŽí?€”Þ?)àO$Æé‹›‡»Ï2÷ãº]?Žƒ`á¸ñT¼PÜå/âdt!A$FGžk ¼öR,¢xª!Œ?”œÛ·"hÛ¦íüÚ"Å SŸA=Íå qƒ17ò½÷~%æÍ–œì>È ·‘i´1漞²#²ǰu%±æbÌ-Xuþ³÷pŸº~¬5h‰Äˆ¶Ä·NÞF×ïÞ;Ð`qrØ)? `E“HŒ ïJðH8•(dR*”sFlEbD¬§Ï'}à ·ç /Âí $Žâ• ;^g×tD6"Ûœi~3iýŠ T‹…"I—>-ßvúø¿œ½^ý»SÍz¦AŽðkoÞdcÙÊ|-4”´KÛáB%%æént}öb0gë!¢+ŒÆ>e®–sFþOÐ ñ“  ¤3Áx\%¸ñÃÓ⇱ãHW ñ–6?($”P6òî1.¦õ5•°…áué·Yïð&S¸Ü|˜8‘÷â.²ð X¯„‡ÇøaDÍ™áïÛÑ-6ÜênŒ{Žþc^lÿ7™™å¯“o3ª܎’§Ô£#ϵ£ß=¦ˆÈ»T¨äë§²’3%Và™ :ÇÞ/憕³»û']ÈoÆ86<ý*Àâ'…³XGgNãÓfˆGòðA4ŸZ›2×Þ·WIÍ?ÈýyÞ¯”魌ԕï,Eß|H·YKw?«p׆"‚µOÇÏãmÞÉæõëóP&EزJÎ’§¼à¿Aµ÷ys’´`½¶s/rЯ™ë¨~ŸRÒØåÅê)ï#øÆ E¨„Èâ–Î?ü„ž‚±ûfæaƒ 'Ϧƒñ¼ž áˆà9>‡)ô9œ0ÚFáÏbX›sÇím‚Lbj íÒ©c§ N%… ¤1ß .½:j¢p;Z•ÑQ™½eU`"k¤»w†Ä``}—-+’EáU¦Ì¬ÝiYC~ƒ)KpÿöbááS¿Æa._¿F9³fÓe¸ÛÑmÉÊÙtg¼iFO«GGŸk{êÖï6Ô# ®ïݽOYØT˜âfë~11Ox`H¼Y³g¥Tª¥ l]£¤?~ÃGta`é,9ãÝ&òY½sÿ>OËbNåfAõ˜öÄ÷ÏëUö}+c†×¶ÙöÔ­'¾à[½T‡=8âËãiÁë]ö¼b6"ë eÏ’Õåg¼ÛDµ9/ž¿ ë×nì±f[š´i⫻ϽλM·é6…#¸ëÁ›?{­w¾×÷Œ•²0bU8ß«8Jº‘[ÑÄbë^c\É8ò¬:’7îôKA=ÂK ?{(uêTT sïWš4©í¹…îyD>«0ðõ#_‘Q!x^±8³;I$Fàó‰øà±¨x-º /0Šhs°ð¶²‰‘ØÜc5k$By/)))))))'$ •$'„&/‘xó% •¤7¿Ž%B))))))'$ •$'„&/‘xó% •¤7¿Ž%B))))))'$ •$'„&/‘xó% •¤7¿Ž%B))))))'$o0ÉeK–P4‹H-IJ@J@J@J@J@J@JàM•@oeÞÅLA¡@ö,`­ð„tû=eëIŒ ¡¶ló(ëѶlÒY ©¶ló*ëѶlÒ™ÄP¡lY²‡´ «ë(^%©pÞ¼t2(ˆ)¹Ô×¼QûIÙ2(§.‡JŒ ¼Ve=&ð Œe_Ö£¬Ç„"ù¬&”šŠŸÏdÒs=ÇV.i“dK2oPúôß„&ñB‘õøfÔ½¬Ç7£%ŠÄ!©$%Žz–(¥¤¤¤¤¤”€T’XBÌž„L¶e ‘wÉó+ Èz|%‹„¼'ë1!מä=±I@*I‰ Æåðþ›Qɲe=¾(¤Ž¤’”pêÊiNeÏÕiÑyÔ…²=ª:œfFÖ£Ó¢“J .x½ÛœáæÚÍ›ôŒ¹Ô[RòdÉ(oΜ–ÉšãËׯӃ臔-sÊ™-›æœrW½””…'x§xqJŸ6­rʰ­hŒAWÃéh”)Cð)7‰ñqL b^“QÌõ2OöcšÔ©•[¶‰Ïé¿.0Œ÷ø³\¼`AJ÷†>«¨0{Þ]Q+ª#îܱ錶 mšQ$ £šÿ[QQrõ*ݽŸrdÍJo-J)’ëþ‰PßR³/#Þ¿ûjîcyÓË‹R3/g#HF…ï/_R赫tùÚuÂ~á|ùÈ›ýŒ&‘_2\aì»JiÓ¤¦…¼)ë[o …¨ë°çÈòí÷‰M†ƒ·n%/¦Y£mPëø)¯,Y(xËVM¶höÑé6b8mÙ·O“ÞÎLJfÿo”a/³HŒöüÅ úaÕJúzÚ4ŽsæˆôqóÌ¢Da„r4~Þ\š³|¹ݹ£FÓ‡õêiÒEˆÂøˆ_={«ÃUöñûá#¨u£Fšt‘¢0Zòüºw×2¿žÇ¢0âY-ܰMV—NžL¾µëØ<¯ç QñÁùjÚTÚ~ð ’Ä·Eòç§£k×iÒDˆÂØ~È`:tòd¼lO:”zµio=NŠÂÞŸ:E&N ÀK—4¬Ö¬T‰æCyräФ‹:‰ñÈéÓÔó#™"xMÃþȾŸÐ—=zhÒô<ÐUIŠŽ1E熒S¹L Ÿ)S¤¤ŒéÒkÒ”ƒ˜§Oé‹ï¾UãlŸ>{F-ôç{Á›0žŽ=«+ëŽÚ²ˆÂ°gW 04«[—0z´’ÕF]º|5Œø¯ ·‹Ñ»­ÂDaܺ?W”ç4Cº´´lãF ¾r…zŒÁG?‹±Q%gÈSêQÍûëÞ]u^û¢ê¸jR«–²kÞ+PмïèŽ'Õ#Fë|Ü…¿ƒ¥Ùȑڗ}P1 zêüyG¡9_T=6ª^2gÌd•¯Í{÷ðt£FËDaÄ`ËþŸñ:¬V®5eÊ{Ò¤Ih&[1JKŸÑ£èù ¬Ê@ïDQ/\¾Lõ»wãì~P£&Õ«Z…Î^¼H?¯]KæÏ£Ü9²SǦ¾zÃáåéª$)öjÓ††õ쥾v;Ÿ);Ðñ@[Žáâ‡s Ê×öE‹Ùt\f^fãê5¨^·®´rs0ÀæÝkp"ƒÞÁBµí9'ýÚw ó¡!qzvN°É/qÖBoŒÞùò´þO;t`C¥i8oƒ?îJoû6åûìÚi˜’¤ÈRoŒ˜5b$uðõ5OÇônû½Û¢9ÝfÙž޳J’§Ô£";l_÷îªóŠÜ×»^ñÑY>eªr¨ËÖ“êq ßDþqmÕ°!Í3–R¦H¡ Fg Ñ»w5}X-ùA‡TQ’>¨W ¶Ì¯ç±Þwý}˜×aîìÙiã¼ùæv§~ÕjT±u+Ú{ô(?o¤iŠÞ¿ýq!¯‚>ð¡cÇ’²H>6í=vÎúîÇ©C“¦æt=ëËí†Û˜¿Äô*Pkä¿iOع‹YABBÅÒ¥©JÙ²ü\ÀîÝ|ë‰ö`ß=[·¡Ëýé›ÁƒÙ´¤IôD<Öx²c¹’¥ø°¨¢ ¡Øv g »qƒo=õÏŒ°ãèÒ¼¹¹¡<Û襃î=xÀ·žúgF…wGò*×xÂ6¡òíˆììÁˆi6t@A3¾îvÉ|ÈkF[eÎ_¹‚ŸêÈ:3°¿òT²ã­È(Îþ{ï”Õ´;iR¥2ÃúÓ-Jö`ÄT¨GëÖE¨Ë‡Íy:YÂÂÅ|?Ü®$˜1ƒƒüö‹/(K&ëâûŽåyЫ³$EIºvÅò”ÇÛƒÌN6ŒKê͸£ÃûÎÜß^ŒÖʾ÷Ф8dõpÅÐYŒ0U^òÞ…­‰À®4O«GgåaX™ÜÍ·§ÔããǸ”»|ø!eLoÝB`5¸\´³õ§˜_7là÷ïÃFy=™ìÁøn‰Âú¿¶qÛ$ÀÀyæÒ%<½F… ”!]:¾ï‰ö`TìÞ²pb¬L@Á—ÅèB¦ÛVmÞL§™÷†n‹ä/ÀG{êV©§~ö±a@TlùR¥¨=*»qëVœ<Ѐ1MÂp¢%)sðD1’ôÄh$ߎÜËŒh°?ÎÙª^¾‚#ìé’WF4PwïRÌ“:wñM\0ŸwWbvz˜R6šD`´çÝ5§Œàv9‡ e6I CûeŠ£õ¸eÔEoŒg‚ƒyÁ{v®ÿrþdí(Œ|«W¨ÈÚä&š‘ #êSoŒÖx^¼ÞdŒŽvÙXÃZ>Qizc¬Î” 6Óê?ÿ¤†=ºÓnÝ™MY7]‰ÊL6ýo4éŠ%èÅ ÒÀ'<ν΋Qs‘º*IiR™Ü¸a¤ŠŸšêU­Jþln?U¬«% ‰5Öž2t%c5ROO¤‹µcQçS\ÇDG«“…í‹À(ŒY' 6ãDft‚'MíÊ•äØñËDbܲµcS¦j‚‚¿ØïC?:¢0Úûîªñ‹Ú…1U¬mœ ~ß±Cþ߂´~ÖlÃÜ«EaŒdн# £³ƒ"/ÌtiÓPé"Eéfd$mܹÃ콈°"ȺfâäjV¬Hç6PØÎ]¾wüm8—÷Ò©‹™K†U:Ü»2÷ö o¿mN·ÜQ ´÷xw“ŒîÆdy£0ÂÛMöžËÜTò2^‘‹,D½Û¶%„§@çt’Öf^DÇÏéë¹È ·ñ' £½ï® ¶tM…vsAn¡Ðí;(|ß~ ØL‹üü=sôZ{3!£HƘ'O8Œäo˜3—ní?@™ Õa¯„wÔ…Ñ’wŒ¶@ñE§Åhƒm‘1²»äwÓ":œ 5êÙ“{M+Ç¢·¢0~Þµ+·ëĬ: ¥›ùr§­¿þj†”2¥§]•$ IãáÃü6Fx0D6 Sg³Aö¦Ý»8 L›;Çd¬O\%dVÏ¥ZNCÏä•Åã;9ú3#¦1Ãä÷ù`zïw •€HŒà6ùË¡Ì cï‰ã Ãm¼à]¿þš…¬0ƈRFGÞ]#*TF…oôfΘ‘`›‹Dè‘_&Mâ§ac[3#HÆÔ±†½0uÀ(.FùÑQAÈ4ƒôGbK^¢0ªï‡iðï—˜>ªð 62(ø…qï?ÿЇŸöãíËÜÑ£éŸ5ké ¶Hèœ5ìÙƒ'VËBÔ¾(ŒÙYçdç/¿òŽ ¦¾íÚѤ!ChÇâ_ÌN1ÙéºN·Ù°“¡¦Ÿôåïžc¤¹‹\ÁCWGÞÝ„ˆmŽ-*Ï<4Âp¿-g%È­+õŒo1¤L»©yÍŸ+?Ä î$W1ªyGÐSÅ ¤S³fêSnÝwãX6àBPL%VPܹi-›*­Ù¹Ÿ±A˜x‡»‹\ža㌎ ~ !x/bzJvÞ)F)ÏÚÖ%é X*  ñZ‚F*€qÞ;O^žÖûxÀwùÛìò¯\«ÄUR¼Ü”t£·®b4š_gî§F¸{6éÛ‡÷zóbüÀAΰ"ì=0ZcN½$Iòäɬe1,ÍŒŽ¾»†²¸‘+-ŠÒbI…ò3Cnw’«Kz{sö÷Çz«± È$HqŒQŸ3rßUŒj^g/_Æj#„žB®bT¦Â—ÿñ?#HF…w`BôiP¯¶ÿgï:à£(ºø£Bh¡†Ð¤ H“ÞA HïÒ”"(¨ABQŠ" %€òI•ˆˆ ªÔP¤7é @-¡é7ÿ¹ì±w¹ Wvö.0ï÷»ÛÙÙÙÙ÷³;ûvæ½7”l]·¢0–aÊ.¡åÌ&I=Xó”ß¶lá `k¯€Da„³¦GÕ6Ê; S Óß0ëEš)Iã¯xô 8 „ßäÖçÛ9ïhÈ‘V¢Ÿ¾\«ú H¹Þx¯#›3oÀG…âšÆì?ô˜_‰8†°%I0„Úsäß±hä!I2ÇJÊ×?(àO$ÆÌÓBî>ÍÜöê™ Á&&sñLVÈÉ ‘aè '(CuÙK£F0€…bÒËW$F'ůÙé"1Žcna»¿bÅèÁÃöî0N5b­A=H$Fô%mÞz‹ymçK>ÀÑ/£ÕlMâ'!¬h‰Qá]  G ,Šª7‰Äˆ)ð¦Lf†Ûk¹Â‹p;°T¼2a¢¢Ç:ƒ"1N ¢!RÓ7ßä¶ÌPê{¹á=zP“Úµ…5©fJVQÞ³'-d£=Ð&B´á÷™§Ïp6ª¸ë+ÇÌ·é’VÕ6÷rB¾ÿ³pä¨ó« ĸÀò'–ÖV2¯[‹}‘Á߆ÐPcL(…_|!à²b_)«ÅV$Æ InÕàSQ–ÌyV ñÍóµÜ‰1`À@J›& ùTîSðŽéãñÑmÄS$FKmaíÙµTV«<‘±à)^4eÁO¡öSààtsÿ‰˜æ1¯RL=ÁËnÿ ŒÌíÞÙ våû¢ÿDcÄH.ÖO‰qHIN"1öy÷]>H0ù‡ï¹Í#¼O‚âûYÿº ‰ïzÝ+í|ðâ 8Ú7j¬À²Mùrk2Ü}šÜœˆw+ŒZ·ô©Ø:Ét§vÔ£»µcs¯FpT(üy˜—:gÉÝÛñEÏ®-øÝ­oß½ËGÄ33oÝ,È¢ÓÃîØŽ¸_1eš#[V»ûlKíêní| õR–ø¶'ÏÝ0‚÷;ì~ÅlDÖ,^o/ggXÜ #Þý¸G¡ô"°«zy+{Úͼì™û÷¸žÓ¿óC|_³‘$¥v̸ÓQï6¡ÑK~B¹®ž[ÑõÄbíZ£5É<ÏOé^EgŒP®&=Û1%yˆ”ƒHŒeq#_‘Ñ6¸_±8³+I$F-•#gd$#ø‚Ç¢âµè ŸÎœ+ #ÞýæÞíÎðië¹ËR[KËrRRRRRRR¯ˆ¤’ôŠ4´„)% % % % % %`Ÿ¤’dŸ¼di))))))WDRIzEZ”°ORI²O^²´”€”€”€”€”€”À+"©$½" -aJ H H H H H Ø'Cœ¿Åk;Ÿ`_­©¨txÒ¹ˆ¡Y L%ˆ³¯ÆH.=ÙŽÚÞDzß«g’îÕ—¹Ï‰ˆºú Ü«¯Âó(1jÛÛjӻϹ¤ZÑžƒI.[¾‚ºvhKLË<)))))))­$àP0ÉÂEýèü™£T¥A­øp»z"öÑ™ËìŠFív ^À#QS_P¥Û–Ý®IbèP|"…]<%û‡¤ç>'ÉçÑ}ÚÂN^…vT"n[““´I²&™—(ÿ?Ò{ó%žA‘íèF!Y‘x%$ •¤W¢™%H)))))){% •${%&ËK H H H H H ¼JÒ+Ñ̤”€”€”€”€”€”€½J’½“奤¤¤¤¤^ ¤'É ܸEOŸ>Ivjºtéɧ@a“üÛq1ôðÁ=“X)¯õVF<wï§ÆÆ'OÊäá¡5‹õ‰À¨\èÙ¿ÿRøÕ(Џz.Q¸0ù²ŸÞ$ã¿ W${/ž§Ìž™¨Lq_Ê•#‡Pˆš>ÿÞAƒ{¶°Êðæ½WÈ;W~<1ñ!5­e½¿™³’4ik¬ë!‹×ðQÚ½cƒ1‰–íºÐ“æSúôLòEíì:xÚ dµú°Í›){Q€  ”hÚÄjÙåß|Cm¼erüé³gôý/+é³éÓyþ¬€êÙþm“2¢wDa„<&Î ¢¹+V˜@È”‡ ±Ô®Q#“|‘;¢0>LH À9³YþbÂ>0~÷yù7kf’/rGFsž·ìÝKþÆòì<9sRØ¦ÍæE„í‹êsìퟄd‹nG¼p>þ-mÝ·ÏFÉ"EèðêßLòDíˆÂØyäúûøñÙž6j½ß¡cŠe´8( #xÛâ ýêK:{é’ «õªW§ùã¨`¾|&ù¢vDbM{ƒ¦ò%K: צóEaܼgWŠ,Hï4iJY½2ÓOëÖQØ•+ÔwL½Vº4•b£JŽ»´£šwÜã=U¥kZTŸcOÿ$°¨{|cð­ž=ø3XµdýT!öBÅ(è‰sçDC3Ö/ c³:uÈ;[vãuÔ‰ ¡»ø®^£e¢0bð!ò6¬ýúëÔš}x§M›†f-[ÆìcéÏù ÔÐ…¥Ea¼Aûôæ|·¨[ÕªI§/^¤W¯¦/çÏ£ùòR×ÖÉõ-€jª$) uè:ú}ð¹²›âöõjoÒ´ Ó¯nóöïÙÊ$(_‹~ÙEÞ9só"u´¤ÞëцµÁ4tÔ$ÊÇtªË¼-÷ßïÐF÷{ߦ*q㮘öí ËÖîÒ™—ܹ ¿œìËî…h\@kŒ¾… ´þºtaC¥žœÛ={Qù6­yúÏÛuS’QiÓ£³ÆP—6mŒS©ý;¾G•ßnO·Xg¶ëÐA‡•$…g{·ZcT_~p0ÿ²ÃËhÓîÝêCº¦µîsæm韔²¢·"Úqؤ¯øËõݦMiþ¸ñä‘AŸyk²Òãˆ^†«ùõðAª(I-ê%ÿ€5/¯å¾ÖwØÏÛ°@Þ¼´n›UI2áh\«6Uó—BæÇñqªiqêÂ8ëïµhI Ƨ4iÒðýÂÌdeüܹôõÂ…Ô¥Ukc¾–8S…ávÈ?qÌÝû4*HȨP©:UªR‹Û¹uߦæ¿~þhïŠ`šz›'NK¬Xª½Ý¸‰Û¹éÖÞ£G¹¨êT©ªˆL·­Œè bîÜ¡ÄG‰tæâ%újÁ|>Ü]Ù°aZJoq7ÂG§\¥\9ê̆¹¯ß¼©7,“ëiÙç¨+~Qÿ¤.+:­u;ž 3|XÂ{6(xÁÆ2o\ùÖ©Zµk+“‘ ÑøP¿Ö-ñ¼øwƒ1:úìJI †¥r¢ò´ÆX‡)Aš7§U7RÓ¾}hdï>̦ì<Ÿþ†íì,6ý¯7iŠ%¼sK/nÞÃ8ö"/F““ìØÑTIʘÑ`gr%ü᧦Zu›0Û£Uäáa„‚Kÿ¶M¨‹Ò‚YiöÿÖQá¢%èÞ½xã1OÏäsª™2ò$F Jxf4¸ªÃ?55ªU‹‚™íQÆ$l“æ÷á¾vÛ6uQš´`ý>{ŽK\4M±°£'Ư˜Ñž4 jÔ°À˜,‘7íÙMØ”©š`/°xÒd]_:¢0"üÅÈ$cíi£FS:¦ü»ŠDô9Àbkÿ¤nQíËyÐìåË“Áø™9T¬Þ¼‰~›5›Ü%+ q†(ŒælÂÃVÁûa׮懅î‹Ä{²Â>ùiúâEôí¢çNLÛ/¦"ù Å¥®\Æ–Ìn Š<¼0½2{R…’~t#6–Ömßfô^DؤiïV­f ÙFÛESèñXúã¯Ó4lÔdÎ÷¾Ð-´hþ×F žž^´qO8m=pv£õ¡—hÒŒeãì«‘—)pT^Ö`že8ísw5Õ«V΄¬§Èí;(:t7ÿc M6Œ³Úo-2²Û›ó7QøÖm½{]¿MšDÐî¡ùög^z½^Qza„·›2ì8Nט,"1–.VœúwìHZ¶d^›¹kl¤¥ó":zÆqÏEwiGx”ÀÕ¸ MQµ|y=na«×Ñçàb¶öOVÓð€¨{51ÉãkæÑÍ={)fßßF'Ø+áÕƒDa4ç£-øhÅG‹ÞÛ"1bdwÙZÃ">8jÖ¯ÁH]/…ñ£^½¸]'œ_ðZ¡mjÔ»Í\ºÔÍÃCŒÓAZã4H`: SbY²f'ŒðÀ]¿[ßá4px ¯}ç_ëL®’+w>Ê–Ý›2fò¤Ao¼öš®b‰ܾùdó˜À¿Ä¡Ãpx¯Ï>ãSÈz€ÁQ'Í5k§/L|¸0jÝç(¼ÛÒ?)eEnE´#øÍ”dØ‹éRŒâbîðWƒfÖáHx¥þDaT_ Óàß-3¼TáA¬x©ËˆL‹Âzèµû`0ï_‚éЯ«éÄšµ[$|œ5í×—`Ö ‰Â˜— ,l_²”2@aØ©M9’¶-^btŠÉ›3—ˆš*IÖ8¬XÉ0r5*ÜZc~ÙŠUŒéØ[ѼÄè(†í›SÔ•K< š+ ö& ÅÀ,%^ª”-g<Œ!ÃÔBZa¼Iï Âa#~îBZaTãrDMׯ]¡y3 Óm5j74:vx/aÍ%5%°ˆÝ3&æY+× Ì™ n©ÍÛÜ4Wý4ŸÔÔºÕK¹8Fšª×j ®JXȰ¯h5!¾Ï¶ÜHm|¼ïØ1‚R &¼d>Ÿ1gá BýòT—seZÆ[·ã¨-›¢Á0"nchØUF¿¢0.üuá‹Cû =bQ©g,YÌw1U«,[£µ6Hö6ÿ­œ>ƒÃ€½Ž}Ç–ÓуDõ9öôO¢qŠhGð 6‚ */Zìc*|ÅŸ"I°1уDaTx&,™z¿c%[×­(Œ--O²IR@ÁÛë·-[ø®£Qþ•ºlÝŠÂgóXO; S ¦+0yEš$=fîÎ#ús>ë6lEùò¤Ø˜´}‹Á˜Ì·dYê5à#Ž9ÓÆð(ÚUߨGÅ|K3/·û´sëŸÆoÇNþÞX¶~£Ö„ó/…¡÷ZW%ìÃ^ £K OÆÎ´¸®±X‚AñZBD„B¿É-ì·ó+àf©Šð:ŽÚÃôæ†ýŠcÊU3lßÁ‡?qÖ+SÓ¶$‰2ý¶çÈ~(ˆE4Ù¹“§ÇjüúSŸ§eZ$ÆÌÓBî>ÍÜöꙌõ&LLæâ™¬“"1ÂÐüP†ê²—F` Ť—;®HŒNŠ_³ÓEö9öôOš²P‘ÈvÄHR›·ÞbBÛù’p4ÀËh5[„øI+šDbTxW‚G‘‹¢êM"1"ÖÓGS&3Ãíµ\áE(Ø*Õ°›5_#T~‘'ÑŠ?©é›or{H(õнÜð=¨IíÚ" ñ:5S’<˜k|Ï÷GÒªßSè6îÙ++uì:€º³cŠ»>ò±ŒbÞ¿‹ÿjÜüš¤¶ÀÔø‡†…õ ‘-ñµAz­ƒ…k‰ìsìéŸÀ‹(ÝŽóÇñ©'x™Âí„ÑÀ¡Ý»3[Á®¢`™Ô+#Fr±~"H䈃 (³‘û¼û.7BŸüÃ÷Ü&Þ§ Añý¬ÿ]‚…ŠÄˆxV0ºWÚøàÅ0p µoÔX+d›† aýg­æ¡YPÈ£T¥AkE’å£:Xß‹¿Í–ÉCÞIF×É &eÜeå0┉Å@ÂèSÚ¤ÎÖZùgÏžÒµ¨pÊÅÖiS¦ã¬•µ%?!b¹|:ÙÈa|{‡-1‘Û;Ç §Onß½ËGœ23o¸,P›ÞSL§bcè$[ ðeÆxšèˆwŒ ̽¡4äaŽè@œ%woG(¸P–œYÿëP|"…]<å6}޽ý“-mìŽ}î×(f+™#[Öög¶`tÇç|«—ê°GJeÜ #x½ÃÞ5˜ÈšÅ‹àí嬟;a|ÊBá…Ò‹ Ìêå­Rj§;Ãb,gA*û÷ïo±¨f#IJí°¬‡;?~¶Bàg+¥K—ž2•´µ¸rÀ%ül! º“¡ -yüRcŒ‰:ÍÖdzBòHmâ“çoݤÇlÝ)‰Ñé¹Ï9goÝ¢§ì^}©ûœ(Öç<•}ŽûÜuŽq"ûÇäæng…Çß1®§j‰·#n*R’/Kò2wXÒPÄ…c/u§œ#ÍC¾ôJ…ü¶EA·t£¸{^Z¶ì–%‘ݽ¥Ræ/1³7_–Dö9)ËÉÝÊ>ÇÝ[È6þ^…~5]Ö,|Yk‘6IVW®³&2™ïŽø^þ†|0ºã½¥9O/ÿ­ª¹Èd…R®’€T’ 3Š®’¿¼®FHC/C¾ 5ºÜ»š—ÿVuoùKî¤ì€T’ì–,*% % % % % %ðêH@*I¯N[K¤©\rº-•7 Â¾œnS$!·Rn/©$¹}I¥¤¤¤¤¤\!½ÛaèÆõ(îÂk~nºtéɧ@aólã~\ÌMŠŠ¼DwãoS®<>äWº¥OŸÁx\I<|pΞ:FiÒ¦¥Òe_£Ì^Y•CºmEc˜[Ñ}-’Š÷£¬Ùrè†M¹ÐÕ7XØ€§Ê®q›>]:*äãcÜ7OÜŒ‹£ËQQtçî]Ê—+•÷ó£ éMo³„ÄD:qþ<Å1×Ë‚yó‘_Ñ¢ä™)“yUÂ÷Eb¼ÿð!ýsáÃO>¹sS@àÃk@IDATébÅÈ+sf§09b“$£LĵktïÁ}Êí“ãUõ<ÞŽ‹!ô7–}ú4‡È›$QÕüÛÚ«ÏÑ2-â^Åów÷þýÙôÉ“‡21ïX=HF…ïgÿþKáW£(âê5BºDáÂäË~z“HŒÿ2\‘ÑÑt><œ2{f¢2Å})W±ïGŸrËb?ø÷ܳ…åƒ,wóÞ+ä+ÉñðKçhú¤Oh_è“ü"Åühõ¦Ƽ‡,¨eÀG=h÷Ž Æ<$Z¶ëB_LšoQ¡2)¨ÑŽHŒ`ñÙ³§ôËòyL&£8ÇçRûŽ}4âÞ¶jv~ôȈ£~£6Æ´’(ê[JIÚ¿µÓ&IF0nkl?HûÎu¯6«S‡¼³e·È̆Ð]<ß|¤Ûba 2EaÄèý;C>äýhí×_§Ö Þ¢´iÓЬeËJË€À±ôçü xq¢0^ˆˆ Æ}zsZÔ­GjÕ¤Ó/Ò«WÓ—óçQ|y©këäÏé‹9~q M•$årº¤~|®ìZÝNû!Wš¶ê@ã¦.¤ ìl‰öïÙÊ$(_‹~ÙEÞ9sóbu´¤ÞëцµÁ4tÔ$Êͦéô"­1‚ï.mkpö;÷Bá—Î&]Ó ›r÷;t ÑýÞWv­n‡MúŠ? ï6mJóÇ' ɧIq²oáB­ÿƒ.]ØP©'¯oDÏ^T¾MkžþsÇvÝ”$~Aö§5FLÎC]Ú´!LM‚úw|*¿Ýžn±Îlסƒ+I¼2þ´Æ¨fa~p0ÿ²ÃËhÓîÝêCº¦E<ðzµ7iZ騠®ÀTÑÖ>XņФÖ÷êˆ^†«9Ó§Øœ¢$µ¨Wßü°Ð}­1î8°Ÿ÷¿òæ¥uóجJR¿Ó¸Vmªæÿ.…>ÌããT/ÒãÔ…?pÖßkÑ’ŒOÊj …™éÇø¹séë… ©K«ÖÆ|-qºÌpC¼P~@ŸŸmUAÂñ?~†º÷iT°_¡RuªT¥’´së:¾u§?{0‚oÿ.hÅÚ4âó¯NÓiIwÂ¥æÓlÛ÷ïçY3?ûܪ‚„¯—-LJE y°o—(òúu¾u·?{0«GûöÆŽ XÐ9a„ ϲ ?ø‚?Gl’^P¥ñ°=•“`{€©Eàƒòëîdïó( 6I¶òbF{ÊÚz}=Ê9r¯šó5åÏ<«+û˜í¤»‘=oÆÆqößx­’I¿ã™1£f[ÜìÁˆ©6P_E¨G»ö<Sp‘ÑbÞ.S’Ž4|u¶óïEY²ZåèÙß‘†aQ|Õ™“¢$E†‡™rù¾=ÁìèÀ™Ì`ÝtzËå ^ÀÀÞ£Gx‰íÚQ¶,¦Óp/8Õx8þ¾AqÈåímÌs§„³a<ª<äe|K8 ÍÞé6{.䯀™3ù%¦~ü1åÌžò3l/¢ÊÚû<ŠâCdpx{0ÚSV˜,¨Ø‘{U}™è˜Zºf ÏÀFyÝ‘ìÁX¹Lá÷¿¶pÛ$ìÀÀyÖòe<¿nÕªlÚß‹§ÝéÏŒŠRެYM äfï ˜6€Â"®˜ÓjGÈtÛ†µ?³5ßNì`€ E¦fÆ&<‡?Å÷³²ùâà%³éÄ‘¿éöíÊçSˆª¾QZµïʽG ÇÅÞâeóú„¡®Hñ˜ƒ'Šž¤%F=ù¶çZ¿lØ@'™¦ÏJ)J5+U¢†5MmÍN…”Óììæ ^ÁÒØÛ·¹¡`ªÕ¨s«V&_7æ×G‡µ÷èQž]§JUóÃÂ÷E`Dsç%>J¤3/ÑW æóáîê̾ ÓRŽ’£#I"0îfCøè”«”+ÇÚ¸5]¿yÓQXšœ'êy<Çú±ÑC;SÚ4i¹wn©²•¨q‹wSù~! G’´ÆhküB<q¯š³·ø÷ßxú³JI †y‘ûZc¬Ã” ͛Ӫ©iß>4²wfzžOît›þ×›´ÆEŠÞG¥‹7ïa{‘£ÉIvìhª$eÌh°3¹~ðSS­ºMØÜþ*òð0 Þa hùß©‹ñ4Œ±7‡¬¢Y ×ÒýûwÇ==“Ï©fÊdÈ{4a,,(!cZÎÀÈ3£ÁÆÆø©©Q­Z<í[ʘä2Ë”ÐìåËÕÅxúgf”½zó&úmÖlfHhãWÌè/¸5 6YÂáAí•Ù“*”ô£±±´nû6£÷"ˆ Ëo.¯T­f ÙFÛESèñXúã¯Ó4lÔd^\üÍÿÚXó#v³‚ÊU¬Js…О“whß©»\‘B>ì•vmûÓdE®gÏžáKIF—²pñzժљõ¹}E‡î¦ã¬¡‰Ã†ñ’pñÿvÑ"ãY‰I^@QX37ˆnîÙK1ûþ¦L‘Á^ ^m–ùʰwPà¸dñ”,£UžHŒ¥‹gÆÚ©SË–Ì Ã`3w´4`€GÏœÖ Â ë…%p5îÕþmªZ¾ü ùY@ÔóèééE÷„ÓÖ×h÷ñ8Zz‰&ÍXFp¹y™Gõ ˤnQmíƒM˜´#ê^5g£-ˆa†½ ¶EbÄÈî²µ†)D|p*Ô¬_?‚‘º^$ ãG½zq»N8¿à´BÛ6Ô¨w/š¹t©š‡‡e‡!ci<ÏâiøâÀ”lŒ0Âwýn}‡ÓÀá¼üοÖÏ˘<Ój5j7ä#L‰0mÞéÎËÁÞ+Ëó°÷ïÅÏWˆgÊ™;¯’%t+£P†¨щÀÆA1Ô9´[w£qnÈÎÆZ3%bÊ#Aa‚Km+F‘ #§“+ €& I 7^{§õú‰ܾùdó˜ÀGÑήß`|À{}öY¡NcØTê„ ¹cíbié×ù<æÊ²e÷¦Œ™<)OÞü<ôÈ”ï 1¾N;@ñwât) £­}° EÜ«æ|cü»e†—êàÎ]œÕ5¯Û–}QC¢v æÞ³Atè×ÕtbÍZ‚->ΚöëK0kЃDaÌ˦ ·/YJ‹&M"(L;u¢)#GÒ¶ÅKŒN1ysæQÓé6kV¬Tƒºn,’-›7Oß¹kÌSù åIL¡Aèøzƒ]¢PCñRSÔ•K|š+ÉŒ®äÛžkæ¤Ñ!#›A‰U¦Ý§P‘üùy‘˜Õt)2’Þ:„g!~îBZaTãÉÏ"úN1’ZÈe‡ y®"g0"0¾ÄA%YÈs—^öêÕAìÖ¦RÍËè±/êy,[±Š‘ýXÖeϑӸ¯wÂYŒ¶öÁzãR_Ï™{U]Òzª˜tkÛÖü°ËöÅ8ž}´€S‰T´@ZÍÌêuïÆG}WnXOú÷HµÁ8lc{?…¼ñø@åJ88ý­Tfe«‹’tíj¿| ¿²F6|“ÒG†ó”œ|ò"Þ–)_…öîÚD÷m7ºüóìo÷Ž<©x¹)ùzoŨ7¿Ž\ï  *«ºËúúò¼=Gó­úA&AêeLà6ÞjàþÕƒ˜‡ WŸâò´-P/I’>}:KEtËs#”;L­šÓã'OŒŽû,d^D×}QÏ#– Rȧàói %OÏ­³íéƒõÄ¥¾–3÷ªº¤ç¬0„’éçߘ4|Ü™—qž³ÏÙŠ¥J›°Q}ÆÔ8_ÏL)»nõRn Ž‘¦êµ(ÙB·¢0 eÚÎÊdíAÒhr*bM`KŠ€ÔÖð`!`™ò°bÓi+þüIÂ<5èÖí8j˦h0ŒˆÛv•ѯ(Œ ]Å¿X1´¯Ð#•zÆ’Å|Ó˜æKº(å´ÞŠÀ$ {›ÿVNŸÁÙ‡7 Ž} 5‹õ‰zÞKX/MM lE“G󬊕kPæÌŽ…¼P×iKZF{ú`[øt¦Œˆ{UÍú#DŸ½ß±ƒúniQË$}¨.O²IRÁÛë·-[ø®£Qþ•ºlÝŠÂgóXO;°˜‚S§pÖ`"Š4IzÌÜG ôç|ÖmØŠòå+H±17hûƒ1™oɲÔkÀ'F¾~åè­&íøñ>ïÕçk°=}ú„{µ¡â'•­`Ú†οv†Þk]•Û-Á£K OÆÎt|±I^ƒm"1‚,I™Ž$Å‘ ^2‡Ê4(C>þ’ 7‘„e&Ï,yD¸÷›Ü‹`;¿,È‘ª(¶IjóÖ[ÌË`;ceÜЫÙún ÄOBIÐ æi¡ wŸf¡öêÉóÕ?L˜˜ÌÅS}\‹´HŒ0F‡q;”¡ºLĨŒ×¡‚ôrljQ‹6Т‘Ïãœicx”„#)æ[šy¹ÝçÏ¡²àíØÉßká…uˆÄhOüBF( Ç½ª„#EÕ›DbD¬§¦Lf†ÛkùÇ*´Àvpí¶m&lJÛ°¥JD“HŒƒ‚hEÈŸÔôÍ7¹=$>È[×á=zP“Úµ…ÁK7Ž‘µÚï\c/íhÊ_¬’µ"ÆütÌX4Ö]»xþ:ýÏa¶´Æ9Êì••ºöJc&- /–VSízM ñ°NÖ-»xá4·?ê?ä ü =žäZœ… ÀÒ%—Y}¨;Œ]#2â"•)_™>eѺ6m§®Ö®t½çzŒ`ú«/Ó¹3ÇÙèXa½:äƒ}üš·édœ‚ämü{ÅâöÄQ\AÒ>ññ#>urúbeF׈ŒŠ¨Ê0—Ù¹c¾H6¬Ù¤ö›ü¡<ξÖ0?|†­©ƒ…Ïú÷§±ƒ?`1fÒpNw>dt×Äðï f›cþëÖ¦-aUn{é››¾ÇåO Ññ³géTØ×ãÞƒ|Šê{fÈmgÊœ·ÒM&7W·£%ž0ùÏùé'B°7g\«¯=zJq·o¹¼Ï±6â=u”Îüs„÷9x&7‡{à-îgI 6å%ÜuŸ>ÇÖ>Ø&`ªBîÒç€%ŒäömĪJðA»%Ý¥ÏyModeÇÏeQ§£™í:wù2DŽֹlñð|,–#ä.ñ¾fJâ$>uŠ®ßºÅÃÆ|ËÚuà{L¢pÛ‹3†Ý'P•º[¢4lë?K·#ô y”ª4èa­H²|Të{ñ·ùÒÞl*ìEôˆ-¨}=’`Hø¢òÏž=å‹2æbë´i1Ü{õE\8æV_$/{'Dì£3—/P';‚5¢a`|‡-£‘Û;‡MSD ,@{HsdËjSy{q¤Tþ4»ÉOD„» FÈáé—'W.Ê”w'% /:v*6†N²…ݵƒ O[[»ïEøpüP|"…]<å6Ïã]Öat7‹Ñ†Ññ´IëbÙ‚ÅZ™Ø(Ö焹WŸcOl —:ßÝú< õRj~I»[Ÿ wîÞå/ü¬Y¼Þ^Ê:nŽàÃ9î„ñ) „÷ ”^¬Ù¦^ÞÊQ|8ï s;Δ¯þì£Þi6ݦTŽ…çà.‹Ÿ­„¯¶¢ÅKÙT<]ºô,ˆ[I›ÊŠ*$£(¾í©a¤k: ,ðšZH$FÈ¡\M"1šcC¸WÈç!ðs5‰ÄlöôÁ¢d!ò^ÕR9r¿HŒà ÞÆŠÇ±3|:s®(ŒPø´´Ÿ¦†Ûö\X–•pg H%É[Gò&% % % % % %à2 H%Ée¢—–pg H%É[Gò&% % % % % %à2 H%Ée¢—–pg H%É[Gò&% % % % % %à2 ¤ "ܰpâz¼¬~ŠC{©1F†sŒ‡â^Öf¤ð¨(Ž ±„l#¸Dˆ0ekÛY®,uÖnŒ®äÖ±k‡_5¬÷‡øeº‘ÕHqb8}޹ê]kDÒóˆXBz‘η*2¬UxJ7Œz#$ºmXåÂZ¦LrÙò”`¶›µŠd¾”€”€”€”€”€”€”@j”€CÁ$ õ³;âvjŽ#Ño%F÷“€#~ÝEÊ9q;åÝï¨#·ÝEÊÉ>'eù¤–£¯BŸãHÄíÔÒ~ ŸJÄmeß|+m’ ËŠ™ËEîK H H H 8*Ù¯:*9yž›I@*InÖ ’))))))÷€T’Ü£Är!¿êÄÊW§ÚÓp#s.&/#Nòy'[Y³”€ÆJ’Æ•ÕI H H H H H ¼JÒËÑŽ)£Ðß«2e~äQ‡$ðYàЩò$w’€|Ý©5$/R)J Å8I)žiåàëQôôé“dGÓ¥KO> óãïÄÑý{ñÆ}K‰¸GgO£4iÓR鲯Qf¯¬&ÇõØbnESôµH*ZܲfË¡,“kˆÄ˜˜øyMž ø;±”7_!*êëG™2e6¹¾;WoÜ 'OŸ&»Túté¨1?.>žîÞ¿oÜ·”ðÉ“‡2yxÝøþ¹pp®OîÜTºX1òÊüra4‚e‰ˆk×èÞƒû”Û;'Ç«>&:-ê^½Cèo,ú2ôiz‘(ŒjþãbnRTä%º›råñ!¿Ò(}ú ê"BÓ"0:úžTdŸóìß)üjE°XcH—(\˜|ÙOo‰ñ_†+2:šÎ‡‡SfÏLT¦¸/åÊ!öý¨éS~ðï4¸g «m²yïòΕ‡9ÈŸŽÙgµ,Œ;ƒ:tÈËêA»wl09§e».ôŤùº=Ì"1سgOé—åóhú¤QgÀĹԾcÌvïØi! #”£y3ÆÑŠÅ³M @Ñ;y5jö¶I¾È]R›Áƒ¬^"lófÊÃ^ø Î#GÐßÇ[-‹ÓF¢÷;t¤‡ 8g6}ÿË/&å³0é»ÏÈ¿Y3“|{vìµI…Ñœç-{÷’ÿ°¡<;OΜ¶i³yaû"ïÕ¦µ¬¿`¾™³’4ië.7yæÃ/cýÍ'´/t‹’Å·EŠùÑêMúõÕŽö¾gL ñŽÈçqÿ‰4ô«/éì¥K&\׫^掣‚ùò™ä‹Ú‰ñàÉ“Ôï‹1L¼jÂþ˜ƒè“¾}Mò´ÜÑTIJLxÀyËɡЕkšð™}e{eÍf̫ӠeËax 3“¡ÛBxJùŠyòä1 íÛ–+U §&-ýÙW\: ^2—Ö¯YAÿý÷MøæGój„ì‹Âf/œ;I_ ¢Ó' áÝÖJEaܵ5„+HPŠ6mÇGÖ¯ æ_ëŸíBÁëRÉRleÓ©r ÑÇñR¯Q±¢I]<(›Wc^³:uÈ;[vã¾:±!tßÍÞð(mÞ³‡+HÅ ¤wš4¥¬^™é§uë(ìÊê;&€^+]šJ±Q%=HF5ï‰ÓÇ_OUgéšu¯>~ôȈ£~£6Æ´’(ê[JI ߊÂƯE…SOÿºüô+]‘ê7jMùòb}Ñ?tîô1áØ” ˆÂhÏ{FáEÔVÔóx3.ŽÞò!aôºöë¯SëoQÚ´ihÖ²e¥e@àXúsþQ°Lê…ñBD5îÓ›_«EÝzÔ¨VM:}ñ"ý¸z5}9È——º¶Nþœš0çàŽ¦J’ÂFú}ð¹²kqÛkÀ'ó¡((JR=öÀ‚öïÙÊ$(_‹~ÙEÞ9sóü: ZRïŽõh{Ñ5‰r³!b½HkŒà»KÛœýÎ=‡Pø¥³É¾ìô¦\GkŒ…‹– Ã©K¯!äééÅ/Ó³ÿÇÔæ-à gÇ–µº)I Æ÷;t ÑýÞWv-nGô2<œæO±é4EIjQ¯>?ìW´(ÍC]Ú´!LÛúw|*¿Ýžn±Îlסƒº)IüâìOkŒJ½ØÎæ_vP$7íÞ­>¤kZë{UaþõjoÒ´ ÓQAå˜Þ['ý+HM[u qSRöàJÒ£=ï½pký<î8°Ÿ+Hòæ¥uóجJR¿Ó¸Vmªæÿ.…>Ìc4[/ÒãÔ…?pÖßkÑ’ŒOiÒ†c 3³ˆñsçÒ× R—V­ùZât;Ãí•Kƒ8¾6ït§\¹ C„!üÄóº÷iTQ¡RuªT¥?¶së:¾M –0‚oÿ.hÅÚ4âó¯NôdjÀc‰GKËV¨B}}jTpl;ê6lÅ«¸~튥ªÜ6oþÊŸ9o]™B”/W.ž.ïçG=Ú·7vTÈDçTåƒâïY¶qáÝðÏF…MØ`jø0äZÉÒ½šZ±XãÛFL³áôÙøÙ.W¬ñnk¾%ŒÖ姬µ:\‘oéy¼ÇYyãµJ&ýŽgÆŒF1Û’ZÈFLµúúû›(B=Úµçù˜‚‹Œ¾ÎÓZÿ¹•’cå5¿.æ;vlÄzä€aJ_uæ¤(I‘áaæ‡ÜrßF0;:p&3–4úqK/`*%Œ–N½÷Ïöö6ŒZ*ãnyÑ11´tÍÎÖ6R”Áx[yÈËø–H©¨[{Æ€™39¿S?þ˜rf·<éV€,0cï½j¡ ·Ï²†ñèAÃÈ_;ÿ^”%kêl?EøÖ0*ÇÕ[{ʪÏsuÚÚóX¹LÎÚïm!Ø&`à4²wæY{žOÃær›þ×›´ÆEŠúêÒÅ‹›À÷0޽ÈÙä$;v4U’2fôä—¾~ðSS­ºMØÜþ*òðx>¨>ϧå?~dzºöfÓ"0ÁŠJ¦om®]OŒógŽç|A™®Q»¡Í<:[Ð3)´ ªñSS£Zµ(xÚ·”QåÒ¯>ž˜H³—/çYvíª>ÄÓ›öì¦N#F˜äÃ^`ñ¤É&Ãá&lر׻MF„M™d¬=mÔhJÇÂq¸ŠDÝ«øÀ!À¶M˜À[0k"Íþß:‚Cä&Ï㦔~W1›CVѬ…k™°øöÕŽjLH[{Ϙ—±/êy¯óǧÂ>ùiúâEôí¢çNLÛ/¦"ù ˆ€c±NQ[2›Ï àôéôoY(OªPÒnÄÆÒºíÛŒžÇ{ ‚4½û«Õl@!;Ãhû¡h =Küuš†šÌù†{é¢ù_[ŰqÝJÞ!a´¨^#ƒ «û“gÏžY=_¯"0 çÝÎ/W½0îÚö§qz5pÊ÷º…q€¼ëU«FgBÖSäöº›Žÿ±†&3(ç[÷ícÍ"«Í‚/6x’@ñQ ¶Õ…K+ÎŒµ;R§–-™†Áfîiiг=sZ]Ô®´½#I¢0£®Æ½Ú¿MUË—· ƒÖ…EÝ«p,ظ'œ¶¸F»ÇÑúÐK4iÆ2‚óÈÕÈË8ªãPÜäy|Ä”}P¹ŠUiî¢Úsòí;u—Ì"öJxFõ Qíhλµ÷Œy9û¢žGðŠ‘Ýek Óÿ%‹1²ß¬_?‚ƒ‰^$ ãG½zq»N8¿à´BÛ6Ô¨w/š¹t©š‡GcZËDZM+c_Pr0¿¸ëwë;œ{4á:;ÿZgñr˜žX¶p?Ï.u6¯,ÏÃX >‰/PÎÜyùVô¾ª´Æ(šg{ë×ã¹ÓÇiä œµ>ûš^{ýÅS\öâH©<0BÉÉ–% yfÊÄ絇vën4@Ù¹Ãâé¸W¿[fx0wîbqdܾùdó˜@¿ÍšMg×o0>à½>ûŒ‡¬°X¹Æ™"0ÆÜ¾M‚挵Sˆ3¥1«Õ‰¼Wá8’-»7eÌäIl‹Ð#S¾[Áy9yì †j0˜µÊœFDaÌÈî{¦Õ0Š‹Q~„]A8΀ô G" #‘ô—Ò{F]NTZÄó^C¢v æÞ³Atè×ÕtbÍZ‚->ΚöëK°õуDaÌ˦ ·/YJ‹&M"(L;u¢)#GÒ¶ÅKŒN1ysæQÓé6kV¬Tƒºn±ÈÞ]›ŒÓsmßíaRBÇ×ì’`¿ÅKMQW.ñ]ÅN}LÏ´3õäÓ™ki…12â" ígˆipø¹ Án¤ šó…À‰Êô\·¶mÍ[ÜÏÏ¢qO1’ZÈëE‡…Z@h«Ä *Z ­fhõºw㣾+7¬§aÝMß­ü$þœÅ6a7ŠØsø)„à½XÙT®„ƒÓßJeV¶º(I×®FðË—ð+k‘‹fñ|¸ÀãË͜ʔ¯B¸ÁîÛntùWÊìÞ±‘'/7%_ï­³õæ×‘ëiK ìÑŒ+½-Úv6NÇ:ˆs®°å5@e­åä™)ü©—$IŸ>] %År#”;k›Óã'OŒŽû,d^D×}-îUK c© …| >ŸÖPòôÜ:‹Ñ7©?>r04Û2 òÉ_˜o]õç,F5ß/zϨËê™væyŸŠçlÅR¥M؆M%åbj¶®$g1Zã=8$„ÂG™(>M§ÛB·‡–QbßÌ›ȳ,æbÚ!éA»à[ó¿æm .Ö«~šÏ×3Sޝ[½”@a¤©z­J¶Ð­(ŒB™¶³rQo³ÑÀÁ½ZÒÍè«Ô°Y{‚RÚ¤Àgv²ètq|4"¢Tyý:M˜ÄwÔ0Œ~*ǰ=qî`‹ôû S…H«iᯫ£MÚWè‹J=cÉb¾‹)>e¹帨­Œ°A°·ùoåtÃt9¼ip컀Q°Lêu¯;¼— Ð«)­(0còhžU±r Êœ9‹ú°°´(ŒUkÔã<Þ¿‹0}¨úä?_Îw«Õ¬¯d ݊¨0mË{F)+j+ây¯e|}9ËË“l’þáíõÛ–-|W¯(ÿ¢0ÂYÄ<ÖÓŽ,¦àÔ)L%D‘f#I™»óˆþœOÌ—¯ ÅÆÜ í[ Æd¾%Ë’¥è§+—† áýVœ•±D˜#Çù—ÂÎÐ{­«ò9sØ`t ôÉØ™&vL–êÐ"O$Fð‡%I 3Б¤&ÁKæÐέãÉ!I¾~É¿âù ý‰Ä¸øûoŒÓªaçNQ¯u“q=á›ET¼„!îG²ƒe`) Åû !îÒþ&÷”ØÎ¯€Ng¤…(ÛJ3ccaEK´~×.‚á7”¡ºU«1t´}ÿ~n€òz¹ãŠÄh ·+òDÞ«s¦áQþá _Ì·4s*¹ÏŸCeÁÛ±“¿×²HŒèKÞjÒŽ÷Ñ}Þ«OX‹“ë „øI+šDbTx·å=£”±ù<"NÛGS&3Ãíµ<º6B˜Àvpí¶m ÜçÛ°¥JD“HŒƒ‚hEÈŸÔôÍ7¹=$FÏŽœ>Í! ïуšÔ®- žfJ’s©îùþHZµâ{ã²"àëta„¨;;¦¸ë+h?~Dë~[Æw»õ®d'Û¢Žïú‹ÆÖŸ×eH@ˆ¥Ô4%;I@†HŒ`_SJL(…}(†ø,)™J9­¶"1ª—<0¡ðŸ˜´¦š²/b›‰ CïÙ“®Ze\V×Aäè÷™WÚp6wcn5a4k°Rúj 0ÅÔIÃG“` ¦ Æ8„Ûê(y"·"1Zâk)‚”5ì,•Ñ:O佊å1{ £,ø)Ô¸ù;4xÄÇÝÿ•ŠlÜŠÄ0š ñ…Û?#óÝûŽÐÍVP4F[ß3¼ ?‘ÏcŸwßå$“øžÛ<ÂûT!xØ~ÖŸ™±0Ñ$#bÑÁaFéƒ^|RûF…BKưþ³v…¡YPÈ£T¥AkE’å£:Xß‹¿Í—Öðf\Jô(é¥[èÙ³§|QÆ\l6-†»c¯ž ˆ ÇÜ £-r°§LlÃörcLˆØGg._ NUªÚ,Ü«0¢¾Ã– Éíã…Ó` I‹žªÃý[»Ê"À"”†<ß¹{—Çʚŋàí¥¬ãf KeN³öánÑçe =ñij¸­'ãòZRRRRRRR"% •$‘Ò•uK H H H H H ¤Z H%)Õ6d\J@J@J@J@J@J@¤¤’$Rº²n))))))T+©$¥Ú¦“ŒK H H H H H ˆ”€T’DJWÖ-% % % % % %j% •¤TÛt’q))))))‘H1˜ä²å+(Ál-6‘ÌȺ¥¤¤¤¤¤¤ô–€CÁ$ ”¿X%½ùÕíz·®ž¡§OKŒºI\Ì…^…vŒ‰:MOØÚZò#D7¨õì­[|ý0Ùç¸Ac8Á«ð<¾ _…>'<þÝ7[ì\}ë§q»P‘’|Y’—¹ÃòȆ/K"1ªo‹Ô—~Ú1Gš‡|é• ùm‹fŸúZ‘(1³7_–D>©±õžóü*<¯ÆW¡ÏI—5 _–äùÝkš’6I¦òx9÷¬®Î÷r•¨¤ÜZòytëæ‘ÌI ¨% •$µ4dZJ@J@J@J@J@J@J IRIznƒiÙ«€Tb”p ÈçÑýÛHr(%$©$½ ·‚ÞZYbL-Ïcji)ɧ”I%éU¸ ä—ë«ÐÊcj‘€|SKKI>¥(Eï6Gäsãzwá5?7]ºôäS °y6ýûì]ºLW#ÃéߟQá¢%Ù¯D²rJÆÃ÷èì©c”&mZ*]ö5Êì•U9¤ÛV4F‰¹MÑ×"©hq?Êš-‡nØ” ‰Ä˜˜øyMž ø;±”7_!*êëG™2eV.­ÛV$FܧÎýÃ1æÎ“ŸŠ•(M™3gÑ ›r¡«7n°°O•]ã6}ºtTÈÇǸ¯$žýû/…_¢ˆ«×é… “/û½ˆ"®]£{îSnïœä“;÷‹Škz\T;ÞŽ‹!´£%B_†>M/…QÍ\ÌMŠŠ¼DwãoS®<>äWº¥OŸA]DhZÆø;qtÿ^|Š|çÉ›Ÿ<2fJ±ŒVE`Tx³÷]ªœ§õVdŸó/ë“"££é|x8eöÌDeŠûR®bßš>åÿÞAƒ{¶°*óÍ{¯w®<Æã'ŽþM_L—ÂÎó¨^³Nùòå/dÌÈ‚Z|ÔƒvïØ`ÌC¢e».ôŤùº=Ì"1ϳgOé—åóhú¤QØ¥€‰s©}Ç><­×Ÿ(ŒPŽæÍG+Ï6EwìäÔ¨ÙÛ&ù"wDaLHx@s¦}ÁÛPÍ?0~>a5kÝQ-4½ëàAj3xÕk„mÞLy˜R£Ðþ'hèW_ÒÙK—”,¾­W½:ÍGóå3ÉWv¶ìÝKþÆòÝ<9sRئÍÊ!á[Qíˆ{µi-ëÊá7sVRƒ&m…ãÃDaT˜¿tŽõ7ŸÐ¾Ð-Jß)æG«70ɵ# ãÈAþtüȾÙ5vuè:0Å2Z…¼Ùó.Õ‹µ:Dö9Ož¤~_ŒaqWM.?fà ú¤o_“<-w4U’Ù ”“)B+×4á3ƒ‡yeÍfÌÃW˾mù—ÚëÕÞdN;JËF‡–ýoïG÷¥ùK7ñòOX°Ç¡¬,nö‚…‹S“–þì+./™Kë׬ ÿþû&|ó£±n‘ QÁó…s'éË€Atúäam!Øi! ã®­!\A‚Âаi;>z´~m0¿>Ú…‚פ’¥*h‹ÝJm¢0îÙ¹‰+HÊ}êŰ®ûm] ¿@cFö¤Òå*Q1ßÒV¸Ò6ûAb¯ŠKŠM*÷ÈàAÙ¼žlÝŒ‹£w†|ȃªÕ~ýujÝà-ö<¦¡YË–:¾céÏù LêÀNâãÇôñ×S“åë•!ª?zd„P¿QcZIõ-¥$íߺÉóƯE…SOÿºüô+]‘ê7jÍ?N1 zîô1û±9x†¨v¬Ó eËñüC@Í^è¶¾«×h™(Œö¼KÕøE¤Eõ9""¨qŸÞœåuëQ£Z5éôÅ‹ôãêÕôåüyT _^êÚ:ùsªFM•$…!håý>ø\Ùµ¸=°o0óú¤yK7‡®kÕmBþÍ+Ñáý»øq¼P÷ïÙÊ$(_‹~ÙEÞ9 Ãùu´¤ÞëÑö¢:jåfCÄz‘ÖÁw—¶58û{¡ðKg“}Ù9ŒÍA­1buàð@êÒkyzzq8=ûLmÞ2¼pvlY«›’¤ÈRkŒ˜ ø2ˆÚ¼ÓÝxOwì6ÞnRžâboÑ!6Úª—’¤`|¿CÝï}e×âvÇý\A*7/­›ÇFfÙG¨q­ÚTÍÿ] =|˜Ï’ÙtZt~p0ÿ²kV§mÚ½ÛbÝzdjÝŽ Ïø€›ô‹²«ÍÖMžG€™4öCÞÏ6mÕÆM]H˜òìJÒº{ øÄ"|*JR=¦êIZc´ç]ªN­ûœ© ିע%-?ž”Õ@ 3“ñsçÒ× R—V­ùZât™áv,I½özMãËû3zbà #D ?~âÛîýF$dT¨T*U©ÅíܺŽoÝéÏŒàÛ¿ËZ±öøük†óù´¤;a2çÅŒe+T¡¾ƒ>5*H¨ ¶u¶âÕ^¿vżz·Ø·#¾ÆÛwèmrOCÑ÷+óÇrïnÊö®|36Ž_ú×*$dxfÌhdIy• ØΙMPœ0äíîdO;º;küÙƒÓlø}6~¶Ë$k˜ÌóíÁh~®²¿riOâc&WnËÓÈJYWlíÁhOYW`±vM{úLµúúû›(B=Úµçù˜‚‹Œ¾ÎÓZÿ¹LI*S¾2Çò׆Õ|>;0ÊZþãLž_õzä•Å0=wäÀ.ž‡¯:sR”¤Èð0óC.ß·#˜8“KšN‹hÂÎá}{®i/FKuß¿{‡g{{ëkðk‰KyÎb„ñèÉcxÕ¾~å,]Âåy•Ë”á<üþׂmÏã¬åËxºnÕª”ÕË0úÇ3Ø_ÀLó:õã)göìJ¶ÛnmGÍ€¹Éóxô aä¯/Ê’ÕýÛO‘¿³í§˜5¿.æÕuì6X©Ö­¶ö`´§¬;´§ÏQìrdÍj!··7+Xç…EˆùÈ2ݶaíÏÜ{ vH0þƒ"S³NcpUkÔ£æmÞ£ëVRßNoQï£èÂÙ“Ü0Ój š>¾^1MÂÔœ9)sðDÑ“´Ä¨'ßö\KŒè°ŽÚÃÙªR£®=ìiRVF(wØýøèQ]¼pšÌšÀ§4*V®Au4ׄo{*ùeÃ:yþ…©­Úw5Õ£9µÆh‰çßWþg㽤(–ʉÊÓ£­ïRQx,Õ«uŸEŠú±ÒÅ‹›\Þ´8v÷þ}“|­v4U’”©2©â§&ØM ZEχï1îS -^ð -šÿµ±øâU¡”¿`Q¾Oå¾éé™ÙXFI(®ãî[vÕUÊiµQ+Þ´ªGOŒógŽçlC™®Q»¡V^XHŒ{vn ýMx€‚?iÆr]_:žInÍaW®~jjT«Oû–2²…æO…}òÓôÅ‹èÛEÏ!¶-^LEòPŠñ#“Œµ§Mé˜Ã…«HT;â„Û6ýaoÁ¬‰4ûëR Ubr‚“;¢0Þa hùß%ã1›CVѬ…k¹CM²gˆÂhÎ&¼¼]{3?,t_$F[Þ¥BÁ%U.ªÏiY¯>¯ O§K^™=©BI?ºKë¶o£¿çWGȤiïV¹î‡ì £í‡¢)ôx,ýñ×i6j2çî¥jE™0Î^›4쉗¤Býº4âž^ØWÛ8>c1•\M"0º“ùõõ¸kÛŸÆaïÀ)ßëÆxEb,æ[†:vÄÃSàãt3ú*ó"z“Îüs„ïëñW¯Z5:²ž"·ï èÐÝtü54q˜áŰuß>¦-2a£CËÖ®áy%‹1kÖ¯ºðü£%ЫýÛTµ|yc9W$Dµ# 6î §­®Ñîãq´>ôSr—qÏÝ«‘—)pTÝàŠÂø(1‘c(W±*Í]B{NÞ¡}§îòY€½žQ=HFsÞ1sÅ-õl!Íˈډіw©(\êzEõ9õêEüüèóÂí4bUhÛ†õîE3—.5^ÞÃ#ƒ1­e"­¦•±/JÜ|˜ßÆÜ »õÎ=šp=7®>´'}лŸJ œü=ýºñ8­Ùz†óâ…Ò¯s#PQ±KÂù–‚‚áË”3w^¾ý‡0Zcͳ½õëñÜéã4rPÎÚGŸ}Í øíåÓ™ò"1‹ï“/¦Óø¯ÿÇ¿Äñ‚…­¦?Þ‡¬p†w[ÏFx«eË’…<3eâs÷C»u7Y‡ìÜa¬*ôÐ!j÷Á`Þ Ò¡_™­àšµ[¤kl*­i¿¾C1·oÓ„ ¹cíb0+œÙŽ0èÍ–Ý›2fò$Dè‘)ß­àˆ`c[3=HÆŒìžaZ £¸å‡;<ÂÀ ¤y8^kò?QÕWÂ4ø²…3x<ˆõ Š‹ŠÂhë»T- Qi`ԺϯyÙ”ÿö%KiѤI…i`§N4eäHÚ¶x Wž er ¥©’dÊ•jðCW£ÂE‚¦åiòjÍH¸ô(TŒ¿T|K–åÚþ†5ÁüÆ‚ö+æuåÏrµ‡‚3Í1¹ë¾V##.ÒÐ~†˜€Ÿ»VÕxð‚ð ÏÂ(„¥ûX]^tºzRÌ$Å×ÏдQ£x¼´}ÿáC¾_²iSÊ^½ÿ•ocp£Æ—òR bÉ/$øOD;‚岫9µÐêpc¶lÞœË;·c“q«˜;èeÆŒ¤ g1ªëÝ»k“Ñ ¤í»=Ô‡\šv£­ïRW‚t¦ÏQø†Må;MšÒ¸>¤©#?¦A:SYÖ7ý“4Ê]®D ¥¨¦[]”¤kW#8Ó%üÊ™W¼}`©&|ÍT¬üϲ 2å ÓÁ}Ûù¾úo÷Ž|WñrSÓ3í,F=yuôZZ`DXþ=šñQ•m;§cåIëó´Àh‰'õ’$éu\ÎÂ/WØ" ²ªNEq±­Xª´É)°YRQÆÅÇSþ;S—ùe‘K’@f #I1L‚—Ì¡[ Æ“C>þ’DÇÙ‰qñ÷߇»Ã΢^êr¬ê¿ ß,¢â%ʨ³4O‹Ä¸‹-u'(Cx ¥OŸžöïÝÆCÁZ|z– #aü¶ÿ&÷1ŒÆB)Ù«·‘•ߣ¦Lf†Ûkytm„€ýÑÚmÛx¸à¶aK•¸‰lÇ9ÓÆð(ÿp…ÇÇÚC¶x/žCeÁ۱̎R‰}É[l9(ôÑ}Þ«Ï ž>}½ڀ ñ“V4‰Ä¨ð¾r™á¹ƒ#Eñ¤tå˜[‘íy—ŠÄ*²Ï™D+Bþ¤¦o¾Éí!1ò}äôigxÔ¤vmaÐÒcd­öð+רK;šò3³T>{àFÀš?ÏÿC§ÿ9Ì–Ö8Gˆ6ܵ÷P3ia+…ÊU¨Ê_$X(š6Áëçòųü0­ËÊ+ËŒd`Sp›™Õ‡ºÃØ5`ׂŸ²H±XÌQJ¸wƒâã\üõÅ`—å8S$ÂëÕÿ ûø5oÓÉ¡áï„»îñ03ÖW›„’ Ûó_›w{r#YÞŽ?wiGxiÞ¼qΞ:ÊïÓó,Îì:àA4ž­/XóÍÆv 2-ú4>Šb˜Ü*¨ÜñMK<ßò"‰l óõ§/†ÑQÖ¡`ålDÇþ kWš;æ “áé×ÙYAfä}üÜY¾ÊöÑ3gèÜå˼ÂN-[Òܱ”Å#±FhÎO?‚½}Èêw”®=zJq·o¹¼Ï±6â¡Ñ7¡ÏÁ3Ù¸ù;Üû ËÏ8Jîò<‚ÿÚõšò>ý0–ê@\/ŒÌ÷ò þˆ-ÿÀ q!wyÁ;FrG³µ!Aˆ,Žp-È]0Úó.µ·»ô9˜êfJâ$>uŠ®ßºÅb¾¡oG¦ïu2‰Âm/ÆöA‰pU™“Š%Jưþ³ty;B² G©JƒÖŠ$ËGuxñÝ‹¿Í—Ö°ejá.‹¸ #È,,Â6¼ÔRò:xöì)_”1[§M‹áîØ«'(âÂ1·Â˜L¨NfÄF1Œa/9F7kÇGlqY(Ké™±a®\yÉ#)f‘3M™±Î\¾@ªX~˜-Õç^iwîÝ£ÜÞ9(wNKÅLòîܽË;¬Y¼˜WI.“eJL ší$0—r,< KGéP|"…]<å6Ïã]Öat7‹Ñ†Ñq-¦‡ÝñyÄý}=’`ÌmKŸý¢öu·~ø@P~µ"wÃ\ö¼Km‘ƒ;õ9OY ¨èhzÄ”¬Ù–ÙS›¶<Ã>b3å«ÿþE¢Ùt›R;¼bà̓Ÿ­”-[öpæ°©8¨ÂEKÚTVT!ÑEñmO½£eiÙz¯¢3F(WÚÆÖøÙJ9²e#üì%„p‰¼WaàëF¾"1¢Íp¿-^ÊÍg¼¦HŒZ*GF†HˆÄvlíŸ`ÝæS€QDŸƒÑqe ›™Ñ  cc©\XV!% % % % % % %àÎJ’;·ŽäMJ@J@J@J@J@JÀeJ’ËD//,% % % % % Œ-À­@IDAT%àÎJ’;·ŽäMJ@J@J@J@J@JÀeJ’ËD//,% % % % % %àÎJ’;·ŽäMJ@J@J@J@J@JÀeH1@DøÎâAèFV£6‰á *ü”Ä(B´²5—jTd8¯óP¼!æ‹æpƒ /GÈ>GH3ÈçQs±¾ïŽW Ï¹z5*Å{#Å`’Ë–¯ ³µØR¬M”Hep(˜dá¢~vGÜNer!G¢¦JŒî'ÙŽî×&Žp$ÛÑ©¹ß9²ݯMáèUhÇû7Nr=Çš|¤M’5ɼLù:µ¿L¢s+,²ݪ9fF¶£Ã¢“'J è-©$é-qy=))))))T!©$¥Šf’LJ H H H H H è-©$é-qy=))))))T!©$¥Šf’LJ H H H H H è-ã$9ÂÌëQôôé“d§¦K—ž| N–ÿï¿ÿRôµ+~éyzzQñ’e)‡w®d唌‡îÑÙSÇ(MÚ´Tºìk”Ù+«rH·­hŒs+šÉ%’Š÷£¬Ùr8‡-ý§‹Ä˜˜øyœ ø;±”7_!*êëG™2e¶ŸI'ω÷é…sÿpŒ¹óä§b%JSæÌYœãØÍÚQ æZT8=¸—¼så¥Üy|Ô‡„§Eµãí¸B;Z"ôeèÓ"7mǸ˜›y‰îÆß¦\¬ ýJW ôé38Ñ‘“D´cü8º/>EvòäÍO3¥XF«ƒ"0*¼ýûì]ºLWYl£ÿ}F…‹–d¿ÊaݶB1Ú©/hÚÁ§Üò¥þ½ƒ÷laù Ëݼ÷ ëDóŸ5É{Èâ5|ÔƒvïØ`’ß²]úbÒ|Ýf‘ìÙ³§ôËòy4}Ò(Ž3`â\jß± fÑ;¢0B9š7c­X<Ûݱ“P£fo›ä‹Ü…1!áÍ™öoC5ÿÀøù„9Ô¬uGu¶Ð´(ŒæLïݵ‰†½ßžgçdÏ÷&öœëE¢0â^mZ+ùG‚ë›9+©A“¶Ê®Ð­(Œ Óø@>éÚºEÉâÛ"Åühõ&} ‹Â8r??²Ï—ùΨ±3¨C×æÙšï‹ÂFOý›¾3˜.…1á»zÍ8åÊ—¿I¾¨‘íѴħ©’”È^ t”+×4á3ƒ‡yeÍfÌ‹¸|žú¼WŸï×mØŠjÕiL/œ¦ÕÁ?Ðü™ã)_¾‚Ôúîüø“'ihß¶üf/X¸85iéϾâÒQð’¹´~Í úï¿ÿhÂ7?ë™…<_8w’¾ D§OÖ‚.Ç¢0îÚÂ$( ›¶ã£Gë×ó¯õO‡v¡àu©d© Úb·R›(Œ{vnâ ’rŸz1¬ë~[FWXôú1#{Rér•¨˜oi+\½ ÛMÚQÍåãG‰ôõ„ÔYº¦EµããGŒ8ê7jcL+‰¢¾¥”¤ý[7jGŒöô¯ËŸA¿Ò©~£Öü…ŠQÐs§ÙÍÁ3Dµc-([Žœ¹ ÝÂóõ-…#€CØû£ž¯W{“)ïí(-›iYö¿¥%pt_š¿t“Eh) £=ú‚Ö˜4U’æ •÷ûàse×âváÜI<¿EÛÎ4þëÿQš4†1hŸü…iîô±´0h2µz»Ïß¿g+W |-úeyçÌÍÏ­Ó %õîX6°íÐQ“tæ×#ui[ƒãêÜs›~<›ìËŽÔñOkŒúÅ(a—^CøÔ* ôìÿ1µyËðÂÙ±e­nJ’"F­1bz4àË jÃ|e:¦c·ôv“ò{‹±NËa%IaÚέÖÕ—^:—ãed>Ê«.':- #^:Ó‚~;Mõ‹À8iì‡üåÚ´U7u!eÈàa/¢ i±×€O,²ŠREIªÇC=IkŒömãm˜×§ Í[ºÑØïÔªÛ„ü›W¢Ãûwñãzš¦hÑ}Aë¶t™á6†Î@þ]ú$ì·ëÐÞñÂV òÇO|Û½ßH£‚„Œ •ªS¥*µø±[×ñ­;ýÙƒ|ûw@+Ö ŸÍp>Ÿ–t“6¶^ÓŒe+TáÓ¨°=S¶I]Ojo嘻líÁˆ¯ñöìV$`@çäWæ5çÞÝ”í#RÄì&í¨ðÛƒ9ÓÆp|‡*Ùn»µ§…‚p“vÄ4>@AŸŸírÉV™kÑŽ+—ñËác&Wî|¶^Z·rö`Œe#I ×^¯iÒïdÌèiä³-îFö`´§¬Ö8]¦$)vHæFÉ%ÂT(â²a±Ë#vñ}|Õ™“¢$E†‡™rù¾=ÁìèÀ™ÌX²¢Ëù¶‡{1ZªûþÝ;<ÛÛÛ0Bh©Œ+óœÅãQå!÷õ+çJ(V¯íÆ™S vƒL£ìV¦4¬^ÐÁè6º¤=ÜͯÕοeɚݩëêy²=-ñ§˜5¿.æ‡:vl©ˆËóìÁX¦|eÎï_VsÛ$ìÀ!jù3y~Õ7ê‘W–ç¦.<Ó þìÁhOY­¡ ™nÛ°ögî½;$ÿA‘©ÉlŽÔEÀáåT¼Dõ!>m†c÷™· 4`LS€0œhNŠÇz”ÀíìÌšÀ‡»+V®Au4ׄo{*ñ0ûpA§\®bUjÕ¾+ݼqÍ–4/+#˜<Çú§ÑC;SÚ4i¹wn©²•¨q‹w]2ê¢5ưó§x;dÍ–ÙwΦGþ¦Û·c(ŸO!‹íªÕ¼Ñ,T¨5F — ßWþg㽤(–ʉÊÓcÕõ¨y›÷h㺕ԷÓ[Ô{à(ºpö$Ÿþ†‰JÀDè™(<–êÕ£­ú‚%^œÍÓTIR†÷`¤ŠŸš0?:-hyxdäÙõ¶ææôI³áz/*ÉFPboÝ í›ÿ0z#À¥ñžÊ}ÓÓ3³ºJžV\Çܷ쪛ì'3D`t’%ÍO×#ŒôAP¦kÔn¨9kŠÄ¸gç1Ðÿÿí]˜EÓ.ÉY‚ä|G (‚(Q’‚H ¡ð!éD@¢ÄD2ˆ€| 9ˆäœ%Ç#çt¿µô0»·»w»;=·]ϳ;3Ý==õVÏôÔtWU;] ~ŸÁSl}é¨Âˆý¿ëÀøºôBñ„El‘*ŒøÀÁvù’¹NðF íE?[`›{µ*ŒW„BšòËNøp‡˜?Π¡cç³p”'¨ÂèÊ&¼%Þš¶wÍVz¬#ìÉ2eÉAF  ñ#û8&ÌXM™³æ4ŽUï¨ÂS}A>K•¤W„»á•‡„Ò“‚$LøXé™G?öÿ†ÑxŸµëÎ8šƒ]±Â€Îõ…"B¡2Oß?JSl“ ŒÊ1ù8mÆUË7†½Ãû޶-Œä­c®üT·a+ŽÏrùÒ¾÷ÏŸ=%¼ˆÊÐÑs ¶Y~Q´#8å Eóo‰>gŽmfª0ÆT_PÑv–*IxÀÌSb"kجݽw‡ÝúWþo¡$¥}!Mœµ†àÑt`ïvºsç6Û".öÇ{€ò„2æ¹Tsµ}À—eí íàÛ—kØqÿžíÔ©Ufë‹oú³Ñ¡/<ZV%Fxñ}Ù}Áâ…óg¨}óšüAðM‡†4gén'g£ Å;*0bZ{Äàžl¬ÝºCO‹9ö½:%fƒÞôI’rèÊlѰ"Û˜ÁÖ̵?’çZ¹U…1qGEL«™GqÍ]p$v(Iª0šÛÓà“Çæ$xÛ=•¨ ã?WR›¦…/ü‡Ñì~æÔ1úþÛÏØ³­ù‡oq¼+;‚¼ªÂS}ÁÜÞVí[ª$ybªpÑ’œuJÄä0ÜMó?IÆ ô¢0r…Ð1¯ »$دH£nYþäñ#¼kîÐdžÛ@0ÚÉg ײ ã‰c‡©]sGì„À/XÈ*Œf<ˆèÛ±ëjÕ¸2Bà>FZlQ wEF¡~çõQ à9}54)!ˆÝˆ‰Î_£V˜Fol(üd0B´£J’'~Ř*U®úÊåˆ(—S4v™1DaàqB Íõ"è©a©ñAcsV¬îŠq„™BPL[0K¶\n0}“5Gˆk[ÁèQ,$)SÖ¨J¢Ì³c(ÆÇýñ–M«£°‹ “ Ä­‹M £™÷©ã‡ò!B­¤zÞ¡ šóck?PŒÒsNf‚¹ ff05Ž% b“Åè‰wwú‚§²þ¦Çó÷Dwç­þk!aù3!öÍσÃ9É<¤ ãOר ¯[Nýz:Œé0M' –û ÿ7’×3“é fMâ/Œ4½Z:L&+ݪ¨”i+W…ñ²ehݤ*Á>§B¥÷D¸üѱfô« ãÌ©£X¡Çо¤{÷îÒÄ1ùÓÑæ¥yd[aƒ4qæš(¿A#g1<‹ÈÇr:v Œà{Ûæu…ÞLåüÃWœOE³âk.gõ¾*ŒðŠÁžE¾hqŒ©ðßçLÁ®°Ý{“·ªÿTa”|FAAu?úŒ·vÿ©Â"Ö;ÍŸ5Ñ ÌS– T]lUaôE_p‚–$ayi€à€XV$â¢ðV[:ÙDCš£ŸŽŠÓBñ –y³2Û7ìܶÑXŽ£qóŽôz¹J<Ì‘ã|hÄõª—àÐù°ÀèèKáacÇü²JŒÀ%I 3ЖÇ1L¦MF+—ýÎim;÷&ÕqvTbœ0z€1Ü}hÿnjR§,ã2ÿ}?`|”æ|+öUb\%–:ÀXP†ðJ mÊ?C]ʃJŒV´u¨Äˆ ™Xó ®ðxÁܺyƒŸC9ÕØCØ~ØA*1¢/)/–°@%¢°&^Fðj!~’ßN>G%FÉÆôÉ¥^ÖXDÝnR‰±n£VÔ7¼0ÜžÈ /ÂíÀaDzeÂDÅŽuUbôE_°ºm-S’°ŠòÇŸv¢SGáÞÁ,¢ Cso$ò¤»>Òó|™è`( Vù-Û÷ ·*¿/“x‹:Fÿßÿè»oZpÝX†„ÍÛte¥‰ÿ©ÄÖ¡…˘P Cü@f%Sæ[½U‰Ñ¼ä´ påüªI%ÆÏÚõFÙñX—÷)ð` ês¡äbÙR‰Ñÿñ…2J`ã²*1bY©c”?Io‹¾©µð³kuu• £¹˜zBpE¸ýƒ0بYGÛlUcÄH®|Ï4üäÉ ƒµéO%Æêʃc†ý‡má}* Šo‹¶Ýl ª£/ú‚ÄnÕö91ååÑ!uÅêMÂól+kãë¡:¦^¿z™—Öð6µ€ï1Ç›A!ÍËUxº ÎÁ|y:áebÅpwÄ©tìà¶ Âè »¿é'ÆCO9Æ kÇ»BÙC€E„ÂH—.¡ ”‚½áiŠ]³2ì+æ`{¯‰~ £»IDŒ6ŒŽ[*Û÷ëÙ3'ÆÜÞú옶g°µ#ð /E«(Ø0BUÀ¡ …ˆ° °@gX‚ £?úBLÚúƹ¬ç´hÑÂmqËF’díX¨ž;1ñÞAºz«ÉzØ*˜–n[Å”®GK@K@K@K@K@K@K ¶% •¤Øn}}------ ”€V’‚²Y4SZZZZZZ±-­$Åv èëk h h h h h ¥´’”Í¢™ÒÐÐÐÐЈm h%)¶[@__K@K@K@K@K@K (% •¤ lÍ”–€–€–€–€–€–@lKÀk0É©S§ÒÎk±Å6ÃúúZZZZZZZVJÀ¯`’ *Q¢„•¼U];vì¤û÷ïQæ\Î+(“2sáä^±&ÓSŽñ”À¨Û1À;%öO¿xz/Ý¿wO÷9±ßqpáYx5Æ€î‘`9ùê…#bmÆëÙñq;_¾|´yóæ§ºÃº~ë‡$š•¤D Ÿã¥W4FÏAœÈxÚ1]ªø´kÇÝçĉ;Ò3“Ͻª1znÿ¸”“2i<Ö<ñ¬m’|H'Nœ Ã‡Ó=áÒB¡¡¡n‹_¿~¶lÙBñâÅ£bÅŠQÊ”)Ý–C¢/e=VbQÆ#ñÜÙ“tòø#5{nÊ™;ŸÛÚáb¸o÷6zN` -P„’%÷Œ\¼p–Ξ>!êËK)S¥v[gŒ°PñÎ[›`]½A2f£œ!y)I’d1†cuAÑÞ÷ïbŒ/¤ÏL¹^ ¥dÉRÆzµ£Ìé“ÿÒÍ×(Mº ôBúLæ,Ûö­îs.\¸Àý;9rä  ”uŸî.Éi*îUy±KÏÓÉGèÚÕË”N´aÞЗÆ„2Û¶­•¯^¹D7®_õÊ{ú ™)Qâ$^ËXi%FÉêz$Þ‹Çéß#û)iÒä”;OJ&RlJžòÅ‹SûöíéÀNÌþùçôÓO?iTY¿~}Z¸p¡‘†FѸqã(aÂ'¨/e*St°~õŸ4°wg:þïA§+ÔmØŠ¾ì>ÈH»uëuý¢1­Y±ÈHÃNÕš ¨{Ÿ‘Q:¡‡ÐoS~¦A}ºpù®½†Ó{u?q:×®«1B9úypOš:áÉ=,P{ü0ŠÞªTË.hÆu¬ÆxûöM6°;·¡q‘Ç¿ý~Uª^לl˾Õ]™^·j µÿô=NN›.=-Ywܵˆòc«ûœ[·nQ† <ò={ölªUËÞûUU;â…3¨Ï—´~õR'¼9rå¥YKv8¥©>°c§Vµiû–õ^ÙîÒc0Õù¨¥×2VfZ¼íغþÓ­59´×‰ÕWK…Qxß1”1s6§tÕ*0îÜö7uïÜD(GØoÙ!œšµúÚ)ÍÊË•¤ùóçSÍš5™Ç*UªP… „Æ—”Ö­[G‰%2xÇèRåÊ•iíÚµ<ÊT¯^=Š?>ýøã4yòdŠŒŒä-Nð¥¬q…;«–ÿNZÕá+¼^®áFLœ$ ߨfÅA*Û5«Á)F™*V­Í§MN̛ʿð‹ÁéÁý;©w×V´gçf#Í’?lYT`\µl!+HPŠ*¼S“Gþ˜?y}Ý®M[°‰òä{ÉÈ1©DƵ+—°‚$Û;¹Àº`ödV¦»uú˜B ¥\!îGT£å9HÚÑÌç½»w¨ÿ÷_˜“lßWÑçܹsÇÀ!û3#AìäÏŸß|¨|_Ž ¦1øqí²ü æ -Lo¾U_¨Ý¿g›r\æ ¨ÀøFXJ•:­ù2ÆþêåŽs;GËT`Ä`[ñžÁèõ˯”¡°Š5yVfò¸Á´ià ÿªœ´ÄÀ­zGÆcGÐ'õÞdÖËV¨F¥ßx›ÜC³¦¡‘C¾£Œ³Rõ÷)æuY’ÄÏS¸nWŽ0–5kV¢ÆˆFŽ<FªW¯Î_k»ví¢ôéÓsÑ7R©R¥xÿôéÓ”9sfiŠiYO×ó”¾bõ&$U<¬±§"N鸫”}‘oHŒaäÈaôè‹ÏÞ'|]ÿúûJ“ö.ºkû&jZ·ï/ZsÔ˜žx54)§}øq[1œ¸¿ì¬IŠ8¹ƒŽÚF±qï®-´NŒÀ5hÒ–‡JSŠï–wLQ~Ö®;5oó-ËÀ׿ˆSãÁØÇEw÷Žè]ñÀÆïøÁ=S«b!ºq¾ Bµ|æ+<.,íhf~â˜ÿŠ‘³n„—î÷@G’î]ÚÅÁ$c»Ï¹té¥K—ŽÊ–-K«V­2Cx?˜úœÏ?©N×.£wªÕ¡žýÆŠÑû'² –çÑ<§ j”äìÅkÿ¥t/dôTÔcz°`\¼àW1ÂÒ”2dÊJó—ï3ú(µ+;V’X¹å|´&î€ FŒ -^0ªÔø¾ë?Žäj F  áƒz>Jç,Ým¤»Ãâ)íÆ¹¬xêsây:ÑŸôéÓ§³‚„ŽÅ›‚„º'MšÄ—èÒ¥‹¡ !áµ×^£2eÊpÞ¼yóxëKY>AáߟÌ44vo XX8÷ÿ˜“FÍ; ^*ú*-^šóV.[À[üáå9uþßÔñÛþ¢¼Ci42ÙñÑ–EÆ/çaQÌ%KÊ”%;áËtFÌ5ÛEª0âkü½:MŽ x0r–7†výÚUÿ!I;JçΜd øZ¶—ɶnUõ9¶‚ˆæbªîUL³AA}óÝO–)HÑÀq›­ £Û‹‰Äé“Fp>füQ<Õë-]Æ1’*òr)§~'qbÇG7ò03c©Âˆ©6Pí-œ¡š¢¯a ¶J*ÈR%iÙ2ÇçI#3X±bB¡r%©$<è°÷ñ¥¬k]VoZÿWY«^³h«Þò÷*.ƒ!PW’JÒ‰YeÀKÖròñùP‰Ñ¶×®prš4Ž‘6we¬N³#ŒGåC’· ÿP‚¬‡ôsØtî:ž÷0¥á?ؘ©ªÏ‰ÙÕí)¥ê^ݺi ¨Y» ¥Hù¼=`<\EFw—ƒS̼™8«nÃÖîŠ(IS…1¡bÌïÿÍb“<ÎS~Âé%^+GÉS¤â}Õª0J;$W'&ÌÎ` tì¨C_°£¥6IðPݽ{—ÂÃÃiëÖ­tóæMž¿‡‘ãÛo¿ÍùÐjÏŸwh¿Ù²E5(ƒçÞ%¾”å“ÿÁC tïÞ]5´íß»îܺ)<´B©|ÅTòõ œ¾1½Â0¨+atùÒE×,ë}°#:¬­ÿ¬eÌÅKFU˜­†£F•ÑA]íz÷îmž75ô{},\¬¤˜–ªì?¤ jÇÍârÁÂ%¨Ú{Ñùs§ýÇÀ™*ú3;Û¶m£Úµk³ú%xßÖ­[×ɾÒ\^žª{õÐÝÌnÊTÏÓ´‰?ÑŽ-èòå‹”1S6‹í*§ŒUà2ש £ùrÎôq¼‹U©`È<•[UK”,G•ß­ÇÓQÍê—§¦-»ÐÁ};é﮽£f*±ÉºUa„"E ^ѹ_t¶„7-ònïZd©’tæÌæ±yóæN¼._¾œFŒA½{÷¦®]»Ò•+Ž‘JžüÉÔ‹<)Y²d¼{íÚ5ŸÊÊóUnñRÁÀÚL0›9uµêГ>iõ•˜v|2­’4©¹¼ty¿yãº9Y;#vb„Ñž4RÁT#çZUb\»rulYÛé‚P”û žØK'HÚñÁƒûÔÿ»Œ¯K!O8\Ä©ès€%qâÄ v–³fÍr‚‡À%K–PžÎšø'–|¨ÜªÂˆ÷äÄYkÄhüd‚ÂT¿qê$l!1R&íxU½K-U’¤?ÜgÍE"gNÇ”"Ë@mr¸Ü\QºAè¤|)k®CÕ¾tã¿v5*ÆÌb¸„¹Qð wh‚äƒÇˆÒ ²Ë³âñec´QñıÃÔ®ù»Ì Âàg7©Æhƃˆ¾»à$Ì»»Ìå­ÚWqû–uü%Ž/Æw^ÏA[Ÿ ã€/=·þ¸ŠU0¼Ö£¢ÏñvÁW^y2rvö¬cêÝ[y+òT´#øJ•* ³wårD6å-æâêª0š!è© þ[ãƒÆæ,[öUa!\àAЉXA˜}É’- …ˆˆÔxVÍ›§1‚y„§@¬Á6z±‚E)$oc¤ìÅ@œb¼HÇR% £G Ž4¦›¤§ZöìÙ9«D‰¼•Þ)æò2·ôr󥬹ûù»r#©™0zâ˜ÃS † ü…ŠóVZüóÁã¿5+óžôr3çÅö¾JŒpoÙ¸õ æEû.?Ä \•Ý2/I’àqü$wå¬LS1}†,l¬)óO~ͤgÍb%u©ês<]ðøñãF–üð3í¨hG°Š h˦ռ5ÿ!È$(SfGÍ ÿTa4³D¨•TÏ;Ds¾ê}U¥çl¾Ž÷¯Ä‘ÁÂÅ^ãC,e©Âè‰wfͳ7žÊû“n©’T©R%æaôèц÷ä1†Æ .Ìe4hÀÛáÇ“¹ã?~‚ ÚAªúœ5kÖðÚ“f ðÔíØ±#'!à-L ì í¾á‚=‹|ÑâSá¿Ï™‚]z¥Ô›¼Uý§ £ä˜à\ªûÑg¼µûOFŒæÏšè fK…*Èï(ÿN5F  #œE\c=ý½n9õëé0¾oØÌáH=‡¾—Hàû)žÏÀ:lð`;yò$aô§jÕªtìØ1öÁYPˆ¤7Bý,XöìÙC/½ô/e#ÉE‹ñ† &æÇìùRÖ3wÖä 2í¸Ÿûò‹¾áû¥©Ì›•9ˆ•\÷±Žd°DÌí㆑k½ê%x®1sð})<ƒÌóâ𘋸xŽó¶<Ža2mâ0Z¹Ìa<Ù¶soñõWóUþ©Â8aôc¸ûÐþÝÔ¤NÙ(0¾0>Š‹g”B$¨Â¸J,u€{Ê^B¸‡7ЇŠ!È.å×R…u ©ês¾þúk^2),,ŒC˜`íH·•†âø˜³‹Tµ#ú’òb ‹¿–Îã%àh€—¼Ú@ˆŸ„°v*Œ’÷é“J;)°(jl*Œuµ¢¾áí„áöDVx¡¨\¾t–/™Ë0á>&ÂÓØAª0ŽN …âŽ÷-"vnÛhØË5nÞ‘°<˜*²tY0¹oß>jÕªÉHË—/ 2„°–›™ 5mÚ”,X`$/^œzôèa¬ÿ&3|)+ωÉÖ×%P'"ÕöíÙŽoHy xtv'®¥è»oZ\'団y›®¬4Éó±­$l|ø€“L'_EWÖ|žûþ¼\åuã F×n“ø°UñîÛ`1A„b ° ”(±Ã“È|]_÷ƒ½᱉‘Ñ@ÖÿòGI’rTÑçÀ åܹsâ™MƺX„;P Æ>÷ëÙ3'ؘۊé`^®R®ªžGÔŸ8IRy™€·Á†€®‰wR„x'¥¶áíež©ðp0aÄ;k}BéE0f9cã.ó9Ñ)I–N·™/œ:ujÂ/&„)‰¼yŸÄvðvŽ/e½ÕcEB¤»†I÷T/nÖì9óxÊÚtѹibÚŽèŒ XÈŽv”Rc ³Š>!ð RÕŽ¸_sæÎ0U`´R9²BH*0‚¯Tâ„_0ÕÑ÷ú;"ˆ<,5Ü„}®–€–€–€–€–€–€–@0I@+IÁÔš------ ‘€V’‚¦)4#ZZZZZZÁ$­$Skh^´´´´´´‚FZI š¦ÐŒh h h h h h “´’L­¡yÑÐÐÐÐÐ x °ÿ~ftóæÍAðՌ9´›«D¬„§•NþëÀˆ8;O+iŒOGËž>¶‡<Í}ÎÑÃŽç1¡žV:uÔÑŽÏDŸ#b´ÙF‘¶]‰/ô,ô«gOy~ë5˜äÔ©SÅŠö7ìm}5-------%ТE ·Wó:’Jø¢ót²ÛãX"ð=í‰ðWš3hÔcðH ˆÛÁƒÂ;'ºÏñ.Ÿ¸’H4j1x$ #n{âHÛ$y’ŒN×ÐÐÐÐÐx¦% •¤gºù5x------OÐJ’'Éèt------gZZIz¦›_ƒ×ÐÐÐÐÐð$­$y’ŒN×6 <l i~´´´žn xõn úÇéĉtøðaºwï…„„¼å<Ñ™3gèøñã\&uêÔžŠÑõë×iË–-/^<*V¬¥L™ÒcYÕª0‚ï˜ÊC5ÆG¢Ï=I'¡û÷ïQÖì¹)gî|/{ñÂY:{ú„(“—R¦rߎwîÜ¢{wÐÕ+”!c6Ê’—’$Iæ±NÕ*0ÞºyîßÅ_HŸ™r½JÉ’¥ J1RT`4ƒ9}ò_ºyã¥I—^HŸÉœeÛ¾ÕÏã… ¸¿q GŽ” ²îÓÝ%9ÍjŒæ ;wŽûëË—/S¦L™¨H‘"”0aBs[ö­¼W¯^¹D7®_õÊwú ™)Qâ$^ËXi%FÉêz$Þ/Çéß#û)iÒä”;OJ&RlJžòÅ‹SûöíéÀNÌþùçôÓO?9¥=xð€† F_|ñ§=š>ýôS§28@¼¦úõëÓÂ… ò5jDãÆ³ýaVÀb*'!(:X¿úOØ»3ÿ÷ Óê6lE_vä”öðáúmÊÏ4¨ONïÚk8½W÷§2PŽ~Ü“¦Np¾’%OI=~EoUªåTÞ§?GY¬ÆxûöM6°;ËÂÌ?0~ûý0ªT½®9Ù–}«1º2½nÕjÿé{œœ6]zZ²î¸kåÇV?·nÝ¢ 2xä{öìÙT«V÷«Çš=gXQ^iß¾}Ô¡CZ²d‰Lâm¾|ùHvÊPx`õ½Ú©UmÚ¾e½WŽ»ôLu>j鵌•™Vco;¶n ÿtkMGíubõÕRaÞw eÌœÍ)]õ Œ;·ýMÝ;7JàQ'ö[v§f­¾vJ³òÀr%iþüùT³fMæ±J•*T¡B¡ñ%¥uëÖQ¢D‰œxß±c5oÞœ6mÚä”îz€‘¨Ê•+ÓÚµkyDª^½z?~|úñÇiòäÉÉ[×óT«À^c*U¸Ìõ®Zþ;ujU‡“^/W‰ð°%N’„Fׯ˃ûwRï®­hÏNï‘ÙW-[È † ïÔäÑ£?æO#Œº|Ý®M[°‰òä{ÉÌFÌ÷ýeQqíÊ%¬ aÄ­bÕÚ”\`]0{2+šÝ:}L¡‹R®Ï#ª1³’*0š¯|ïîêÿ½ãÇœn羊çñÎ;ÙŸ b'þüæCåû*0‚é£GRÉ’%yÄ #GÀš={vî‹¶nݪ—ù*îÕ7ªPªÔiÍ—1öW/w|p'H`ßh™ Œ—.ž§¶Íjp?úò+e(¬bMži™}zΪ^½:•*UЦL™Býû÷§Ì™3ËS”mUaÃ1•‡2p+†ÒÒ½³c#F9’äî‹«A’œýáÇmÅ0è>Z¿z©,î´ÅÐ/´þMÚòP)2?nÑ™Þ-[±t¾ÿJ’#Iª0bš±kïô®x`ãÇw<^u¶¤Z Ñ¥ˆ ôè´üV’‚£¹Q§MÎ_vx­Y±Èœe˾ÊçÊ–-Ksçε‹§‹¨ÄøÙgŸ±‚„Qú‰'FùõÄ“Õ骞Ç&Ÿ}é–U|ØI%©Ü[ÕÝ–‰6ÑÇ3Uÿ^¿œ¤ ™²ÒÏ“ýN鲩v墴yã*ÎÇÇ©Ï$ÇïìW©ñ!}×=÷œ£3Ì”9; ԃƎøªÕjh¤ûŒÓË –nOŸ>8t,fÉÓõ[·nMÛ·o§Áƒ{Úž4iWÑ¥KCABÂk¯½FeÊ”á¼yóæñVõŸ*Œà;¦òPñÏ?f_%fÉÓuk7øŒ¦Îÿ›:~ÛŸÒ¤u(°îÊx©8‹b.YR¦,Ù _ 3b®Ù.R…1ohaz¯NS££tNyóahׯ]µ "©Â(œ;sRL-vc|-Û‡Ëd[·*ŸG[x¹˜*Œ˜fÃ(häÈ‘±¦ áúªïU\ÃLÓ'àC|̤{!£9KÙ¾*Œb$ TäåRNýNâÄI ,˜m±ƒTaÄT¨vƒNŠPMÑׂ0[%déHÒ²e˘ǘ.c2|øðaZ±b—ƒòåJP’0 wð ³ÝŒk9«ŽUa1•‡UX<Õ³iý_œU«^3OEœÒ¿ âtìëÁkWø”4i^ðõÔ'å}ììÄãQù‡ä-ø„g_÷‚ ã~;€Î]Òó¦4|…èky•Ï£¯¼¨*¯ ãªU«˜åfÍšÑóÏ?¯ŠýÕkçóç’y3'0_u¶ŽVR…1¡bÌÞÿÍ¢?þœ•¥GÂÀyÊ/Ž~¹Äkå(yŠTV@ˆ¶U¥’«3Pš´/°3ò=H™³æŒ–G_ Xª$Áë t÷î] 'Ìiß¼y“çïaäøöÛoûÊÛ?ïД³e‹j|/¢óçNûÆœE¥U?Û¶m£Úµk³úxÔÖ­[×ÖQUwîÜÉ­oâ!C†°Ý(úQØ$………QãÆmóàSy¯ºÞjs¦ã¤¢ÅK“T0\ËÄèØÇéoUK”,G•ß­G‹L§fõËSÓ–]èà¾<ý GŠ®½£f1ÂäZ(H0ÂÆŠ¼¢s¿èloZäÝÞµ*ÈR% në c›iùòå4bÄêÝ»7uíÚÕœíþ•+ŽQLžüÉ4<1Y²d¼{íšÉëÈ­ Œ²î`ÙBqÁÛL0œ9uµêГ>iõ•9Ëï}ÝräʘòåãìãÚ•‹¨cËÚN2½@ŸÁSœ†Ã Ää H0>xpŸú×9îÒcÅN±EªžÇĉ3$ØÍš5Ë >á –'O§tUª0^¼x‘Yþïÿ…u8Äüúë¯:„[QM*ŸG3ïð°òËœôQÓöæ,åû*1öì7–2eÉAF  ñ#ûX&ÌX­dtŸ€ËŽ*Œå*T§iÞÓÅô~rÊ#Ì".œ£¿þœkx/"ä€ ²ôîG‡‚[>¾ÀÓi={öäônݺÑéÓ¾}qJ-T÷øØ&c“ëõa`ªZ³M·‘6î½I+·^ m»qúÏCzÒ…ó…˜üüƒ„öï;Z|µàeâãT”JŒ¹Bò³±;äãIÐù³§èãÚehï.Çh«_" Œð(«1B<*òŠ_P¬:IÕóˆ2('„p§Nb¥Î#GŽá>Î* ÑÕ£ ãíÛ·ùÒ¯¾ú*Û&Á£žÄÒPöJ𪳃T>fþ1Ú‚kᣥÜ[[Hs¾Ê}•aœ=ÿñ">8%5oð–ˆ×æ1”i*·ª06>°÷„ó >@k”¥¦uËѤ±ƒ 8‰9>lŒ‹v,U’d`GÄ<‚§¾@R¤HAÝ»w'9U¶k×.ŸXO•*•QþêÕ¨F¯èÀ@3Úc|§£0Hv¤Dƒ&íØàíˆ@ˆÍZà Ø<|`w@Üîß³Ý1ðÅ7ýy=  }yì`N†'®ô>u-§êXÆ6®¤6M«±þÃhš¹x;Í[¶—`‹„³æ¾Er„G6Y¯*Œi_È@g­£ñ“ SýÆm¨“°…ÄH”'ʨ K•$éšéÒ%'^ñ’Í™ÓaPåNÑq*ìr€seP79ìl.‚ˆÞ thv ŒvðíË5` ºv5j;fCº @æO;Líš¿Ëõ ~v“jŒf<ˆèÛ±ëNÂܹ]– ŒÛ·¬ã/q|1¾ózz54)ÿd|é!­õÇUÌ"P¶o÷óøÊ+OFÎΞuLK+÷¸bUÓ¤IÃWÓnf¹råâC»ÌTÜ«f<ØGÐS·Æ]³•«Â8B¸Àƒ±‚0û’%[.þ@ ©ñ¬.š7M9>\@FÔ0a"Ž=צS/V (…ä-`Œ”½ˆS .à,U’dœŽ4¦Ý¤÷Œ}¥%Jð)ÒËÃ|¾ŒÀ-C˜óTì«Â¨‚WëÌ÷Ø]Q\Í„9ßÇqRÆLQèÍe=íÃm¼eãJüÕƒ˜í»ü੨Òt•Ý1n^’$ÁãøIîÊY™¦cú YØXÛæŸüšÿHÏš#ÄJ(ë²ûyÄÒI’䇟K•¤J•*1ÃXZDz¤!AcظpaÇÐŒá_ƒ ¸$\äÍÔøñãyéŒ4!²·¤ £¼Çô¥D4SÐlárYŒ HšýÛ8Vn0¤š'ÔÑÁʼ˜lQWë&Uy¸B¥÷D¸üÑÖýúhÔ¬ # ÛñÅŠ¡}Iðœ8f Â"ð8ñ‹‚#l&Î\å7hä,†oäcY;HÕó¸fÍ^{ÒŒžº;vä$±…)¤ cXX³+˜>”¯d–•/_^&+ݪz%Әއ㠨îGŸñÖî?U1Zš?ËÑfÖ¬[*Ðì"UÑ·"4Ëœ9s¨téÒlŒ~ÿþ}6P6ÄO2O/ªÄ«êy”N(çÎ÷~2öÐõWiYK@K@Kài•€îWI+IOëÍ­qi h h h h h $­$$>}²–€–€–€–€–€–ÀÓ*­$=­-«qi h h h h h $­$$>}²–€–€–€–€–€–ÀÓ*­$=­-«qi h h h h h $­$$>}²–€–€–€–€–€–ÀÓ*¯Á$§NÊáøŸVð—–€–€–€–€–€–€–€§ˆÛ^ƒI>÷œ#HÖa{ZiçÎ"‚ç=^kNcŒ»رc'Ý¿2çr^ ;î"ŠÊù…S{éÁSŽñâé½t_?Q?Ž¥< ýªîsâØMéÝ«ŽÐ­›×=äyU’°æBè?ÍJ$£1z¼?âLÆõ[xý§YIJ”ð9^zåiƘ.U|^»M÷9qæÑóÈèÓÞ¯ê>ÇcÓÇ©Œ”Iãñ»ÃÓÚ&É“dtº–@°I €%‚ ŠæGK@K@K .H@+Iq¡•4ZZZZZZ¶K@+I¶‹\_PKÀO èu”üœ>MK@K@KÀ? h%É?¹é³´ì—€žn³_æúŠZZÏ´´’ôL7¿§$ G’âTsifµ´â¾¼z·ïáÇtâÄ :|ø0»Ø‡„„Phhh”*oݺEÛ¶m£ˆˆÊ–-—I–,Y”r2áúõë´eËŠ/+VŒR¦L)³lߪ gΜ¡ãdz-\¸Ð)½Q£F4nÜ8ÛfìÁƒ4lØ0úâ‹/çèÑ£éÓO?uÂl×Õ}ms;p®_ý' ìÝ™Žÿ{Ðéru¶¢/»â4(G?îIS'8î]Y0Yò”Ôã‡QôV¥Z2‰nß¾IÃv§ß¦ül¤ae¿ý~Uª^×)ÝŽ«1ºò¼nÕjÿé{œœ6]zZ²î¸kåÇ*îÕ 2xä{öìÙT«Ö“v÷XР«1JÖöíÛG:t %K–È$Þ"ÌþýûÒTX±fÍš´víZ¯l£¿mÓ¦×2VfªxwlÝ@ÿéÖšŽÚëÄê«¥Â(¼ïʘ9›Sºêwnû›ºwn"”À£Nì·ìNÍZ}í”fååJÒüùó 7&¨J•*T¡B¡ñ%¥uëÖQ¢D‰ ÞQ ”¢÷ß_|e'£)S¦ð—[:uhÇŽT¸pa.`•+Wæ›#RõêÕ£øñãÓ?þH“'O¦ÈÈHÞ•+ÞQ,sóæÍiÓ¦MŠD_½ Œ¾´yô^bÕòß©S«:\Ñëå*:”Ä⋎ù zÕ²…¬ AÑ©ðNM=úcþ4@öu»4mÁ&Ê“ï%®gíÊ%¬ a4ªbÕÚ”\œ³`ödVºuú˜B ¥\!QGTc„Æ›$ͼ޻{‡úïPèÍévWïܹc@ý™‘ vòçÏo>T¾¯#˜>zô(•,Y’û]ŒköìÙ¹/Úºu«r\æ ¨ÀX­Z5J›6­ù2Æþ‚ xßü¬™ŠvT<—.ž§¶Íjpôò+e(¬bMži™©÷&×_¶B5*ýÆÛtøàš5m òe̘•ª¿ßH ,K•$ M7lØňFŽ$¹jêyòä¡^½zñˆIòäɹØ×_M9sæäý9sæJÒÒ¥KYA—݆ (}úô\¦zõêTªT)V®ú÷ïO™3g–—S¶U… -ꈯº½{÷Fù²SÊ¥bU}is–,?ÄtX÷Οp½1ÂÈ‘¤:µ”»¼Å5¾V4iËC¼Hü¸Egz·¼cJnÅÒù†’”3w^êÚ{½+ØøñW݆-©VÅBt)âý#:-¿•$m’Ta4 gÚ¤áüe÷FXZ³b‘9Ë–}U÷ªd¾lÙ²4wî\y+[•?ûì3V0J?qâD§Y;ÁªÂøÍ7߸…R©$Õ¨QÃm«U=¯_Î R†LYéçI‹~§tÙŠT»rQÚ¼qçã#O5©Â8vxf½Jé»þãH®’)sv>¨ñU«ÕÐH·§¥†ÛÓ§Oç‹YArÇð+¯¼ÂÓmRABÌó¿ûî»\üرcÆi“&Mâý.]º ^{í5*S¦ çÍ›7·ªÿTaß­[·¦íÛ·ó›·¡þ¸ŠÑ—6WñÏ?f_^fÉÝu ¼Tœ‡s1.)S–ì„/Ð1G.)ohaz¯NS££B::§¼ù‹p‘ë׮ʢʷª0JÆÏ9)¦»1¾–íÃe²­[•Ï£­@¼\LFL³á4räÈXSp}UQ·;:t('7iÒ„í¯Ü•±:MÕó!F’@E^.åÔï$NœÔ€€Ù;HFLµj7há¤Õ}-Sp°URA–*IË–-c=-W®\ábr´+V¬à4(_®$•¤ƒmJ\ËYu¬ #ø>|8KZÅ«¿õ¨ÄèŽ'wm•i›ÖÿÅÕÕª×Ìïjo\sÜ«iҼൎ«W.‘|ÈCòôZÖk¦ýœjŒCú9ì:wHϧv?¥á™vß«°ìsª0®ZµŠyiÖ¬=ÿüó>óeå ª0ºãN1°cµmÛÖ]%iªžÇü…Š1¿ÿ[4‹MpðH8Oùe§—x­%O‘Š÷Uÿ©Â(íR¦Jí!MÚ¦  cGÕè–N·Áë t÷î] 'Ìiß¼y“çïaäøöÛos¾§?ܼ«W¯æì7ß|“·Ð€ÏŸwhÊð~s%Œ>à‰b©Àhß¾\ÃNŒîÚÜ^ý-»o÷6>õÞ½»4jh/Ú¿w;ݹuSx¬…RùŠ5¨äë¼V}ñÂYÚúà ´xIgåÔ•KÅsp›çÍG ýžG­ +Io„UöZ¯•™*1nþ{¡S.X¸U{ï#:î´•¬Ç¸.Õ÷*ƒ§8 ‡»­ØÂDU<¸Oý¿ëÀœvé1„â 'ŠØ"U÷jâĉlefÍšå€ðƒ¤ ãÅ‹™ýÿþ÷¿Q`À!æ×_%x›AATMª0ºò [‰·cÇŽ®ÙJU=`ºg¿±”)Kš0jÙßÀ1aÆjÊœ5§q¬zGÆrªÓ´‰?Ñ >Åô~rÊ#Ì".œ£¿þœKÛ·¬gX9 ‚,½ûÑ¡€à–/0Ä»@ZÏž=9.ÿ§O»ÿâ„gƒ?~¼á]$ ´PÜãc›T`ŒmL®×· £§6wåGÅ1 AUk6 ©ó6ÒÆ½7iåÖ Ô¢m7NÿyHOºpÞ¡ôs‚éÞófNà”ð¾£Å×¶s,™\!ùÙuÃxtþì)ú¸vÚ»Ë1ÚʉŠÿTa„G \ß«û *òŠbÞ«Wu¯âƒ /nÄÙÁ‹õÔ©S¬4ÀVðÈ‘#ÜÇyç̺\Uoß¾ÍL¾úê«l›>xKCuØ+ᵃTatå}êÔ©üN¬„]Û’UÏ#ê‡qöüÇ}>Ü$5oð–ˆ×æ1”i*·ª06Ž2°÷„ó >@k”¥¦uËѤ±ƒ 8‰9>lŒ‹v,U’d`GÄø§¾@R¤HA-Se»víŠÂ:¦å¤›í Aƒèõ×_7ʤJ•ÊØw (cÆŒF9•;*0ªäןºíÀè­ÍýáÙ×s¤§Gƒ&íØ¨÷*‚=6ký aÔtøÀî(Õîß³ÝðÅ7ýÙXÒµ¼áà1/Œ¡cçÓ«ø7rÈ ×sT«ÀxYL#ŽÜ“µ[wè©‚mŸêTy¯" "ÜÇÂzdƌ̼l¡@ÙAª0Ój¹ÇèÜáÑàdW8UÄã?Lƒ0€àAlw0PÏ#Àü³q%µiZˆðFÓÌÅÛiÞ²½[$|œ5ÿð-’#f§«¼WÝq€¥‚$É?y¬j« c¡B…˜å•+WFaA&AÒ1&J‹Ta4³‰Y B­x 0i.oõ¾ŠçÄRJv*Œžx_8÷ÿ8 €UyðYª$UªT‰†÷€ôHC‚<ƪŒ¢| ñžøà‚¢h»£ p2\äÍÎÁÒ'iBdo;HF;xé5Taô¥ÍcÊ«¿åJ‰ˆ­ ÙÓÇÑe1ú!iöoãX°qžPÇKù­›Tå¡ë •ÞaþG{4V†Ñ7–éÀо$xÐM31•—FxœØA*0ÂiâÌ5Q~ƒFÎbHð¦A~×^Ãí€HªîÕ5kÖðÚ“fðԕƾb S;Hư°0f!V0}( Sá, ÂG¬¤ £ä˜à@r l,˨ުxÁ3F‹@óg9ÚŒÄßëWi©ð@ùÀ–ÏŽùŸ*Œpqõô÷ºåÔ¯g{f®a3‡#IÌ9yÉ1/}IDm…ŒþT­Z•R® %Gz¨õíÛ×ú„+*Cº–))P Ï‘,XöìÙC/½ôÃç ¬½c×ü²*ŒÀ¯@9„/c˜À5WÊìׯɯ?”WEª0úÒæª°Ézß©V‡ÆýÜ—Ÿ†ï—¦2oVæ`dëW/å"_…1¢kO=ÀXÛíÐþÝÔ¤NYY±ý~ÀxvM]µ|!¡(CpÍÅ}¹Q<̰ Ù¥<àZª0¢î`!U÷*¢ÿcÍ/(X‚kGâ9”Æø@³‹TaD_‚Ð,XÝ téÒlŒ~ÿþ}6P6ÄO2O/ªÄ« £äY„2†wIlªç±n£VÔ7¼0ÜžÈÜPT._º@Ë—Ìe˜pŸaMì UG §…s¦p?ØÛ6Òž›Rãæ ËJ©"K•$$ƒGD«V­8¤ŒG—~¼ì±–›$ó:n® áÊ2Ò(#PP0ŒPòPž@ˆqÑ£GÃè[ž§r« #x6óÒ âòbŸ3-üS…Ñ—6·ŽÛªR¤|ž†_H}{¶ãŽeŽQÁ3¤“°2?t˜ —亮L¿sç6ï~Ö®‡ˆG“‰õÝ$a êóνy}8™¦z« £;¾ã e”À$+wå¬NSu¯b´1ƒ0Ê‚Ÿ$¬+Ù§OÛÜÿq]UQ÷„ xê žÅpûadþË/¿ŒbÊ™ŠþTbDÜ>àuîÜ™·±ñ§êyü þ§Zd̰ÿp¼ xŸJ‚‡-Wd:á®Pš`1A„”.]J”8‰)׿݈“;èØ¡mAƒÑÅ;·¸³6+•®e¢;¾wiíÚ±%hú8¡œ;wŽà†‡®'“€èp™óƒñyD8˜2À˜[:ʘyöu?Ø0ÊpÒ£ÏW<îÊ[Ÿ¯‰¾,Bôe)D„mx{Éõ#Ýñ“´ˆS¢Ï9}ÎÇÄhÿ ¦â®ˆ •Ýé oenœÛÉïOzŽ¥#IfFÅ?« ÓyóæµºZ¿êS…Ñ/fô,`D¨{×p÷Š3q’¤„PÁB*0ºbK’$™k’­Ç*îUøÆ†‘¯'Á©ÀˆkAy õtY[ÓU`´R9²BªžÇT¢/Ã/ÈjŒPøä$vâ³ÔpÛNÆõµ´´´´´´´TJ@+I*¥«ëÖÐÐÐÐЈ³ÐJRœm:͸–€–€–€–€–€–€J h%I¥tuÝZZZZZZqVZIгM§×ÐÐÐÐÐP)­$©”®®[K@K@K@K@K@K ÎJÀk€ýû÷30ļxZ ѾAcÜná#‡v3ļxZé俌ˆ]byŒ¢¦†ƒÓÇSõó¨F¾vÕú,ô«G;žGÄözZéÔQÇóø4÷9gO9ÖêôÔ†^ƒIN:•Ãñ{:Y§k h h h h h h Äu øLÁÅðEçéä¸.ð|cÜoÉg¡‰ðWZ8ˆÛqã³p¯jŒqånôÎç³ÐçȈ۞$¡m’NÿÙOI“&§Üy Pê4éÔb‹ôBÿüóOä¨Q£¼”pŸµhÑ¢È|ùò! ÓïóÏ?7N¸qãF$Ž]Ë¥'RÄg2ÊÉÑYEV«V-JùFE %LóyLÁüýû÷#là=z´Ï˜\OŒ7oÞŒüâ‹/ l²íÑæ3fÌpeÛ§ã`Áèë}í È¿VýÍÏã¦ý·#}ù ;/2G®¼Qä^·a+£žUÛ.FâX¶‰Ü&Kž2²÷'åÜ]÷Ç1sóÒ¦Kﵬ»óÍik×o Š>÷ª”»íìÙ³}i:§²Ár¯J¦öîÝY©R¥(xчûKÁ‚±L™2Qp¹¶ç°aÃü‚,ÁüÚµk# ,k… "ÅǶ_øpR0õ9¿L_)>î¢`lÙ!< >Gbô$$Ë?…æÏŸO5kÖ÷!Q•*UH4’Ðø’Òºuë(Q¢DœŽ¿?þøƒÄÍÉ#LõêÕã¡ &ШAƒôòË/Sþüù¹¬)mÚ´Q®…„ pº£e*0bïGŒz–-[–jÕªÅ3-¤åË—SãÆé¯¿þr+‰*úœcGÐ'õÞdvËV¨F¥ßx›ÜC³¦¡‘C¾£Œ³Rõ÷©€#†z¼¯šòµk×"1* 8üé§Ÿ¼Ô¹}ûöÈ1cÆðȉ,ˆó3dÈÀç>\&Gþþû¼óçÏé6làt\ïôéÓFº/;Á‚<~:t0¾ìbc$IU; å/²W¯^‘m‘tìØ1÷wß}'“}ÞK;úr_û R~ñ˜G^¼í¯Ür>#A¸§¾ì>Èë×ÖÔùGví="rÞëF9œ‘!œÿUø#Ý|ÍÏ;÷æü7ªðÖî‘$U÷ª˜¦a<â¥ãk3E[>XîU0Z±bEÆY¿~ýÈ»wïFË{L Fw<ã9•ý­0mpW$Ú´`Á(>4‹0]pzŸîÛ·ÏÀˆçÄ –>§ò»õK•Fþ½ï–ѵéø=§c„Éœnۗ=ÉÇRÃíéÓ§Ú¬˜J÷ gÂW FMÌóú°/*Z´(ŸtåÊãäI“&ñ~—.](}úôFúk¯½Fb8•çÍ›g¤«ÜQ…<·nÝšÄÃË£-B!T Ãkݪ0¾òÊ+Ô­[71J‘ܸ>l;Þ}÷]> “‘®zGF_îkÕÿüc&ÁÆèåWʘJóz¹¼¡…é½:M)~ü'ƒËBÁ¢¼ù‹ðyׯEµë8wæ$ ØP®eûp¯õ«ÊTÕŽªøõ§^UÅK”–.]Ê,9Òi¤ß>9GFO< :”³š4iÂöWžÊY™® #F’@¯¿þºÓû38’„ w•nUõ9;·ýÍ|×nЂž{î9CMÑgN8ʶJF†…;–*IË–-cÖü]ÆFvbtˆë(T¨sÅŠ¼¡DW’JÒÁƒ]³”«ÂfÅèK*a܇JUbtdžTˆÍ °»rV¦Ù‰ÑÓ}m%wumZÿ'ת×Ì]v´iW¯\"Ù9…ä-¥ü~_sZç®éùÔî§4¢œdq‚íh1ë1®NÆU«V1Íš5£çŸ>Æü¨(¨ £;^á3nÜ8ÎjÛ¶­»"JÒTa,^¼8óûÛo¿±Y `àŒé6PXX¥J•Š÷Uÿ©ês R¦rvbJ“ö6@Þ±£jt€'Ÿ¸J€¯3¶¥ððpœ¶0€äù{Ì“¾ýöÛNW@CƒäöíÛ´{÷nêÑ£D•*UŠ0— ‚,¦ØxžP®„‘걃T`´ƒo_®a'FtX«W¯föÞ|óM_Ø ¨¬JŒ1¹¯b>†'ïÛ½KÞ»w—F íEû÷n§;·nRNa[T¾b *ùz§šÀ÷•KÅó{›çûG ýžG¢ +Io„Uv*»ùïUô¿E³¨`áTí½èü¹ÓNùv¨lG`€fíÚµÙÆ} vH®ØÌǰW®ä^±xÊ Œ®üÁkQÞ3³fÍrÍöé8˜0Â+ú›o¾‰ÒžÂ0ß'L®…ƒ¥Ïùðã¶Fÿ‚~jþ_û#Çÿ¶*²qóŽæÿ šäÔOÉ~0º­ÄèŠ]Ç…e$cŠˆ<!VÒzöìÉ×€=Š0°6®‡˜H°]Byá‚Êé'Ož¤W_}•Ä ÈÇæùÇçÆÖŽ Œ±…ÅÓuíÂO9ì=~üx'o+O¼Y•®cLîk«px«öH ª5ÅWæ¼´qïMZ¹õµhÛÓÒ“.œ?ÃûøË’Ÿm—P¾tÙŠœ~þì)ú¸vÚ»Ë1JŒDx”9´—Þ«û *ò —‹­?Uí»9Œrbªq½N:E¿þú+™# ú,»HFŒàƒÐßÂ6 }ð$ž;w.§# Ϩ¤ £+ïmÁµ0+Q£F ×l¥Ç*1Â$Eö¥âƒÓÀxMÛEªúœ&-:ì&/E\ Ž-kSò¡Ô´n9š4v-Q¢Äƾ¥;R[r·õWSÊNÕák#D‚ñÈ%K–8å™DG) _¹¾ÖÄð?Éà<üD`JsqÞGŒ ä ;¨(y1IŒîø †‘$+ÛÑ#ê–í:hÐ ×lŸƒµÄÝ}í3@q‚üâ‰îËHæË‘¤)s68}a e)#DÿOã8åÉs±ýcõ‘HÑ1q9é=òçúì1‡ºÿ\wÜ87¶G’TÞ«æ¶Z¹r¥qß^¼xÑœãý`¹WåH’;OdaÐÌ8ÅÔMŒq™  F3OxÉø}Â^Çœå×~°`nþÆ=)>6ù½)ùH1 Çé9ó×û;úÙ'­Ûu5²ÏàÉ‘BaЬߏMd§®#1’-û¨q¿þeôGòœ˜l%FO7¥#IÒðVL‘‰þ÷ !:vΜ99Á[ôSD>ÅÜ8_kø’ùÒÓ Ç®„ˆÞ DˆµƒT`´ƒo_®¡ã¡C‡8®xÁ%ùç V”UÑÌ£»ûÚœ¯jF kW£>™³8lù¼Íã§Ï™:vÀu`Îÿâ…³´}Ë:¶SÂã;¯ç WC“òïÝòޝW|é!­õÇUø<Õv¶#°ÀCSÒÙ³gå®Ò­*ŒiÒ¤a¾…²…ÿ\¹rqšp’§"AF3¯"È1ÇáCZÓ¦¯(s¾ê}UaƒBÜAxëaö%wîÜ´xñb&yä ñèì }Žä;aÂD„nm:õ"¡ ‘P”($o±B€Ã¶îE7Î%òÜ@¶–*IÒ}#Í„i7é}£@od^’D –(Q‚O‘Þæóaœ’^næ<ûª0ªàÕß:UbÄR5åË—gcü† Ò€Ž—°¿¼ú{žJŒîxrw_»+geZ¾Çîûi&,pâØ!Nʘ)ª3„¹l2Ó’$ Dx€ô²°±6 ¶Í? …KBzÖ!òPéÖîvÄRA’䇟Çß çþg#Hnò©¼õ;ÏR%IÚÁ{@z¤3y,†ýHFT1bA³‡G$xÅõë×1g,G`¹‚‹¼¹“‚ "t£"{ÛAª0ÚÁ{L¯¡ #î x8Âîìƒ> ´"§Ç©ÂèË}­w)•4{ú8º,Fx$ÍþmÏí#¾QžÐBœ«&U}¹¯Uc|§Z÷s_‚ñuÃ÷KS™7+sÀµõ«Em^$|¬Z¾e¨DÉrüŒ¼Qá2ÍŸ­? ܪès°È¶4ò•϶bÁ€ÜÿÑÞÁr¯‚1Ú`„á8щiðHáMŒ"~Q0a^{Æ}-<öüÂãî¤`ˆ%¾¤s”lGláäÎáÉwiÁÒçÀ½ßŒ ûèÇ`È GúyŽÄè?Ҟ߸ [Ú¼y3áçOmDQ†¡5l¼ Kà îµXd0cÆŒ”D,¾éóå™3göZ¯·:ÌyÁˆÑÌŸû£w)ª¸W}½¯½sèÈ]±z“¦¶•Ї5ŽIq§2ׯ]aÃ댙³‰……S8å™îÞ¹Í!ˆç1]º ”(±÷çÑ|î;·xYXúK÷.í¢];¶MŸ',û€Å˜1êmÅôp0>¸_aÊcniæàoâ¼`Ã(Ø—ê_0bO—/_æÙDØÆû4Ж`ês>| FÂOˆ0w)S–ìÆ(x íxãÜNîW=é9–N·™™EWü¢#Ü´yò䉮˜‘FÏ›7¯q›;ª0Æ&&×kkŒO$â˽êKÙ'WP·‡pþ®!ýÝ]-q’¤”=ç‹î²¢MK’$Y´eTPq¯ÂÀ76Œ|=ÉIF\ ÷khh¨§ËÚš®#ð©À|Pr¥×bl㵺ÏÁº’ˆ¼m7ųû‚úzZZZZZZZqAZIŠ ­¤yÔÐÐÐÐа]ZI²]äú‚ZZZZZZqAZIŠ ­¤yÔÐÐÐÐа]ZI²]äú‚ZZZZZZqAZIŠ ­¤yÔÐÐÐÐа]ZI²]äú‚ZZZZZZqA^ƒI.X°€BÆ šG-------$àW0Éçž{޲dÉÂÑ­ý¹h\8 ä‚Äq]¿xÔý[ФÛ1èšÄ/†t;ú%¶ ;I·cÐ5‰_ aeoäu$ÉÛ‰:OK@K@K@K@K@K@Kài–€¶Izš[WcÓÐÐÐÐÐð[ZIò[túD------§YZIzš[WcÓÐÐÐÐÐð[ZIò[túD------§YZIzš[WcÓÐÐÐÐÐð[ZIò[túD------§YZIzš[WcÓÐÐÐÐÐð[ÿùߨ‚üž¾[IEND®B`‚srt-1.4.4/docs/features/live-streaming.md000066400000000000000000000205531412557703600203650ustar00rootroot00000000000000 ## Live Streaming with SRT - Guidelines **NOTE:** See also best practices and configuration tips in [Section 7.1 Live Streaming](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00#section-7.1) of the [SRT RFC](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-00). SRT is primarily created for live streaming. Before you use it, you must keep in mind that Live Streaming is a process with its own rules, of which SRT fulfills only and exclusively the transmission part. The Live Streaming process consists of more parts. ## Transmitting MPEG TS binary protocol over SRT MPEG-TS is the most important protocol commonly sent over internet using SRT, and the main reason for initiating this project. MPEG-TS consists of single units of 188 bytes. Multiplying `188*7` we get 1316, which is the maximum product of 188 that is less than 1500 (`188*8=1504`), which is the standard MTU size in Ethernet. The headers for the IP and UDP protocols occupy 28 bytes of a standard MTU, leaving 1472 bytes, and SRT occupies next 16 bytes for its own header, which leaves a maximum payload size of 1456 bytes. A 1316-byte cell is a good single transport unit size for SRT, and it is also often used when sending MPEG-TS over UDP. Note that SRT isn't limited to MPEG-TS -- it can be applied to any "live streaming" data transmission (as long as you use Live mode, which is the SRT default mode). You can use any other suitable data format, and any intermediate protocol on top of MPEG-TS with an extra header (this is an option that people often try with RTP) - note that 1316 is the default maximum payload size, which can be changed using the `SRTO_PAYLOADSIZE` option to no more than 1456). However, the transmission must still satisfy the Live Streaming Requirements. ## Live Streaming Requirements The MPEG-TS stream, as a good example, consists of Frames. Each Frame is a portion of data assigned to a particular stream (usually you have multipe streams interleaved, at least video and audio). The video stream always has its playing speed expressed in fps (frames per second) units. This value maps to a duration for one video frame. So, for example, 60 fps means that one video frame should be "displayed" for a duration of 1/60 of a second. You can extract from the stream one video frame and several "audio frames", that is, single units that are part of the audio stream that cover the same time range as the video frame. Every such unit in the stream has its own *timestamp*, which can be used to synchronize the reading and displaying of the data. Now, imagine the very first video frame in the stream, which is called an "I-frame". This frame is just a compressed picture, and it needs no additional information to decode it into a displayable pixmap. Several transport units (of 1316 bytes) are needed to transmit the I-frame over the network. The audio frames for the period of time corresponding to this video frame should also fit into this "duration". So, you will need to send several units of 1316 bytes to transmit all these data over a network. But you usually spend much less time to transmit than the actual "duration" (1/60s in our example). This doesn't mean, however, that you should send the video and audio data as fast as possible and then simply "sleep" for the remaining time! It means that you should split the whole 1/60 second **evenly** across all single 1316-byte units. There is a "unit duration" for every single 1316-byte unit, so you send the unit, and then "sleep" **after every unit**, if sending (as usually happens) has taken less time than the exact time predicted to be spent by sending this unit. Should sending that whole "I-Frame with corresponding audio" take **exactly** 1/60s? No, not exactly -- there may be some slight time differences, for two reasons: 1. Sending over a network has only average time predictability. Unusual and unexpected delays, coming from both the network and your system, may slightly disrupt time calculations. Therefore you should not rely on the exact number of bytes sent in one "frame package", but rather on overall transmission size, and do frequent periodic synchronization of the transmission time based on the timestamps in the stream. 2. Not all frames are I-Frames. Most of the frames sent in the video stream are "difference frames" (P or B frames), which can only be decoded if all preceding (or even succeeding) frames are already received. As you can guess, difference frames are much shorter than I-Frames, so there is much less data in a whole "frame package" to transport, even though these frames still cover the same time period ("duration"). Taking the above into consideration, you can understand that the most important factor in synchronizing the streaming data is to make sure that the entire I-Frame is transported at a specified time, and that every subsequent frame is received at more or less the same time as the I-Frame, plus one duration period. There is only a slight time tolerance, which results from size differences. What must be absolutely adhered to, however, is that the interval between the first network unit transporting the first portion of one I-frame and the same unit for the next I-frame must correspond to the interval between these same I-frames as given by their timestamps. In other words, there is some slight buffering at the receiving side, but the requirement for a live stream is that data must be transmitted with exactly the same **average** speed as they are output by a video player. More precisely, the data must be produced at exactly the same speed as they will be consumed by the video player. ## Live Streaming Process Now that you know how Live Streaming turns a bunch of MPEG-TS encoded video and audio frames into a network live stream, let's complete the definition of live streaming by describing transport over the network using SRT. The source side should be a real live stream, such as for example: - a file read by an application that can generate a live stream - a frame grabber or other device that is capturing data at constant rates, and then passing them to a live encoder - a live network encoder sending a live stream over UDP - a camera that uses some simple encoding method, like MJPEG `ffmpeg` is an example of an application that can generate a live stream from a file. Note the following: - The `-re` option is required for making a live stream from a file - The `pkt_size=1316` parameter should be added to the UDP output URI, if you make ffmpeg output to UDP As we described above, you must first split the data into individual cells that can fit into one UDP packet. This entails defining time intervals between these cells so that they are in synch with the timestamps in the transport stream. If you have a device that is capable of grabbing individual frames, it will usually capture only one video frame and the corresponding audio at a time, which must be split into single network units with appropriate time intervals between them. This can only be done by an application with explicit knowledge of the type of stream and how to transform it into time-divided single network transport units. The `srt-live-transmit` application, or any other application that uses SRT for reading, should always read data in 1316-byte segments (network transport units) and feed each such unit into the call to an appropriate `srt_send*` function. The important part of this process is that these 1316-byte units appear at precise times so that SRT can replay them with the identical time interval between them on the receiving side. When you feed these 1316-byte units into SRT it will send them to the other side, applying a configurable delay known as "latency". This is an extra amount of time that a packet will have to spend in the "anteroom" on the receiving side before it is delivered to the output. This time should cover both any unexpected transmission delays for a UDP packet, as well as allowing extra time for the case where a packet is lost and has to be retransmitted. Every UDP packet carrying an SRT packet has a timestamp, which is grabbed at the time when the packet is passed to SRT for sending. Using that timestamp the appropriate delay is applied before delivering to the output. This ensures that the time intervals between two consecutive packets at the delivery application are identical to the intervals between these same packets at the moment they were passed to SRT for streaming. srt-1.4.4/docs/features/packet-filtering-and-fec.md000066400000000000000000001203121412557703600221540ustar00rootroot00000000000000SRT Packet Filtering & FEC ========================== - [**Introduction**](#Introduction) - [**Configuration**](#Configuration) * [General syntax](#General-syntax) * [Configuring the FEC filter](#Configuring-the-FEC-filter) * [The motivation for staircase arrangement](#The-motivation-for-staircase-arrangement) - [**The Built-in FEC Filter**](#The-Built-in-FEC-Filter) * [Sending](#Sending) * [Receiving](#Receiving) * [FEC Packet Header](#FEC-Packet-Header) * [Cooperation with retransmission](#Cooperation-with-retransmission) * [FEC Group Dismissal and Deletion](#FEC-Group-Dismissal-and-Deletion) - [**Packet Filter Framework**](#Packet-Filter-Framework) * [Basic types](#Basic-types) * [Construction](#Construction) * [Sending](#Sending) * [Receiving](#Receiving) # Introduction SRT has a general-purpose mechanism for injecting extra processing instructions at the beginning and/or end of a transmission. This mechanism, based on packet filtering, was originally created as a means to implement Forward Error Correction (FEC) in SRT, but can be extended for other uses. As of SRT version 1.4 there is one built-in filter ("fec") installed, but more can be added. # Configuration ## General syntax SRT packet filtering can be configured by an `SRTO_PACKETFILTER` socket option, which gets the configuration contents passed as a string. How this string is interpreted depends on the filter itself. However, there is an obligatory general syntax: ``` ,:[,...] ``` The parts of this syntax are separated by commas. The first part is the name of the filter. This is followed by one or more key:value pairs, the interpretation of which depends on the filter type. Note that all keys and values are case-sensitive. You can try this out using the `SRTO_PACKETFILTER` option, or the `packetfilter` parameter in an SRT URI in the applications. The packet filter framework is open for extensions so that users may register their own filters. SRT provides also one built-in filter named "fec". This filter implements the FEC mechanism, as described in SMPTE 2022-1-2007. ![SRT packet filter mechanism](images/packet-filter-mechanism.png) On the input side, filtering occurs at the moment when a packet is extracted from the send buffer. A filter may then do two things: * alter the packet before inserting it into the SRT channel (the built-in "fec" filter doesn't do it, though) * insert another packet (such as an FEC control packet) into the channel ahead of the next waiting packet from the send buffer. On the receiving side a packet is first reviewed by the filter, and then potentially passed to the receive buffer. In the case of FEC, the output of the filter may be: * nothing (this happens when an FEC control packet is received but does not trigger rebuilding) * one or more packets (when together with a newly received packet the FEC filter succeeded to rebuild a lost packet) ## Configuring the FEC filter To use the FEC filter, set `` in your configuration to `fec`. Then add the appropriate key:value pairs based on the following parameters: * **cols**: The number of columns in your FEC matrix (which is the equivalent of the size of each row). This parameter is obligatory and must be a positive number >=2. * **rows**: The number of rows in your FEC matrix (which is the equivalent of the size of each column). This parameter is optional and defaults to 1. If the value is >=2, this corresponds to the exact number of rows. Beside this, two other special cases are allowed: * 1: in this case you have a row-only configuration (no columns) * -N (where N >= 2): column-only configuration. In this case N designates the exact size of a column, but the FEC control packet for rows will not be generated (in other words, the **cols** parameter designates in this case only a number of columns in one series) * **layout**: The format of the FEC matrix. The possible values are: * **even**: block aligned (default) - columns are arranged in a solid matrix; the first sequence numbers (SNbase) are all contained in one row: ![Block-aligned Example](images/block-aligned.png) * **staircase**: non-block aligned - column starting points are staggered; the first sequence numbers (SNbase) have an offset equivalent to R+1: ![Non-block-aligned Example](images/non-block-aligned.png) * **arq**: Optional use of the Automatic Repeat Request (ARQ) protocol. The possible values are: * **always**: ARQ is done in parallel with FEC (a loss is always reported immediately once detected in SRT). * **onreq**: ARQ is allowed, but a loss is only reported when FEC fails to rebuild, at the moment when an incoming packet has a sequence number that exceeds the last in one of the column groups; such a packet, if still lacking at that moment, is considered no longer recoverable by FEC. * **never**: ARQ is not done at all. Packets not recovered by FEC undergo TLPKTDROP, just like those that fail ARQ recovery in a conventional SRT exchange. For example, this is how it should be used in the URI: ``` srt://recv.com:5000?latency=500&packetfilter=fec,cols:10,rows:5 ``` As there can only be one configuration for both parties, it is recommended that one party defines the full configuration while the other only defines the matching packet filter type (for example, the sender sets `fec,cols:10,rows:-5,layout:staircase` and the receiver sets just `fec`). Both parties can also set this option to the same value. The packet filter function will attempt to merge configuration definitions, but if the options specified are in conflict, the connection will be rejected. ## **The motivation for staircase arrangement** Normally, FEC is done using a solid (block aligned) matrix. Packet sequences are arranged in a two-dimensional array of R rows and C columns. With SRT, the problem is that the FEC control packets are only transmitted when the last row is retransmitted. Let's imagine 10 columns and 5 rows starting from sequence 500. The rows begin with sequences numbers 500, 510, 520, 530 and 540 (where 540 is the start of the last row). Here is a representation of a series of packets transmitted starting from around the middle of the second-to-last row up until the end of the last row (H = horizontal position; V = vertical position): ``` ... <537> <538> <539> -> end of the second-to-last row -> row FEC packet for the second-to-last row <540> -> beginning of the last row; end of the first column -> column FEC packet for the first column <541> -> end of the second column -> column FEC packet for the second column <542> -> column FEC etc. <543> <544> <545> <546> <547> <548> <549> -> end of the last row; end of the last column -> row FEC packet for the last row -> column FEC packet for the last column ``` Given a constant bitrate at the input, there's a certain bandwidth normally used by the regularly transmitted data packets. But when the last row in a series is transmitted, the transmission will use twice the normal bandwidth because of all the column FEC packets. There are three methods for mitigating this: 1. *Limit the bandwidth in SRT using* `SRTO_MAXBW`. This means that packets will not be transmitted as fast as the FEC mechanism requires, and will add extra delay to the transmission of regular packets. Normally, this shouldn't matter much, as there's already a very high minimum latency you must configure with FEC. This is based on the size of the matrix (50 in the above example), multiplied by the bitrate, divided by a bytes-per-packet factor. But if you use FEC and ARQ together, delaying a packet at the sender side may challenge the response time for retransmission. 2. *Delay sending the FEC control packet itself.* **-> A concept, NOT IMPLEMENTED** Sending an FEC control packet can be postponed for several data packets. The problem is that this would increase the time interval between the first packet in a group and the control packet by a factor based on twice the matrix size (100 in the above example), thus increasing the required latency penalty. 3. *Use the staircase arrangement.* While in a simple (block aligned) matrix, the packet sequence numbers at the start of each column increment by 1, in a staircase (non-block aligned) matrix arrangement, the packet sequence numbers at the start of each column increment by R+1, where R is the row size. Let's again imagine a matrix of 10 columns and 5 rows starting from sequence 500. The rows begin with sequences numbers 500, 510, 520, 530 and 540. But the columns begin in staggered fashion, separated by an interval of R+1. The colours represent consecutive FEC groups (note the "staircase" pattern): ![5R x 10C Staircase Pattern](images/staircase-pattern-5rx10c.png) Here is a representation of a series of packets transmitted starting from packet 537 (H = horizontal position; V = vertical position): ``` ... <537> <538> <539> -> row FEC packet <540> -> column FEC packet <541> <542> <543> <544> <545> -> column FEC packet <546> <547> <548> <549> -> row FEC packet <550> -> start of next FEC group <551> -> column FEC packet <552> ... ``` Unlike with the block-aligned arrangement, the transmission of the FEC packets is well spaced, thus avoiding spikes in the use of available bandwidth. Note that with the staircase arrangement, the initial packets sent when a connection is established will not be covered by an FEC group. But it is generally not a problem when packets are lost in the first 2 seconds of a transmission. If needed, even this can be mitigated by adding a series of unused groups to the FEC matrix before the transmission starts (a concept potentially to be implemented). Another advantage of the staircase arrangement is that it increases the probability of recovering a long sequence of a lost packets in a case when consecutive lost packets in a row will belong to different column groups. In the example below, it is likely that the entire missing sequence from 572 to 583 can be rebuilt, largely because packets 573 and 583 belong to different groups (once these are rebuilt, it becomes possible to rebuild packets 572 and 582 via row FEC): ![Rebuild Missing Sequence](images/rebuild-missing-sequence.png) Although in a case of even arrangement you still may have a good luck of having a long loss exactly at the border of two column series, the staircase arrangement slightly increases the range of prospective long losses that can be successfully recovered. # The Built-in FEC Filter The built-in FEC filter implements the standard XOR-based FEC protection operation. It qualifies packets into groups (see [Layout](#heading=h.wkpnk3l9ni7i) option). For every group, an FEC control packet is generated that can be used to rebuild a packet that was lost. The built-in FEC filter allows for the following possible configurations: * **row only**: a row group is a range of consecutive packets; no columns are used * **column only**: the row FEC packet is never sent * **columns and rows** A row is a series of consecutive packets from a base sequence number up to a number (N) of packets, with N being equal to the size of the row (R). A column is a series of packets that starts at a base sequence number, with subsequent packets in the column group separated over an interval N (which is also the size of the row). It's important to note that the size of a row (R) is equal to the number of columns, and the size of a column (C) is equal to the number of rows. ## Sending Prior to sending, `feedSource` gets the packets to be sent so that their contents can be XORed and written into the FEC group's buffer. Data on which a protection operation is performed are: * timestamp * encryption flags * content length * contents - padded with zeros up to payloadSize() For the timestamp recovery, the header field TIMESTAMP is reused. For all others there are extra fields in the payload space of the SRT packet: * 8 bits: group index (for rows this is -1) * 8 bits: flag recovery * 16 bits: length recovery When the number of packets, applied in a protection operation, reaches the desired size of the group, the FEC control packet is considered ready for extraction, and the current FEC buffer state (after processing all packets from the group) becomes the required contents of the FEC control packet. This will then be returned at the next call to `packControlPacket`. This function checks if the FEC control packet is ready for extraction, first for the row group, then for the column group, and provides it if it is ready. An FEC control packet is distinguished from a regular data packet by having its message number equal to 0. This value isn't normally used in SRT (message numbers start from 1, increment to a maximum, and then roll back to 1). Figure 1 - SRT data packet structure ``` 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| Packet Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |FF |O|KK |R| Message Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time Stamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Socket ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | Data | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` Figure 2 - FEC control packet structure ``` 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| Packet Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |FF |O|KK |R| Message Number = 0 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time Stamp Recovery | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Socket ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Group Index | Flags Recovery| Length Recovery | -> FEC header +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | Payload recovery | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Receiving The receiver must first determine whether the packet is a regular data packet or an FEC control packet. If the `getMsgSeq()` function returns 0 (signifying the packet is an FEC control packet), then the first byte from the FEC header in that packet is extracted to determine if this corresponds to a column FEC (contains the column number) or row FEC (contains -1). If the `getMsgSeq()` function identifies a regular packet, then the packet is inserted into the horizontal and vertical position of the FEC group to which it belongs by performing the XOR operation with the current contents of the recovery buffer and increasing the packets' count (the number of currently processed packets in the FEC group buffer). Note that at the receiver there is only one buffer for an FEC group. There's a counter that is incremented for each packet on which the XOR protection operation has been performed and added to that group's recovery buffer. This operation is done as well on the FEC control packet, although this is marked by a separate flag. The FEC control packet is inserted into the group to which it is destined (its sequence number is the last sequence in the group). Once you have a state where the group buffer contains the result of the XOR operation of the FEC control packet and N-1 data packets from this group (with N being the size of the group), the group is rebuild-ready, and the contents of a missing packet can be rebuilt from the FEC group buffer contents. Every incoming packet that is applied into the group also marks the reception bit flag for its sequence number. The sequence number of the missing packet is therefore determined by examining this flag for all these sequence numbers to find out which one is missing. The packet is then rebuilt by: * setting default values in the header (socket ID for the connection, message number 1) * setting the sequence number to the number found for the missing packet * setting the flags directly from the corresponding contents of the FEC group buffer (Flag Recovery from the FEC header); the retransmission flag is always set * setting the timestamp directly from the corresponding contents of Time Stamp Recovery * reading the payload size from the corresponding contents of Length Recovery * setting the payload contents from the corresponding contents of Data Recovery, up to the payload size as read above If both columns and rows are used, the rebuilding happens recursively - that is, after a packet is rebuilt its contents are also XORed to the row or column in the FEC group to which it simultaneously belongs, and then that row or column is checked again.That may trigger the rebuilding of another packet, which in turn causes a new column or row to be checked, and so on. The rebuilt packets are stored in the provided array so that they can be picked up by SRT to insert them all into the receiver buffer. ## FEC Packet Header In SRT, FEC control packets have an extra header so that all the recovery information can be contained. This is the breakdown of the FEC control packet: ``` 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| Packet Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |FF |O|KK |R| Message Number | Regular +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ SRT | Time Stamp Recovery | header +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Socket ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ------- | Group index | Flags Recovery| Length Recovery | Extra FEC header +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ------- | Payload Recovery... | Data ``` This is much like a regular data packet in SRT, except that there's an extra 32-bit special FEC header with additional information, and also many of the fields in the SRT header are used for a different purpose: - **Packet Sequence Number:** Contains the sequence number of the last packet in the FEC group to which this FEC control packet is assigned. - **FF (Packet position flag):** 11b (solo). FEC is currently used only in Live mode, where this is the only value that applies. - **O (Deliver in order flag):** This flag is taken from the first data packet during sending and kept this way for all FEC packets, without checking other data packets. It's assumed that in Live mode all packets have this flag set the same way. - **KK (Encryption flag):** 00b (unencrypted). The encryption specification doesn't apply to FEC control packets because the protection operation is being performed on the contents, whether they have been encrypted or not. - **R (Retransmission flag):** 1 (set). An FEC control packet is marked as a retransmission to prevent it from being treated as a reordered packet. - **Message Number:** 0 (a special value identifying an FEC control packet) - **Time Stamp:** Contains the timestamp recovery (XOR) - **Destination Socket ID:** Same as in regular SRT packets, as required to dispatch the packet to the correct socket. - **Group Index:** Contains the column number, if it's for a column group, or -1, if it is for a row group. The only functional purpose of this field is to know whether this FEC control packet is for a row group or for a column group. - **Flag recovery:** Contains the recovery bits to recover the flags preceding the Message Number field. Currently applies only to the **KK** flags, because encrypted packets may have KK set to 01 or 10 for the same transmission. - **Length recovery:** 16-bit field containing the XOR of the packet length. - **Payload recovery:** Contains the XOR-ed value of all payloads from packets in the group, each one padded with zeros before the operation up to `payloadSize()`. For reference, here is the FEC header for transmission over RTP according to RFC 2733: ``` +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SN base | length recovery | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |E| PT recovery | mask | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TS recovery | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` - **SNBase:** Minimum sequence number of the packets associated to the FEC packet, and where 16 bit sequence numbers are sufficient, this parameter shall contain the entire sequence number. For transport protocols with longer sequence numbers this field shall contain the least significant 16 bits of the sequence number. - **Length Recovery:** This field should be used to determine the length of any media packets associated with the FEC packet. - **PT recovery:** This field should be used to determine the Payload Type of any media packets associated with the FEC packet. - **TS recovery:** This field should be used to recover the timestamp of any media packets associated with the FEC packet. These fields have equivalents in the FEC control packet in SRT: - **SNBase** (both low bits and ext bits): This is equivalent to the **Packet Sequence Number** (which is already 32-bit) since the sequence numbers used for FEC in SRT are just the existing sequence numbers. - **Length recovery:** There's a similar **Length recovery** field in the extra FEC header. - **PT recovery:** This field is specific to RTP, but is similar to the **Flag recovery** field. - **TS recovery:** In SRT FEC the timestamp recovery is stored in the SRT header's **Timestamp** field (being Time Stamp Recovery in this case) - **Index, Offset:** Some similarities can be found in the **Group index** field. However its purpose is mainly to distinguish row and column groups. The index itself is only for prospective sanity checks. ## Cooperation with retransmission The ARQ level is a value that decides how the packet filtering should cooperate with retransmission. Possible values are: - **NEVER**: Do not do retransmission at all. This means that packets are always ACK-ed up to the last sequence that is received, and all losses are ignored. - **ALWAYS**: Do normal retransmission - that is, the retransmission request is sent immediately upon loss detection, possibly in parallel with FEC rebuilding. This might be useful for networks with high bandwidth capacity that happen to be very unstable; working with both FEC and ARQ on the same edge increases the probability that whatever can't be restored by one system will be restored by the other as fast as possible. However, both ARQ and FEC overheads will apply. - **ONREQ**: Lost packets are recorded by SRT, but the loss report is not sent unless a sequence is reported in `loss_seqs` (see "Receiving" in the **Packet Filter Framework** section for details on how packets that are not recoverable by FEC are reported). Note that in this case the lost packets will be reported with a delay. At very low latency there is very little time to recover by ARQ in general, and with the added delay incurred by using this option there is even less, to the point where it may always be too late to retransmit. Note that in ALWAYS and NEVER modes the filter should not return anything in `loss_seqs`. It's very important that the latency is properly set. The FEC mechanism may rebuild a packet, but it's delivery will be delayed by the time it takes for enough packets to be accumulated in the corresponding group to trigger the rebuild. The minimum value for the delay due to rebuilding is based on the number of packets: ``` N = (R * (C-1)) + 2 ``` (where R = row size, and C = column size). The SRT latency should be set such that, given the current bitrate, it is at least equal to the time it takes to send the N packets in an FEC group, and even this minimum should be increased by an extra safety margin. If this condition isn't met, the TLPKTDROP mechanism may drop packets even if they can be rebuilt. The standard recommended minimum ARQ latency for SRT is estimated as 4 * RTT. If you choose to use FEC in cooperation with ARQ with the **ALWAYS** level, your latency penalty should be the maximum of the standard ARQ latency and the FEC latency (which usually means that only the FEC latency penalty should apply). If you use the **ONREQ** level, your latency penalty should be the sum of these two. With the ONREQ setting, the built-in FEC filter collects the sequence numbers of all packets that are lost and no longer recoverable. This is recognized by the fact that a packet has come with a sequence number that is past the last sequence number for a particular group. All groups for which this packet is "in the future" are dismissed, that is, the irrecoverable packets are reported at this call and the dismissed flag is set so that it's not reported again. Note that this group isn't physically deleted at this time. ## FEC Group Dismissal and Deletion At some point, row and column groups are dismissed, which means that they are no longer of use. Missing packets are reported as irrecoverable, and the group is marked dismissed (so that this happens only once). This happens upon arrival of a packet with a sequence number that is higher than that of the last sequence number in the group. Dismissed FEC groups (and objects representing them) are deleted when no longer needed. This happens when the whole series of groups is already in the past relative to the incoming packet. Column and row groups are arranged in series, where the earliest active series is 0, the next is 1, and so on. When series 0 is deleted, series 1 becomes the new series 0. A column or row group is always deleted with the entire series to which it belongs (never by itself). For block-aligned (even) FEC arrangements, a series is easily defined as all column and row groups that fit in a given size of matrix. All groups are deleted by deleting the entire matrix. In the figure below, with a matrix size of 5 rows by 10 columns, the green region of column and row groups (series 0) is deleted once a packet (#550 or later) from the red region (series 1) arrives. ![Block Aligned 5R x 10C](images/block-aligned-5rx10c.png) For non block-aligned (staircase) FEC arrangements, a series has a more complex definition, and the trigger for deletion comes later (and may therefore use more memory for FEC groups than an even arrangement). Let's look at a specific example. In the figure below (also a matrix size of 5 rows by 10 columns), we can define packet #500 as base0 - the very first sequence number in a group that is still active. Red then represents series 0, blue series 1, and white series 2: ![Non-block Aligned 5R x 10C](images/non-block-aligned-5rx10c.png) In a staircase arrangement, the minimum distance between base0 and the first incoming packet that might trigger deletion is two times the size of the matrix (an arbitrary value chosen for the sake of simplicity and to provide a small margin of safety; it doesn't impact the functionality because deletion is independent of dismissal, except for the fact that the latter must happen first). For example, deletion of the column and row groups in series 0 (the red region) will be triggered by reception of packet #600 (or greater, because packets can be lost). Once the trigger packet arrives, all column groups belonging to series 0 (red) are deleted, as well as all rows that begin with packets from the base0 column group - that is, all rows whose first packet falls in the sequence from #500 to #540. In the figure below, deletion of series 0 corresponds to columns with a red background and rows with a red border: ![Non-block Aligned 5R x 10C Deleted Packets](images/non-block-aligned-5rx10c-deleted-packets.png) After the red series 0 is deleted, packet #550 becomes the new base0, packets in the column from #550 to #559 define the new first row group, and the blue packets become series 0. When column groups are in the staircase arrangement, the penalty for loss detection (encountered with the ONREQ level) shall be always counted as the size of the matrix (a product of both group sizes). A certain number of packets must be received after the packet that caused a loss detection in order for the FEC facility to report it as lost and not recoverable. In other words, the FEC penalty is the number of packets between the lost packet and the first packet that triggered sending a loss report for that lost packet. The FEC mechanism always waits for the moment when the lost packet is declared irrecoverable. # Packet Filter Framework The built-in FEC facility is connected with SRT through a mechanism called "packet filtering". This mechanism relies on the following checkpoints which allow for packet filter injection: * Sending: * a filter is first asked if it is ready to deliver a control packet; if so it is expected to deliver it, and this packet is sent instead of a data packet waiting in the sender buffer * when a new packet in the sender buffer is about to be sent over the network, it's first passed to the filter * Receiving: * Every packet received from the network is passed to a filter, which can: * pass it through * provide extra packets The built-in FEC filter doesn't use all capabilities of Packet Filtering, in particular: * a packet may be altered prior to sending; FEC only reads the data. * a receiver does allow packet passthrough; it must process each packet and put all results into a provisional buffer(\*). Note that the FEC filter does allow passthrough for regular data packets, but traps all FEC control packets and provides rebuilt packets. (\*) *This provisional buffer is the socket's receiver buffer that is passed to the packet filter framework to accommodate packets coming out of the packet filter (see the *provided* constructor parameter in the __Construction__ section below).* The sending and receiving mechanism of packet filtering is defined in one class. SRT is generally bidirectional, so both directions must be covered in a single mechanism. A user-defined packet filter should be a class derived from `SrtPacketFilterBase` class, and it should override several virtual methods, as specified below. ## Basic types The basic packet structures are: * `CPacket` is an internal SRT class, that is, it gives you access to the exact packet to be used in the operation. * `SrtPacket` is a special intermediate class, which has a static definition and is used as an intermediate space to be copied to the `CPacket` structure when needed, automatically. This is required for cases when SRT is doing some specific memory management. The packet filter framework cannot reuse the same memory management for keeping the packets in the receiver buffer, or have access to it. Therefore a filter should provide packets using appropriate methods, and SRT internals will take care of copying it to the receiver buffer. This is why the `CPacket` structure cannot be used for that purpose. Both classes - while characterized by completely independent contents and methods - implement a common C++ concept consisting of the following methods: * `char* data();` * Returns the buffer in the packet. * `size_t size();` * Returns the size of the contents in the buffer. Note that this is not the size of the buffer itself. * `uint32_t header(SrtPktHeaderFields field);` * This accesses the header. The field type specifies the field index in the header. Note that if a function gives you writable access to `CPacket`, you can modify the payload, but you can't modify the header. With `SrtPacket` you are free to modify any contents, but to access the header you should use the `SrtPacket::hdr` field, which is the array to be indexed by values of `SrtPktHeaderFields` type. ## Construction The constructor of the packet filter class should have the following signature: ``` MyFilter(const SrtFilterInitializer &init, std::vector& provided, const string &confstr); ``` Here `MyFilter` is the example filter class derived from `SrtPacketFilterBase`. The following parameters are required: * `init` * This should be passed to the constructor of `SrtPacketFilterBase`. It will provide you with the basic data you will need when creating a packet at the receiver. * `provided` * This is the provisional buffer where you have to store packets that your filter will generate as extra packets (in the case of FEC, this is where the rebuilt packets will be supplied). * `confstr` * This is a configuration string. You should parse it using `ParseFilterConfig` so that you can use it for your purposes. Note that this configuration string is still parsed by this function internally in SRT, so the official syntax must be preserved. The base class will provide you with important data from the socket through the following methods: * `socketID()` * The socket ID that you should write into the ID header field on the receiver * `sndISN()` and `rcvISN()` * It returns the very first sequence number in a particular direction (FEC uses it to initialize the base sequence numbers for FEC groups). * `payloadSize()` * The maximum size of a single packet. This determines how large a content payload should be used for the FEC control packet. The FEC control packet's payload recovery field must be of the maximum size of any packet, and any shorter packets being protected must be padded with zeros up to this size. The following virtual methods (to be overridden) are not direction related: * `size_t extraSize()` [REQUIRED] * Should return the size of the extra header that your filter will use, in the count of 32-bit data. The current FEC implementation returns 4 here, which is the size of the extra header in FEC control packets. Your implementation may use some extra header in regular packets as well, so this should be taken into account here. This is required for checking if the `SRTO_PAYLOADSIZE` socket option is properly set (it puts an extra limitation on the value for this option beside the overall maximum of 1456 bytes). * `SRT_ARQLevel arqLevel()` [OPTIONAL; default implementation returns the value extracted earlier from the configuration string under the `arq` key] * Should return the ARQ level in the case where you want it to be specified differently than the default (for example, if you don't want it to be configurable in your implementation). ## Sending Sending in SRT is driven by a congestion control mechanism that determines when a socket is ready to send data at the currently defined speed. There is a function called at the precise moment when a socket should provide a packet to be sent over the UDP link. There are currently three packet providers. When one provides a packet, the provision is done and the others will have to wait until the next opportunity. The providers are checked in a specific order: - **Case #1**: A packet required for retransmission. This is based on the sender loss list, updated from `UMSG_LOSSREPORT` messages. - **Case #2**: If a Packet Filter is installed, the filter's control packet, if one is ready. - **Case #3**: The next waiting data packet. For Case #3, this function is called on the filter (if it is installed): ``` void feedSource(CPacket& pkt); ``` This function is called at the moment when a packet (already submitted by the `srt_sendmsg` call) is picked up from the sender buffer and is going to be sent. Note that this packet is bound to a buffer representing the input, and its size is set to the maximum possible (see **NOTE** below). Since this packet is allowed to be altered, data can also be extended up to this size. Note that if such a packet is altered, it will stay in this form in the sender buffer and be retransmitted, if needed. The current FEC filter implementation uses this function only to collect the contents of the packet to be XORed into an FEC group buffer. For Case #2, a filter control packet is provided by this function: ``` bool packControlPacket(SrtPacket& packet, int32_t seq); ``` If the function returns *true*, it means that it has supplied the packet. The function should return *false* when there is no such packet ready, in which event Case #3 (new data packet) will be attempted. **NOTE**: If *true*, the contents of the filter control packet are to be written in a special packet buffer, from which the contents will be copied to the target packet. This buffer is already the maximum possible size of a single packet in SRT. Special control packets are distinguished from regular data packets by the Message Number (MSGNO) field. For control packets the Message Number is always 0, while regular data packets have Message Numbers from 1 up to a maximum (after which they roll back to 1). For that reason, you are free to store special values in TIMESTAMP and SEQNO fields, but the MSGNO and ID fields must be left alone (they will be overwritten anyway). ## Receiving There is just one function for receiving: ``` bool receive(const CPacket& pkt, loss_seqs_t& loss_seqs); ``` This function is called for any data packet received on a socket. It is understood that this is a data packet, the rest is up to the receive() function. A packet received here must not be altered. However, you can decide whether a given packet should be passed through, or dismissed, based on the return value. Returning *true* means that the received packet should be passed on to the receiver buffer; otherwise it is discarded. If you have some special contents in a packet that you want removed, simply recreate the packet, then discard the original. If you want to inject a packet from a filter (such as an FEC-rebuilt packet), you store it in a provisional buffer (the reference to which you received in the constructor). This method can also be used when you want to replace a packet. The order in which packets are stored in this array doesn't matter, because they will be sorted by sequence number order before they are returned to SRT. Here are some additional considerations related to the `receive()` function: 1. It's up to you to distinguish regular data packets and filter control packets by checking the message number. It is important to use the `CPacket::getMsgSeq` function for that purpose because this extracts the right part of the MSGNO field from the header - `pkt.header` (`SRT_PH_MSGNO`) will return just the field, which contains extra flags. 2. If you use an extra header for every packet, be sure that you can recognize it correctly as you have created it. 3. When creating (reconstructing) a packet, you have to correctly set the sequence number, the ID field (get the value from `socketID()`), timestamp, and the encryption flags (other flags are not important, they are set as default). srt-1.4.4/docs/features/socket-groups.md000066400000000000000000001071771412557703600202540ustar00rootroot00000000000000# Abstract The general concept of the socket groups means that a separate entity, parallel to a socket, is provided, and the operation done on a group will be implemented using appropriate operations done on underlying sockets. The groups types generally split into two categories: 1. Bonding groups. This group category is meant to utilize multiple connections in order to have a group-wise connection. How particular links are then utilized to make a group-wise sending, depends on the particular group type. Within this category we have the following group types: - Broadcast: send the stream over all links simultaneously - Backup: use one link, but be prepared for a quick switch if broken - Balancing: utilize all links, but one payload is sent only over one link Bonding category groups predict that a group is mirrored on the peer network node, so all particular links connect to the endpoint that always resolves to the same target application. Just possibly every link uses a different network path. 2. Dispatch groups. This category contains currently only one Multicast type (**CONCEPT!**). Multicast group has a behavior dependent on the connection side and it is predicted to be only used in case when the listener side is a stream sender with possibly multiple callers being stream receivers. It utilizes the UDP multicast feature in order to send payloads, while the control communication is still sent over the unicast link. Details for the group types: ## 1. Broadcast This is the simplest bonding group type. The payload sent for a group will be then sent over every single link in the group simultaneously. On the reception side the payloads will be sorted out and redundant packets that have arrived over another link are simply discarded. This group is predicted to solve the link disturbance problems with no latency penalty - when one link gets broken, another still works with no extra delay and no observable disturbances for the client, as long as at least one link is up and running. A drawback of this method is that it always utilizes the full capacity of all links in the group, whereas only one link at a time delivers any useful data. Every next link in this group gives then another 100% overhead. ## 2. Backup This solution is more complicated and more challenging for the settings, and in contradiction to Broadcast group, it costs some penalties. In this group, only one link out of member links is used for transmission in a normal situation. Other links may start being used when there's happening an event of "disturbance" on a link, which makes it considered "unstable". This term is introduced beside "broken" because SRT normally uses 5 seconds to be sure that the link is broken, and this is way too much to be used as a latency penalty, if you still want to have a relatively low latency. Because of that there's a configurable timeout (with `SRTO_GROUPSTABTIMEO` option), which is the maximum time distance between two consecutive responses sent from the receiver back to the sender. If this time was exceeded, the link is considered unstable. This can mean either some short-living minor disturbance, as well as that the link is broken, just SRT hasn't a proof of that yet. At the moment when one link becomes unstable, another link is immediately activated, and all packets that have been kept in the sender buffer since the last ACK are first sent. Since this moment there are two links active until the moment when the matter finally resolves - either the unstable link will become stable again, or it will be broken. The state maintenance always keep up to the following rules: a) If you happen to have more than one link stable, choose the "best" one and silence the others. Silencing means that the link is inactive and payloads are not being sent over it, but the connection is still maintained and remains ready to take over if there is a necessity. b) Unstable links continue to be used no matter that it may mean parallel sending for a short time. This state should last at most as long as it takes for SRT to determie the link broken - either by getting the link broken by itself, or by closing the link when it's remaining unstable too long time. This mode allows also to set link priorities - the greater, the more preferred. This priority decides mainly, which link is "best" and which is selected to take over transmission over a broken link before others, as well as which links should remain active should multiple links be stable at a time. If you don't specify priorities, the second connected link need not take over sending, although as this is resolved through sorting, then whichever link out of those with the same priority would take over when all links are stable is undefined. Note that this group has an advantage over Broadcast in that it allows you to implement link redundancy with a very little overhead, as it keeps the extra link utilization at minimum. It costs you some penalties, however: 1. Latency penalty. The latency set on the connection used with backup groups must be at minimum twice the value of `SRTO_GROUPSTABTIMEO` option, or might even need to be higher in case of high bitrates - otherwise the switch into the backup link connected with resending all non-ACK-ed packets might not be on time as required to play them. Your latency setting must be able to compensate not only usual loss-retransmission time, but also the time to realize that the link might be broken and time required for resending all unacknowledged packets, before the time to play comes for the received packets. If this time isn't met, packets will be dropped and your advantage of having the backup link might be impaired. According to the tests on the local network it turns out that the most sensible unstability timeout is about 50ms, while normally ACK timeout is 30ms, so extra 100ms latency tax seems to be an absolute minimum. 2. Bandwidth penalty. Note that in case when the Backup group activates another link, it must resend all packets that haven't been acknowledged, which is simply the least risk taken for a case that a link got suddenly broken. However, how many packets have been collected, depends on a luck, and worst case scenario it may need to resend as many packets as it is normally collected between two ACK events - in case when the link got broken exactly at the moment when packets were about to be acknowledged. The link switch always means a large burst of packets to be sent at that moment - so the mechanism needs large enough time to send them and to consider them for delivery. However, if your bandwidth limit is too strong, sending these packets might be dampened possibly too much to live up to the required time to play. It is unknown as to what recommendations should be used for that case, although it is usually more required than to compensate a burst for retransmission and also the maximum burst size is dependent on the bitrate, in particular, how many packets would be collected between two acknowledgement events. It might be not that tough as it seems from this description, as it's about starting a transmission over an earlier not used link, so there's some chance that the link will withstand the initial high burst of packets, while then the bitrate will become stable - but still, some extra latency might be needed to compensate any quite probable packet loss that may occur during this process. ## 3. Balancing The idea of balancing means that there are multiple network links used for carrying out the same transmission, however a single input signal should distribute the incoming packets between the links so that one link can leverage the bandwith burden of the other. Note that this group is not directly used as protection - it is normally intended to work with a condition that a single link out of all links in the group would not be able to withstand the bitrate of the signal. In order to utilize a protection, the mechanism should quickly detect a link as broken so that packets lost on the broken link can be resent over the others, but no such mechanism has been provided for balancing group. As there could be various ways as to how to implement balancing algorithm, there's a framework provided to implement various methods, and two algorithms are currently provided: 1. `plain` (default). This is a simple round-robin - next link selected to send the next packet is the oldest used so far. 2. `window`. This algorithm is performing cyclic measurement of the minimum flight window and this way determines the "cost of sending" of a packet over particular link. The link is then "paid" for sending a packet appropriate "price", which is collected in the link's "pocket". To send the next packet the link with lowest state of the "pocket" is selected. The "cost of sending" measurement is being repeated once per a time with a distance of 16 packets on each link. There are possible also other methods and algorithms, like: a) Explicit share definition. You declare, how much bandwidth you expect the links to withstand as a percentage of the signal's bitrate. This shall not exceed 100%. This is merely like the above Window algorithm, but the "cost of sending" is defined by this percentage. b) Bandwidth measurement. This relies on the fact that the current sending on particular link should use only some percentage of its overall possible bandwidth. This requires a reliable way of measuring the bandwidth, which is currently not good enough yet. This needs to use a similar method as in "window" algorithm, that is, start with equal round-robin and then perform actively a measurement and update the cost of sending by assigning so much of a share of the signal bitrte as it is represented by the share of the link in the sum of all maximum bandwidth values from every link. ## 4. Multicast (NOT IMPLEMENTED - a concept) This group - unlike all others - is not intended to send one signal between two network nodes over multiple links, but rather a method of receiving a data stream sent from a stream server by multiple receivers. Multicast sending is using the feature of UDP multicast, however the connection concept is still in force. The concept of multicast groups is predicted to facilitate the multicast abilities provided by the router in the LAN, while still maintain the advantages of SRT. When you look at the difference that UDP multicast provides you towards a dedicated peer-to-peer sending, there are two: * You can join a running transmission at any time, without having the sender do something especially for you (the router IGMP subscription does the whole job). * The data stream is sent exactly ONCE from the stream sender to the router, while the router sends also one data stream to the switch. How much of a burden to the rest it is, depends then on the switch: older ones get one signal to be picked up by those interested, newer ones pass through this signal to only those nodes that have IGMP subscription. Nevertheless, the advantage here is that the same data stream is sent once instead of being sent multiple times over the same link, at least between the stream sender and the router. The multicast groups in SRT are intended to use this very advantage. While the connection still must be maintained as before, the dedicated UDP link that results from it is to carry out only the control traffic. For the data traffic there would be a UDP multicast group IP address established and all nodes that connect to the stream sender using a multicast group will then receive the data stream from the multicast group. This method has limitations on the connection setup. You should then make a listener on the side where you want to have a stream sender, and set it up for multicast group. Then, a connection is established over an individual link, as usual. But beside the data that would be sent over a dedicated link, the data being sent to the group on the sender side will be actually sent to the multicast address (unlike in Backup and Broadcast groups, where these are normally sent over the channel that particular socket is using). The connecting receiver party is then automatically subscribed to this group and it receives the data packets over there, just as if this would be a second channel over which the group is able to receive data. Note that sending the data over a single link is still possible and likely used for retransmission. The retransmission feature is still handled on a single link, although most likely it can be allowed that if more than 2 links report a loss of the exactly same packet, the retransmission may use the multicast link instead of the individual link - whichever would spare more bandwidth would be used. Potential implementation: Instead of `groupconnect` option set to true, you have to set the `multicast` option to a nonempty string of any contents (just limited to 512 characters). This string value will be a key under which the group ID is recorded. The first connecting client will be identified as joining this group and this way a new group will be created and its ID recorded under this key. This is in order to allow library users to use the feature of listener callback so that a group name may be decided upon per user or stream resource specified. Once the group is known, and the connection is to be accepted, the newly accepted socket is joined to the group. The accept conditions are just like before in the groups: the group is reported from `srt_accept` when the first client has connected. Once the group connection is made, the sender side can use the group ID to send the data. Note that the group on the listener side collects multiple sockets representing particular clients, so sending will be for the sake of each of them. However the sending function will not send to every individual socket, as it is with broadcast group, but rather an extra socket will be created inside the group, and this one will be used to send packets to the multicast group, as configured during the handshake. It should be decided on the listener side, which IGMP group will be used for transmission. Clients do not know it, and: - for simple single-signal cases, you have just one IGMP group configured for the listener and it's used for every client - listener callback may decide particular IGNMP group to be used for particular signal The listener side will inform the client in the handshake, which IGMP group will be used for transmission so that the client can set up listening on it (join IGMP group in particular). The client side should have then two things: 1. The group with its own socket (and queues). 2. The group will contain always one member, this time. 3. When reading packets from the queue of the socket bound to the IGMP group, the packet will be translated and dispatched to the socket. What is important here is that the packet sent by the listener side will look exactly the same no matter to which client it is predicted - which means that the SRT header will contain the same data. This is where groups come in: the "target ID" in the header will have the value of the group. In total, the listener sends the following data to the client: 1. IGMP group's IP address and port 2. group ID that will be the same on both sides (if the group ID already exists on the client's application, the connection will be rejected). The listener side will then send payload packets to the IGMP group, however all control packets will be still sent the same way as before, that is, over a direct connection. # Socket groups in SRT The general idea of groups is that there can be multiple sockets belonging to a group, and various operations, normally done on single sockets, can be simply done on a group. How an operation done on a group is then implemented by doing operations on sockets, depends on the group type and the operation itself. Groups have IDs exactly the same as sockets do. For group support there's a feature added: the SRTSOCKET type is alias to `int32_t`, and it has appropriate bit set to define that this ID designates a group, known as `SRTGROUP_MASK`. You can test it by `id & SRTGROUP_MASK` to know if the value of `SRTSOCKET` type designates a single socket or a group. For groups you simply use the same operations as for single socket - it will be internally dispatched appropriate way, depending on what kind of entity was used. For example, when you send a payload, it will be effectively sent: - For Broadcast group, over all sockets - For Backup group, over all currently active links - For Balancing group, over a currently selected link - For Multicast group, over an extra socket to the multicast group Similarly, the reading operation will read over all links and due to synchronized sequence numbers use them to decide the payload order: when the very next packet has been receiver over any link, it will be delivered, and when older than that, it will be discarded. The TSBPD mechanism is used to determine the order in case when a packet was decided to be dropped on particular link. That is, if a packet drop occurs, then simply the same packet received over another link will be still earlier ready to play. The difference in reading between groups is that: - For Broadcast and Backup groups, sequence numbers are synchronized and used to sort packets out - For Balancing group, message numbers are used to sort packets out (sequence numbers are not synchronized) - For Multicast group, there's only one link at the receiver side group, just the group contains additional socket that should read from the multicast group, in case when packets are expected to be read. By having the target specified as the group id, it gets correctly dispatched to this channel's own buffer and delivered. For this purpose, however, payloads sent over the multicast link must have the target defined as the group ID so that all data in the header look exactly the same way depite being intended to be received by various different network nodes. # How to prepare connection for bonded links In the listener-caller setup, you have to take care of the side separately. First of all, the caller should require minimum version from the peer (see `SRTO_MINVERSION` flag) or otherwise the listener won't understand the handshake extension information concerning socket groups. The listener socket must have `SRTO_GROUPCONNECT` flag set. There are two reasons as to why it is required: 1. This flag **allows** the socket to accept bonded connections. Without this flag the connection that attempts to be bonded will be rejected. 2. When `srt_accept` function is being called on a listener socket that has this flag set, the returned numeric ID identifies not a socket, but a group. The group returned by `srt_accept` is a local group mirroring the remote group which's ID is received in the handshake. That's also apparently the exact ID that you should next use for sending or receiving data. Note also that `srt_accept` returns the group only once upon connection - once there exist at least one connected socket in the group, there will be no new connections reported in `srt_accept`, just rather a new socket will suddenly appear in the group data next time you anywhow read them (data reported from `srt_recvmsg2`, `srt_sendmsg2` or `srt_group_data`). When an accepted-off socket is being created and the group for which the request has come in the handshake is already mirrored in the application, then this link will be added to the existing group. Otherwise the group is created anew. This group has a range for a single application. You rather don't have to maintain the list of bonded connections at the listener side because still you have no influence on who and when will connect to you. On the caller the matter is a little bit more complicated. # Connect bonded At first, please remember that the official function to create a socket is now `srt_create_socket` and it gets no arguments. All previous functions to create a socket are deprecated. This is also interconnected with a change that you don't have to know the IP domain of the socket when you create it - it will be decided only upon the first call of `srt_bind` or `srt_connect`. In order to have a bonded connection, instead of creating a socket, you have to create a group. Groups may have various purposes, and not every type of group is meant to be self-managed. For groups that are not self-managed, the socket can be only simply added to the group by application, as well as the application should take care of closing it when broken or no longer needed. The groups for bonding are always self-managed, that is, they create sockets automatically upon necessity, and automatically delete sockets that got broken. For example, this way you create a bonding group of type "broadcast": grpid = srt_create_group(SRT_GTYPE_BROADCAST); This returns a value of SRTSOCKET, which can be then normally used in most of the socket operations, just like socket ids. In order to establish a connection within the bonding group, simply do: sockid = srt_connect(grpid, address...); Mind that in distinction to `srt_connect` used on a socket, where it returns 0 when succeeded, this time it returns the socket ID of the socket that has been created for the purpose of this connection. This is just informative and the application is given this socket just when it would need it, although it should never try to do any operation on the socket, including closing it. In order to create a second link within the bonding group, simply call `srt_connect` again with the other address that eventually refers to the same endpoint (note that SRT has no ability to verify it prematurely). Unlike sockets, a group can be connected many times, and every call to this function creates a new connection within the frames of this group. Also, as a managed group, it always creates a new socket when you connect "the group" to the given address. The library, on the other hand, doesn't know how many connections you'd like to maintain, whether the list is constant or can be dynamically modified, or whether a dead link is not to be revived by some reason - all these things are out of the interest of the library. It's up to the application to decide when and by what reason the connection is to be established. All that your application has to do is to monitor the conenctions (that is, be conscious about that particular links are up and running or get broken) and take appropriate action in response. Therefore it's recommended that your application maintain the bonded link table. It is completely up to you, if your list of bonded links is static or may change in time during transmission; what matters is that you can always add a new connection to the bonding group at any time by calling `srt_connect`, and when a connection is dead, you'll be informed about it, but the link won't be automatically revived. There are some convenience function added though because of inability to do operations on a single socket in case of groups at the moment when they are required. 1. `srt_connect_group`. This does something similar to calling `srt_connect` in a loop for multiple endpoints. However the latter is inappropriate in case when you use the blocking mode because this would block on the first connection attempt and will not try the next one until the previous one is connected or the connection finally fails. This function will then try to connect to all given endpoints at once and will block until the "group connection state" is achieved (in case of broadcast group it simply means that at least one link is connected - other groups may use sometimes more complicated conditions to be satisfied for it). In non-blocking mode it will simply behave the same as `srt_connect` run in a loop. You have to make yourself an array with endpoints, then prepare every endpoint using `srt_prepare_endpoint` function. 2. `srt_connect_bind`. It does the same as calling first `srt_bind` on the source address, then `srt_connect` on the destination address, when it's called for a single socket. When it's called for a group, then the binding procedure is done on the newly created socket for that connection (and that's the only way how you can define the outgoing port for a socket that belongs to a managed group). # Maintaining link activity A link can get broken, and the only thing that the library does about it is make you aware of it. The bonding group, as managed, will simply delete the broken socket and that's all. Reconnecting of the broken link is completely up to the application. Your application may also state that the link need not be revived, so this isn't interesting for the application. If you want to revive the link and you believe that the connection can be still made, or it's only broken temporarily, or the link should work, simply connect to this address again using `srt_connect`. The simplest way to maintain the status of the sockets in the group is to call: srt_group_data(grpid, &sockdata, &sockdata_size); You have to prepare an array of `SRT_SOCKGROUPDATA` type by yourself, and the size must be properly set to `sockdata_size` before calling to at least the number of sockets in the group. The latter is input-output, that is, it will return the actual size of the array, possibly less than given size. If you pass too small size, then the required size will be returned in `sockdata_size`, but the array will not be modified at all. Therefore you must check the returned size always and catch that case, as if this happens, you should not read data from the array. When you call `srt_group_data` and the size of the group is less than your last remembered one, it means that one of the sockets got broken, which one, you can check by seeing which of the sockets, that you remembered at the time of connection, is lacking in the socket group data. Note that socket IDs are created using a random number and decreasing, so dangling socket IDs will not be reassigned to correct sockets in a "predictable time" (you'll have to create and delete sockets about a million times to make it happen). A recommended way is, however, to use `srt_sendmsg2` and `srt_recvmsg2` functions, which require `SRT_MSGCTRL` structure. You should place a `SRT_SOCKGROUPDATA` array into `SRT_MSGCTRL::grpdata` field together with its size in `SRT_MSGCTRL::grpdata_size`, and the socket information for every socket will be placed there, including (just once) sockets that were lately broken and have been deleted. This last information is not present in the result returned by `srt_group_data` - that is, sockets found broken during the operation will be only present if you review the array that was filled by `srt_sendmsg2` or `srt_recvmsg2`. # Writing data to a bonded link This is very simple. Call the sending function (recommended is `srt_sendmsg2`) to send the data, passing group ID in the place of socket ID. By recognizing the ID as group ID, this will be resolved internally as sending the payload by approprately using the bonded links as defined for particular group type. The current implementation for most of the bonding groups (broadcast and backup) relies on synchronizing the sequence numbers of the packets so that particular payload goes always with the same sequence number on all links. Every next link, when the group is already connected, will be first created as "idle", and it will be activated when the opportunity and the need comes, and at this very time will be the sequence number adjusted to match the master sequence number in the group. The same payload will then have the same sequence number in all sockets in the bonding group, which allows then the payload to be retrieved in order. Exceptionally, the current implementation of the balancing group type is using message numbers because packets must go in order of the sequence numbers on particular link - and in this group type packets are distributed throughout the link and never go in the order of scheduling on one link. Therefore this group uses message numbers for ordering. # Reading data from a bonded link This is also simple from the user's perspective. Simply call the reading function, such as `srt_recvmsg2`, passing the group ID instead of socket ID. Also the dillema of blocking and nonblocking is the same thing. With blocking mode (`SRTO_RCVSYN`), simply wait until your payload is retrieved. The internal group reading facility will take care that you get your payload in the right order and at the time to play, and the redundant payloads retrieved over different links simultaneously will be discarded. # Checking the status If you call `srt_sendmsg2` or `srt_recvmsg2`, you'll get the status of every socket in the group in a part of the `SRT_MSGCTRL` structure, where you should set the pointer to your array of `SRT_SOCKGROUPDATA` type, and its size, so that the status can be filled. The size of the array should simply correspond to the number of bonded links that you use. If the passed size is too small, then the `grpdata` field will be set to `NULL` by the call, whereas `grpdata_size` will be always set to the required size. In this structure you have: - `id`: socket ID - `status`: the `SRT_SOCKSTATUS` value (same as obtained by `srt_getsockstate`) - `result`: result of the operation; if you can see -1 there, it means that you can see this socket for the last time here, and it is **already closed**. - `peeraddr`: the address to which the socket was connected The whole idea of bonding is that a link may get broken at any time and despite this, your application should be still capable of sending or receiving data, when at least one link is still alive. It means then that when reading or writing, the "sub-operation" on one of the sockets in the bond might have failed due to that the link got broken. But if the operation can continue successfully on another link, the overall operation is still considered succeeded, though you might be interested of what happened on the link. Only if **all links** get broken is the operation considered failed. There are some differences as to what exactly happens in this case, depending on the socket group type: 1. Broadcast: the data are being sent over all links anyway, so it doesn't make much difference except that the broken socket must be taken care of. 2. Backup: usually when a socket is broken, there has been a disturbance notified much earlier and therefore another link already active. The bonding group is allowed to keep as many active links as required for at least one link to remain stable. A broken socket is then simply a possible resolution for a volatile "unstable" state of the member socket. 3. Balancing: the exact implementation of this group is still underway and what exactly happens may depend on possible subtype of this group. The current proof-of-concept implementation behaves like the broadcast group - if one of the links goes broken, then there are less members to distribute packets through. Usually the group may have defined some critical conditions that must be satisfied so that the transmission can continue, mainly basing on that the critical network capacity needed for transmission is provided. In this case, if the bonded capacity drops below critical capacity, the whole bonded link should get broken. On the listener side, the situation is similar. When you read as listener, you still read if at least one link is alive, when you send - sending succeeds when at least one link is alive. When the link gets broken, though, you can't do anything anyway, so the listener doesn't have to worry about anything, except the situation that all links are gone, but this is then reported just like in a situation of one socket - the reading or writing operation fails. Only then is the connection lost completely and is sending or receiving impossible. Most important is what the caller side should do. When your link gets broken, it's up to you to restore it, so you should do `srt_connect` for that link again and count on that it will be re-established, while simultaneously the transmission continues over existing links. A single call to `srt_connect` may also break, like any other operation. When it happens while another link is running, this link will simply never reach the state of "idle", and will be deleted before it could be used. And finally, a group can be closed. In this case, it internally closes first all sockets that are members of this group, then the group itself is deleted. # Application support Currently only the `srt-test-live` application is supporting a syntax for socket groups. The syntax is as usual with "source" and "target", however you can specify multiple sources or multiple targets when you want you want to utilize socket groups. For this case, the `-g` option is predicted, which should be best understood as a split-point between specification of source and target. The general syntax (there will be also a simplified syntax, so read on) when you want to have a source signal as a group: ``` ./srt-test-live -g ``` and for sending over a groupwise link: ``` ./srt-test-live -g ... ``` Using multiple SRT URI specifications here is still simplified because the most direct (but hardest in use) method to specify a groupwise link is: ``` srt:////group?type=&nodes=host1:port1,host2:port2 (&other=options...) ``` Which would be the same as specifying: ``` srt://host1:port1 srt://host2:port2 ``` except that options that are group related must be specified the same way in every URI. But, as this can be handled with SRT type URI only, and as usually single socket options apply the same for every link anyway, there's a simplified syntax - HIGHLY RECOMMENDED - for specifying the group - let's take an example with additionally setting the `latency` option (REMEMBER: when specifying the argument with `&` inside in the POSIX shell, you need to enclose it with apostrophes or put backslash before it): ``` srt://*?type=broadcast&latency=500 host1:5000 host2:5000 host3:5000 ``` By specifying the SRT URI with placing `*` as a host, you define this as a "header URI" for the whole group. The nodes themselves are then specified in the arguments following this one. The list of nodes is terminated either by the end of arguments or other options, including the `-g` option that can be followed by the target URI specification, in case when the group was specified as a source. So, a complete command line to read from a group connected over links to hosts "alpha" and "beta", both with port 5000, and then resending it to local UDP multicast `239.255.133.10:5999` would be: ``` ./srt-test-live srt://*?type=broadcast alpha:5000 beta:5000 -g udp://239.255.133.10:5999 ``` Note that this specifies the caller. On the side where you want to set up a listener where you'd receive a caller's connection you must set the `groupconnect` option (here let's say you get the source signal from a device that streams to this machine to port 5555): ``` ./srt-test-live udp://:5555 srt://:5000?groupconnect=true ``` At the caller side you can also use some group-member specific options. Currently there exists only one option dedicated for the Backup group type, which is priority parameter with a `weight` key. In the simplified syntax it should be attached to the member parameter: ``` ./srt-test-live srt://*?type=backup alpha:5000?weight=0 beta:5000?weight=1 -g udp://239.255.133.10:5999 ``` Priorities in the Backup group type define which links should be preferred over the others when deciding to silence links in a situation of multiple stable links. Also at the moment when the group is connected, the link with highest priority is preferred for activation (greatest weight value), and if another link is connected with higher priority it also takes over. Here the `beta` host has higher priority than `alpha`, so when both links are established, it should use the host `beta` to send the data, switch to `alpha` when this one is broken, and then switch back to `beta`, when this link is back online. The stability timeout can be configured through `groupstabtimeo` option. Note that with increased stability timeout, the necessary latency penalty grows as well. srt-1.4.4/docs/misc/000077500000000000000000000000001412557703600142255ustar00rootroot00000000000000srt-1.4.4/docs/misc/images/000077500000000000000000000000001412557703600154725ustar00rootroot00000000000000srt-1.4.4/docs/misc/images/srt-history-good-signal.png000066400000000000000000004570071412557703600227250ustar00rootroot00000000000000‰PNG  IHDRr~×ømsRGB®Îé pHYs%%IR$ð@IDATxì|ÉÇÐB uÜ‹{qw;ì°;ÜÝÝݢŊ;‡Ë!Š»»S(N‘z‘ÿ¼ 3Ù$›4õ”{OØÙñýfÓdûöM¼ŸÌ€Œ"@ˆ D€"@ˆ D€"@,–@|‹MŒ"@ˆ D€"@ˆ D€"@8réD D€"@ˆ D€"@ˆ D€X8r-ü ¢é"@ˆ D€"@ˆ D€"@HÈ¥s€"@ˆ D€"@ˆ D€"`áHȵð7ˆ¦Gˆ D€"@ˆ D€"@ˆ !—Î"@ˆ D€"@ˆ D€"@ˆ€… !×Âß š D€"@ˆ D€"@ˆ D€„\:ˆ D€"@ˆ D€"@ˆ N€„\ ƒhzD€"@ˆ D€"@ˆ D€ré D€"@ˆ D€"@ˆ D€X8r-ü ¢é"@ˆ D€"@ˆ D€"@HÈ¥s€"@ˆ D€"@ˆ D€"`áHȵð7ˆ¦Gˆ D€"@ˆ D€"@ˆ !—Î"@ˆ D€"@ˆ D€"@ˆ€… !×Âß š D€"@ˆ D€"@ˆ D€„\:ˆ D€"@ˆ D€"@ˆ N€„\ ƒhzD€"@ˆ D€"@ˆ D€ré D€"@ˆ D€"@ˆ D€X8r-ü ¢é"@ˆ D€"@ˆ D€"@HÈ¥s€"@ˆ D€"@ˆ D€"`áHȵð7ˆ¦Gˆ D€"@ˆ D€"@ˆ !—Î"@ˆ D€"@ˆ D€"@ˆ€… !×Âß š D€"@,™@P@¼¸w¾…†Zò4inD€"@ˆ D Έ÷“Yœ? :"@ˆ D€X!Ð![>®M’$0ïêõX™ j‚üý!‘­-Ä‹û¾"?ü¼É`kggphD€"@ˆˆVQÐuAˆ D€(#«F ‡ ??H%+ÔíÕ¬% ³ÿëÇŽÁN9:Sfèà>#ÌúT!Š D¡oÀ«G`Ëô©ž ƒKRh=ir„ÛSÃðè”3;üøþ7·ï&k¶ðwE-Þ>{êT’½-}ðH¦)Aˆˆk¾øùÂó mªLàì”<®Mßä|?}yÞ> CÚlàhïb²." !@B. D€"@ˆ€EQ½:||ù‚ÏéÚáÃp`ÙRð¼}7L/¿-îÓÀ‡=âÿìúur-ê ÿdެ]øÞGÆZMœñâžghdŽ!.µ".ÎùüîÝP¯OßX›þÉ­›cml˜¨% V† ƒŽ$°k«„(‘-4©Û*”¬mPçwÈhÕ«¤<ŒUsNÿ6‚§ÀhÓ§Œ<¶ËïÊ4%ˆ0N€„\ãl¨$8sì\>{>}ø ñãÇ—dŽP¼œ(ž3Ìž¦Hˆ j>ùêd£@ôÏÄ Ðtä(|ýxúѸß-_ ·J• —ç’hé¿Ùuº‘ûOhcC"n Ÿ:ñ$¹«VáÑu‡+X娻p!ÏÄy‘"w |ùú>3¯Í°lö’€¯ì™ Ààîs ©sʰšÄzy`?ØÚ$ ×_ýæ• NSícuª•)“"@âëWAý~ýã°Z‚¡ˆ‹öúÉK˜Îo7‡òMš¾ÔlDõjðúÑC^4ýÄ)pN•J­åÅ0ÅwïÃ÷oß Uì_^drs §€7HÈá†#QL@µÇÑ>)dÏ’_3[é' Èüý¿ÀsŸûðƒÅÅF»ÿø*´ï_u›¥ŠTÓÔµÀÿë·Ï¸\zo]rÓä Ó¤Ì>ož0q:5¸ÄÐ õÚinÆ:عÀê¹§–ã1}ð}xŒdD€˜G öi™7OªE$Uów±}ä~|¶ F+ô¶øÉ. ~È/ñíëŽ@õ¿ËB„ֲ.%ˆ D n¨Ãâãîš;‡OzAîÐoÅʸu4["ð"` "®7‰¸J”&qŸ@¦ ¹`x¯ªòöýKè6¬:|ûÊ˧-è S†m€œY ªÖíÌð¬9¿`ò¾Øžn„ÆÿÉ®ÏM^Ç/›qÔT*#D@… ¹*P(Ër àžRÄí5¼9,™KgÂ>ÞïÀ}Ärðeáâ±dD€"w ÔêÚM ¹·OžÿOŸ ‰“SÜ= š9 D€"åR$K ›=¯CÏ‹Wš‡Nnk=ÎC’ÄöQ>uHˆˆ-$äÆy7BÖ,úW¶+Z6¯ˆ‹…iÒ'‡™«ÃÛWÁ:!â%ˆ qzøá¢I;fÏâ³W¯.L=v<ÒGòùÝ[Ø8y\س~þz;ÍS¶44Òç4ŒÑºrØxÁSSšïË—0áïú2+^¼x0|Ë6¾¿bÈ`¸y lìì`ôÎ㶪ٙ;`Û w°²¶†Ñ»v ñõã˜Ô¨!„²°vg‹º4m¦Ö\;r,_÷ÎÕ)Ï”¿TmÓŠýù§N¾þÎÈÕ ÈÏz,\óæ…ç·oÃŒÖ-¹ˆŽuó”- }—¯Ôoftÿ[HLkÑ >0VŽÉSÀÈí;XüÚ˜½Ñ:ºv-ð÷ýÄÞÛAP¢N]øôö-LnÜ>¼xÁç!Oµc—Î1øþ »çχ‹ûö‚ïëW²,~üP jh:b”ɰ#kV‡À¯_¡ëÈR¨ÌËô©pxÕ*‚;Lš6-?ߊ֬%ûWKüüùö°˜¯‡V­¿e•ô¹sC½@-&íœNÀ›½wyË—‡6'ó6{-äŸ%±0Y²té¡õ¤É«¤v!¬ˆãlŸ9‚x»¤éÒA“á#¡`•*|_í¿íì3zjëHìàãö¨{…Æñãàôöm€çž 9J”„f,všlÙÔº†;§OÁæiSáù­[²Ü>iR(׸ ÔïÛOæ)£kÕ€€/_ Xí:Ð}¦MÙž… ÀkÓFy>`]ôæ-øG5h0p $OŸÁhó%ýûÁݳgÁ•}Vzz.æõNnÞ [gºÃ×÷š¸žØWåV­¡ñ°áFû¡"@"OÿžÌ›¸Z÷. Ÿ¿~à¡ f-#ú, ³stzóξø}‚ä.©Á9œ! °ý‹WcæHšœ“…9fLTÀ9}ÿñ….på Ã…gÌà@Æäà‚sÎìx’:[F(#?ÿÏðöƒ$`ßÇÉØ{Y¡ß3oŸGü=Ã÷ŽŒX:xìŽiwK?šßŠ@»Ú#ù2ô¤E½!uºäÿ©ã§ƒ%D€üô(àAþþüP1Î%þTéš7·~&> )2 +£ÿ¬ / ­ØÎ˜mœ4 ®Xf¬˜ç©Yº0Ni= dÂÜe–jzî¥+\ÌòìÓ‹ ÅX©Ç"O(PY]ë”#› T¢^=è0}†j¿‡Xœà˜†Öqæ,(Î*¥¡0Ö§x1&Â~Uf¤mí`晳`(‘AftÈ–…çã mla~·.:õ¬&„E·îÈÕ2‘Ù{ér6òb—oÅùê”2%L:t”)LœV³¿‚:³÷+úâ9ôëó§_·j»öÐxè0ýl¾?œ‰Ûož>ái%KQùÓÛ70 t)±«ºÂnÒ$K›N§Ì£s'vsâ°Nžr';ïæëwX.øÛ»¸À¬sêïå‹û÷`ÌŸLDãr¨PµêÐmÞ|å°2ݯd øòþß_tû. ­\|_i…Y‘%lììaîÅK·W …ÒD ï>¼‚Ž+òZò”1ýÕÿV*»yöâ>ô¥ýŽÜ¾ìŽÑˆO½ïÁ  M˜X¨ì‚§sd)£û-Ķve"ãåë'ÐoÌ_€¢§¾U*]zµ×ÜHò5[fÂÖ½š>úu•û¯<9ŠÊ¬–½JÂW?_.\n]zKæ‹DÛ~åÁ÷Ó¾»cù] €¡“ZÀïÛ¢ ß&´¶ES°8»)tò•;¯™˜={É`¸ûð²2[¦sf-­++Ýð…çtƒ ׎ÈzÆëæ]Ð^vr\ôÌ&Qøgá%cÍÀ/à ŒvožêþÎ Ê«=ÚM„D Õošc=»w™ûqHê’n?¸ç´ú‚è«z…fЩŶ˜z|‘E["`QèÌ´¨·ƒ&å}‡@ÿà°ª›U¾kã1èPw´¯£yazÓŠÿ1­°ïqÌŸ²&ô„çÕ°‹ \>{& ðO÷Í"Kgûå“L¼<&¬•ùŸ}ý Ó_cؼFB—ãÀÿ«áQyïÖСÞhÍ1°ùé=ü¾ˆb“Ûëîÿç×ñ³~Žì9g² "@b’zØ´š0Q9Œ %5ô’UЏ½–,\¤ _½—-—Ý^Ü»¶0@¥ý5`¤Ì”‰¿D>z ‹<ܦcž¼è‘ˆÖ`ÐQ <{÷’ieg ´`þM//e±Nz«b>ú".VD±[)âVëØ ¦ó‚1»÷rÏBъѽ ³˜aˆW¡AÁ:"®C2wQæ]h®usË«qKFZÄ5w\cõ¾…†êˆ¸ja:ЋV)âÖdá=&2!ÔóÎ=¶=¬#ÂÍéаOSöÞÛ[GÄ­ÎÞ—3˜HÎHa×…wµâ¸ÈÇíØºµånî2eù<ð|EÑ0s¼ìåýû²Ž~â[è7.¦ Åìöî3Pa[§O?__è–/¯q+4og͆D¶¶¢džÞoŸ?“ûáI ,SZV¯Ó»üܹŸ: ¶LàDóc^ÓJ;³c»qqA Ù.ñvø^4¬ù|)=ê•mÃJ?cÞ½cjÕ”Ÿ+ë„Ð}Ñ"À1ýW­¼á!ìòþÿ1îFb×è¶gÁRÄÅ7Ý,âÞÆ¢~>'5ü[ìÒ–h"1]vžï0ÁNͦÎï }F×Uq±þ½GW Y÷"È„Q5»vû tVCUÄÅúGNm‡3—ö˦ï>š¾fýtoº}g 7¢‹9ûý»æf)Öùøé-4éZÈ@ÄŲРhׯ÷Æ}}›¿rt\Õ¨ˆ‹õQàmÐ)óòÕÌIôñÕ_÷ï·È×ßknÔ‹|qLßÿ.½|ó$´èQ̨ˆ‹}8¿w)À<±?Š®nƒ™h?jz;6¹¹ˆ‹þwl=tfȈ€¥°²Ô‰Ñ¼ˆ€<³Â­+yѼÉëaæÊAjÕÌÊ›9z5ܸ¤~á³oëIÀWîY`à„¶ªý…‡ÂÅ“š;¢«ì‚îUëaæÎõG¸Øûè®7´î^lléÔ½tú6Ü¿õ”çÃþ§`[¬MXpPxLZC&·Y|‹‚3ÎSßž=z=›Mç¤à¾b€êÝDœ»§û&¶ €î—0ü`w‹þ kí†VÝê@…Ú»ÁúãÐ> D ¦”ªÿlœ4Q>Þv×Nþˆ¼r|åÍ>e¾Hzó†?úûvÌKoº×I¯Ô|åÊs‘¬K¶’4Xÿ·d1ÔîÑS ^›5|¡ o¿¤éÓÃćxžþIÓ¤‘Y虪fƒX”ácóXWÍ[Vôa“$‰¨.·s:¶—#óåƒ,¼C<…'I÷ù à;Ñc…\ìkQŸ>ÌëxŽìC?¡$в/ƒÖ®×‡!‹ŠC«T’a+ °Gò{,ôE±¶=¶NsÃÔ)U*˜zô8ˆÅ¹”ç²·Ib}–/‡¬,$‚ÒRftåBâªáÅÝ»¼è Ÿ‘¿Ree54>¶V¶Qch=q’,+Q§¬= Ž­×x(Oø«>ge…_‰·OŸò”«››Îbø¸þ°Í[!80€/Æ=üXH´Ì ÂÐM›¥Wz&OiÚ^¼ÀËû+·.ì¼Ì¼Þ›âÖ.Ì+ö°Æ+vͨ‘Ðåj^×ÜÿžÝ¼)ÏO¼ÁQ½cGÙÔ)EJð¸rðóé˜"…ÌÇÄV÷érþ›<üˆÈ¨Þ¡#Tkß|<YáÚNT„Di>f,Tdµ°\¥JÇå+<”Êæéöèò%øàó’¦I+ªlÑ£™ócÂ* Ã^`x’q¿ù§7nð›7äée€2ˆ@”(š¿"ó=Êû<å(äήù'Ù|£YñFÑœñ» ]ªÌ¼844öY +7Nãû­{•†Mž—Y:žhηãgwâÛxñâƒû¨-%cn¾ß)7 ³»0QíͰ.-GCµ yôEÃp=㙇«ÒrfÑܤSæ™›nß_ót†ƒ}RæM¼’9§†WoŸÁIMe½FÔ†%îÚkMQ€sFK’ØtY2ä{gž÷ìå8¾!„„ñý9Ìk·_gwžÆÿ†õœÞ¿b‹cKlkÃ{/”uðÆ|R§”rßœzW›ÙAV­Xª4©×ƒ÷ÊB><ÅòY˜8íÇë´gBõ–%7e}µ„ûÂ~ðøùm^Tç¶P³r3vbïáõÌcZó[å݇—püì¿P¾„öfªZ_”GbƒyäÆu3ÂzÐ\@c¾ï¿À¨žó …H3zÒi–Žˆ›){:(_½”®\R¤v‘=ܾúwœ)÷c"qÎ놎ˆke­ñ|J˜HsA æ€ñ‚•"nšô)øüsºe‚D6 y5ß_À}äJÑDnQÄEob!â.Úöªmzփ¥´?@VÍß w®?–í(AˆˆMž@ØR—R)¾‰|SÛE,Ô°^žKTÅR¯šŒ)ªÁùÝÿÊtDn•*Éf÷ f2ƒ%ެ[Ãw•¢+ =úvåàA™ÕlôX™ÆÆ;½qì˜ÌÎÄ=e¢…¥IÌ£TØÅ½»¥Ð*òô·©²dÑq±» Ë&6øÞ={Æ«å.SÆ"D\1gñÝOœ’B%æãÅ¥0 1ïêUW”ã¶«Ç|¹»ÙR™6–¨ÆDG¥ˆ+êµ;N$¹g¯¾wé—+aíß< ч Êø†uEžØ¢°QÃßFVi«çœbsÊŽ.3kAؼø†üûî£j÷õk´‡£¶Âºyç¡`žÒRÄÅÊÓfƒ¥Ó5Â8î{Ûi(øŠù‹L<‘‡ÛÜÙ Ë9ˆ:am‡Lj&«ôë<zw˜)“¥ã¡lÏ\Ù Á†ÁÁNs ÿy'O˜ÓU¶QKwÞĽЮÉ`H•<=‹ÿ›Z6è m› ‘M¶ìÖþî”™” @€„\ xh æ@1³XÙ|²÷“×Б…8uøªÙô[V€7>š #ôŒ]ºc,ŒšÙÚô`q ûþ S—ôƒy†±G'5\4íøÿ4Þ*ràhL¬ôØÁ{/U¹¬Ø=–lË·ýÆh/$Þ¼úGvkî˜â—ÿ¬ÕƒaâÂ^|þƒ'µ‡E[FÁà‰íØ…Xva¨ëq‹¿(â¢aùœµC ÇЦPîÂP¾Zè1¬,ܬ1¦ [Î~¨üäõé?"@ˆ@lÈV¸82±G؆ ãEÒ¬íËe=ñHºÌP$Ê+³#¿<8ÅáJViÝFÖ÷Ú¨{ˆ'6näååkNo礽8ÅÌ«‡ñ2ü¯fÓƒX§`Õ?pÃíñÕk"i°Åø±¦Ù7hÀ2¶º»Ãí“'yQæy9x½æÆ¡ZÝØÈCQÕ&‰á÷^dælda0ѧxÄ^ìëo]X˜a"|†ØÇ­SJM9.׳`~øè£îI¥l£LÇÃqVÔÁ Úè¶L,,„ãO®P^ÆN65výþdñ reàÀŠr?2 á-Ž\ôWÓïWÌ[ä‡ФÁVÍ^TʘGóùÆý/_ŠlÚ"M0ܰñu=\œÜ&Š hŠ2­–0æëÂ<8…Í]>”/Ø%öcsÛ­õX£Ã×®ª½¡tõVøŸ®ÀŽÓ§Éf´ÿ¨.3££ì²OÇi2­–HÀBT8:hn~þüù0ì‚1K•"#89$S-ÆEÜ%L¬ZF™DÀRhÿºYÊŒhDÀ b¡×ˆÌ‹D{ñ‹ÅìÙtœ/\vö˜ú…©×‹²÷\Lè!d¦"‘1KöXöK=scÂÐë¸ë ­g–Ú˜/Ÿ¾‘ÙeªêÆï“,·ú!¼ö_’UJU* Óú‰†­µúvFì‹^¿OÚ'D€D–€‹¡éê–_v£\€H_p‘•XâƒÏ+¹ûòÞ]ããÜ{ÉÊ‘Làœ’üò€Åǃdç÷ì‘éôÌ+³’"F§÷;²ì[Hˆô,Á8«úöå½Ö+(¡ba*ýzb_éMúáå ‘ém(›ç>Ï…²Ÿ¦ÃGÊt\Mà"b‹zõ„ÑlQ¬ÞE CçÜ9¡cŽlüUǤËñâS߯íÑÞ€ f"â òe¡W¡‚,†³Ö£L¿MxöM}nÂÓ©ºxbüÿÈ*xÞuΕ†U©ÌnD÷b­ÌˆÏ;~~6MšÀÙ/èÞ-J˜§cX S†»yælmíµ7ÝÕ{sú :D€˜OÀ/à‹¬ìâœB¦1ñæ½ö»ïþãkpóÞy£/'‡ä²­¥ 2rf-Ì“¸ØXÃNn°tý$Qd‘ÛT)2Èy½û¨ë¥, ÂH$´NF¨+~ñJë››…PË’:koŒú¼yj´z<½XÇúó¨«~[Ú'1A€„ܘ LcD ‚%rÂò]ã¡yçZ:‹‡}ÿþƒ-âµMßd0îé#šG=± CŸ¿ Êõ3 •Ôzþ>{>ý¾ÌÝïΘ2¥ œ,¥®W–©vX†a„¹$7ý˜¤mQn±XÁdD€K!0b+[Èë—!.@øõ«fj&< ý?}ŠÐô󖯡vÊFfÌ’»»-|…gvj½‹Qè*ZSûÈàéÛe›&Né–ã ÃIúùÉrk…7±ÌÔK8&×^”úýb§W'"»8v½¾ýdS\,¼qŒeãXN`lÖžL,]«\Ü·^²xá‚qèI­ô¦Žî©bXƒÅ÷èxR°ë¶L›ÂoD\9¨H£{.‘é?UæÌ,öðu&̺ÉnÞ>{ óØBj(ê¾2¢?ï‡ kÍïdùÀ~èQÀ õî)ûŠH”­²?—´iänHP LS‚Ë%ðä¹ö†h2—ÔF':bj+0õú૽üýÇ7~¦ [Òf—y»­†zírÂÐIÍÁçÍ3™o)‰„ µ×w¦Bõœ½|z²ÑðXô_žÝŒ±Ãa÷ïÂeÎŽÚß7Ï_jEàpuÂ*‡%ô†·?ªO¢š€ÖÝ0ª{¦þˆ@ ¨R»$àëʹ»0oÒz?NãÍrîøu(U±¸Ñ~¹~d ¤ Kd›P$nS¤I*Ë^û¼—éØL¼~©G¾Bá{´å•·¶íÇwŸ¡íŸ#Ì:”Ÿ#×,NT‰˜#€ž©"æì°?ªÂ¬3g!¾ !7±ƒƒœ\–B…`èÆÍr?ºÙŠ‘C\Ü»þþõ¸øÍãÇx~­nÝùûçÀ³+—C+–Cã¡Ãxþ¥ýÿã[eÈžñë?¥.zúñ~”ôÒï_h=‘”±KõªEh÷Ov,–/ƒë=‰‡TªSP_±ÕP¦”Þ ßƒÖm€T™2ɸµS¢Þˆ€ !×ÞšBÔ(X<'_¬W³Iàï§ñ–˜5f5_(LŒ$’:ad¦^"y ­Çë'ߨóZÒ&\»Ÿ?jçáèb¾¶<{Ç$á‡*"@¢›@« ¥û•…@o¾*«Ú‹y8¦Ðzi<»qCdÇÈ…V ¯àïë ïž?çc*=Uëôì%çQ‰=Jޱ±_(†~ýð—ç,QRÖS&’0Í¿bn††hŸ¼PÖQ¦}j½TÒæÈ¡,Š’ôÜ‹—¡Kž\|î˜h¼qò$)JGÉÑÜɤF åÍÇŒƒŠÍ›Ë}KHàùR§GOØÉÝÙ mN‡vÜk×ÜP±}8Ïž =¹‡ó¬ömáî™3|JÂbþ5ãŸO A2ûÜ@i!¶2ïäÇlq½Ìù‡‹2v¼æ ¹Êö¸`  –OàóWÍw'Î4®F'¼zîi£e渱þ7,¸¸€ØhwvÓé×"Y'Îï[›$ЭÍ8s»ŠÕzíú±øå¿DÜ|9‹3z&‹%›TgNýÇ6€˜óÊE5Ö|1÷åë'r®ÓEýïÙ9%ˆ@, Ð ±üÐðQK >‹™;{íN½ŸjãÿX±˜±Â0CX¦o]’)îð…Õ0Ë­¬µÇ০ÍRé…œ-wp_>À¬×ÜuCÍéžê"@b”@Û)Såxs;wc«XI¹ Ñ7öx|TYhPØÂ)ŽU¶A#9$zîõ\Ä÷QÐJ`¥½¯^­]{YïèÚ5ðòÁ¹ßè—‡®Ìø•ÈZ¨°ÌúwÞ<™6–P.ŽñËX¿Êü Šx¨™‡îã«ÚÐFÊz–˜~qWû8®¥‰¸’ó>¯Û«7ä-§õÂ}ç­¹I ëÄÞ|°z­œ©2†´ÌTI8¥L CþцÐ:ºVÛ‡Juƒ, W†7K”êYFÀíÓ\jå”Gˆ€åXþö÷‹SJPƹÆÙ*ÂeT…‹S ð˜°&]'»;൙?y 3,4ñöƒøùkÂP¥JžÆZe âÆôÔE-78Dã¨ej_¾úÊâÔŠxÀ2“Dà7!@BîoòFÒah  X«\Ä C³IœH$á[¨ö‘™©—ðñ~'sÒ¤ÓzsafüÚ2ö¬lM Gg{Ùó“/dÚœ„²íËgo!i '³^æôMuˆ 1M ôß Àê—.®Bÿéõ“SpN•Z–_>p@¦#“ð}ýʬæ  ’õæ³…šNmÑ„vHM7DŽKš4ì‚SóýrjÛ6XÒ··l—&kV™V&”½^ÿl`ßoÆ…êû.@hæ& u¢D€Þ¼ÑaÉÒ§‡¦£FË®'5ü[g¡7YGožj½~bóÙâ”+´‹¼ßy‹ç˜0SÞø¢Žr[¼v¹»¸_™VKàbwÂÚOwIÚ"`¡|?¿ƒ]VÈÙ)EU‘™;»6äÑ£g·Ev”mse+ Y]óýêï'ór5ü^"eÌ…)0}xk~—`­Fuºš®f©&®AˆO ™êªhþJ²xÁª12­–@ŽoÞkÂà$H`Ö,ø] h•¨ßõé¸þ“~°…0„9»h…Ï´´<§†ító’ÖÊ5[ZÑ%ß*½{•±gu*ýÚQ>B«Vž¼ìy2Êên?—isiÒkÅèÿðyóšÓ?Õ!D€Ä4qûöË!ß¿Ðü€—z‰þ«×Èœ,>gP@€Ü×O`ÌΣë™G óØ3i¬ëšc(œ¢}|ùÞ2á­lÃF|+þà »$¿Ý~vó¼yú”å*]ZT1Øâ£ÞºAذ*•Uã ‚iÍšˆj0õ˜—LGG¢2 ¡ Ý0´Šö‚,:Æ‹²>±–o:iÐíçwï`xÕ*ùÑ‘±jÄ0¶¨ÙTî9jÐ?;÷ÎíÚ)³“¥K'Ó––èWª„ Ÿ ?7åB„J¯züí4¸B9~A¿ î#a3;<ÖdÄYýñ•+°‡ÅV³}žžpû×9€ –¨[O­å"`!^½}mû–•³É’1/¤Lnø·ñ¯šd13´i™iF"¬ë;¥©Ú¶Ä6škT¼nŸ³–doßû¨NçÛ·P³Â*X[YóöÁ!Æg© —9´§‡Ìñ:» p|c6Ú]ûDSQ·Š2¦¯±ú”Oâ2«¸ö÷Õ žßº —®@|E¸Ѹpšpv‡f¡'‘‡1qõ­Ý”i<æ©2¿Jë6Ê]ƒô¤‡ wQMˆ…>/yŒZ×|n¥`Aðg†ß¿pÐkYXÉúõÁq‹n½ó_è–/÷þÂÐÙÚCŸ¥Ë¢{ØHõßkÉR˜Ëæ‰6»m(ÎÄ; ±ðõÃGØ>k¼¼w—¡7¸)ïg^)’ÿ½÷~'No„ýË–BV¶h^Š•! ;Wïœ9 ÇØŒ¡ŒV JUéÉÉ!£¥9¾÷î­Z@Bþt«Plì+»rPëßaÆL9¾ï›×ðÅ~îœ;'¤pu…ü쳘»TiðyôŽ­[ÇâMkÎg M’ÉÍM¶3'ÞZ­'N†UÇòêÛg¸Ãþ¥K!{Ñ"6{xyÿ<¸x”"óä#ÇH0.Õ!1Là‹Ÿ/lݽNœßÍcÔŠáS&O3Fo»:ÛÂnåy¸…?¾C@à0®!¸Òz¤*+¿~ç Óæ÷†>§A†´YeQ‡¹×ç´ÁÁ^{3+|þò^¼zÄë&¶u`‹…i¯ÅDéY_w^æ»k¶Î‚öM50Ebá±+êG÷¶nõ¶°y·æ¦Ö–=‹ Qí.:!)^½õ†Þ#k›5 {;gö^hž’Ú±9Ô«ÖŽ·‹Èq¥O“•ÇƶÍ{Ï©ÀÉQ÷÷ËTöþ\¿s†Ü÷ÐÄ7k²T‰ÄAºŠP<šò‡À+æ`X×9¯p6èØ¿Ø;$18xŒ{;¤Ó,™ïšQ'÷30Qך ¡¡LìÅ/ƒ%3·@Ç~ d¹H`ÙÎÚ~úŽi)Št¶IìlåÂjÿ,ÛMÚ×Ð)ß»íl^¾_'/*vPÞµá(ïjÊÐ¥0oÃpƒný¿¨žó ‰½-Œc«¨ ë;º%̵ŠïN¸0þ­’‘¨'¶Oúr$#D€X*é^' kžÜê^‹z“î·b%l?³ÅÐ^?~»çi=>”ÕÑÏ!YRe–LÛ¸ ºpc7ƾsÁG—5cXÁ˜o­.]¹+:Á "Œ‚ÈÃm¾òå•»<íV¾‚Až2#‰“Ì:{•/ ¡ÁÁÜKø¤ñ¥o5:uÖÏŽ–}d8ûÂ%èÁX¡Ý<~ î;9Š–ñ¢¢Sd½n°¹âïßñ¥´>ËW¦)“Àçþ}ev”§+4mwNŸâÖΟ|é[öbÅ ÇÂEúÙµ_¡Ys.<‡°°Wä/ý Vgçd‘êÚßQ.,Šu„ìæ|¼}ú0Ö2¾”–(qb˜yæœêçHYO-]¶Q#¾¨ÏÚQ#yqÀçOl^‡øKYß&Iºi $eaOȈˆ]Wo„zír†9‰š•Z@§ZÏ{µ+g„Ö}J³¿ó?àáÓ¼ßd.©!uŠŒúýÝyÿRŠ‘Øþñ³[:Bn(óýàûZõ.ÉÝLrƒ#tŸxß…÷_É!‡ë×h“=4ßÑÿ\ø²¶¶aשA0¤‡”(TUö »Ä€ñ„Q€EÏ׿:äü¹KAR–wëÁExÃm´‚yË•›šâÆæÕàÏN°xíx^¼rã4ÀŠÙØï÷£œq6×fÛÍ»‡ `'7Úô-Vìf õ_ý¿ÀÅ¢vØç¬1;b\7÷X¨ˆ*$äFIê'Ú ˆ‹Ý,ÜA¯f“Ù{+¨ñwÈœ==ø²8¸—ÎÝe(~7nrƒy ÖÆõÕÜm<}ä*\9{º lÈbÅ:³ôßáôÑk°ûIÙ.CæÔàV$‡ÜW&Ú÷ù æNXdzöo?O¼„Æíª3OÞ@.ýìÏËpa±ð†APŽ£Ÿ®×´’rQ°m[{ÔnT Ïɼs¾ÁÎ GàöµÇ¼Yb;æy eGg;øÌ<Žý™Çqû:#ᯖU Hé¼`k›‚ƒCÃ.ü18ïuƒ·½fo£Óí"@¢‰ÀŸÝ{ÀÙ;ÁÆÞά¬¬‚û©3°¨w/ „âšöi:r42þ™8nzy±ÅŒ¾2ñ3P°±µ·\<¬~¿þàÌT2fÖ̳pñÝû0«}[ÀÅËBX˜[¾ø”rñ2eûÔY²@þ*U½,ã±›ŒUÛhL@ß:}$_?y)!·X­ZpùÀ~¶¸]"°û†B9žZZÌ1[á"jÅy½™×°/óÊžÓ±||åߘ8ŽbyÁªž?øãñÕp‰m W«nÐ32(ÁP rÕr‘éV±" ¡ñ„/zgçì"²ù¶põêü<[?nÜ:éþŸ?ó¹$bL1”B§Y³!yú :mÄNþʕٹy“iÃ*‰2å¶@Õªì†Æã0=´+4oÁ¼T/°Ï‰úç²bË–pbÓ&!D”c´;‹ûôf^ï7Ùgî3Éñ3“!wè>>;~Cµ…·îÀùÝÿ¾%‹áÓÍùŒŸ='øv LÛñbÜLùó³¿ÁPú¯¿D–Á…r|m›9.ìÝŒ/ƶ±³Œ?\¯w_æ \É 2£Xí?™Ø~l:(ëáòäe‹}‡"Ì;ŸŒó¨y³ªµt´O ¹³†AÝf³¿Ñag¢íöe·¡ÇðZÒ{X¥+ÆI“2”/YGìò­ûÈMÐ{T] òãÂïýǺaûÄOºÎ·\%tÚ‰â+C¾œ%àÆÝ³"‹‹¸¸cŸÄI晓к-™S[SGÍãw錣ÐcX-ðyó„Wºvû´ì¯Å»·™Ù2»Á•¦…Üš•šÃ!¯mðø¹æÉ%ìD„EHb« {(;7‘Hß ÖÏ¿ýÇþÍDò;¼æ·ïßàåkÍESàgŒÙ ‰mÔ¿£D=¾0h‡Ä.xÌÓÀ˜ãHìÎŒF'*º6æ,,–€…X¼}ŒQ \ÇþsU†‘Y™s¦‡‘îå¾ZbP‡ðZÏ?¿\¿x6¯8À÷n 6¶‰têÝ{V/ØÅóúŽiÅ„ãì:åj;Ρ}QjE:yS–ô…”© =Ê´s‡o?éÔ5¶ã¹u4$Ldxe¬>å"@ˆ D€"@¢‚Š€?˜ç¬Ò¸Ç[$ù.fˆ·ÊöÊôw& ^¼îÇNï„§Ì£6u WÈž9/Ô©Öl%1zM‰}„„ÃŽ}ËàÚí3ðù«/k—Z6èNÉÌò ÅÁÖl™ žÞ„´©]¡m£A`—ÄQ9=þÁî=¼ ™]ÑëW÷†V €;.ƒk†œà¬v@§#¶óðÉM „aþüù°yóæßç "y$oÞ¼OOO¸wï^${¢æD€"@ˆ !`‘FÔ†"@ˆ ÿE·nÝ| K 888@êÔ©![¶l(Q"Q¤³½páìÝ»²dÉ-Z´Ð)‹Íœ“¿¿?äÉ“rçέ:•ÿý—×iÒ¤‰jùïš ïß¿‡€€€â§OŸ`Μ9€çȈ#"Ô‡¥5úøñ#¼~ýšsÉ‘#‡¥MæCˆ D€ßž ¹¿ý[LHˆ D€Dǃ¯¯¯ÑîÒ¤I:t€xñâéÔñññáû៎¿dÉèÔ©“ŸÄ‰ëì[Êz¿xñ0”À³gÏþ3³1Á?ºÄ÷˜˜;Aˆ@Ü!0`l9Ùò%jËô91É£;óP¾ÏØ'q‚¦õz@a·òBrãîYX¶~2c¨xP¬@%hm‚ùÏÍQ4ª×.§H†—ÀÖ&‰Ü·ÔDÿ±ÃsŸG`m•FôY¹³69ÕŸð:ª ¾Ÿß‚]bGX1ë„Éúú…÷Ÿ\‡Ù‹Ã÷ßtŠâÇ‹vIÏ—Œés@ÍJÍ yÒÔ:uâòNr—Ô >»yÿã;,\5š„Ü8üf’Mo^P`0q±ûJ5‹GÓ(q«Ûï߾Ó/½—&²†tSB«>É¿|òãí’:@ræùª}$òëgÿp±cýØÉ^Ö ­`öš!8Iø¼±Â=`4ðýðÐs-K® àèdø¨­þ0Ÿ>~…Ç÷¼!~üø§`V~¼úuLíûx¿ÿ¯¦ªÈ2gö~£§{\´ÿ„ÙãÖÀï?àù£W!Ëïóã .¾4g"Õ0¬zä¾zõ BBB a„|???¾HT™2e xqÍï|ô½`ëׯè½¹xñbøòå ¤OŸŠ)§OŸ–Ó[±bÿûŠíÛ· ¹°k×.¨P¡¯++þJœ;wNœ8ÁÅdŒákÊ~üøݺuã"ôúõëaèС¦ªë”=}úvïÞ >|àù8÷zõê‹‹‹¬‡Ç…c a<^ô\FË!Ô¬Yþùçxùò%ôìÙSòâØ¸Ø¶Å2¥aÆŸEç®]»*‹àìÙ³päÈ åùÎÇÁ8ÆJù,\¸0ö/2Âyág9sæ„Æ+«ê¤QìÞºu+ n*n0zj{xx@Ê”)¡Y³f²œ·««+ —6ÎõàÁƒüñf@Û¶mu<¯Ñ#[ŽC°K’$ ´lÙ’÷‰Ç€<ºwï;wî„«W¯òseذa<>¯Ï///îAŽy8ä‚㪆sÀ¹áï6'''£ÇŠqs—/_%J”<¿•†aH–-[¥K—†’%K*‹øŒÑ|ýúužcà|0Ö´Òðs„õ®]»Æ³ÑùzõêP¸°á…>zÆoܸ‘ÇñÅÊx>6hЀ¿¿¢O< W‚Ÿ9ôF'#q@Hh0vhÈ7Xï¹:ôÓÞ1µI…1ð«aɬ-²ÖòÇ›þâb7#Q?Ù@IDATŽû¶š*ëOñì )Ó&•ûa%±Px†UM–ãÏ­šÇšefIä/¦]fÙœm0vn÷82sš& æÀÅÎP¼Eñéùóç5kVÞ …\|‰X¹˜‰°(&¾yóÖ®]Ëÿ΢°äííÍÅ,È„¡ &âî⊓ØvÏž=ªBîÿþ÷?ÞÔ/`ì/Y²d<ìÁׯ_áâÅ‹ª}йˆí¶mÛàÆ\ K‘"qî(^¢ØŒsDC‘Ç@CÁQ†Y@ñïÝ»ÇEÃZµ´ù¡€‰ÞÍh8/Q÷‘#ŠÞú¢ßäÉ“9{ûðØq<ô6F1¹råÊ:B#жÈûB±ù"|ߌËÊ•+yÝÚµM{½a¸ œ#Ž£4ß¾} 'Nä¢*ž3X÷Ý»w0mÚ4¾0šñøE¬]ìC°SŠ»Øš§§'0ñØQDÆs=‚±oä‚[ôG.˜F~Á‚ð×_A¾|ùxâ?Œñ¼D&(–#Ì+X° ¨"·xŒÈQÛçÏŸy õJÃúîîî< ç„sÆ~ðFB¥J• lYÍÓO˜7kÖ,^o '¼y€"8Š×ÂÄârxCÏ=<~¼¡2{öl5Jûd3rÅ:xž’¸NàÂÕ#Ì?Qó7¶sKí¹×+ªæÇÄwÐði­`Òà5áêzÅ?ÚkÑ×ÏŸš›“áê(†*'J”˜ý¾€„Ö6̳ÔQuÔúísñü¬®ùÀ}ÔfÕ:1™ùwÍŽ°~ûöݤñ]³e&´jØ_u ?˜7醲lâàU2mnBŒƒõí˜÷-zâ¢áç˾} …&*ó<öûeÞŠá°lÃXãq¬Äm -w¶"ü¸ð¿EkÆÁ”aëå>%â¸}Z0ç3G5^9óe²àYÆìÔ>}øj0àÄAK`œGƒü°2ôE\¬%âÂ'¬ö1Y/~<9\²”Z%™É(Lß¼ôR¥M=†k½v”ub2]ªrX·døi.>7.ÿ4i_ÃèÖ.Þ-ËÒgN.¢{x DÅe«R»ú÷,<üŠ‹ xÑIFˆÀïC hÑ¢€ {¡€(„\SG‡^øý5xð`.´¡…b0ŠYcÇŽåM1®¬ò1},”(.âBXÂPÐBC2s E1ô&Þ·o_˜B. Ë(⢀«ôˆEÑÅHô ÆPhÂÃE-F. wÈ Å\¥ûøñc9õcÇŽR8=yò$/C/Pa(Ø!»¦CÏÐÌ™3óïeM µh­[·Ö©†b3Š»(Æ¡ØkŽáEze¢×§ðšÂv(° CSib½yÑлYð\D} €â.†Ð7o…ˆ‹ejü1L†DÀß0ýû÷×õû3gûAQVé1ݨQ#ÞT›9ý(ë ø‹áÐÄmd*<‚•".ÖAÔ,Y²`R†-À4†@kذ!?^¾Ãþ«R¥Šä(ò"²Å(4#}O` ÍP®\9Þ- µÈ=cÆŒ¼1ŠË„—2ö«4ýÐ"xÃ?áý|(û¤4°¡ìÑùÐoš›Oys·”iYÜ<ªWh*çtù¦á÷€,ÔKœd B‰ï¥…Sè•Zî®þßAË©vfY]óBªädÆ  eZ$P\½vç4ßµJ` -öEQ¾ÅX¹ÝZ…ÅÓË›ƒÁ!Ð}¨qç¦(ŸD4uX·šæ÷!žÛo?øDÓ(Ômt !7èîݪýrÈW8{4Œ÷»ü³‘VÈÚ»EËËœ#;¶ï¯æèlǼGÌ»Ø5§ßèªãäbÕë—²U G×ÑÒ¯kÖ4àšUãå‚Ì»Fuœ‘Ý=d>zšÚF2p|æÁle•ÀäKGxNÄO ùó{òÐå8z4m"@ŒÀÇÆÑìììŒUÑÉGUxCꄱ“=»æ7Æ•+º·nÝâ" ªáµ¦M5».\0Ꙋ} Ñ =^•/Œ}‹†µ›kè)‹¦lƒc ·%Æ9L±ŽÛÑÑw¹ /d¢Õ¬N:<ûÑ£GÅ;WŸ…,/t¦OŸÎ·(âšû¾ªõ%òP¼ÖS\x‹ãuÍÝ*TÈ *ÆóEB¶~ 7†qu… d5orcýˆ¶æl1¾3šZŒ[e{qƒßSåù…iƒÑÄûŽiqüãÆ¥77–鞣ø¸NàøÙÝòêTk#Óæ$ð†Û—¯áýÇ×Äñ2§Uøëù3ï¿wèþÆQÔBéÝ9m~³{]º~¯‹a R$ÕÜ(3»1«ø=¦ÿéË{ÎØ?àkxšZt]|Oß1ð3;”a ";éy÷Ȉ÷_cžÔº7„; ª"‡ÑÇ“/Ü%3¢)‘"YZ˜?I{cùå›'ðæW|Þ°†Ä›,xîû~~ÇnLjn¸„ÕF¿ƒø|̃oÜD…Õû%äb_úñŸ£¢ê#ú XEÿÿ½n_Õ^$$ø%Öü÷(˜>â?ê–‚ K4Þ>[W¥°kªå—Ï~¬Y¼¤YÇZpxÖÔT;*‹!SÚC—ãxã·ŸÁg߯àè¬}|›COb4_›³…¼ÈÌ#àÀ¼Òq¸ïÌ÷Ø3¯gªEˆ@l"’±E¤¢j~E`ôFÅÇäÑÃc£âŹð´ ïXØg©R¥øBk[¶lÑY¤Kô…èQ‰†! ŒYx„²bÅŠñؼ«V­â¡DÜW|ôãîâëþýû€â5ÆFEÃ0„ '³ùb+悪1±pš¾ø‘¾Âjƒ|£Ê„—²±óAÜ@PzO‹cª9è÷#ÞWWWý"}øèÑ£€/5ï+–aHô>ƺkÖhn>£woÅŠÕšRø-œ½¬õJÏÆ<Í1ƒšv/Âã€êׯ^¡ ti5F?;û?aìŒNpåVØ;x !‹NÃ¿ÛØC{<>8$€ Øöê!ïÄ‘ÅhΓ£¨j™2ó; {ѺW)&â^+vn9jTÔz™+Û…7ýÁ×ü›îáí›êGr£˜í»WÚ…IªÔÑÆm kŸgoaÃò}ðéÃøþíójL•j‡ÒU †ÕÔìòo¡ßa뚃p÷ÆebhBkÈé–œ]túH.ä-”M'/:vòÊ 7/kÂP<}èü?þË9 ­»œJ±rùÂ%ä>{ì;×o?³…CBØÅ¯5¤wM·® .É´^=r€p$ð¢yÛÚÃ<¦lñ²ù ‡"6ò§_`7[ð, óT­ßB{ÑïKœ9vÍ`”ƒ»Îèä•­ZlL,çµÿ"x¼ì»øCXôþ-÷GáHý@ÀI$bϵ–ƒ=¿lÞm.ÌÛ0\ÎoP‡™2ÝwL+™Žz¶>ºÿÊT*Yrj¼Áp¹%3·€÷[ˆÇþ¥HãØbsáY`C¥ìÞt¾²÷ ½…S¦I ؆ÉS™þñƒ†mªñ¹a½ëïƒ[g]Xí¨œË' <…gjtθG€1PQ¼jÙ²% ЦŒ5ÞñÑKóôéÓðàÁéýªìCxŽbžˆ«,iµ¢L+Doá Š^ÅhøX¼››ìرƒ/p…1c16/š2F­Kˆ„¼‚â?!Ž*C(ŠM&ñx1„²EÖAÉÀdãX.1’•B­rJB´U.$‡Ç&x)놕6·Ã…øn¬_1÷?ÿüÓ ƒh#Þw±ÂméÒ¥y¨Œ™ëååx.á焌üŽ®(Â8;%óç, GOkD2µÊÿ;ö —¯fQ'kµ*aæ¡ØÖªw)U¡X­±¹;ÔÚ†'oP÷90fF;Þd9[À¬OÇ©&›×P–›»ˆÜÞ#ëañZŒl¬—¸~ç 4윟ÇZM?^iØ»=†×’".†È—KWs²x}  ‚D,°Ò6í^ë·ÍVf¤GOo (x.œ²ß ,<½ÛO¯³ÿJOßsìfEñBU`àxíûñtcÚ:4ÆÖ²©É‡õöy :<žË]†üoÞy«–cæð©-!G–‚0uø£uö³¸¼ W6Zî¹f,lØá«çœ6Z'¬‚¤Î©¸( »¡ç°µU°šP¹ !7ŠßŒSG¯ÈËV),ÓÆo|>À¸¾ Uã>¾Ç¡`«ÚwdÂSÉŠùuaVþÒY[áÔaíÜD£'÷_ФÎvÅî :ûѱӠõRÈ]1w;ŒÛÝä0Ÿ}ý…9´2U 4Öxóª°÷—©_côž>zÒgJcfw“»ë×3µÿ-ôtm4p‹†^ØJ!wá´MpÿæS^¦rw0Qùðî³<_ùßúÅ{”»L~dqéêÞÝügé>Ø¿ã”N]±óè®7¬ôØ£ÙÔ"e¢¾©-¾O¸0W0¿ý¿Â¥Ó·¡p©ÜpáäMž‡mÓ¤O¹ d1ÕM´—­õÜ Á!p÷Úc˜ìÙÖ,úŽì>§3î‹go KÃñPµnI@nS†È“Øb|ú†çÌÅS·ÀÁɦ/ë Ù ˆXñònRÈÅ $äF„"µ!–GàέW‘““S´OóGaLxchâœ#<6z9Ö«W‹§›7oVícÚ¢¨/¤©V63¡Çã@oÜ‹/òÅÝ„˜‡ãùùùq@ý°"ªZè^„lÀx«1Wæ=Šî_¾|pQ5\|+.˜ˆ×|óæMÀEåô  CË;·,Bæ(¨ãû‹Ü•&<±•yèަ¶¨˜š@#ætûöm.º*ûR¦3eÊÄÏô6+ ƒ²~ð½*P Œ?ž‡aÀã‰ÊsU9¥‰@l¢9søgç<)â¢çãøA« WVÍ5Õç/aöÒÁ€Âp`‹Z<§4§[ƒ:ÝX[‡ÐjVjí›eßSV,Ì@(LœÓeJÖ…Âù˃]bíÓ~˜]–?·Vðƒ.ƒÿ€E‘Œ'<¨Û˜ì¡Ñ&Ïëët—7ñ¼qrH*çS‰t©3ë …ñz1Ž®Òº «!E\µÇ^Éc´'Þwaôôv|á=\dl.»©Ò«Ãdesž¾}ÿ¢Žˆ[…ñÆ÷(±­Üyxvì[ŸÞà^åÈ?©sJƒ>ÌÉpMŸCzW¿ÿøŠ‹ðæ´£:–AÀ¼¿H–1×81 eX示ž®ú𖉸C:Í’".zø¡`Wãï26£æ‰?€ÏØ —Ïh/ õû kÖØÕRĵ¶¶‚<̶nÓŠ1‹®l{[ÀWÒäºÂê?¢å8¾ðhyþø»ëfú®!†`Ö„-fŽ:tEGÄM–Ò 2çHŠç„Ti5 ¤`?(ÎMl(Ú…5F(o;Ö#E\ãÌ /1sj°NhÅ_ÊqDžØ¦` ú6²Ç<Ï5\ø-kJµ¸HÙä!á?.ýñz Ó>º1oÒzþHí‚)Ú;¡ƒ&µÓokû?~ü„fH×Î!1¤L›TGp=¸óŒüL¨Mô&óÂUЏè6C î…‹ï †•a'Ôú+OvE„§« •"`ÙP,Ü´Is׫W¯(™¬XÌKM$äÌ™“'1Î) m!²†1kQøzúô©jWbQ­={to>ªVVdbèc†‹i¡aÌ_Œ«ôhÆcÂßD·dIÝœ(\ão ôÈUÆÓÅþ°XœM¿–›k¸˜zôâ{aìQsûŠ©z(†¢¡GnPPΰ譵k×.ž'âËâŽà~øðaúÈU,„¦,H™Ró›Ußëßëõë×+«ò´ˆ½ëããÃC(+`˜áÑ.Þ+¡Õae;Lë{Ÿá !+hœ×ëׯ¹P­ßí¸D ˜=ú/Ì&Qb‘TÝ¢à‹B.þ­Ü´èšq1ÏÑÁF÷[.NšÏó›÷Þp÷¡¡Ö5e}ß²pïx\¼ªS‹‘\ÄÅ ôÅ1œ4×`(¦–)Z æ-kªË(+‹ÇÄØÜÙŠÊþN°…ÌŒŠ“ÂÚ5"’anK±ãÒÃ6/¾ëv—".6,š¿,™~DöÇ[¼v‚ UáhŸ4\".Ž“7g1¨ûGþã:±ð"·(¼+ Ï›¹Ë´Ç?}äfèÞvM.Ø–)ZÜGm†y÷²ßh¶ì&ˆi½Iv¤’È’QŠåÑSÀ¯R²,” ¹QüÆ  (,q‹>ácê†NíÓ–öƒæj±Ç¶«Ã„ù=Ù#ìÃD1xL\'ÓáM\¿pŸ7±g19=·†ãÚ@½æ•a »#ºxÛmw,$>6ï¾b 6/šSÍ:×’#l_{H¦Õ'Xø4›Ä‰˜àlúÇŠh/~¸7íT–íÇ<(ÀÈ] ÷ÈÜkÓCÁ½X¿}ÓÄüíÃÚöl2QVA/Õð„(ËB ÷€õ&òÄVßSÃc¼xª=Ïl 3W ‚±sºÃøy=aÁÆ<-&vÿæ3¸wó‰ØÐCm *¬ó@V†…~PÆÍù±µ}ûê¼íˇŸµf0x¬S<û‚çÖѧ`V9­åÌÛݘ͵JuÒn ôâŸQ|_F¸wæåæ\XÊŽL$DÜgU¨ˆ #pòäIÀŠ]sâĉ°xñb>ËúõëGÊ#Vy¨VmÆ \¨B!S|·‰zè=‹¶s§æ¢°D‰¢(R[ Õ`Ì0Ôzή]»VŠh(¦íß¿&LÐ~O(ûÀ¦>ä"òSZªT©ø®ˆ‹Â©°|ùòñäöíÛù½-•†¢]™2ex†?À°h(>Nž¬ñxA/^1/ŒÀ艋">²q{cÚΟ?Ïß `Ž¡wªðf6m<þœ7C1ZpAQ^¹p\³fÍxsçÎñ¸ƒãº»»sïo^¨øÙc¬]ôàõððàÞ¼ÏxÒ$õ‹môàÅxÎh8|¯ðœÆófêÔ©€ã¢áܳeËÆÅ^ü|‰ΰ =·ñx„°yxÎíÛ·“Üp¢­­­ÈOOOþaHd%ˆ@#ðê­æóŒÓNÒÕäì§Îë-Ë=&ì1ºØß‚ÉÚÏÐ’uêÇeG*‰Íì|a“‡ª_¿¶n¤½Ö|öBó·Z´‰îíøÁ+åÂ+Uf(Ë6h¯ñrdί(1lT» ”(TÕh%'G­8i´’JÁúmsa¼½ƒWÌ>¡R+ê³VmÖ†5¬U¹dˤù.Ö ¿[6è/³‡NÒ„eA8³Æ^«ágñ#Ž"Ú¬jo§}Ú SÚ¼eÃåîȾÚÏÌü•h£8ÿ/^?¦Süüå} ây¶6v/G1r±ƒÞÁ›]a¡"µßm¢ÌÜ­‹s Y=©É⫸5]Ílñ@ÜÍ·´ù±G»ÑÐÛ.[üɘy¸$Eà ՋBö<®UQ¬lÈbqn^±Ÿ—íÛvjü¥¹H1¨l$cÓŠÿÉnñ¬Òл°PÉÜÌã÷6øûÎ߯6æâ£”©\Ö-Úͧt€yIâcüjvtïy™Ý‡‰°æZ¹jESýãííc [qæè5žuÅVŠ}¢žÚvâƒ-¼V¼|>è2°±Zµ(ÏÛ½ñ¸ì…w[&lëzçvÔMÛÈ‹¦X KwŒÕ¯®ý¡“;@ŸVSy!<â9Þ¦‡F@Wg&*ã{}œÅý5fµ¬Â>aß¹GñU?¶pÿq­¡]푼kôÜU3 Ñ Ú|…³A‘2Ú»•¢~–œéaÉö1€¡"cŽÎvl9Í£ÂAÁóLßÔ6z  Ç˜ðt‹Þ‘¨wK#€‚zê{+âOœ8¸h>&ކ¿”Â> Bšð¸ŒªñQlKŸ>½ª`‰ß«½{÷æ‹a817qÜ©S§I¹EaÈuë4÷Ø¿_±ö)B6à¾2®ð¬Ä|cLJ¡°Yõ=AÑønݺؖ¡8{ã’æîÿã{Þ,Ô†î_ Ç!¬ßØÖ"i°µbaJ\³¦5ÈO.L‡B.ÆÖžÃÓÕY*T€7BTy>Æììi´ÈèØ±c„š£'( JJ½   #XêwŽb.®…B.ÆŽuuu5ˆ[ŠmŠ/Ç×ÁôûRÛ8p ÕÊ07|½#õ cãÜ0þ(zz¢W% ®"„~}·cžbiÓþ Åþ0<‚R¨ýŒ5ŠÇÉU2eb[±bEÀÆ~ÅXºÈXxŠ:b‹óÔODnñøÔÞ|x˜apñ;|!{<—ðØÔø‹1Q´=z4_ñýÅñ…ªÆ ßœö±ŒñF—ˆ«Æ?KxÎ<ý?{çEñÅñGBPBï½÷¢tv±¡(`Ç‚"þAÄ‚Š(н bV¤JoÒ{ ©¤óŸß³7»·w¹$w—»Ë{|ÂÎÎÎÌÎ~wïn÷·oÞìÙ#EÔ±»Î *ÃÐ6<‰11Î)ÚÐíž{î‘löîÝ+ÛÅñYÙ£üðáÃ¥à\¥J½:§Ã„Ào¿ýF=ô!$G¸›>¡Ríꎉ~íŽùôé`‚-½°Þš‡Rêå ›FÈ e™™jµn·|gÚ—´c³ãË"îCOßbWÌ/y¿}¿ÔhwÌóÃŒ´»DëMh“ˆ÷ CL梹ƒumþ8ïºú&‡Ö}a…@ÝNˆ­î /7C#oç¶¼¿6(¡;¿ö1AÙñ‡e±ýºÐ‡3¾“‚ÛÛSæ ÌXjݱ‰¬‚õ×&}Jðx…Õ“Yy e!?ý׳oGZô›CP^ðý2ê?ð¹§œì\:¸÷¨L×iP£P{?•–A¯<ómÿÏù¦¬P ‰JÝûv ?çÿ+«Ã›ó—oÓ%×ô.ls®gòÚ,•õµ«…Ž'8†TH@`_»ÜñÕfÌK#9†$}7ç/ºäÚ>”ßä~Ø_‹z–#„ieðÈÕ=À‹ë³§Ž——ÞX¿~½is¿~ýØ+×;t\Ê~üÑáµ2`À?î…›fL€ 7ü&+ChŒž±ó*WeB}Y½Z]ãâ= Ö=bcÄ,÷Ÿ¼îx–1*û8¡R4›.<€•÷«¾›ËÎBWö»UÏ húÖëFBîôwžL–ËÐn6/‘ý¨[EŒºˆ.pŸî{¹!âÖ«Ý”FÞö µmq–©Ç' ¦­;¡ÜL-+ð„=å/ºý¡>rË/}Fý/ì÷a÷–nH6Ö<»u%âb[Å …sŠùgù|!;„p„¡P“Íõ:ëRZ²òg¹Û§^¼&=þ¡]ü–÷×RghÌ2Qæ‘5Õâœ!BÚ¶èF“ž(\ßð2^D› ˆLv„ÍÀÎjæc; âˆ@ȹñññ2†–~tx`A¬`4x—º3=|AÍ:UéžÇotWÔ” ¬ cuÖ =;‰7yyôâØwm›@¬ÚÉo²ÝˆLÄ VB.&wSBîüy ݽÿ#ím"]Làvâç:œÝ‚5­#ÂZ8¾€8F ¾[j”ñ”(+êƒÓ˜á¯ÈbsÞù‰0T¿Rœý0POmfÂÄTüUÔ©RÕé[6PöÙGgU^œýˆŒåÚ÷²³é¯ŸVÈüW'~LOxêÁh$XZ¼=OŸa_uÿTšsT&d ~ˆÑ®,=ô|N3@8p ôLááâ¤ÎûbL ˜¬]»VÆÖûtÑEÉ‘3z^8¥kTÕñ/Mõ˜U°]L,v„ "7oÔNŠ›˜Èª8­a½–òwŽMiéI2 Àîý[(÷Ì</¿«ÀÝKKO±QwÊz˜ìêõIó ܆]Äb½sð“4û3Ç(ÞŸ¾J°]Yè°vûÈ/!'ÀJ)_Xq¾ jðâ>û £ÚG¯:ŸÑºëZ¶êWB™M[ÿ•“ÕU©\8G/cH|òåt£ôwšGT7ÒâÙnÞ±Ò(WÐBy¤Ÿr„L)(ï‚î+)É1zõjÕ¨_Ðê\¾˜ Dóþ½Þ½ÝÛT¼eŰÖ`4ˆ¦îL÷ôƒÐÖ›?wíyÊß°r›qQ¦¡,tA eŸùÅÓžšñû¶Ò¥#©Ju§Ð¨&´Â°}~4 à©ûœ1˜†('Á‚pÞÿºséüþÝä_dz[èøà¹ÜïÊF‡o{шyldú)¡‡S¿¥ùÚ11yœ²º…ôh^÷ï1!—ãáª×Œ“MÞzÏUªiÚºqÅt¾Õ36„Xס2Lço;•žaì‚…\EÐ&Ö­['E[këÔq>HY·ñ:L¾Å"n Hó>˜V;wvéÚ¶mÛÂzÔLšŒcNJvŠ2F晞£â*)Áë4ù3ç–«å^1QS¤˜è챑¯Aó!<® ·_úFß;Š[ÄU|FÜâ Qðæ‡hÜËw¨Mtõ%C´·‰ßm½áª{´/W^t«ðìíf45|´ÓûÜÈ,D"%5Éc-L°;-þY'æ²V5‹Å*ßWËÝû:Z«f¾Ÿ‡ÈWýävì „„‹Ø¸ÙÙö>æz¸ûC,žÜá‘ë.¦-&œRŽjb%õ!`»·¢ñÓï¥Y_§)ï?F“f>@/¿û(½ûíDúß碿úáM»CîºÜ(öñ›ßÓæu»Œõ«o:ßH{›ÀLÈ'’eq ûï}¡ë ž·mYË ~5hâðα‹GßeŽoc-ï«u]ì^½tS¾ÍîÚêüA­U¯z¾å­à•:mÂÇFöD1³«2üˆAWöì£o©dÈ.ËÇ:¼´qÇ>…·ô˜ÇÑÑQÞVãrÅDàškÜ €ÈËÆ˜`L€ žÀ/¿üâv§±ÀFt~/ç=ÌãÏ.’]{ÿ1p¶mC‰X»ön¢¬,§Ã‚ma™Ib¢¬¿—~O'’Žy(U´M}{]m4°d•óÂþÂXf¦óxOe¤Û6/Ë]û6ÛnË/sâè÷Œ")i'é…ë…MK<è±ê%}±jÇL¾ÙcÙg¦:½˜'ùÄcY»ÇO¡œça£ÎãUå_û©°™i´DxèúÛ>ùr}úÕtc7/<õ¹‘ÖðòV¶hÅO*Y åCÃ^0ÊÿøÇ'rò=#Ã’ÀDx˜<Ðjk6-&Lâ—ŸíèʪÅÕRI^†r{÷vƒtçÎtäÈ‘ Á­O”.b³º³ÒQ¥MÚï/ÃL-£Ä~1¡Yú5¨jÊTСúþê'ÚíÒ«ÑN>™*7EŠÉÉ*Vޱ+æ1oÖÔyÆöî"„„µŸ½.èDÊÃ<-%þ²Ò(Š Ìv[Îèº.„™Z"»ˆAŒ†ïý;AÛ'ƒ„À®]»\Âûè]³†\зqš 0&À˜ðObíŠ+èøñãþÛy1·ìô´%"ÀáÀb×¥[=l ‹ßµo½ûÙóvÅd^¼Ó0åÍGD¸.e¾_ð=üÌziæC4hx—í}{\)ó0ÑÙõ#:Ñ5w´2þnÙ…îxø<éùúÁ/S¦¡âímö¢WÞ~Œ†Ž*ø3 K§ÜdD—)G•+*GçsãmƒqSÃsö…}®5 Ìœ*LƒÊ„8}ÛC½…ƒš{@•µ[Fˆx¹ˆo gØR!b®Zÿ·]Ñåí=¸Ý(¯„x•qý•#T’RROÐø)ÃŒu•Èá(àÍ|èÈn™U®l,µlÚQmözùäsCŒ²7 xˆð\fg£†¿dd¿(ÄlLç[;-Že}öÍëtãÈ®ôÅü7æû_0„Z4v½îQàÁ;'åàY¼lõoƺ5±î¿%áû´eB¸Ø˜Š†÷<¶Ýóä¥"TÉ)Sõ=¶ÉÏÑß˾“ýÔ7Ž™<„ž™r§lûáñž_b<ìpšÃõT©bU½N‡§š¤]²d !X½'kÞ¼9%'»ÿáòT××Ûê7®EG'ÊfámSÁ) éûzvÆýôø]¯È¬gšI¯‹É£ôaÝzY¤1Ìž¼VƒüäÇ¢ó.9‹n·Ä‘…P Ïàµÿn¥G†¾$CTŽ«H*•¢\ÂpnˆœøS? Ö}`}êø a`/¾ó0U¯U¸àå²ÿÕ»-­\ìð4=‘$KÖ¨SÅE@ôЄ±)B‹'ëλ¡^ÇVƒˆ;éD¼Üi²úOóþ¡³{·£ÆÍë¸9ˆê‡÷;ý½;Û‡Á­¶ïÒ\þ¨áGžÀ?}µˆ.à~¯ïâô£w¼ldÝýÈuFÚÛDâ±$úwá£øðGi•À5óÈ„Ûé¹Ñ³dÖû¯KwCY”<¬?½3Íña’8®™sÿçrýÁSy†Ò×,ÛLïýð¬Â!—¸â&ÈÏÓ¢.âOçgˆ½ÌÜzôè᱃ˆãŽø|:uòXŽ72&À˜`¾#ðÃ?PZZšÇÏ9çÂÏp´³:žG ~!m¯xÚX&ÖÒùááøôK·Ë¬ï¼OøkÖ¨=5iК’SOÒná)z$a¿Q¥ß9©cÛ^Æ:.þÖXÏÎÎ’"B((~ë8ÊÊÉ¢ß9î¥U>–™éòñr!d}ýÓ;taŸtÿ“ôb¢/fÂn\%%¸šŠyåå§¿ aö5Ú)U艟¯“¤%§&Ê8®‡µ¥~}®£†õ[&ðÚ¶kÜ&AS±t{™@œá‡ï~™¦¼å›'NNL_B• 8±Xçvç^›þïJ¡CDÉs ç¯/f­§¨ÒŽgÄWžüä§4æ¹›d×nZ$…ù 1qÔ n3Úwh‡xU÷!Žcâ¸Râ_AlùšßkîõZk;=»\$'RKÂ8ì÷Ÿ¦û-1k­u<­åú,mWþ±‘Ó¨÷Ù—Úm’yM¶¡Kλ‘~ù{Ž\þõûä²cÛÞT§FC:,^À›ׇ²!B+Ö*ï¥ÿÍ5®I¼T¹qdgñ\!ÞÄ“ñª˜\ZÏ;¾”á…'C¬aX”eâ6Oux[ð°Í<ý#OCYU7SRRCßÍùÓhû áy=L|^!âÚD\XìÙá~¸ÒæõŽ· (‹}²/Lfvì˜ãE‹§^bN6&À˜`L p† qzñ¹ÛëæÍ›Å0ÿ,w›C:ÿÂ>Î{ßþùÊã±th݃Þ{e‘©ÌŽ=è×…s…÷ெ†ˆqjq‘ߤs%þ¾ë§ßŸ¾ÈôÌôÛ¢yr’2%â^}ñPšðØ{nW¯pN÷Ë©{ç~F¼— jcxÃT%''Ûx†µ ‹­›u¡ODŒc1kQ'%ímÚ¶Â$âÆ !ûó7×§£H£œ·‰çµð}/Œý<ßj“w:bý¾ø+£ïùV###éÒ¾Nç/kÙ™Ž7²nô¨‘æDèj!·K—..oWžþyzòÉ'…뿘LLüXèÁ€½tT¤†%<@³³rdÿÜy2BŒÂÐ|„˜ûÞÏt,Þ<ŒǃIººöjK×Þ|¡mœ×Ûï»Z k[zÏó]cÑÀÛöÄAî¹7¤Ú6^¥$§Ó‹cgÓÝGd@íÙÓ¿¢]oÂÙ°i!T’/⮺¡¯i›·+±ËÓÉÄ#®¬»zCï¿–ÞxaŽÜ|ã—¹+&óë4¨AÛ6í•i»8:•DÈÓ3žÿL„†Ønj«YëtÿØ›¤×.¼Hq-ÅV0¿FˆÉÊ*W±¿©ÁDrŠ5<¬ q¾®è¬¦ ±mã™v÷bÏNyï1šðð›”t"Å(†ð §`µ ¯è!¼lÛÓÏN[4ÏNU®^Útã°Ë¨mgï(UO-?×£² ¯Ýkz³¬òõ%D[x+ï៾úGˆÍ®ÞÕz=Ý@„$ˆ¯búêÛìÒ1Ú¤dj;ÂdžÊ¢2ÚùRÛô¥òÒF^•ªöçqrg7>¡"ý¶Æ%Ð|‡³[д;'ÚÄgaM6««ïÚ”>t&”®[¼¼` ^Ö!›ø®ÀyÃïÑèÑ£åwz|6¢-[¶Pff&uìèú›Ä|˜`L€ øŠ@Ÿ>}\~{ï¼óNúè£äïþœοÑ-št¢ÑZÁ"²EXO‘bß°^ ºåºjµ@K},½â¾ƒ;¤ †¼îxÎãó„8xó"¾iZz’ÞŒLwëtá¯(Ö§[¯ª×«Ý„ðçµiޕ𗟵lÒ‘¿wºm1x;_ªM"f-TSˆêW]r»5Ûe½G׋\òÑ®U7ùg»Ñ’ QÞZãú­è®!Oy[Üërúd|ÞV‚‡9þ cð.l]oö‡öñ’EÑâM=k™æ"ïÃw¿dÍv»^AˆÿúKwuè'1å+yüœº«ÏùÅO ”øQ ©'ÏÉ“'ÓSO=%…ÜâÇgß ÃVÞ{c^F-Ú6²/èçÜ}»Ó8ñF6ð¶‹èŠAç¹Ý#„g !‡Å1óõÏžt[–70&P4i©§ ßvBldâíEkk”~6!ä¾ð RÈ èÎý¸3ÄÞþñÇ©I“&Ô¦M›Bïé™gž¡˜˜zôQ~Ã_hˆ\‘ øˆ<W®\I—\r‰xÑYÆG­†O3K—.¥'NPÿþÞ >ásäá{$JÈ ×P vgî`üºWLŠtù2ð!»b~Ï›ûÝôé7ŽQ…/ýï ž•ˆ¯«&K«^µ.½ý’ëÈ7¿w˜wÀJ 'ÑЇ^ꗯݑ·Ž/Bÿ#¼9„`ú! ÝY†¾p3þˆ7¬}YfåâFsç]|¶‘¶KD ÏOeÑeí‡y¨í¼dL h6­Þa4pµ‡ð+F!N%w£-‚²³^t ñæW­ZEß}÷¥í‹¬[·NnèÙ³ðC!í[vÍMOO§_~ùÅㄨ(ƒÀS§NumÀG9ðÌ~ñÅ}ÔZÁ›™2eŠ<Æ`ºW+øQ8kà…Îë‘#Gœ™"õÇÐsÏ=G0å‡ò BˆAdU;wî$\³¾4Lð„ý¤¦¦ú²YŸ´…cUÇŽåúõë ßC´_ý•V¬@ìÇà°ýû÷Ëë?'Çsè´àèmpöÏŠáöûœéºbh}ô™¸²ß-ø0¿â~ÛÞC ¯W†I¸ÄX%µê²¼ýŒ„ .»Óe;g0&à§0qË8#͉Ð"à3î¦ß»ví¢¦M›Ò¡C‡¨víÚnJ.;„\ ¿*%B+dÓb1,{ØCHÛS^žóÇsÕ’MÔ÷2÷bî? V5ç— 0ÿxëå¹²q„_hÖŠã©ú4·hßÿ½Üe „\ˆ>Ë–-“Â×µ×^k{¨ð´ƒÀéO‘a$ì ‚cùòå©J•*v› ”‡{0xSV«VÍ¥BàÞûƒ7u¨„,œ×ŒŒ ºúê«ÃÙ´ieggË™çëÕ«gäƒ?¼N5jdäé LTQ,îaõ~á8ÕçEÏWiô÷Ž;î1ñò½UWUBn9mÚ4yNí:ŽðjW^é}ˆ&»6Š;×0^H 6«·öÛo¿Ñ¾}ûäܾøîðv¿áT.žýÁÃø'¼ref¥SjZÅÆTòÇn<¶‰ø¦åD,ÕS§R(9%‘®¹£55mÔŽj‰IªW­M)¢_¿°}·ã¥/kݬ+]Æ“-yäÊ™€¯äååÒ®}›dsçv¿²Ä½ôòÇ`h'äîCåÇyüô{hìHG\ÄÀ=«wÛ€Ÿï>v¡ù_,”ûý`Æ·”ƒÇ@IDATpìwñYT­FœŒß‰ ˜híëO§•‹hä]?ô,ؘðøƒ "æ®ã%Ë­#¯òøÉ@(i?ùq… ˆøõU«V•¡'ò+ˆíuêÔ¡«®ºŠjÕªå·Ý]ýõ"Οë ͳgÏ–û7®èÞo¿íˆ…o×Öí·ßN‰‰‰a!âz:IC‡%Ì<O·9sæÐž={èÁ¤Ê•]'†yã ÇÌÜvìôv†ç1 ×(†‚ãþÞ¨;vì Å‹KqzÒ¤I4räHªQÃs?Ð}ô÷þÔ=½:7ðÐ…÷0¼¯±Äù<çÇðO÷Åí¿óÎ;tüøq5jU¬h“ßûå6Å”Ú$º%…G1¡W™¨²”•AOi~ÀÜ?}}9~öF9I:°SLº…?«¡ìu1àAë&^gLÀOþZêp¸@ówÞ4ÆO{áfA $…ÜPøq®S¿:•-M§2iÞ¿‹[«^5êÑ·#-ûËñÖsþÜ…„?w†I½¦~0:hÀÝõ“ó™@(øòÃF÷=yÉ…8ÁB„À¿ÿþ+{zÅŽY¨ƒ¡Û¸_èܹ³_»ÒºµýÌÁ~Ý©Öxƒ án±±±ÒK1SMÖ áò¬³Î’?ÿü3-_¾œfΜIO?ýt‰¦àÁŽÉ«£ûµ×^“‚n( ¹áx­†Â1©¡ÐW_÷qî[k)þØ~ªYÍ9jÁ×ûȯ½R¥"è¥ÿ9Fž-[ý-X8ÞE9¹ÙS®"µiÑ•^q7U‹óß ÖüúÈÛ™@I%pAïk¨m˳¨z•:¬ù„øEP(!^ê¦ÓÓñãÍ:nÊ܆ºtÈX(ý8w M~âJLHr‡ÀïùÃD7ÝÕŸ0”{Óš¶û‹,I=}3µëÒÜv;g2&à;›×í’Ý22´‡ŒúŽHè¶ /‹BwÛ¶mrò3üî¶oßžúõëç±¹… / Õ0bÜ+|òÉ'T½zuºôRÇ$,ªxïΛ7OzÛYÃÄÇÇË‘—PT/6ôá³Ï>£úõëӹ瞫v/cbnß¾nºé&™÷ÁHÏ9iðpU÷8IIIôá‡Ê¡ù;v¤ .¸ÀhC%¾úê+y/Ï_Ø¢E‹d[jû·ß~+“pÙe—©lBÛˆGœ ó0”~РA&ï^„ÐcŪ¶¢¢¢ŒÉ™0ûèѣƱ;8“øë¯¿híÚµb$@ž Ï0pà@Û0è  X¾ûî»bxì)â¶Ûn³q®~øáJNNÃiËɺvá¬}*Ì:BüùçŸrØb¾þþûï¿ “y]~ùå„P ðÔU¦Øá¾ÛuƒG3®7¶ãZ·›ð×l¥J•袋.¢%K–È*T »îºKoÎ'i|f³÷ÊðP½ð Míâs9þ|Âu kܸ±ô>W×®©°›ÄäEjUž]=ºô#ü±1&<ŠóEOðPýžx-äâf¯y󿄇/nHp'×ÕÍ òð ƒÉ”áÆÊFM/½ô’Z•7ÿÞNî€çP±íÑ{?<[ìÝ­PIÌ>q¨Ñ0ÌÉÎq|½>ýF]N0&P4¯Ï[´¸vÐçÅ×_Ý$Fb¨7D¥ÇÜ–¿1L\q0±ö­B.< !†@,xòÉ'MmBœB¼×‚R¦´ÄÆÅPuôCr1D÷îÝr?,Õ½†¶Oœ8‘pŸqx•ýóÏ?rhÿ½÷Þ«²äâúª„\|@•ADU¦„ÜW^yEŠŸÈG]2CŸ}öY¹o•0ˆÂê¾ ëz[ýû÷GÁ±c­áÇfµY³fÉ¡ú²¯Ûš5käêÒ¥K @)ôk̘1¦—ï{Àý‡ð1yÆŒ¤†É«ú¾Zâ>ç ý Y×yè÷ˆÑ ‚š2½¬.äB¨G»0`ˆ«üÅ_È4†Äëqb?®wô†þ ¤ˆ¯Ä9}÷Üs½úê«„kLrñ™úè£dQ‰¸~0Ñ þ‚"?m”ÇÄqè7 ÷ôø¬àÇËœkõ9VB1îíuõðÙÂv†~PŸMì}V×:Ži\¯ø>;v¬ËwÂj F²2|Æðüï‚óÏ?_e»,Õµs­;œ /ðÆ5Ãv¼@•ºuëÒ°aÃd>bZë×›~mBð¶r”• ø>¯Ö‰)q|ønÁÜ&7ß|³©Eô}ÅñÄÄÄÈk¡;àí­ Ÿ—éÓ§ËU®m|£ž'±\Õ–¥~ÍKŸ¸L€ 0&À|IÀk%7˜•ù±Ç“e¸IÀÛý&C:<¨¨I)pcƒ·ø¸ÙÂÍ_ÜDéâ.*¼5þqö–”ûr¸9g×=Þ˜(Éà­…ßÚ‡~Xzx…á}:`À<Êž›º!^.>ˆ=x¬ Â0 ÷Ø—¢†ÒûÛ ÀtêÔɘTëý÷ß—¢ïË/¿,ûï\xãÞåù矗ޟðÒó4qD`Ø3Ï<#—vÂ&ެit!&(&¿z뭷衇’u}ôQ¹ôÔ–,`ù\•ˆ ¯»fÍš%p†s± Þ{Vƒˆ ‘K ßß|ó!)a×D\x[ClTñ8P†ó¶ð¦ÆKkŒ\¬Ã<±ƒ/D\«ð/c¼¸€DÝpß‹?LÄ…x½»ü!âbŸÊYÞÛÊpn!âB@Çõ¡Dx(O:UzÁCˆõdøÌÕ¬Y“lúœ¡]ˆÄ U,bL8of|f­“áûÖ­[7O»+ð6ýxuoXx‘ÂCV÷8ýüóÏiË–-R€îÕ«—±/\ßqñ\2|øp#ÇVwB.¼¼qmc_Ö—6¸Ö ââE.lªkÎ,ؼ„qmªb¾Ž‘ odˆ¸¸ðBFyÁã¾»ð’‚|=äqãEÄXý»t±ëJÄ}ê©§Œk×¾óBÍôß”Pë;÷— 0&À˜@~"ò+ ¶ã†". à ñV7Zº§¶á†Cž`¸ù‚· lĈr‰›m˜~u÷ÝwËðyS‡Ë0&À˜ð5p~P„‰aºÊ”(†—´v3üæ«áê D¼ætƒG c š(Sžna-[¶4D\ìï–[n‘»Eÿ â¨0x­ŽEy(¥jum)áL÷æ-ì> D Üè".òÔ½¸ãžÊj×\s!âbÖaðöS!lÝ0Ä>”L]—VïE ‡HQÏÎzA/ýsbWÖ×yêÜâE€q±¼øÀõ ÏZˆvž Â3ÂAX_–\rÉ%²šòÎÆ ªÀð¢A7|FÔu >Ëúö¦qýO›6MVןçë¾” ºqãFÓ.Õ÷Š.⢄ikžªˆ6 ÞÃcÕ*ââeQ|ï«}ªzê{/aaðÒ†Á+_‰¸XÇõ FMè^õè7L™†u|?Ø™þbßëùyxÛµQœyvßkÅÙÞ7`L€ 0_ðZȵz$  oÁuS^$*O½IWx ÀÍ1n<ôá<ª|~KþqÎogL€ 0&PxÑÑц7–j¿Û0ˆ7ŠtƒÌ.¦¨ò(TBÊ)!ôÀªI Âpx˜UX“™~øO‰³ªi]À€7±nj]…‘зù2 ¸¢šöÑÊÎpŽaÖs‰<ÄÎÏ BÐÂP~ kUÃ=¥;ïê²eËÚŽ»wïÞ¶Û‘©Â@؉kJ¨ƒÇiaL‰Òº&>ês¬_û+W®”»°zéf¿“'O&ü!¬ FÿÁà%­¼Â=µ©âÝÂËÜjð:öÖð<óå—_Îû#<âR #`JØÖ ¨ïG=ÏŸi% wíÚÕe7x™†s¦?/©r8>„}pgꚇ‡q(Ž=œ_´†ò¹á¾3&À˜€oxZÁº;õ êÁZF_×=LàÅ Oܰáï‰'ž7pzywiýÆÄ]ÎgL€ 0&ào%íAùÒ ñO÷¼ýé§Ÿ$jw‚¼6!¨‡kˆ0ˆ ð S¢#ò”È ¸â2ˆ2¬­¦Ä»mÖ²Þ¬C]±b…ôžôõ½u´”µ?¥qN {,`X¬ï½÷žŒ%ŒÉœp/ˆXȺnÝo0­«ø°-UøoúWÜŸ{„P€yê³î=íî˜pîáµ G Õ¦»²˜çâwÞ‘ñRï¸ãY ž«0o„YÐÈ ®øŽÁ÷&×BŒV«¡Ï˜„ ž³i¡›þRÇc׆^GOcB9&|³;Çjˆ›­ÇÎÖÛD òûÜâE>÷¾OñÙDÜï·ß~[†ºëÛ·¯Ë ŒÄ@=bô$®/xØÃqG ù8>_ìCýÖø¢-nƒ 0&À˜@0(´«<¬Ã‹Ü¤îñ€5Ĵ†ì!îb<ÙyXÛÓoÔ¬Ûx 0&À˜@ Ø=ìjßű%èB~“äPd=_ï>ˆ†»„I¦àù¦¼[‰± /TŒàÁK_Î@o#ÜÒjÒ&$ð:D\M\OsæÌ È¡b˜8¬(×0DŒ¬BìTˆzðÐÄ&Ò'ˆ Èb'J˜¯W¯ž«ìšPÞžvÛ‘§<`õê>øþûïwÛ…ü&Ó‚ˆ‰®ÐޱeQ/iTÜ[½qÌwkEÅ@F=›JÝ}îõúù¥Ý…:ÐëaŸè3¾‡Àñ_ñ=‚ïxšê¦¾«ÔRßæ.p˜Ô“¶mÛÖå™D}Vð}¦ÇâÕÛ«T©’¾ê—´:ÿž·+Ó½{wË1‚!„Cÿûï¿MÛáß×_ýµ<ß〸èvžÈžúÀÛ˜`L€ 0ÿð:´‚çë†I/`Ö›\Lœ ›zíbäêÛÆ$0ÜXxc¸9Q7SÞ”ç2L€ 0&À˜@Ñ ¨aøjX>ZܰaƒlØÓhKTyâBD‚Pƒ— "¯šÙ‚C8¼Ý >ACx xƵjÕJÆ7õÕq«!ÒîÚÃ$P0_®3€¸œ;w–m~öÙgrYØÿ”§laë{[OÝKÂ{"¦Ý_~½ÝWaËÁ ¦&­BZ…?°ë¯ÊC9O†¡ó¸Ÿ†3ÅwÞ)'ïB|]kýôS)â"Ž/âûBLEŸU8 }÷*ޮй­os—†°¯Äqxš[E`O!gëÒ¢¶»þ©|ìC]»*ϺÔGAêÛPç“™!9¾‡ô0ª,„rÄVñv!ꆒáÚV/jB©ßÜW&À˜`ÞðZÈÅP›sÎ9GÆC6l˜œ1÷ì³Ï6 ¯ÄNqS€LxW˜7*Nnqã‡ÄxR“hÀ#׳{ËìM=.Ø`LÀ—ò{˜öå¾Ù~›ÕÐdµ_!ôdõ€¡Ðž ‚îàíÃýL .©0þ|™×ºuk¹ õÿÜ]jØ{A†¤T B|Q˜¨ !÷Rxï !W§«®ºJ&݉Hª\~K‹Ô[ïF%°¹íô0 Ö}ãZ>yò¤äaÝVÜëðŒ…ç4Î‘î ‰ ‡aê³W˜~*ÆÖ‘p*öª]›¸Ÿ‡aB¯U«VÉ4„Ô@™ò¶ŠÍê“]?0ZÀÎÜåÃCþ¢‹.’UðÜ£^¼ÀT?ômîÒêû “CûÒÔ †%K–¸4»oß>™§{q»jD%¼oÝöƒð7¡öìjýuÇŸó™`L€ ¸#¯«pãÌp:{ölB,<õ@¦7>oÞ<9L7|O>ù$µhÑÂô@ˆx¸FÙ¸qc C{p3êíÃÿ8ë´9͘`LÀ· @N™2EþV£eüöÏš5KîD‰uXQ1#!~èâ®,hù^§0Äh„é¿ù¢˜®ˆé¨„YÈËÿ Œbr'럗ÕýVL1H{ÿWMÀ„ø¤·a8v0wgÊ ÷O0=®°]+®¸Bfƒ ¨{'ˆP¯¿þºÜvÓM7ÙUõ*á~øáùBоÞŽû>Ýkêe€¾M¥Râ"®55áµUÖºÄý$ ý%„Y:ùðš„)ñQ ]ÈSCäáxpüøqdIÃyAyÄþ-ˆáœâX¿ÿþ{¯«AˆÄyÁ®?úow|Gej áI`ˆÓºtéRÒ=˜qlð\ÍÏàˆC•aBB £wgølâšFñ¢G½,Ð˃›:ßz¾/Òݺu“ͨ˜ÜXÁ5c\Õ¾àåËøøxyB´Ä1¾ñƪ˜Ë×ÂÁáu–ý¢®µéÓ§ßhŸ_|ÎÔ5¦UÞÂøÞÃg/ôó¥ÊÙ-áðbýNSŸ{åü²`ÁcTÚ@¼cxà $—øïÍ7ß$ˆ¾ê{Ÿum«>âû£-ÁJ® ¼˜)H¬aU·¸—î^¤w¿xÿL€ 0&À|A ß¹ˆU§~ø×Îà ¹»8o¸aÐg¯ö¦=½Œê‹žÇi&À˜`&®Š˜ÜfáÂ…4wî\RxʪØ ^æb"¤ü õ”˜¡< Uxó*/· /¼Pe组°ƒà?«Ýwß}2v¯5?PëqqSƒÆGrkÔ¨!…MÄÊU¡ ÷H;vìPYÆ:î«ôs‚¶Ü<é0êÝwß•#¤0JJ®[´§< U~A–˜ð/â­Âò•homOï­Ûp_¸uëV#ÂQ~…ª0BwàÚ‚Ã&Ð…aä˜:>Ÿp@€Ð‹?˜b‡ë õÖC ܲÀ™ÿà¼PS­ðbÌÏ”£¼Cqžtƒ` !ò†nгe×=•áå „X]ŒEOœUc!÷õ^•!<Æš5kԪ˲ÿþÃóÎ;Ïe»5K"d\pÁòzÛµk—©ÏmÚ´1²è̓„Iœ_x+Ãõã7ªUÛåÝwß-ãñBXÇœ JÀ½õÖ[e[;§NêR÷²Ë.3åõìÙ“bò„ ä6ˆ°øü»3õ½æîe®]„×€C ®gxnëqqí ô„.¾Â»¢/þtÃ5¦<ßá¹ne…²èÏ=÷Ü£W ú4?+ý)â2&À˜@ ”?v§‹Ø†¬Ž·»ø±‡G®7u…Ý'â5M›6Íð)l;\ 0&À˜@a@¬€‘ba¸nàá b†¯X±BÁ‡X¦†óªã…Q^ycð”„÷,:]lÂ~1©<Õ°­ ¹>3»µ*†ÿÁƒ¥X¡†à£¼Ìà‰‰ž¬¯P¿šMmW|[Rgòè3DL«eddHÏfxC°Öë¡ïð ƒ'ˆÈúòÚÕÛƒHá ç•J,„ç âª—G}ÆqB´Ãu Amãø¬†6ÐO}rZUûDßôm𾄨†?\3¡UŸT=ì_ XJPÞ8W8úµ Ú†hA× B JTSí@tžp ؼCÁF©X¶våTžõúÅqá»ÅS8UýÀ¾Ö®]+Ï >;è·~¡,£¯¸V°Äqà\ÙyôƒñêÕ«%#ÄùÅÄ‚úõÏþ¬/pMâšÂ¹¶^×è3¼ãñ} Áb2ö­¯©þ‚¾ƒP¾C‡rÿj›Ý߃ꅀÝvõ½†m(‹s‹Ø7Bpີ~>Pmâ3†Ï®“Fb¢Iëõk |Ï£ðò#.ìÚC›ÁjñáqŒë‹ 0&À˜@89!wôèÑò&ÑÓMN8ž(>&&À˜á*ä„.„W ×…øï/6&`GbʲeËèÁ4yÚ• õ< ›Ç°}ˆzú°öP?.½ÿÊá°U…ïз#Œ„ã±cÇÚŠšzYN3¸þú륧: ¹þ"Ìí2&À˜@qÈ7´‚·Ä›`Ìb ¯Þ³1&À˜(n¡æ¥äK^j¨þСC}Ù,·f âÂûOæf‡hNx1†«ˆ‹ýæ›oäñªÉ ƒ x†BĽýöÛYÄÕÁpºX”äßçbÎ;eL€ 0€ð™‹^Ÿ{î¹~ï<„\þqö;fÞ`L€ 0·0Û=†ðÚ %v[‰7”(ç€X ùÅ# (q¡&¡ —cÒan÷à*®ª¾“iÁsk°1â$ÀN?ÅIŸ÷͘` àS!7æç@Pæ}0&À˜@~JòKÅ>}ú䇇·—pˆŠ¿’b£F ëCE\YL’¦Ox¨ð€ôUN3b#ÀN?ņžw̘`"ÀBn€@ón˜`L€ 0&À˜@(ÀägÝ»wÅ®sŸKvú)a'œ— 0&P ¸N[äøÇ9ÈOw 0&PB”dÜrŠù0™`L  ðïsž4î2`L€ xM $…\þqöúürA&À˜ðþ-òXn– 0&À˜@! °ÓO!Áq5&À˜!)ä† ]î(`L€ 0&À˜`L€ „ÇÈ fÞ `L€ #r‹>ïš 0&ÀB—{ä†î¹ãž3&À˜`L€ 0&ÀB‘ ¹¡xÖ¸ÏL€ 0&À˜`L€ 0&`"À¹&¼Â˜`aH $…\ö‚ Ã+‘‰ 0&bø·(ÄNw— 0&Àž ¹aŠù™`%ž@H ¹%þ¬1&À˜(v,äû)à0&À˜0àÉÎL8x… 0&ÀÂ@éP;&þqµ3Æý v¥Æ."ÊÈ önrÿ @`ç“ݨIÕr¨ÁE™`L€ 0&øEkxœG> &À˜°'ÀB®=Îe%‡À·Z\tÉ9æ0=ÒԜӔ‘’E›Ž¦³€sÌŠ€Ì»`L€ 0Ÿ(3n e§fû¤­ n¤Ápª±|yPw‘;ǘ`L (BRÈå‡ç¢œr®Ëì ÌÐÜ~ç† ÿâÓiÌü]!ÓßPï(ÿ…úäþ3&ÀJ!ââtFÇ–œ“ÊGʘ`%’@H ¹%òLñA3&À˜`L€ 0&ÀŠ@àÛ;Û¡vpW½uÞvJJÊ$~ÑÜç‰{ǘ`E#À“×fL€ 0J€Kè‰çÃfL€ 0 &À¿ÏA}z¸sL€ 0&PD,ä WgL€ 0&À˜`L€ 0&À˜`L€ ø›@H ¹ü–Õß—·Ï˜`ùàߢüñv&À˜`'À¿ÏgÎ{dL€ 0ÀI!7pxxOL€ 0&ÀÌNŸ>mÎà5&À˜`L€ 0&À˜` ÀBn ó.˜`L ü°ÇOøS>"&À˜}üûúç€ 0&ÀÜ9!×ý¡ð&À˜`#ÀŠcÍ{bL€ 0&À˜`L€ 0¢róòò(""äºÍ×`L€ 0&À˜`L€ ø™¿hõ3`nž 0&ÀŠ•@È)¢›°X¯Þ9`L€ œ!ÀŠ|)0&À˜`L€ 0&À˜@ „œH8¼/&À˜`L€ 0&À˜ü¢5tÎ÷” 0&À N ä„\„V`cL€ 0&PÜøA±¸ÏïŸ 0&À˜`L€ 0&P²„œ‹Ð üð\².R>Z&À˜`L€ 0&À˜€7øYÑJ\† 0&ÀB•@È ¹¡ šû͘`áE€Ãë|òÑ0&À˜`L€ 0&À‚@È ¹<ÙY°_RÜ?&À˜@É ÀBnÉ8Ï|”L€ 0&Zø÷9´Î÷– 0&À F ä„\ÄÈåç‚d.͘`L€ 0&À˜`L€ 0&À˜@h9!7´qsï™`L \ðKÅp9“|L€ 0&Nø÷9œÎ& `L€ X „œË¡¬§×™`#p0)“–îM.X%.͘`L€ 0 ÀBnœ$î"`L€ š ¹…FÇ™`¡G =;êMXF½^]C~ÝzD=æÅ :Ü&À˜`L€ 0&À˜@ ’B.?<—€+“‘ 0ŸHÏÊ¥˜'ù¼]n 0&À˜`ÁB€ŸƒåLp?˜`LÀBRÈõn“ 0&ÎN!fŒqóNSÇúÂùPvlü 0Ô¼#&À˜`L€ 0&À˜J‡Ž‘jgŒû˘@qˆOÎ’áÐ[ΪIM«–£uûS(M„YHLÏ.îî…Üþsss‰ÊT ´Üærg;̘`áN€_´†ûæãcL€ ”l,ä–ìóÏGϘ@˜¸Tû™¥ò(‡t­A×u¬Nk¦ÊõÿØOøc+û¾§a«Iü-)De®Â˜`L ÀN‹‚R¥¼SÞ`L€ 0&àk!)äò[V__Ü`áHàÄ©ªò´Ch„'.D\X纱te»j´ò°CÐ Çc÷ç1áY8~÷.*[£&ÅÅÆøsWÜ6`L€ 0"8|<£D‰¸ü¬XäK†`L€ 0 &’Bnóä®1&À‚‚À“™Tâ2Ù]ÄUÖ½ S+¼,¼¼<ºöê‡éþÇGSŸ> T— 3&À˜4«go ô.yL€ 0&À˜€Ÿ°ë'°Ülð¸oð$JK9ettǪ̀NýÆ:'˜@¨HHÍ6DÜ›5OÜP=ž`í7{üë™á~1&À˜@I&À¿Ï%ùìó±3&ÀŸ@D¨"<¡Ø˜€/df˜'yÚµå€/šå6˜@±¸cÎVÙ‡›»Ö¤AgÂ){§Â¨<éfL>&À˜`L€ 0&À˜@9!Ðü–5„®° íjNv.ådç˜z·aõvÓ:¯0$ â·ÂêU+Gƒ:9bâ:rø&À˜`L€ „?~V ÿsÌGȘ(ÉBRÈ-É'ŒÝ7RSÒ]ÚÈB® ÎAg&¤>pŠ^ú›½ÌýyùAÑŸt¹m&À˜`L€ 0&À˜°`!×J„×K“Ç“]Ž3=MÌèËÆ€ÀwÃÚEEТ'YÌ ƒóɇÀ˜`L€ xO€_´zÏŠK2&À˜@è`!7ôÎ÷Ø–ý½Þ­pL x d<Û[vŽÅÜà=GÜ3&À˜`L€ 0&À˜`!Pº …ƒ¡,ÇÈ †³ú}XòçZÛƒàëË g† èÒ”þü9Tþ©ÅÒ3‡ðØyõŒ#ydþ.Ú‘”e¬s¢€.C/l!Ú³¥€¹8`L€ 0&àOì‘ëOºÜ6`L€ 7r‹ï?ô ¤$¥ÙD‘“T½Vœí6Îd¡F œ¯pú…s¨ÔX³˜»æ@*íˆwjÇW¬ý-[ó%æ˜'M,Ö>ñΙ`L€ ¸%€ÙPÏÒw[†70&À˜`ÁN ä„Ü`Êý mñXÈ íSȽ·!pzRo*5z¡á™Û¯ieYê™KÒÓ7²©ÁYžäñ6**оþúkºæšk<åmL€ 0&À‚‚@©GþŠ~¢ì‘ʼ&À˜(.!#7//øÇ¹¸.—ðØoRbŠÛ‰?xÌí6ÞÀB™@Ú¤>²ûˆ™;ué¡P>î;`L€ 0&ÀÜàgE·hx`L€ „rÔ …ÀÞ]‡ÝVß·;Þí6ÞÀB™@yf!óÅs‰ÊDRr2ÇÆõŹäE_Pä6˜`L€ 0&À˜`LÀ[!'äz{`\Ž ¸#°gÇAw›hßN÷"¯ÛJ¼ „2‘¥èôdá™[6Rö¸BYŽ®"§Ž»É˜*¦_ê‰L}’yÅ<|¦q‚~ü±ó¤©o¼R²ð‹Ö’u¾ùh™ð þI¯­¹Vþ½±voåVüB äžâZ …À– »ÝVßϹnÙð†ð!púL˜…ð9¢â9~P,î¼W&î<Å2С:}y[›à@„£ärr#÷ò‚°oÁqÒ¸L€  µG¾£5 ßQÞéÿ!÷éçÐ Á|9…Fß¶ÿ·×mGùúr‹†70&`!ÀB®¯2&àw1ex0ß!óBžÿ>‡ü),ðÏ¡÷Œã\sô[BîƒïÐñSŽgØ)i÷É©Y\/£>'ÜÈÉË¢_÷¾â¾€¶%Bˆã­«œO4)rKi[8É‚‹@È ¹ÀÇ?ÎÁu…Zor²sC­ËÜ_&À˜`L $!pxôDI:á|¬L€ 0&๧³½n6Ox¡n:¾@þ iý*UÞºlL  „Ük}ö˜ ÆË(´ûd}1ÀBohŸOî=ëwG öËûaL€ ØHÏÊ¥SÙÞ… ËR2s);×»òh;Ë˲ªoˆnœ‘+† «÷K´Ÿ&þ¼µTÑ÷oö¶A.Vø÷9¬N' ð)ÈRQÔ´rùW+¦Å•­+|o]e±O6?@§r’}ºonŒ øŠ@ÈyärŒ\_ú’ÙÎîmæ‰Î*ÅÅRlÅ:¸÷ˆ¡Zwlb¬s‚ 0&`G€í¨p`&pÏWÛiæR1Y«6ÅÄ–3¯mF#zÖqéJ®(SyâRJMJJ`Š }ŸMõãʺ”Ÿ»î(ÝðÉ¢3±g):’²&öv)g͸ñãÍôùÚ£Ž}ˆÑ©=U¤¥÷u¶£ú“ÿ¥‰ξG”¢~ÍãhÁÝí]Ê"£ÛkkhÅÞÑî™Î‹þ¤ïIåË8&ñ´­$2>ÿ/íKÈ GΫK/_ÙÔ]1ÎgL€ 00'Cý?îr”ÛDÝ_,!>“ ïð‰KYÎ`ÅM ä„\ã‡çâ¾lBwÿk–ÿgê|».Í)²t„IÈݺi ¹&J¼Â˜`L€ #vSWÒ¦ƒiÔ¶n,M¸¸!¥däÐí_l§‘ó¶KoØÑç;‡…b"°¨1ÿHQö®žµi@ûj´î`*=175xv9íz²;5®êsßù÷0Ýõù6yØ÷ô®CQ‘4}á*ó„hÃ՞´œâ…8;¼WªSš&ý¾Ÿ–íN¦V/¯¤-žeÔ,õ¸h''†v«E—´Š£ø¤,zè‡]ôÛÖDzæ×½4Nn‘O/¦¼´ªV9š?§.mŒO£V¡˜±‹éôKçêEMéÎÓWÓ¾c§è¼æ•YÄ5‘ ß~V ßsËGæ™@N^&-?<‡¤nÞ¤Iâ+6›ÊD–£ÊÑu©GíÁT£¼ý‹¬¥‡>¦ÄŒýU…úÖ.woÔß÷Í ã{(//‡*”©A}ê ¥Zå[Øvâ¯ýoQZÎ ª]zÖ¹Y–A›î“’³Ž ¯×RÒûµ_ƒûå~ôF¶&þM»’þ¥<Ê¥jeRwÑW;;™yˆ–ˆ¾â-au1AÙÙµÙ+T^‹*çRl´˜PtÛ“Fý¬Üt:œº™jǶ6òôDJÖ1úçà{‚Ñ^ÊÌI¬Ë Öu¨gí!T½|þŽa˜„mãñ_(5û¸x›C±QÕ¨}ÕK©UÕ¾únLiœ—e‡?¥Ãi›)#G¼Ø\Ë–Ž¥z±í÷!QÎTÞº‚˜Á¿ï{ŽœÚAY9éT1ºõ­w·¸6šY‹z\ß|üÚ úžžH¹âú(U‘êWèH½jßB‘Q.uO‹söç¾™t*7YœãFâß(Ëü°ë9ŠOÛFåÄÄsW5}Z\gÕ]êr†+r9´‚ëIäï lÛdžè¬MǦ”C Ye4²sË~#Í &À˜€;ü èŽ ç3&à `µXáªìÇ͉RÄ…@ù׈Ž*›níZ‹"_H Qô±¾õ…„cÓÅïl"îâû;Q¯F•dæ¥-«ÐCçÖ£²BTíÿþFÚüˆShU"îŽ1ݨi5ǃᴫ›R-!ú9!¼hÝX|Z6¥NîC1g¼d>·>UýßbÚz8’„Ð\©¬ãñcöÀæt‡quÖ£6ÅŽYD㘅Üq¿ì•"î-âè÷áŒ*ïÞÐ’àýëÎnŸ³•ÖîK¡¾‚ÑŸ#wå9Ÿ 0&Š Î}´ù^JÍJpé>ݤÌxÚ›¼Šª–kH7µšæRfå‘/¼Þuobðg„ ØtK‚Ý[§f•{ÑeÓ7Éô†„Ÿår-¢ñMôý®IbŸ«Må |¾»ñN:§îPêTã*cÛßÞ¡ÌÜT£~ëªý¨¢ 7wëhQ.M–Ûyru©y !T‚¯¬NLk!À6¥cé;&ÚóÝÑî]c]%¾Ü>–¥šÄ ²:X¯¬ÖÓUqÓ25;Þßx·6ÕÐÇæÔ¬ãBÔÜJx‹FtüÌT+ßî|†ö%¯uÉOçæø©}´îØ|j#Ø]Øà^—2ÈØvbý²gŠi®Ï·>FM*w7å»[IÊ:BŸüw¿¨cŽ=œžsRöaíÑïé‚ú÷PÛj™šÈÉ˱‡‘‰k¤[íèÍuƒÅˆLYý8,Ž…\6·+®Á@Ü Ž ,äÇyÕ^;rÂÔõº jPZULy'9Ž ¯0&`"À¿C&¼Â˜€?ˆØ²ž\äò÷ýǽÝ.B*Àtën´­†$­9o‡ý¹MÜ !X‰¸*?ZŒLŠŽ¢-‡ÇÈ_+¹üæ¶6r©þ‹;÷sžÚö¿Ÿ÷ÝxjY»<‹¸ J YFD„Ü#n 93|˜þ"°þض"®uÇOí%xßz²ïwNpqõò;N.¡=I«ô,—ô;nwqõBðb…·®²–UÎSI¹\¥ Ëj?%â"!|)âªý\ÖèQ•”K»8¹ü7ÂEÄ-mªau¶à`µ\áyûÞÆ»\D\½\¶=­B錵ƒlE\½Òÿÿàåj5ˆÇVW/³ëär}Õ6 å70õ-¢T$E”2û‡þ±ÿ :zÊ)†Û56cÍ@CÄUÛOŸö.n¿*_’—fâ!@‚ Cà$qO$$™zWG¹Vá6)ÑùÐc*Ì+L€ 0{äj08ɘ€o qõ•Ë»´Ù·ie#ïX’Ë婟wy*‘œ™#“‹÷$S—zT6•+AvåsÌNA´â€ã^èÁ³ku½Mè^êÎÃçÕ£Ÿ…ñrá{KW×6ÞwždJ¶ñB–mˆ ° g¼y劇ÿ¦/Ûº£?ȶõzW7'Ce Å/{^¡)ëÍ»“VÈp ±QU¼yƒØjŠ0¥E„{_uißZëèuÜj*U+ç¸W'2b +ûrÛXÙqŽZuYZ½‘Qáؼ#’B.?<{wr¹”+57†ÚU¦4U­î|(B~JrºÚÌK&À˜`L€ ž@éR2ä»Ëû™3MZàðVµ+Û­¾ã¡±sa§Ä$gnË‹IÒ”­>àðÈí"bïúÂ*G;9v$œ2šK!šMYII"6®'ËÈ.¸‡âÿBÈÝt(Ò³ró ÍÓþy[èàgÅÐ;gÜc2B †E‡êý©fL3ª-BX­cõËieü<ÂÐw˜€f­ƒ°ƒ[½"¢¯:¼Û[UéKoo¸ÕT ñjÖY‡ø×‹m+BL0ŠŸÌ8d¤Sµ|éÊZó¤XZ1Úùâï—ÝSòHX‡î›6a¥tDÏWç„Б‘Ž¡ Bõ -_«ŽßOx¦mû6½¶æZU„þ>ð¶ˆýú?c=áÔ#ÄNåÍ Îø[ÿ…†UÁ…g«¤\ÞÞv–)ÁµÍž¡¹Û§#"Ö¬²¥‡>¡‹> V)%SL@ªÙÐv¼¡·EÜ9ôõާ…¼A+aNžÈ¬—zäoYFý¥ Ì*/¿eý¸hzkPs.&~‹!*r^þˆ²3CÜ<úõ>"ø;Î= ±"®mjre‹ÕQ^¾”ÞwÒq­æ ÓOÜ—¦çú76·Žn2m+¹B\¶Š¦/þu@ëÞÈ!än?&8nn¢ȳx}”-WƠХ{k“û/ ¹N0&À˜€=¼ó 9J¥''SùŠŽáãöµ8— xAÀËx®9zQ¿·ÖÓïÂãuÒ‚½FÃ1Qôp_³÷T}÷6~|O:ûõµô«ðÊÅŸ²ÚUËÒ´+›¨U¹œu] :*&GûvS=ÿÛ>™×ºN ­Õ•Z‹IÊ’-b«Ts&õ¡/¯¤×þq¸~ï¢ð[¢=ex7<Ò•Ú¿±–^ø}ŸüS¬Óûƒ[Ò¢ÝÉôŽé`µÑ÷vÓVÓî£éô¼¨«%ú½îáu{ÆÊœ 1%ƒ;2cÊDÒÇCZÑÍŸl¡:“ÿ¥Ó“ÏQÅyɘðHàŸe?ºlßã'\— Ã*âÞÚæ 1ù”cÔšùyÏËd( м,º?Õ¦0.ºàqj¤¬7í¶zyóïDQ€ƒ×ï±S»Muz׹ʹîËœÿX&kWí2ce"ÅÈͪ—oJ˜hÌ[«Z®!JuŽ,9–¾›êUhï±zLé8Óöä¬#¦uoVâÊÕ£CiÎýƧn£ŠUœ±é½i£B™jÂCÚyßpKëâ}l„7U¹Œ°ë¨ÜdðÈÎÎ!kXŽ2ÑN!·ýY· ô~çfÇBð ÷ˆ 0`!`}C,ýâ~Ž@®øm±Úš ¨÷ÀÖìb]ÿrÊËôó¬·¨jݺÔýÊ«èœë¯§juÍ"_±vwn"àÍäez…߆w«ˆ]‹Ð0;7ÎÒ5+”¡}cº9ŠÂ(§ižÎ‚gRß më,«µ»ý1gx*UéôËç©$í}¶¸ïrôÇ&Ý®v žØ[–CEÕÛΪEo2ß—a{U!NÛÝh×î8‡ Á¢±õø‡t©Iøc+ø÷¹dœç@冭渠ØçÁøÝØu¡÷Ñ^x€ê"nAÂú\!hFŠ ÔtCˆÝjÆ4×WM郩Åo‡Af¡ÏŽ¡b™ê¦zXi^¹7mNüÃÈŸ»õ1#D“ÊÝMë¾\ùxóý.Íõ¨}£‘g kpÒ3×(è&Ñ­Ö ôÍŽqÆÖ¯wr‡ŠNÂ&DS«ˆi×2ÊȲ^6ÊÚ5ä!OõÇC¹I ²^ôCµ£ÚuWÅ]¾ªÏK&À˜€·N&9c¸ª:Á'䊷fše‹ÉÍtËÃùw'­Ô³òMÏ\w=%fˆP:gì›ã…¸k§ïROöÖº›)37Í(òÑæ{4îB3\ÐàS9= @´ðˆµñ7.Ä ŽkEü\š¹öJ²Ä’íUçVS‹Q",f©YÇi×ɵsò˜˜tî´|êȯ_Á,Ê"wEüÜ4ÒX·K4­l;ùçþ™vÅdÞéÓyt@ˆùlþ#’¹üðì¿ "\[N8bŽ[[Á<,¢LóG!¿™Ã•`L€ xO 7ÇüPƒšÿYè}*yü€óa »ä—•Ï»aL Xð³b±`»â·29Õ9”\àÑ„3acTF±/ͯ¯¶$þE•ÊÔ¢; Ê­Bàû@zǤ›â>‘Þ©hÛ,£–qçå+¨f‹¸¼³Öß,Ëéb¦êG¯Ú·¨¤i!<+F× äLWÑ«›Ž7•-Ì ¼‚!”ægÍ*÷¢®5]Ë]Óì“WíüÝ“Å$mu©~…ŽT!ªº`I3ñÔ>¹‹ÛÚ¾EËÔ0vOæ#iÛõe‡?!üEGÆ áÛ9ázZV¢h¯š,w~ýôçþ7:ÛO,¢Ý'áÌ ^@IDATWP½Šíe¸ƒc黌mHTžÎU,“ÓÕa ަï4Êý±ÿ ÑæLqv]ϯQHKÀ¡/2rRdnÎé,ɱaÅ.T;¦¥ˆÁœGðêÝyr©˜W5CDK*G#:~ªµÀI_ˆðech‹>A9üö±zùfÓAµíÜÌ´^:ª´ðÐuþâ:ËÎr2kªÄ+L€ ”húwF‰Q‚ÞÎ#7ëÔ© "r`›ÍìÚü¶2¨Îw† 0ßàßgßò,©­åäf»yñéðHn«_nÚÝòø9ôŶ'D¼×÷¤ˆkõ$5¶¬DŠ Òœæz¬˜àëâF:‹Ø¤0Áš2;¡°²>ÛT»PqYv«yƒKÆ›XcêÚrÉr>á»l²Í€|iÃGè²Ææª0¼j!Úêv"ã ­¡ ú@.•ˆ‹2Vâë[¼(DN³g/Êé".Öug„Fˆ³³9§3ið´¶Š¸åJW¤ÛÛºŽ¼º®ùdѳüg=7ý¸†–@_”ÝÑn¶¹aɼ7yµ¢?£ã?§-‰Jå5iEUç¥ ˜Ï¤öWS,äú‹lx·»â³k¯óÍ_¾8úò1æ/Ôä“Î7báM‡Ž 0ÂàÅÂP ¯:¹Ù®¹8‹lqõ§ãÇ»ìžï¥\p`L€ 0œœ,Ózq®”²Ä«…ب۹õ†QÏÚ7ëYFâçÝ>1ÖóK”Šå?áwtA×Q ì°öˆÏò(&ÜêÛ]5±[onýší6•Ùºêù*i,+E×}2·±ÑC¢Œž½±ŠÑµh@ó‰tO§/¨y•>«\Ól<]ÞxŒÇ2ØØ@x«b’0«ÝÝácªV®±5ÛXG Ü(1é›n`Ö¯Áz–KºkÍtgû÷]ò‘EwuøÐv2ïjÿQ¾¯AÄÙqŽ‹íÚh)º¼‰•ùšAØ OÀõÓYø¶R“>‚9ìvbe›¶jàrŒå„›–êô¤JNJ£ª5*»”ã &À˜` `ç‘‹üùoΤ۞„d±Ûþ-[\ú`õÀp)ÀL€ 0&À/ZCøäQ×­øÅmoRÓ“)¶|E·Û½Ù€I®1dOËaõžê\Þø ÊÉËÞŽ™¢lŒ­ yV­2@bæ–àEE–¥Zå[Pé3‚à½B Äþʈ8³ù†ù£üñŒ½””q„Ê”.G5Ê5õª®j»Z¹Fr"¯øô­”–u‚ÊEU¤š¢?f_UÚº,%Ä´?e½±!?oQ£ M"¿ Ålªä›Õ¤r7y|§r’E<áý”™“&ÏKtéXÖ ®Çs Azp«©Âã;ŽžÚA§²“©td4!|kL\gW pã/%+’²Ë}–)]žbKWqñØuÖr¦pí€ÅÉÌC"ð¹¡rtm19^-™.K±B¨ý\zÕ¢¬•*A²rq”'¡p]áWn ¼q]ýEá…|o§yò,QÆÖ+ÙnœgO ä„Ü<1Yÿ8ÛŸLÎ-¸ªI¥{xß1jܼnÑåÚL€ „-þ- ÛSëõ¹r·,]âuþ,˜™I§RÌ“}b§ó\‡Jú³Ü6`L€ 0P#ðóŸ¹íòš‹èœnýÝn÷vƒ;±Ì®>Y%ÊÚmG´ªeÈ?kxغ›`ÌZV­W-ÛP´ÕP­jY«|K1ôµ`UñÂYqQ»Nl›‚5 ÒeP7¶m¡ö†óq» /_;O_oÛ`Œ?;ƒÈŠ¿ü­”¬ëË¿üË:J@À/¶¢p•ʋަ_[`\¿â ËÆO&:r«ƒ+]Ú~HF»®ÍU¹Ü¼Á4Ü´‘W˜`L ÄÈË͵e,¡v¯_gÛ?»‰KÜäl&À˜@Èà­!wÊ‚²Ã;÷šCóé\³a‘¾Êi@¼YÝêUh¯¯rš ”x,ä–øK üìØ¼Ït šÔ6­«•ö]š©¤\n^çœÕÑ´W˜`‚?(–ìËÀˆ *§Åè¡ìŒŒbôÆ}÷Úö_ŠÛbáL&À˜`^ضË9äß« \¨@–þÔT¾k¦u^a%@H ¹üð\Ò/Û‚ÿέf!·a3ûa›×35|üX’iW˜`L€ (k=Ù®õÅÿ—š˜hÛÅÓYÁ– g2&øY1<ÎcqEV–ç—±i§\ÃgÃi߈㛕›n:¤;™Öy… ”t!#—½HsÉ&$$Pll,•-[Ö«¦¦¦R¦ˆÅWµjU¯Ê²Ð®­L»kÞºhq~Lñ `%–?(–ØS/1û=Ùg“&Òøo¿÷TįێîÝë¾}Vrݳá-L€ 0&Pâ >jvŠ*]†²s² .ééæÐ}ƆMô©;”¥ýGâ_»ê—ê(Ϊy˜mŸŒ¯Ú»Îm…j•ÊD”£VUΓb¥Ê˜¾Ýj]_趸"W,äúðÌBdÎñò"##ór‹r0”õµA„ݵkuëÖ­PM'''ÓŒ3(::šžxâ ¯Ú˜2eŠ,7nÜ8¯Ê²UÈmÕ¾Q wÏûbL Ìð Å0;¡…<œücÒE::ˆ¶v­«$L€ ˆ 3àŠ\ sÒëêùC÷)ËÝ&´µéÿaOJJ’Ú6lX¯Û2T+ˆï¾ûn£ýÛªËÑórÒ Ñü}AÔA‘›W&Š&ˆb@b€^ÏîáôIJú¢„p|Ç„Õ@=;ì7¶KG ;WýWL2gaÄ Š@IEùå ˆb@f€¹2gçUÇ"—ÍëÜp#"cë÷ß!¬›u®Z2”çæª¦“¸ùü ÒOðáwº‹¹ÆðH2Ä1à˜ Ðüì˜ß›½÷zæ9¡.î=¤ß=$@ Ä€™0Îq›™íKuö¾pÞ¼y°wï^())Q¦ìVáÎ;ïÔäó1}þùçPPP I“##FŒ€k®¹F†Òõ•W^‘|à>ðÀðöÛok|õ,_¾‚ƒƒ¥ƒÊøgʃǸ•0÷[^®«˜œ1c\xá…¨ ìܹ6m“ÔðáÃáÚk¯•E ^333á»ï¾ÓôQž:u*\|qïNÄ”ë0æššœÄÆM6l=0> É“"ñA€ ˆb ›µÃÎ&Οè©éatÃ'C{«Ö/;*Ì@[Üusw“{ÄǶnE2“.¹¬[«H#E®‚ ŠÄ1@ ªjðû²»›‡”7sÊ%ðÇö5rûl‚›®~@ƒ)B Ä€µpHE®=¯²º²Ó¦}|| ©© êêê ”uäÈÉ¿÷a+.Ç•¸\¹9}út „ââbøê«¯ 55U²bõ÷Ç[9xÙ+VH¦q…hYYøùùÉUê\322€+wÏ;ï<˜2e øúúB.³öùöÛo+—gΜ)õ[Y»‡àJÜÉ“'Wör%ð?üiiiÀý5J)®ç¼¼~ÎÉÒ¥K¡ÿþP[[ ü1ìÛ·Ù!.C„m¨:•ô1¡¦ [GF‡¬161åçãm5(“1@ œÕ Øó\tV1V|c-ÞÕâÃænµ{¢¥±¼Øœ+†6¶;fÕ¿þ)&#|`ý:˜~Å"”f hf~v•aø´iH‘kï‹âʾSœ ˆSPû-6µ’?{8šº ~hÂ8 1 ¥g L€ ˆk1@®,Àôܹs¥ZýõWT;WŒrËØ¨¨(ô²Ç·Üz–+dyœ®ô”OÛ¼y3ªG\Aúøãøqã`>³r7`µÃ­hŸ~úi˜3gޤðå8ñññ0{öl©ºääd¹ZÍ•¿èq쥗^ AAAÒáiwÜq‡”/ŽMSHùþûï%ôä“OJãá€ï¡‡’ÒW­Z%]-ù§¾»œ‹ 6Ø\PV¼·¶è÷ul°"Ê$ˆ§g€^þ+68ÀZ¶8ª AÝ®yâÆŒU&Ãþuk–Á“ ;aؼ¼ð¾år¶t=¸áw„G6ã]4’E/«6¿S ˆb€ ˆ]Ž¥îG‰ÃO”0=÷!ZÄ€  E®ÈçŠU¸¯[eøé§Ÿ$¸`Áe²ÞxLLŒ”Ç­rÕÂ=÷Ü£–lRwÝÀCa!>Œ§yxxÀÀyT¸šîË— Ù"Yœô\˜¯>nlè@8Cõ›×ÑÑÉ\:`iOÓ·¨âÄ1@ µåxÇFPD÷N› ‚«¢#bÅ*çîÀúõPÍvÞ(ß~£gÍR&AÖáþá$¶ø‘GÑâ1ÏçFT€1@ ΀øîáàáî[™¼ÂtÔâÄ1 ˆbÀÖ 8¤k[“ÖSûÜRÖÓÓZ™ß;î2AV†rW>nYÌÇgÈ ˜÷‡ƒ»€Css³˜dvÜÑÞÍ__+æ–Éú¸èkÝTž —ú]pÜïÎ=¯=…}èq™4vöð˯kšHß¿Oç‘&vèç‡÷߇҂"£`ôy³@ô»[oÂÁ§r…5ÌÕ’2pÿ¸);w(“H“‹Ù D NÆÍÏNö…Zy8ùEØeÂÐxíNî/·½C»c³±©|}ðŽN+w—š#ˆ³‡Tä:Âä|ýõ×K˜íÚµ ®»î:Éz•[ Ž9Rç6ãŠÂwß}ªªª€[«ò˼½½%e©Žp/¸ÂöÍ7ß”«nnnR\Ù̲¦î¸ž½ˆò±'[ÛrEoXX˜N<ÍËËK'Ýœ I»N ê%`ÿ·(S¸û…вjMJnFÄ Á!ˆb€ t\+œQä<³ÓEfˆûÈU†^‡0ua‹£ÿÞ±SéÇžÄÀ}éz9_rÅog‡v· ¯;$:Zg1ÒÐü-¶O˜ ˆb€8›hkoAà êvăÃáTe‘&¿¼²âI‘«áƒ"Ä1`H‘k!ž èVüp¹—»9¸æí ^#{ù{íµ×Dñ^aîÖ[ÇòCË.Tlûä–¹ï¼óŽIur%.†±Ü-\éû—¿üEŠ[ûÏæõ{Q“óbõ(S†ŽCŠÜãG2I‘«à‡¢Ä1ÐÍ€#,*Òwe9ôYäJ-rŸ´ ÿul7L[ÀÜõóOPœŽ·lÞøü HÑ:lê48¹O;­ÿðXt¿q§b' >uùl<ˆ÷*)r%Zè1@ 8)âož““†eB‚°"÷T3ø8Ü=¡&‰bàlf@×ôÃÎÙp”—nYËã»W8zô¨—}ÌJàÌnµË?TÌR!--Mªšvfl­j•ò²›>¾žÜ*ðr\Am«“®]-å}˜v~·ïâžú3tTI9œ…°±`Ï–dxdékðñ« ;½ÐØb$G Ä1à èúÈÕZìL¾ô24‚_Þ|CÂ_þýo(½ÿ!0ëÚëPÚ‚{ïExû+6~xù%”ý×—º±¨Ô8 ‚yTŠ1@ Ä1pv2‘s <À?áèˆX„óŠðâ,Ê$@ Ä€… E®…ˆåÕÊ–¨«V­’Z™4i’jk²ÏÜ¢"¬xüì³ÏTå{“Ò= q+_9p· ~ø¡ u®\iþÞ{ï!½}ô‘$¯#/&ÄÅÅIî~øá1KJÏÏÏ×I·d‚«›q·{ô€pÔÚªn d”hø|Å/PY^»7úÞxöK#J‘1@ 8 ¢rÌQúMý4µê‡ñÚçÝ| jäè–-ðÔÅóÑ|Êþ¶òG$ÇAÌà!(­©¦aC C8„tâ…u‹÷æ?…µ°¡ú( ˆGd€ægGüÖì£Ï›vþŒ:rîäK5 ¿ÏŸÌLFùˆb€°äZÁ‚,GDDHÛù!f<¨¹UàéK–,·Þz V®\)ŒÆÝdeeA@€ù§skßo¾ùFò“++Xóòò$ÿµüÐ2µpñÅÃÆáÅ_Þ'ÙB—»T¸ñÆÕŠ ´›o¾^}õUàÖÀÏ?ÿ¼4___(--•¹ü¸¥K—¢2æím¨*þ@Ç­ˆ A!þH¬¡¾ acÀ®M‡AìCM/ÂÆ´G2Ä1`}èEÑúœÛS‹µ§°"7øŒ\ÞÇ(a±“»a]1Üú¯WÁGežç.¸¯\ùà3®œmf.¼ýñÜ$r‘Å2U/6ßÊ{zPGÙݤì3ʼnb€ ˆK3p0y+jbæä‹5t2ÂéÙÝ»nQ"b€ ,Ì€qš- w”êíåcâĉÀã¾bõ)f™»§Ÿ~Zò9Ë­rsssá‚ .€|Pr_ ºcðóó“êÔÇ›Z~bb"<òÈ#R}ܶ°°®¸â X¶l™T?`M\iËûÌÛ}ê©§¤+?,Í•šÂýø>þøã²¨æÊ´¼Œ{ì1É'//ÛÐÐeì4mç~z-¥Äå}(+©D] ҾТ  *rë›U¤ '}ýŸ5:¤ÈÕ¡„ˆb€pX™yeà X9ø2×Jü 1}+Yg.¾J_¶ŽÒ¶Š-€öö®ù ‰ ›6]ƒû‘E®† ŠÄ€ó3@ ­Îÿ[j„Í ¨êð°þ÷ŠC¸®ÁvnQGÄÀYÅ€®æÍ·ïhŠÜ ÿô¸µ¨š‚ô‰'žÐ)º|ùr4e·„U \ÙªVß“O>‰Ä¹Å­2í®»îBùj€+‰õ~ÀÿX3”U æ‚ú|A‰z€»þ·à÷\ggS@·î‘v,Z[Útj¯¯ÓºµÐɤb€p8èEÑá¾2«v؃ͥ- wFÊÆß:¤„:ñqÌ=«Ѥÿþчpû¿_Ó`µÈޕߣäùÊ/‚I®£=K¡ ˆb€ ,ÀŸ›[°K½ í"­š¤*‰b€èÆi¦zUµe Ñˇexu¶ZSe¢! ‡°© ³£Óè"¯>¡Ç·1-c4‡$H Ä€£30ÿ¶ÛU‡pÅ‚›»‡jžœ¸ø‘Gå¨tÝ«Pê¢ ýã›2U“+èqÎ:ÓPCb€pBh¡Õ ¿T+ ©¹¥Qrÿ'7åêêîž2¤+1@ vÀC*rir¶›ûÇn;rdêÛ䙣î ˆ/½mmí=‘òSŽd¢£ ‘1@ 8$49ä×fµN/\v¯N[þÌý‚Zº(¨ô·+æ©áýk×¢d_…»$ž!ºV EqDb€ ˆb›ºÏµ‘©psS_t ð•E¤kqi.ˆb€°4©Èµ4)T¿ã3 ú£M>ФA ‡äìÊa”©¯?ý¥Q” œ™Rä:ó·Û÷±ñËD?¹/¬ûÝ芹ke(ÉÎRBßòÝ7Ï¿u)Âà‚O;#E.¦‡1@ 84?;×÷i­Ñ>¾55¨ÿ`„e0h„•®é9É ˆbÀÒ "×Ò SýÉÀäs±oÒž=Ž#iWŠŽ5®»‡;*G~rˆ‡d€”`ùµÙ¤Ó¥¥Ã'YðAJ*¼¸áàbt?âÇG²©»w#¬Ù‡+!Lœ¢RƒîaDb€ ˆb6íø±pÉœ¿ ,ƒxA‘{2먜EWb€ ¬Â€C*rÅ«0E8 ¢5®›»þ“Ãõ j’àŠáxRº>QMúçïh¦á‰§„ø¡1š|ÉHÉC˜1@ 8.49îwgíž»yx@t¢ºe¾¾Œ›3e%mØ€°tvt(!k+á~€-rÉI.¢‡1@ 84?;Ùj¥áœÌ>‚Z:ÚB„e0(Ïç%eô~'sCWb€°©Èµ5ÔŠ£2šœº?tÂÆÿ$ÖÙÙ…°N$gASc J¾ëÑ%8 ·‘J="‰1@ Ä€*3/Fééûö",ƒÿ}ò‰•®~*V¿¢Rƒ,reˆb€ ˆøagj!&*%—W!L€ ˆK3@Š\K3Lõ[´ã9¨Í!#!l è'øì©Ì{ÿX‰DÆMÌ­BÂPì›7,rOˆGf@TŽ9òX¨ïöÇ€_P°Qúõ·Üíÿ~atæ´®Ó:2”@ Ä€³0@ó³³|“ÖG{{›ÑÅDÅ#Ù²Š„ Ä1`iÔ—™,Ýjê'+’>w–-È)E#Mè…E®Ú ·ÊuuÕ]û(È.…¦†fÔæí\%áþÃQzEY ˆbÀqPû°öh¶®ü ÓÒ ­¹:ÚÚ`î_ÿ ‰&Z»Ôž…ð ‚Æí¼‘Æ¬r‡O¦i­«« Ú[ðnÁÕ¾ìZÔ¸ )B Ä1@ @~Qb!,¤?ÂJàë „ÐىݡLÄ1@ X€]­”1g•¤È5'›ÎYWQ® È†­bµŸ?v¯P]Q«Zô™åï¢ôþƒ"ÀïŒkOo”×ÑN="„1@ ô‰íL‘»õÛo`÷Ï?Áþµk uÏž>ÕG…c€+Í•ÁÕÍ2ëâÓ]©l¶}ÿ=Âe¹¹èMOðòóC2ˆ¹§Ov¤S%Ä1à@ ØÃB«ÑE]e äžD<ôŒE˜1@ öÄ€Ã)r9y49ÛÓ-d}ikÃÊÒàP¼jjlýƒ|‘hUE¤ÅnxÚKïÞÇ/Rðòò”£Òµ£½aÄ1ภØÃ\T_U…Ì;~a–a M°‚u÷À¿õæjuÜsPU9ÇŽ"¼íûoŽJÀ‡œÉ™:÷*™äÊÔЕ ˆb€€”´$ÄÂЄñ÷ÈØ¬'†(Ÿ ÌÉ€Ã)rù6B Ä€5 ÂVM§J±Â„÷áÍç¿B]I`‡›)-ŸD‹Üv²ÈE| ˆ¾1ÐPY‰*È=~ a–a€»²P7aÑN™×—xâ„ ¨xE~>›¾ÂsÐ-ÿø'Ê×€~‚k…Ó¤ÉÕpCb€p:t¯œn„4 s3œºU9yüù‹ 6f(JJ>Ë£LÄ1@ ˜™Räš™PKV×ÞÞùÂKœ%ÛsĺsÒñ©¡þت֔1 ‹Ä3Sñ tÊ‘LhkmG2½²a77WpqÑþ›ñÕÚvÁb @ À=¼(¶ [ü«KJ†?GE®§e,r=¼½õÒÔÙѧ…ÅíÇ«Ê÷¬È°®"wÙ¸1pûDÍ'?5UµŸ”H Ä1@ Ø‚ÊjìšohÂ8ƒÝ˜0æ<”¿ÿðf„ Ä1`I,ãÔÍ’=fuÛÃ˳9†øüóÏëTÕ~óçχ)S¦èŒó?ÿùTWWüyó`æÌ™:e)`ç¦Cˆ†ÉçFØ0bL¬Y¹US$#5Oç‘OÞø áQƒ§'ö‰Ë¸2·­MkIÞÚÚîù¯‡ÆK€ ˆ³•Ñ"×R®8¿aáPWqJCõþuë`Êe—Á‘?7jÒx$8:a%Pîáé§»¬«ÈmomUvö®þÒÄ1`.œå]Ñ\|P=¦3ÐÓ=4q̹°z皊Û®‰S„ ˆK3 5´tKfªßÙüÏðI›YÜðWâòñmذ^xáÆBBB$ån||<Êã ^®n¶z"!3ßÿ]j«¡¡ÁL5š·š£°£ú©çéuÃÇ`ž‹òÊ5u•WBMU½óÈÿ=v-Â2puw•£Òµµ’ƒ2 Ä€Ã0ÐÓC¾Ã „:j2­‚\7•E<“+ÕS`ñãœïžN»~ù¥O¸ðB„•@¼W­ù,ÕÒØ]Ø?üŽÿþWÙ=ŠÄ1`VÄß<³VN•9µuØMU¿~=«H" ›ð{!Ê$`× ð`«[ŠáxŰ!÷uØ\ðH¯ÚMíµlÿ’ÖË®A;ëp8³@gó‘Ë—/×Üxìeçå—_–ºG…±cÇjòn¼ñFM\i;³µ¶ƒm³´tÛheÖ5~*'c[ºýžêolhA"!á›D &eÙú@ !nH øú«oå¹ÊÐÒDŠ\%'•{}Q,L? †sTZ¢ßí-ØG®%-rdz]8ðw--Muµ8¶u‹6‘ÅæÞt3Â>r­éZ¡^ðãÌûÕ\¯{x(ê/b€ ˆbÀJ $݆Z5t2ÂjÀ×Û%·µáwP”IÀîèìꀵÙ/CIc´wé~w)L©+o·@HœsÝ#'Ñ•°9§Èµ9c«+\uÕU°jÕ*ÈÉÉAŠ\s4ÍýìrËÝ€€ê¸B¸‘YÎp­»»»N¾© ²Õ®µ¾]l«hsþ! Õ§©ãå‹óË¡±¿Ä?øìM¢˜ˆ‹„Ôäl ÎË*‚˜Ø ¦1@ ½a@Ü®.ב¶w/)re2,tmkÆs»…|äòîûøãyŒ[·6××ëøÇŒ‹Ó;ZqÑÁš¹%9ÚùOo)áxæÒK 8*B¢¢%——ß·Ü¡úOu~Äß<ç1°/ :¶7j:ÂjÀÇGPä¶·J†Xt况e?i§ÛacÞ;Q­ýι¢vpÐLôŒ„@Hè`2µ­¥ìS éL®¹£R*7JŸiÑ7À䨫íg@Ô“³–‡Säò—gÿ”­Ž=…õk×ÂÁƒáoûóÅê ÉÉɰzõjÍÍûÆohâË–-ƒ°°0(,,„O?ýn¹åHIIH2Æ ƒë®»x[?ÿü³”§)Ì"œcn)Ì-†yà¾yßyç)Îÿ¼ûøÍ7ß qg^ y}\ *drÎ9çÀ‚ 4e,•¸®®.’Ú¾´ÅëèìÔn©hb ܧï]ª eV¿A~(M †ŽŽEŠÜ¬´˜1ŸD®”§81@ 8¶ž‹òO¤¨urß^˜÷WÖ™ª¥(ÑÚ‹\7]ÿè¦ÔgHÖ…-ðú1×J UU±/ŸzRç‘èÁƒx¯²G)«…ëÖª¶UÅæ 1à×Wµ%Úœ¾Pœ‘.}äÎ̹ñ&ð –!]‰b€p(ŠJð‚cbܨûïêâÊÞ3Ý¡£C>øšµ4‚·þwÂ+%‹2PÕRߦÞ'µÁµ¦EÿÅ`›ób—3}¬Îz ëÂÞ’oa_É÷pçØ¯ÁÃÕÇ`YÊ$,É@Ï`,Ùz/궦I/ºg–"¿œñ{Ç<3aÒ¤I’R—Ëq7 ó÷§« »wï–”¸ÁìA{àÀÀ-yX¿~½¤Ä‰‰{î¹}ôQÁ á<¿ýöÛš*üýý¥z¹r˜‡Ñ£GkÚ4hFîÍ7ß””¸±±±R]=ôx°\®€^³fFΑúºFT­›{ß×)bb#Q×ìaÊoüüïOA2":ûÚÍ:Y(Š&ˆd@TŽY{ylqN-T©%SšhmjBµyxy!ln *j“Ö¯CM,ìÁ"Rç^µ¢&÷ÀZÜW¹ã©'ä(]ˆ5ïiòån×UVÈQºvÁ€Îož]ôŠ:a¯ ””ç¡®ÅÆçžÊÝÍ•kj&?¹ˆ;Üç­¬Ä ˜ËƯêQ‰+wŸûL¾rðópÏøÁÛ5@ò›ûÑÑ¡®­L¡+1`uú®é²r—M‘Ë]ü÷Ì¡ÜíA~~¾¤D]²d‰Ž2V¤š»,¸Œ\™™ Ügí<æG+\ÕBzzºdË-c•aîܹpÑE!W ×\sdÅË­yËÊÊ 22’­8ºImqelEEÌž=BCC•UÁ±cÇ€»SHHH€›nÒºøûßÿÿú׿àСCR,õpµíI¨?ã§7 £Bànò³K4©«¿Ý¬‰óHüÐ`È—.—2R«èæXYLjb€è ¹ÇŽ««)Ǫ́*@‰}f ±ûxõ îÞ½ÒçŠõTpù½÷ÁëÕΫ¢Øð©SÅ$Œ¹Ö|–êhW÷ ŸÍv›3÷“Ý3°æÝ:}¬cÏ…ýÑI§b€ Ö6ì2/4Ø8xîžÌ ·A3Äšº* ‰Ö`c#7Þ;Z™k†Qñ?hÜuÓ3àéaÙbcûæ rGÊ×ÀŽ¢Ï¤¡Ì}†…œß«a¹ös‡ÛÇ~)¹fH«Ú_¦Ü 7|‚’B–÷cãÆ’/[sõ‰[áŠJ\^···7RâÊí?^ŠÖ3?|ƆíÛ·K¢J%®\v̘1R4/¯xÊùæ¸n߀¹]ynŸ«åŠ\CÁo\¹œ»`lÍh¹t%ˆó3`©E)c{š—rLU”¬ãTi1kbSm÷cr¥~A–ÝV>bú ¹)Õk@h˜jºœ¨s¯ZÑ"WîƒxMe;…(8Ü­³6Ðé4w“A°'t~óì©sÔ§a`Ìp¼ˆš|Âôy­¨$šj¡½½r Raó®ŸáHÊ.§áÈÖ)jHÑ(qoñN¯•¸Êq\ÈÜ-Lg¾ryàV¾§-༲}Šœ‡´Èu¦É™»:à¾håÀ•|Ÿ}ö™äÛöÕW_…gŸ}VÎêÓUöa«¯nÑÛĶŠV1|üS\\,‰vò‡v#÷£Ë÷‘+†Ú3/½¹¹¹º¢L_qS#>|&~HL_«„þô¯ÈÆ îþ¾}nƒ* ˆb 7 ÔUTªSQ´¨ R*g€Ï»ÒG^hsqq‘9e7D"SºŠ\ËZäòöù³Ü?eêÙ—ŸøÜ¤V²NsÅ+ ôV•}ä°Þ<ʰO „óä^¦¥ÉQºÄ1àP äàß/o/?£û?eâ\Øy`½FþÐÑpÕ¥wh°1‘—ß¹GGì艽0uíXÑ!ÆÄîÛö猧¤R—Æ?!^M¬A¿ø$vàYEKžthÚûG–À}~fÂýô bÀÌ 8¤"×ÌØUuüeë¶Ûnîk¶ŽmÝ”¸ü€4îoWLJŸ~úI†F]¹åWæšËŠØ¨FÏ•cË4_óø ‹T·²âéa‘Æo¥åÊ€®®.ÍÚZÛÁÃÓ]ƒÕ"•å5¡Þ¾š<¥Ä€u°–5cO£é`®yô…SÌßz¸â J}rΞÞÜÜ _}õlÙ²Eòù.÷4;¸¢Æw Ô÷‡z¯0hqó…vWèdéÌîÜØ\æÙÙ¾m5àß\A P½?ìg9xED‚?ó ÐÔ¾A–ÿ­5ó\Èb¾è«ƒ ”-~<õÒK’µ®Ü/µk3ëg ë¯*jê`Ù²e2Ô\¹òƒø|îîî.)±}||$ŸøÑÑÑÒa©C‡___MC‘œ£ºŠg¥|'S¢»²vŒ |G_xæÊwgü\‚+®¸æÌ™cWCÝ¡G‘›¯ÇR×®:O!ˆb@…]6 Ô93!lxzâ÷MSŸÿ±â^Õê Š3UÓ)Ñxj[Ë ±½{·ðìw¡‚Í9°1tvµÁøð…0:ì"”/‚?óß…Âú£àï—'>î.Úïý/#Þ‚ÏŽ/…Íï“"W$ްE E®Eéí}å²k~ÐXO+ y0dU¤¯~˜J%®>Yž.¯p«µÄ^d+++ûÕÕwèš¡ºû’W]ŠŠG 0ì/ òxE‘gÞø?1É  öƒêJíá8Uµ£¿Õ,ÿ‘¥¯iêŒMìϽ­»õF#@b€°:ú~¬Õ®Óöþö+,dd­aÏž=ðüCš8õÞ‘PÒ.‡OpÕ>€ëã‡{;ãêÂZ~0¤ü„ÖgA‹‡'œ …7¾ü þÖ?Æ2«YK…]…EÛ€T}‹{tºt+@+³ËkÒ¿?’+ÎÒ=m™+±yèǶ#ö;Ý ®]íàÆ^t8V Ó§O—žø‚³šr7“rj(¤ìÜ c/¸Àˆ”ÇY½õÖ[¡ÃÅZÝßöÚcÅv*àÊ8?zôEI¡~þùçÛE/;™û}JŠÆ3®µì¢£Ô b€1`ëù™¾Ça M8è,,Ô:W557@vþ Ç!ÊÁzº:ë©Ç—Ä=¦ÓóïÓÒ¤m)ø€)`ç³µq¼sIȪÞ©•›$Xßv ~Êx®öºœ ¾îÁî“§š²`É0%úZMEˆK2àpŠ\N†³OÎ555P^ÞýbfÌü€$«Z~XÚÔžN®î&ÙŠ–?œË¼rËÑŸæ~^tChh¨”˜”” ,@çw¬^½>ùäxà4õÉB\ùË-|,ÒRð¶”Xæ¿ÖR!$,Ðd߸:ŠÜS†¹Ï?øÔý¼¬nŸÅ(‘1@ Ø”ù7Óà¿Ù\±¢/ì[»æ¬Täò¹éÑG•hédJ¿=Cn†ÿx}4õ*ý4³äM×úY‹*Ý]¥[áþûï—êûç?ÿiò\ÜSG–.] 999pbÀÅPaþí=µò»: ¼òÄ•í]ûWšËáî»ï†Å‹kæzÑî´+ÁÞ_WËâpdó&£¹/¾ø"tõsƒãÿ®)ë쑉©ÀsÏ='Y“ÛÃX¬_gÝ >Ä1`V:7U¾Þþ&Öπ݋ ¼`{{›=z¬cÙ—ö(C½g ®µ[—28xz•Ô¶–B§ºŸûÁU†Æ¶*%”â× { V¾R«7“"W‡J°©Èµ”2ÐR$ª—ÆýáòÀ_Ìù¡cmg¶Ë^y¥q>zæÏŸÏ,7ŽJ>ÿø•o%½á†¤­†ÚæyÜ…W¿öÚkÀ-kx¸K®à凿ˆa„ ðÇÀÁƒ¥Ñx_ùö¿¸qã`ûöí’Rù•W^¨¨(ˆ‹‹ƒÒÒR(a'766ZÌí±¤tÔÕIÓG!Üðê§ÌÿMn)ìÚtíM…—ÿ³Üäê‚ñCASäê ;þ8µÕ :Ù©ÉY0b\¢N:%ÄÀÙÇ@{ÛËËÙ6ô³)ð¹èšk®‘æ°wØ;üfµjP5ø˜†i'?ƒ¿ýío ¹'â[äûøÜÉ•¸Ç]•aûZ]ßË»¸Á©ðIÒGªŒYíök.†)Ù?w×Ä?|ù½÷ÞƒÓ —B\vâ…ó‘"· Õ8k¤cÌ¥DYИ¾÷Ýj8·f3e.?H–Œkë°‡-ÔS …gzWtαŸuõUL«UÂz¸{w]dB ‰‚ŠªM‰â²\ˆ0TƒÕ"¼ÝêZ#wÓ¨U@i(`nxðrUtc ý]­š:ô)q¹@lÀØ_úƒF6Âw°&®Œ¸ösYy¬L§81`)úþ†a©ž¨WMÁh@Ün³ø*\qË5ã»Hà/'N„Y³f··7ê»üP"Zñ팰­‰Ü ì’Áϯ{ë!oƒõ•Á믿^RïÝ»6mêÞ60cÆ à®Ü_Bù/ .„5kÖHÊY^wHH¿Há¾ûîƒ;vÀÖ­[¡°°Púð .sá…v YàoyIªuÄØ„û™/\þ™0uD¯« DeO•wûìA‰gÀ—ïÿª– k~ÜFŠ\Uf(‘° âo±5{ÑÖƒ"WßhköÑZm­\¹>üðCæjÀvp®OŒµšÆíxÁÞ14—¬´O¤9ïR Ä¿ÿ¸PÏHž›íB‰«Ö]æâá´o,ìãcgî"O€Ó…¤E^ψ(ˆ9UÆ\3t¿(Ç †j(ÏÖ.(Søý\høÅX!îQ÷îû†?×Ùƒ"÷Ä®yíb~¥]˜bÀ`¶‡¾Pì—êº Ô9oÓ]÷D„Ƙ¬È}üå¿ v½=ý ­£…5£5¢je.<=°." —‚úd)ﲄÇUeîó$ŸZ í]-0*t¾ªŒœå; y²k÷C°çæOW]žË5‡–ŽzðrÃ\r]t%ÌÉ€C*reŸ°æ$Âuq«SÂE]ü£¸â—ÄÞ£¬¾zõZÆÍü£/p ücËÐÏ…os±Ÿ3(u¦´?8È™ÿxüc¶]ºS†èš“^ˆ0b€8{hc;/”ÁÓÇZÙ¡[r,!¹òŒ\åÌáÉ'Ÿ„Ý»wC¥ÿ`86äFûªwlg.Î?ò2<ôÐCðé§Ÿö©_Û¶mëSy«îç eÌõÿ„V„Q¿CvÌ@ˆ¨ª€A‡ºÓÈ\Ij}"u9·n_ÎÜÝ–­WÒŠÖÕÁìð»j¶ÛJe¹¹H»†d>èj[l¹ÐjÛ‘Së¦0rò1 "lœÈHÒˆ–dkâj‘Š*¶Sµ<e½÷ÊzXþÌåÐШý½¯o¨ÏRä"¢Œü 3Â}Ô »ÜØÂÿ9‘‹¬ Æo´ô1TÀß#\Ê.mL‡¸Às ‰R1`ºOÉ2KUÖ«„&gëqí-U–k'=Þ_W7û»­EEnY¡®"·‚#=OìJþÛZµ«´ÊtŠÄ€m°å\$Zäz°lÇ„24ØHÙsÇo¾ùfI‰{8îjûQâʃd Į́Ymø…N7tå.1T†Û'<5> <$ rBõðiÔøÜL·”2ª^»êÇzÖOr­`ë.®ÿðÔ…ÈøxˆˆCiÅ™ lÉ€-çg[Ž›Ú6{×£sÏ3^¹'’€ÝþdçvôÚØîE ‰†àðñÂs\½B©«§¨ Ôµu‚Þ¬§óŽ“zVÒ˜fDI„è;Ö»»ûÞWM Îb‘«EúÄÀÁ½xÂ5~pŸê³Dá˜AØ’¨´¸R§™§—­@i^>žÌW±ö_”Bר€­ðPÄ1`Ulù¢Xɶ[+C`Xx3‹Ge¨«Ð]0Ræ;rü®»î‚üü|Ø=ø&¨ m—C)bŠ\Zzpƒa—7c§Ž ¿v±ï©’)¤—,Y®Â¡§Ì “Q¹p8wÁeë°ãÇP®~ô1ˆŒEiÅ™™ lÉ€-çg[Ž›Ú6Ỗ¨À¬i 6 K‡Ä²óSV‚ƦzHË:¤L‚¿yÿóööEéõ ØP e0È@tIù|9ÔZÁ…-àóÐê;k­Õjçìa@«%r 1ÛÃäÜÖÚ©G³!59N°C¨2Nè·¤t j²«)‡ñËÃè‰ö§Èõ“skKâzão{ ¥¿¬½ðÎ2ðñÅ[jj*ëP9Ä1pv2P†°bG_Ák¥bÛ³3±ôí·ßBzz:d–¸mv¼•»_÷#–ì»Þ™¾SÇÒξ§­Ãï„ ¶¸Šoúú+„õí4úeœ/ÇÖϼܥB•°p4øœI‡Èæ®(öÂýØË7áXýpwó0¹Ã ƒF 2e§ V‚Þd~ü!Ð?Çw/FG…Rä–d!LÀx=º ¨d…®ñ%{/YÑœ+ŽòÖûJ¨$1`©Èµ‡É9'£^}â3xõÉÏàßO~o§J«P£±‰ý¶wðâÃètñ¡çn–Ò.^|.ÊÛ¼n?ˆbàìd  oÝ‹=ÆÏ™ƒÈHéá”y$ì à¶ÛnƒvWoHM¼ÎAz ÐÖ†w`8LÇ-ÑQ¯Ø9äfhõð„zo©…ŽÖ£Zê×Vm”œÓuq•7@pp°M‡ôë ìö)2!AêOØ@l=V]Öí“Ц¥Æ‰3 "—n…žÈÊÃÆ1¡xqª§ò¦æßñØ<"#‡NÒ¤ Ǿvs ¸hТˆ*ýýFIéërþ©š¯\œT0xú´º¾§´ñ¤TÊ×ݶs¶®S–“1àæˆã±‡Éùà—•óØÞÖž^¦oÉpÄïÀžûìáé~ôø$R[ݹ™ÅˆÎÛX ý\º½úŒž0åÕÕ4 L€ lÇ€-ç¢RÁå ‘#ÁÅ[…¦be¯í˜2OËßÿ=p¡»FãƒBÌS;¯…mÝïd.nøCº«';g óÙÛvúòÂÐÛ6í¹\‡<Ôy3÷ ¡þ…ùÐÙÁ–l>féi(D×çC±/V’wø¼ŽFiaÌÿµ-ÃÁõÅý*u'|à@Ô­jÁýÊ$@ X™[ÎÏV*5×K2²¢’±zoIéîîííÚEÉšºJ `“Ü™P[_UÕx±ëí×ÈÙÒuHÂX„s Ó&`<ñçH mx×nS{5|–r;{äè‚ÿQ°xðKFWÊŸå>geÛ«ÀÝÅîû ¸öÓªÒ:ºZ”¸FÓI‚f`@{÷™¡2kUa®ò³ñ1{ey ôa-¨Æ€èkÖžI „ª íéãÅùåð”pÀ™»‡œ;o¢Áað‰„P Rä™o½ð Çò§®{øíC:Ë-ÿÅ îžLñèÄAz€þüs¨ò‹ð0ËHû5Àˆ’Q—Þc}•þ‰:jƒ‡3…£iN®‚‚½ÇÆÎC#î‚Ù‡žƒ’Ðpˆ®<õÕÕÒv}}CdþŸ#kÒ 8 ïRÑ'ï éAõ¹Ò0 SkŽ­£½]§9Yë€ÿÛÉò\‡+J°¶œŸm7jjÙNfb7C±E¬)u ‰ '2’4EN¤'ÁŒIið«ïݯ‰óˆ¯O ÄÆ`co/|ž wÅ@¡· ôo×hüúdäß} ÝéIJ\^kQ} |z|)\=äeôľûÅVóëÚ엡ët÷AfíLi[Þ” Ѿ왅µÙ¯H×aÁ³¥+ý!¬Á€io#Öè‘mØëä¼iý>¸éî…FŒ€DÌÅÀ–ß±«QÍUµÙëé?()r¿xwµNï­|J'- È”–¸yÌ‚7nˆcùÖ%d ®V×òº´¨ú 7XŽ2‰ge ))‰Y»´ÃÑW÷}ˆm5p~ÊÛÐ-ŠyyyÁ`æ–âꫯ†I“&¯¯öE*'':ëÖ­ßòRÍ]ËN$f‡˜Œ¿™õΙÍ uÊǧۅ€!Kçq¥©©-B1S^gDÎðd[Íd¡,÷#/b*Ä–ï“`My¹AEî‚ €rml1”[K›3ðq¹ôa'·àî2³û ö²8>ïà÷Ž››íÕÓàg¬@…ocs~T1`nìõ]ÑÜã¤úzÏ@ZÖaTẍ集2kZ¬È=¨Qä657@JúTÝótïl@‰ÌÊÀâ¡/Á·©Ëá×Ìçྠ¿Hu_Å”¶_¦ÜÍâ݇§r ݯNÜ.¾piâ£æn.’_øŽÓm^µv}Ƥ»ååöˆôéVÄ·v6@Nm÷÷;£ÿM²]‰‹3`»§Ã> ÍÜVi9¥P^RžÞàééQ1¡à¨}™»ÚÖªk¡Àe¶mH"E®H–…ñÆßö .¸öƒ2m úŠ„ã‡25½ÈL-Ðîiê@IDATÄydô„ÁÀ-rÅÀ•¶GœÔ$ŸLÉ%E®† çŒ|÷ñz¥Í!E®+” 2àÉ?­MMšäš²2ŠÔ,¥Ép°Èã?\ç¦n6fH ë`Щ’÷‹/¾€HÜÄÇÇÿ\uÕURÕ---pìØ1xæ™g`röJ)­šmõOz‹A…nhh¨1]³¸L;´ÊëŒ?ÚИpÇëohÚäÞ\Q^WW%%%p„)°“„˜ÊCÒ‡ š<‚`?;¬¬OJOM‹9.–¹Á¶w ÞmÙ¢ÑDo¿ývøí·ß`öñ75iæŒlø\¯«;ÿÈKlQ@Ýg^¯+eù³îêÕº ¾}©ÓÔ²Ûè¾ÏårçM“£t%ìšs¿+Úõ`©s½bàT%vm7,¡Ûj³7•%ÄBÅr µïmo|ô(Êó÷ †ÁÌ‚—‚eñÈ\ 0—]-°¯d%L¾<"`éèOà³ã·¡ÆÛºauÆs(MðpñÛÇ~ .g·L\ÞÿFÚ1«4J·ºZ#‹4cÞJͽÊúÉ›?A~v‰¦“£'‡_¸YƒÅHzJž˜$áÎŽns{ÕLJ´ uZ…o aØ@‹´cŽJc XSò{úáoQmfÄØx¤È=q$ .Z4SU–ƒ–&]ë®ã‡3aÎeSœc€N2 sÏEæ %:q0äÓú}Ëg~r]‘ÛÐÐ YGìO¸¶OÅ”l“”¸7Ýt,]ºÔ亸õîäÉ“áGfzÿô©P-m-χه_€ ÿ!p|ð_°B·µRj#""Âä¶,QÀƒ)j½Ïl÷gîhnjѿ4޵ú·n%b'S(vôEí50ûÈËÌJw<¤Ç.2CûA­Ï¦-€}kÖÀ…·Üj°N®ÈÍË˃&ÅB…ÁFdnß¾V®ÄÊJ#Š!®ÄŽŽ†§Ÿ~¥¿¼ùœ0pás¿®ÁƒªÂ*yIë×£v®|ðA„ùo Òt[ìð`÷bÀÖ ØãülkN¨}à ô垉 Ã;%«kNIut¶CRòÔðý·ýa%SÛ;ÚÀÝÍC)Bq¸}Ì—ðŸäkaé0(`¼ä Á×=îûs•ð7œ0¡6€!Á3áâ¸G4ev}5-Ý çD\©I§1` H‘ËX.+î~Ù’ ?~(CŽª^Ó™E¤¾@þKõ1c™t~Àœ2øúy+¡]ŃB±?9eçîzt‰¢ø9ÓFŸnФ¥&gkâ±?šZ ³³\Ý\Ù–XWÉÊÚ”‡ÃÊSlë°J}¢ªˆP’•0å{5g×Z»A’ëtó`–ªgBtb"R䤞€±³gËÙy=räˆÔï6ß>,Ôu6Ã’-0gΜ^)q•Ä5ÕÖ‚ ³` ­©‘> ÑýÁ¥1KRèæ‡Oì1…®+ŒÍ[§,f_q~¸˜ðûÇjr]ÙX¯¿ù¯’¢õÕ—_‚ßÿÜ!uÙ°w Wê1p±óàÜô/ ëø1£j‰5JÎX¡ôôžý#S—'óQ=bÄcD%™?˜ò×Ë€OÙˆ  £ë²” šÏÛ°øpàÈQŸr\Ó…,fÉ=bsÅA°1¶šŸm0iÜl)OíOhp4TTi-…KË ``ÿD5QJ3‚î&áÂØûacÞÛ°*ý É—+r=\½»Y¨o;ä½Éºü`9õç"v.?mAü’Û¹ÙÌêÝp¸üWöäwûŽYãºÈYt%¬Â€C*r͹]¦³³Ëä³r3‹ô~9UL aûp½t¢Œ†ú&d •fž}›ˆ-IOPˆ¿jõ>¾^0uÖXÕ<ž‚òÚÛ±òe°9ß3·õìޔì 'ÂÄé#eØã5y?˜P9é…?t€z&¥ž5 Ô2¢Êàªý lO/Ê0¢JÙžµã¿þú+œî£Ó‰_K[ÕM±šÔ7ÎF¦ÈU†áA°ò·µðÊ+¯ÀŸþɬ~÷k²-2‡åª¦:³EzÐãBiVjK¶–}ô‰'áø·ß@óÖ1)å=Hu/’3tøvÿžµ¹ÛïÜmꘌ‘o®×õƒ®,—ºgL]¸P™dõøvÁR9ZÅB8aüx¤ÈMO:@Š\«SÔ ¤ÈUc…Òd¶ïÅ ­GÏ’³zu  Gåš[ ³«SÇ7îSüɉ "´?Rä–”ç‘"W$ÉD<zPÿŽ€æVƒ‚ý0`ιȔQÕœ¹ÁÚÍXvp—2žÔúiS¦;Rü óÕÚʬ'úšJ`É’%}©BS¶©+r}ü»éžxâ X»v-Üpà ’Þo¼–/_®)gWš\q¡@Ùo~Ïs Ñìôk­·†e¶éqféÂCŸ?òílzEŽU¢¹?{&L˜€À}Û:ì[óêÂù×^‡0\‘« é(!ʼn›1`«ùÙf¦†Mbà@òV$?mâ<„Í}ájÆ Ÿª“¦L-{KÊÔÝ9*ËP¼gfÆÜ"ùÈå’?ž| ¶~ªZ(À#F„ÎÁÁÓõ*q¹O\Y‰{å!›,¦UɤD‹3àŠ\C¹™©ùpë‚§àÙåï!kM}L~ûáÕ¬dÅáR¢@}]£˜¤Á†Ü.h„(bŽ .0bb#ÌR¯¥*Q;@oÆÜñÔóá= ‚æÞ­É–ê&ÕÛ6®Ñ}ù®8Uct-M­eó¾¼ R¦]0ÐÅ\lp_˜…iú­¬{ÓQQÑæ¢UäÆƒª-+Q¦ƒî²ˆûeí}àÛåNÃ¥—^Úû*%këÀûŒ"—'ú°ÃæøÁ\¿ÿþ;ÜvÛmv{ð…Ò¯) Ûÿû#J6uÂ^LéêÃMãî%¦fý€òzºØvÄf/oÈN>Ò›âYF´È~Å"4ŽÔ=»¶È¾I—èþÿ :uíT>)!lÆ)rmF½C4\^Qˆú;pÂæÙù)¨š=Õó|Î,r•¡´÷S™GqÓ˜u-\÷˜T(ùÔZx÷ðUp²j»Q•œfÏ»‹¿‚‡¯„²ÆtæN¡Ü:êcà‡'ŒªŒ„ˆ31àŠ\}“3Wâ¾üèG5üð²û®ººÔýÈüÞ«þ‚Ý[Ë·ÃûRåªéjavm:ŒZ˜sÉ„í ðûö³5/Á=_ÃÙfÁapǃWÕÍɳ°rfï¶£F•#!ë2 ï DÑz\_¯Žn¢ú‡"QÑ'4Ê$`5d%˜¾¹ˆw„û4~ô¼saÝûïÁs /ƒä-›ÍÖ¿ѵ‚B‘ë勆¸2ÙBAP^²Úº­ d*‹\o¦Øt´À_Jô…Ã7¢¬)  ,û\îª Ü;[:šQ¾© Ê/¹ÎèY‡ñœnj=Ž$ß\-rϹøÔýŠ‚„­ :˜Õµü;'·­vh¢rƒËñÃÎ(öÀ€!£{èõÁ¶ TÕ”¡ÄDÅ!ܦ·˜·? K§7_ÎˆŽŒ•£ÒõT¥þ]ÀH€Q pKÛÿ·B½cÙSP—ä—+g¿<ñV¹ JÓ º¥*[ò ¨!’Ë×ÂÉaJßÅp°ì©ÁAÓáÞ ?ƒŸ‡þïÛ¨Î1ÐGº÷´õ±k×÷òüú3_ ®4Ö7?jÔÓMÞ³ÒòQ]2"rq釔ҹxbËÑÕü ˆ\O>+;Íßbßkdïª0ù¼ÑÒǔچ Ç ñ€>Sê"YË0Ð̬iõ)Z·ü¾Ý0·Ç†ÎD2cÎe%Uè…š/R JˆFrlÀ¾¹ˆ÷æÑófB­ÂŠ;ï€×vï ð¾ï¨,Æ~ÚÃ8¿ßäÓÞQ½ÿ’»ú¦hæ‡)ƒ¨ÌRæÙmÜ€kåáU¼ÿS.ÊÜQçžû×®O¦ìã!¶tä ˜/Å{ó§Æ/Âê3Ï2E.¶êö³ƒÃÍ”ßݪWÿ¥„0pÔ(„e .b´5›÷Mn‡®Ä1@ ˜‹Óì°Iq¡ÊËÓ§ÏÕO?6nÇ;ZäJ—ßö9jðß÷H‘k®^er¶× :ºÚàÓc·B[WÔµ–ÂÆü· Öá“× ý·Ýî´2ØyÊtJR‘«•U÷ôääiz¹Eyú•®e5ª_øž-xëß„é#àப²”H ˜‹q‘¨*~¨ûbà¿ìÔÛ¡’âJ½yÊŒ¶ø¤ #Æ%@SC3ìÚ¢u¥±uÃøë=—+Å(n‡ ˆw¼‹ÜÅ‚Y¹EØB#"[p¸º¹AgG‡†•†êjð Ö`GŠ´ŸQ‚KÃòì>„¤™)™¼½½û<|Q‘+ûÈísÅV¬@|‰•›îRÜ7rš¨¨VúEucò! ùЗ õMîRS™IIr“gÝÕÅÕUúåÿ«rÈ:t'N”¡U¯»Ww[ÉÞðÌsr]Å{£,r?lÇ€þwEÛõ‰Z¶ZÛ »1ëm/§NPWäzzxƒ±>x£En9Yäööë豜›‹Ü5î[&w*šóàTS.»fé–pwñ‚0¯x÷fßd¾s)öÆ€CºVÐ?9ënL;–£—óÌZ×Ô´ û»™mµšÒ…'Ó4ñ³-¥°tæc-ï­ÅÇo+ÞAM%ŒÇ¿'(“n•« ä'WÉÅmÅ€¡ùÙV}¢v탚ºS¨#£†MB¸·ÀÃÃK§èS÷ “f΄†¦:XµÎ²m˜³¿T1@ ˜‡Tä*·q)©èh×µØikk‡®N]¯šEÛ”sñ–ˆŒùÊ꡽½:…º||½`ð°H®€FDÁ² ¤ËE Œ?ag ®ž…†µsã!„ÕÀŸü.9ôoa[ûí9¯?󥚥õ‘âBüP8ïòé0~ÊpT+ÿýÐøÁŒbðó×*‡&NÅum^¿_'l ½(vuêÎGÂ!Yj]nm‹…ìÔ”Âjeå´àH¼C¡¾JëwS–q´kd]H}¢¡,p¬X±ÒÓÓû4|QQåé£ý_íSÅV-¬ëŠª‘m§W*x¹ßVÿÕ^ͽùf)½“¹­è`‡ô%„ÖekŠï_·N?Û"âÿ-wQ ö;bi^öýö+jâö×_GXÂý_WY)Š&¬Î€¾wE«w„´{ÜݰûºÞvØÍÕ ¾ÿ üûéÿ²[_b~q/„ £ÏímuF•{ü¥ëTå*ªð¢¬ª%Ä€C3àŠ\}/ÏÊå·Òζ"‹aß¶£(‰[t†Gá–zÁe[k÷ ÍrA¹ ‚"WÜf-ËÓÕ| äd¢ÊFŸ3agÁaÝÂÈcÓw¿Ëùÿ~êsæ—¿¬WUÖÉÙt5Ça J/𸺺He3ÕZË3eúºÿnSBˆMÀæSÏ‹ò«*è{D„Ø!P6&w+Êz - :"ÇwîÐI3”$(rªß‚{@%¶z74~}y©‰×BƒWÜu×]°eË}b=¦‹[ÇÓµžø ÷üº= ûêSfŽ›=G‚íînÐâ¤Ì29îßR.g>Rwéî„2¹B;/ÐÓÜ­ì¾µýÍŠ‹¼/=Ð8d¶fË8˜¤ʼn›0 ¿£Ù¤qjÔn¨©Å»{<Üùó:ÞUЗÎ{{ùÂæ+÷Â󮆿ݻBÇÍš1uE ±¢’„epðèv(*Õ.„ÊéüZPÒÇÅoee'ˆ»dÀ!¹¦®²¶3«\1Ú‡}ž3}$s_”Ú-‚Ü‚Ž[ôÊAôËêØí»WPQá«Ì£¸™ÞA¹âÌÙƒ»{ÐÐÞžÒpÕ,ËyF}]œ8¢;‰·4µ:;MVßÁÝ)¨Í)çj·"‡G£¼ôyË ¤[0-º¡[I"ç+—ä´Æ]+^9®Öa@í{1ÔrOŠÜ޶6PSï_·ÖPµ:yÞ~þ(^d Ë>Ô þ>wŽÆ÷/÷\š£þ"–6lø¶âÿQÆXœ4òhð ƒ^xzè¡!T«·­ÿï9¢kné-†ÿþã”të?ÿ…°È–ºì¸2 ^™Õ«x@s·ŸÜª2ç·"ª*.F(\£ˆ‡Ëí\õ_$k*hg§³·46BkSH¾U¾weÉ[6+!„ÇÆ"¬FLŸ’3’H‘‹!`LŸmÒIjÔê ”žÂg¬„JS«wH¥ÁÄA#QjzN2Â2xùÿ“£:×ý‡6é¤Q1@ 8©ÈU›œK„­Íʯ)E°˜ãy©Éør™mÐ.ÌŠNšµŠ¯­ðvfѧ®²Å-Ç@[›þ-ê–kÕö5óû>|¥ ¢Ÿg9ï‘[ÿ-Géjaª…CçÆ+Ü L5µ¾VúòÌjÁº–Ïj¿qÂŽÜŒ"T7ûa@Í_;ïWÔ â!Z²ìµZE®¾ºeY}×Îí¢¤>K§ŸÊÇ Ï/\`T“‹-—ÓlgÍé.£ä{Ju/t9>|æÏŸ[·ní©Ê­ѵ‚šU¨xo:÷<4n%àcn÷í^0Èš¥Ì2-ÞÑíJÄ)y¨+/7­¼J—æb몈AZeé÷ÝF$ú«E™F€'æÍƒ{Ç…eãÆÀ²±£a§ŠÏme5¢âxÜxQQ)+LJO›.G¥kÆü¬Œ2 Vb@í9ÊJMS3vÌÀñ“PïD' l !VPäfÓéÖ?ß]΢Ùs‘ž°}¯ö¹Q%Ä€ƒ3€µ–2µÉyýOú·žîÛÝ(ðaŠV³ÏleŽ„·3ìß¡ýñ<²Â1÷²©Æ‚B°ÔÉ”\ME 3°w[2¼öôðÌ}ïÂý7þ¾|oïK'í<Ž’† @Ø™A˜`á™™–¯3܃»OèÜß:B” 1°áçðùÛ¿HŸÏØ5ýx®IÌìÙ‚·{sw žžÚƒÉâ‡â{³SÅoê ápÄÈèÕ> 1¥‹.P&«0 6ñ†7}ùe¯ÚçVsjA©.ËÁJ àþýÕŠè¤éSëZ(ATòfø¶q5 d± “'OîNj–Œ¢œ)¸2l"l÷8TyFÃóÏ?W\qìØ¡}Žèhoƒœäd8U û댮Š22}^=ïr)c.<šÝ\´¿y¨#€]÷ýìqf¡+˜íÁz܈®÷ZDüޤým9s&ª·'+~$,nñ^]‚ÿg¾~æiA ÃÅÿϹøŽ;±€ ŠNÀŠ:v¨ bÀÖ è›ŸmÝ/jß¶ ìÜÿ;êÀ왋¶‡¹™¹Z]ï_Ssì=ôêêâKî@¸±™\°!BNÈ€C*rÕ\+¤Å/·Êï*yÿI%ÑŠÑÅ¥Ÿ&Ñõs4qáŠM8­‰Ioí‹Îô Æ£Ìmÿ£­eˆ`õ·›!åp&ä”B]MlÝ`˜»»°"wâôjw®¬sçMDÚõ§îgï¾ò’! Ÿ4¦¸­­m”>uìº[PÌê/Ùspo*¹ìj] 6/o$#ºhIbŠwe˜6{œjâã§ÓÄy¤¤ˆ^–!vþøüÓ^õ&ç¨úö9^YÊ?¹¥ÙÙ¨îH…5Ÿ2cè”)JG6mBØÚ@T^ÉígáO344TŸš³J.fž««7~;l½J[\á™gž .¸®;î9^¾z1¼´x±N[¢E®—o·›%A{NžgŽ [êãÆb¿ÜâPV­Z­ÌÂ|ßàëÅ,“ðèÂß¡SÞò¸;g¥ÂbL¸B‘ëê¦ë«ÑÔÃeî^¸|¡Õ\¹›}V¹â}Í E`ãME!ìœ{RäžÌ:ë7} ë6}kÿüNd´söœ·{¹ø¹}ú9ÚÝ`Ew X){û# >s½ÈMW?„Ò80d±«#L Ä1àp 8¤"WmrÖwˆÿFÄŸV}…W±f*”c¦a¥`ՙÉԶ!*¿íÉ ¿˜<=MpÝ ”¥8f òT N`¨ž)Õô…ÒB¬À6Q×:rÔøD$“rXkùÆWÄß–¸Áºuð Ü=t­Þ:˜o ¶c@m.â½i¬®îU§Dÿ”ÊJÖ¼÷žK²³”ɡLJåÔ…—#¹ý ÷ (ÃJ 0/¨ÊÍ~óܳrÔàõ¥—^ï66Wtÿ?j°Be¦Gì}¿d¡Ûáâå¡a5`”‡„BC­îü$*¼Òµh§œŠÍß|­dÎ[r-ÂJ°gÏxÝeAÌjÈ;J™eZ¼«<™k…1̪WÄC×YN-×QäÆjÆÕ½ûi0”åa—$(S(HMÑr\ýþ…çå(º®ÿèC„{Ræ#aÄ€1 fôc«.®øì)øèÛáão_‚O¾{Þûüi[u…ÚpuqRlý}ðÙ;Í-Úw⽇þd¹õ¨“¾ºIrÉ6¥‹n$P&b€pxR‘+NÎÜMBgg—Ñ_ÆÉc¹Hö‚K°åÊd€+ZʄÈü|˜èZ¡©±åÐÏ@W'~¡ä’J—bÉŠ2¬$‰.Š8-ï}q »7AI ®=¿º§J1¨ÀY²O茶³£Z[Œó%ºwëQTÞÇ×\ÝtGŽKDr¹Ù%œ´3Eç‘à¬Ð@™ Ä÷ú]¸¼(Oز ¨)r¹òDŸ…÷¦(#]o§ŽnÙ¢7¯8½»\I– ÈÕc‘;岨®Ô]ŠÝ%(Ç:àÈŸU*ÉÌTMg̘nÌZqlNßëE˜Yèîÿlû(”†z_ÈŽK—.e‹<Ú'¥« ^Þ7¨{ XÞêíïÇÓ.Ç <‡wß}žxâ ¨÷Š‚Ô„kº{ùwDîÏÒËç wáíû~ñE/ktŒb:>rcñbt¨à.¥è¤ú"ˆ¡Ñrkr}»pQ;ìà†ßQ‘y7ß‚°) ·VĦ´A²Ä€!ÔægCò–Ì«®-GÕ•â5(“€Å¨­¯Bu»Ø¡—wÐÏ+r[ZµŠÜWß[ŽÆ7p„‡DKic†OEyIÉ[&@ ÎÅ€C*rÅÉYÍ¢S<´¬¥¹UúæNwÖ±öŒŠéÞ¶)µ¾~ÞrTº–œñ05ÑW©¨,kmic~ÞŒW.£Ï"À­"Õ|'þ¶RBƒé|;˜²MDî•yÎw÷À[/O•v?˜|L¶âäÛ¡•/0\á«ôuË)-müâsÔÚè9s ¼¼’Ùao?ü°ä7øé§Ÿ†ê.OæOøAÈx’ï ˜~ì-puu…ûî»\œKSÇß%,F» ãŸuíu¨ÊÃÿ@¸'ðñƒ÷#‘QL!û+Ҹ¯Ü¬Ã‡4iüàA1ˆ f1_‰˜;e¨.Á NÊ<ŠÖ`@œ?­Ñ¦Z-­Ílר®¬úz~SãË’iÇSñóýðÁ,Ù\¯ëõ¼¢º†*Øs/zß°øðp÷Ô´3FPäfæ9dzf€QeàXê>X´t¸æsåm#Uå(ÑùpHE®89‹[œ¯]z1ˆ®N•vO˜G“N¢oÑË[û(gŒ¶BÙŸ G™²L&N×ý'qwÇ–’õµ Ê"Wa ®Z?G{¶$ë”8,.a@y¦SØI†ŽC#I;–Ï=ð>Jã®?â‡ÒD‹Ü¢ür${¶}îXª+ë{¤dßvüp',ìˆ\p)vßrüP&åéZ,©ùÁUÖåã륄·1â\Ä»³áãP¯=ð †,r‘ ÞvJzÿÞ{Wä²Å¦(S†–Fõ2¥Œ%âJ¥¡Zý™?øåóÏ?Îf“þ•ZUK«»Ç>ÆÜ.<].nP-)t»ÏŽJÃÂ%ß¿ÜÿïQ_˜Ã¶üóñ–­štž·úd\{íµðÀÀ¡C‡ Ò?A²NÞÇ”¸à·|öfìåû˜oÜxã7¤âü€/ñ^mnèùw¸7mÛC™.A©ãæÄ ¬ì»:ñ.$Cc8°~Žk—å~ ÜB?fØ0TôŸ×j]c|ñÄßQÞÐ)ز eª€Àp¬È­*Å®:TŠP1`QÔæg‹6¨§òúFu…mfîq=%(ÙR däâg÷QC'Yª©>×+Þ¿·V³îÔµ,™yÁ8Töð¾4(¶£ž0Ép0v2~HÞµù°Ž %`Š™Û }áϵ{t²Ö­Ú†Òæ/š‰ðÙF ÷Þºƒ¯þ¨†ŠÈèPMœGDÏ(ó,ù9†_2›{ðq“Q„˜:O8„e2Ù¿ä6°~ޅĦΰ>&¸jص‰~gôqe‹tÑ=Âù×߀ºÑX£þRW-(>¼aá²ûPÙ’Ì ¦È­Eiþ!Á+A@x¸BUq1ÂÖ);w ¦"ât-ÏS÷ìF2ú@8·â mȆ  Üûn~ÌB÷iØ6l)t1‹ÝæCwÙ²eª.‚ôÁé‡ã®‚.AŸ”˜‹äôcƒÀÁ¸«açÐ[`ëÈe°uüS°uâsplÈ_™y¸y=ê²`dáï°páB;v¬†ŠÀpüÌvª @“çlQ1ë¦r¥8æf…f1O‰¿²“.½”ùnï62xæ—_•¢Òî´TvpIL¬ 7¿üŠöŽŒB2Nüý¡°[zzWìmÇ?øúyx•mqëãÇáý/ž…šºJƒUg©æ§ç`¥¢ª%š•Ü‚TTߨa“¶g *çÞ{åw£ºKn=Œ¢É¡…’Sðû$̶½kzLÔyã0Ï“¹qm™MJ¹JU_Ûˆêå.x=+Ze×u5 H~Æœñs nEO¦ç2jóLãYš°ãí¶5M¢IbJ¸Õßm‚-ë÷ÃÁÝ' Ö€…ªPÔ)àуØBZ9¨ì“ÝÊweZN:V M;_û"¨”sæxÂK[}cœÎ"<<Ý5Ù~þ>š8Èþ¢QâYÄCÆÄáç)$ó²TI ÅöòöÐùͨ¯oB2c&EX˜{¶”â MlÀr.â=èìÐÝ>é&ìÔ`ÚÕΖ ~JC¢£À‹YÑ1ÿ H¾©+rý‚Õ]+ðBâvçÊÛ(r“7oFc9s&\|ç](í›gŸEØX¼x1Lš4 Æçÿ þUº Y†Êš+ï´ï Ø6ñYHx)¤œH…¹sçš5kÌU½Ùë© åS ~*Ã&A}Èhèð‹ðb‹ÌòØœÁù7ž‘ù5ÄÅÅÁC=„ªf÷»2öâ€/ey{Žë(rÝ=tº;f J;ºm+Âjà“­îÿúâËQîÂb8;4P>}ìÉ‚Wüí\S–Q‹>%‹þvQ&bÀ ˆó³¹šÜ°å{ØÍ¶¸oÝó+ü±ýؼkµÁª·íÆ (²p ÛMÁº ˆg²øùX·&´æéá­W:aÐHˆ‰Ò]çÆŒ˜ŽÊýoëp.ÚÚ[¡³Kw×Îs®ÒhTpHE®R‰Zƒýp…Gu¿ÔŠV‹™'òAÇg-~?V%H-QßÃÁàƒx dF™gÀ^ý~ýn |õþoðî+ßÁŸ·Â¦V—#¦OÂ.+Ä14Ê.1_©°óœë»ÿøxyÞ/AC÷ïkinCùg#(/­FÃŽMˆFøÄu .´{ó$;y†®›$pôˆ­r•2ÜW7w‡aL‹ Bbü𿞶®£úÌ€>¾óŽc¥bpT4³†Ó.ªj¸¢QrÞ1Ü?”ÉÀ ÿÏÞu€WQtí“Þ{OH'¡$t½Io¢ *ذwTôçóCý슨(ìH‘"Dz BKƒRHé þöfÏìÞ›{“ÛïÌóÀÎ{æL{wsw÷ì™3ãÆÂœ¨Hù¿½+WÐ*­ÂÙgN£z1ÉÉ0òT&ñiªD?ü8cW·ìUà©ÏÜ»ã)òI–tËÛÉBLŸ>ê¹ø£,aÜJÎ@ß‹?ËŒ¸$<:Sžä6ì2ÕD_çth2ï¸^øeñê4>+?:‰ ¹˜G¹¢™dÈ /하ႺM0}Æ€F ïÏšh|ªØÙFQè¾?EqJÙ?ž#]÷Á¿‘íà {Eg§8¼Ê—çè)/òYÉcdX$¿ÕBè¤Ì€N ¶©çÿo<<=4Ì}m8<úÊ`P7ÆÑÓ»%Ç\W_#)gBÓbÀ( ¹BÜ#{Ï 3Ògp„… õöF¢7ê&&+^æÝ1L¨Šò´‡hI16ú•—~²Jeysß…þÞÙ#'y9…r\|½Dž'Ww„Í ôÒU4]+kKHê‹_Ј’£³=Ò5wÜJ.>-¤6=¼Ýx›VúÃQp¢BWˆ*Ý(ÛÈlÜýƒU“”{zb/«ó§³$õ˜P» Ð/Šgö઎œG1ÔÐÞmôòg2JÚkµ}·î²ÁOx/AWgFñ}°!÷bjªÂê¾÷®¨ìÒQl˜)¨( —m{ø7}pí1njáƒû¦#¬ ø?.$‰™ëÉÅÌíy怆JUªiGÇÎ vužµ·-aøðápãÆ íôc$­ºß8 ýOüœk‹œ§… *¹w»v¨Œ¾fP¡ƒ†:¼"F‘G} µÙXAf¦ÒY/}þYTÞuø ¡¤ÒÜÅ_J‰å²ûþo<¯jÆÅÓ ©ÖÕ°—HD:g€¾?kb»¬5só–â½>DÊL 7vìYúîßs4†¤ ¹NŽ®0a^yAÛÇ ;¢UTÝ2øþôÌoÙµ\4ÕÏ–ÎÉ ȹ­©UüÌ›zj—¢ªLn" ¥!Wxs.¢–Hwl8ÆÇËåÏÕ¡]xyçàÑ=ù"Ñ1:>L$ãÑÁ|Vt¤½|s3¯‹txÁÅ´>‹ŽÜRisHdI8¼}=¸X¢r1Ñá½ ³/çËå$ãå놰9ÄdìýBæþÙ/Ò?þ´‘²¡žx™[x®;ÖB‚¸„pöòÆÆÑ´—Û×7m à £ø7„×=½p|í5ÍËéÍÕŽìOS¤Êä:d ã¾¶ºhú0eçä„FQu눼V»u•éºÖ&w__Tµ¾V±7þÞâ0 ¥œwi[S£’0|Ä^éVQQ«^8HÌÜ_~ùE š0 ×çÂ#/L²ùYT|(Лš)›>½û©2]S+ËÎÄRF&ɦرkšjÆÙl„IŒmÚ{׌•ržè䍸…šÖåq»0?>+;JÅmC h…áGEÒÁn™”0¹y{Ë lÓ2AAÕ-ñJ ÚÑɵـÙ^P»9kïŒ+›KÔËÃrME¹z•TÔ>°v Ò ëÜa;²¡› eRý!VP Úq7n„AƒAxÁ>Há ‰ÖåzòTçbØJx-maÆŒæ3·±¢9ÃmÊñ”¹\]œaÍš5°dɰ¶Æ¡`„çM˜wõñB¸rNñsR4" 2ä*ðš%S¢¼Š6[õþÿ¹k®¥ôÁ®=’*í¸p0,1LúþLÏéÍ‚“iûdâó—NÀÔÇÉŠNåË+JéfdX‘A63WùoØÁ£Û$ÛcBÍ3PV^‚¥=WQ¡€µ?dÀo‹SaäÀû cL2´ ˆPidÖ6øCàõÂ\•ê1%Ý1p›Û ¬L"ìEUu9çe[­Ò@}ÿÒ›5õ„+«ÅïH£gÀ( ¹üÍ9ûötqrq@'$,*a! ½u…e$O{1 Ëý”^„Á¤NÚI±W‘õ¿?ÈA2íø»u/²’°0çò54ºÀগ¸9œqR˜Öþ¶SiÉ>ƒ›<Ö„ºæ–ï=(ÞüäqxýƒGÔšú*6³Z•X™þ˜C¦Â‡< /®]-B3=}ä<®nNÜ‹¶ð«7*–R:'ÇHê¶$¤Ã¸äç6‡!i©.+×<ÕŠ=hcee™Ø#WÙˆ’¸˜«R)ŒŠu)¥CËhC)gÊ$ZMŽÏîÞ-Ï·&³ã§e¨Ú€iØó‰ÞäjÍ'#}uÁ›o¾ ëÖ­ns­Ÿ Ï)ÎÈUƒÿ–Õm³uú2ÏÜ;œQwÚ´i­kÂXjÕ•BÊ©÷! ¥˜˜ýõ¬_¿ÜÝÕóìŒL$†”æÔZ£~s †—«¯«Eƒ¢µÂÂ>“ð³Ð¶e? ‹eùÕ$’MŸÿ–HF lìíÁ;$„Còè{D2Uü³9¯/õ[×±#c@Û Ð×#ÝßÊó­®®Ê+ß›«kªè&äøÏubÏ:Rxþò)¹ɸ¹à÷ÕôðE1 2õõu\ÜzaX puöP¹¾>]à±™oÁÂWVyÃûOAºk·ü€0úgàËeŠïÓ¹yZ Y±DŒÁÂ4aÄCܪææ÷R¢s£;Í õYÞø0JC.ï‘KÇÇM <êBž¡¤~ Ëøá^ÖÒ‘6Êd]ÀÆfR¿ôÆ-h”ˆ¿É·]YQÃgMúx%›2äÞõfŽïæM~ˆjkêDKÕ]ŽŽ5sp›v-5> ©8ËNNöòPTØz#ÂCT<î”Ij³F ¿äCŸxû¸‡— ÄuÂ×»ª†S+ΟÍVµ*ÓÓÂŃëÖ¢V£’’åØ7¬)|/¸vé"Ÿ•鸙¨=ÆŽ£E2ª‚!7 =öæÍ:—†O`eñ7wüü“dߪ oà‡Èè$üw3ã?o£¦²Ná_T¨"puu•¿þúkðww‚”ô/¡÷éÀ¥4MÅ4¤ÆqwÇ=¥¥¥ðÉ'8Œ„†z0Œfl=àXø¸ma'Ož„a:ThdlÈmiƒ/UÚ44†Z¡1Aìu+oÏñã…Ò÷áÕZ·aVA˜Æ>ó¬äæiB>ÿÒO¿ðYùqÀ´{åyu3~áøwîêyüñSÝö˜>c - ïϪ¶SQY¦PõÇåï+,;Ÿ)}ß:{þª3fØ]E˜í0PÃé…©5ï÷Âú†žŸ|ÏãhˆûlF˜ý3pàèV…ƒXôý Ëø‚Ã'þá³²cÓG" ðõÂNŒišV5#eL†£4äò7çô“™èD$tÔ…ú¡r!èÞ;^%ó½8oG:YÛXÓ"„iO^©ok߉ê¼qš¹•åø¦ƒ*˜ÈÉĆ\"€O¾”×óÞmÇø"vÔw•/Ó@ÙDµ)˜¯¿'§-þû¾t.WV.µiaDLóõŠiLŸ3îl4Ìxb ÌzRÚ@×B²âøÄH¤Æ ¹ˆƒc›7¡>»)Çá øÃa.µd<“2^Ò¡üÂÂäm 3ªrƒ©¥ÒY§N ›€ãÛ?Lż¶ì>Ï}0¢ã»ûú þ9£+½)Ó©ø‰*¨bccaÕªUðÝwßA(÷á¤[ÖJYìV÷â$†-µAÕÞr|’áï¿ÿ†ª*Å]mèÁ ª–{t€=]Þ„|Ïΰ|ùrY|àî#:)²KW¤ž“¦cÃ;ê]; ²ì&jØÎÇÏú‡!”S›çmüú+îys<ú‰¹¨Ž2@6˜òàCpß‚ÿÀ«,‡/ŽŸ'5½¨…í¶ÇቮœK³C ˜½uæ¡ìø×o;6EPS]‹¦E<“ù4jR?>+yl‹=U$•˜PÎ@x{¼Ô"û¢8ä‡\ÙŒ2¾Ø8F_W55õ26.žËA¬ô€°¾€¥à%ŸŒŽë«¯q™K¿BC.½YX»˜æØÇa %ô&NYgN£r©Ø·^xéàÀy³¶”‚¢›ÇAt ²›ê*ËÊ ¡ÿÏùød<"Ë·ëª[·B¤¼¤ ÏÖÞa ~`Ÿ•/Å;Qa€=”lÀEbè2JOË ºÝÎ-á–´nŽªçXØd(..–…YPµŽ&õ¢rÖAÂÅ_Dÿb3W@Hþ?`Q‰ ŒmêÛÁv%¼ÄÅo» S¦LQ¹) îÙÎÔmÈUämÏó@oWÍ}!ií§Üß© YZYÁøçžHtŸõ£BÈ]1=C¼îYe=¶–áýYÕ6rKnr¥šWqØXÛ‚»«j¶¸ä:ªÄn•—¢z hžô‹ø™bxÊTÍwb`-ú…¡]ÎÆÎ¨ð.˜ýÂ@˜4§Ü77 ^z{ \Ì:#¥Ædmd€Žýá›+ ¶}7Ôê¾Ô- Á•|±Ç®‹s³ E¨KòÅ%x4]ΰñ2`”OÍäæ¼øÝßë1ÃVÜ=Uó: †aúAÀ/¨eOÐŽ]ðò²ãíªŸ·£¡õ½ë8qÆ$O;.þ#E &H eÒEŇš8š^x46äfQj¶7Ãl-Ú ŒÞ,ŒŒÚÁÉýÍCÙT‘)ìk“¤7W¤ãøÄ Í`û¸åûÂdÍíD/¼w8¹á‚tLÜ’|ü%eÜéЧ¯° YžôÓRjƒWªdeË«,g¡{–ÏjüHbè¾ñÆðÏ?ÿÀìÙ³Á½®H¶YW·ô¯j±ñYS—{ÄÙølÉÎh¬‡tãx•_ýó¿™×÷€óßCÊñÐã̧`_v±í#´q†]ñOÊ\°ÄÐôo‰păV*Ý(ÅÆX^çB&þØêÊmZŠW»:†pNžÝÏW—ƒ›Þ ûtäSÞ¢¨­0`mÕò³“V:Öa£!AøÙ¯¥X©'¸ëµ¤¬@6ªšr¸”}6ïüC‡#6®h㸵µ xºû°“v`/raáÏ+ñ‡Ü~É÷‹¡cL2ÂÿX‡0¦Ã€Ñréx3玑<+RKk&Î*©K ÉCÀ¼ÿ͆ù= >ÞùêYð’ðÒˆï|9@IDAT¥ëEÅaoÑ«YMd)\^NÓ$_gêCÃeY:¶.Þ¹}‡W3¹c}]šåX(+6®7Ò‚Ø„p!dù£r?ªÛ7yÂûlA˜ÓaÀ(Ínݬ¡!ܬyÈIíŸÐ-Jå3Ý! "cƒ!42C|ÀÂҢźíÂðÃìõ»ñn]²յ▹¸5otáæîŒÊ/ÞÝh Mœ=Ž=~B#±Ç(™&ï­,5eß¼$^J‡Éšl$G¤¹ÔFsÍš¦›Ë¼˜&ÝAÚ«›ö¨G•8Ô§#-Ò&¡!â;GÀ\.Îö³oÞc§ ÐÛX̵ãZn«†úz4ý.C°!J€[œM˜|B¤¯Í¥.Áâ'áÃ=ûà¿›·€þè l£¥ü•Œ QüÒÎɪMxá%T}DzVTÞÄKF£ºá¥cÂvF>‚¼¥œqû篫4lØ0XðÔ“àË ÷ª«œ‡î{à[xD£ÝŸ‰ºOÖÞ5Êp¯ÑNZÑXdµF +W®„üì* å$ç±Ýж%^] ÜÞ^y娫«kqtIÜ„iÿš¿„Ðèó´G®O(þðOO°çØqHT™ û¸s$L÷ÎSõ–÷ h~öõ ƒnÜßKŒ}1 Ì{9']á°¤6©ÎÊÅú¸«‰z£6JË ÎÊ=‡p‡è¦û_xp,’ïG–´Ç@n~ßôõ¿oj¯wýµL{~g]Á×£pdßüò_hl¸½Y®J¾Í-9¯6{óùoäÐÎdH?ƒÖÖUÉëL\û.ê‹ÂÙW™AdBÀ( ¹™ç¯¢Sàå놖± ;'Ç¡L6˜" ÚÈX]Y#kuÿ?'PëO¼:áöT¸€S©¦û‡'ŒLHˆKŒ@\â+’1Aë°áBU˜b:º? ~Z¼¶­Ùßâô*Ëñ/´} d–âߺyà.’èHH^Tˆç: ¡ì¥EGÃ1»nç'vlGóvñòâ6ôrD2) õ²ÈëI­$áË4q$}þÈÔTï‰å8 ÿ+«¼•Q·ñæz“úù—.Q­jVp1}]jª!‚óZuââÇ_ÝÝÓ¿â:½£™Ž-š~‡W¬X¡™ö4ÔJ¯ñãQKaœŽl G®ï”ÓœwycÓ3 RR‹{”cОþùkEtND:Ä oJ©¬{¼ƒÚµyzd³2CH6vöðÝÅ˲ïnÿîû¿†0,63e@Ù}”{ ¤(ïz–B}ƒø”¢ç-á}¯ ¿«¶ Œ”µko‡ŸH/–´Çm´÷÷‡rÒ^ïúkYÙõOjË®ßi‘ 7Ô‹¯}IE&T‰öáÓv¶@âmóéžÁ3ù¬ìø×¦¥ðÓŠÌÝÕalmÄNUÕ"=&0~ŒÒ[z£yi(9óÞÃ/¤ÂÓ×)\ÁÑÙ^%¯ZTI ¼¬R´A[büU¶÷ÀΨ§Ã»O#lJ ë"~À‰¡ŒØü\C"pÌ5"÷òqã‹ÙQETñ$W±)ƒP«®ªÏÞþöqGJKÊ!ýL¬úi›Â±UPF\¢¨èA\ÙÃO~ û`æÇ¹†~zý54ñ¡>„0ˆW˜Jòó…P«yÿÈö¨ýÓÿî„›×±lâ‹MaäŠÜÜ„IÝ Ïò.\Vzþ¨ð.H‚Ä_q²ºL|,`2sÿE0°}$8×BïÓr¶ÜF ¥ÊÖÒÓÓ5Ò–¦‰LÄÞ¤]²1 9AŽ½Ò¾h[WœûdèÙ¼³³³•¶E/ϯ(-Uªoì…ŠîCÂy9páT¥Çqç†ú[U¤ËäŒÆ@Ž)~^¤7:•~ÑæçÓìEïá†NH\Q>ݪÀÞŒ¾M+mHLL[{^Mv,cžˆeà½/ž‚és»Ãø‡ceÿ*9—¤£'w¡æº'¦ lÊ€ …)ïþHAÊž˜7\´:‹¯#õƒ/3Çã½Ot•_wäú+º¡Þsüª ß"Úî‚ ·zã0¡é!}Öo_†d¯>¹atŠÃaª~]ý_ÄŽ&Ä€Qr-8Ï>Yr¡¼|Ýy(:’0?nX(ÿ·ø7D:ÚØÚZ£f_}䄽ýܹͼp<Ë®½â‘΢2„M ðÂäåë!„òü³o‰=M’ûw’—³Œj XšØ‹Þî-GE¿šSK>\Á-{7ìÛ~éÇtC˜>~Ò×c¯ü±…®Ç°y1PSÉ…ù©­E“õ¸tlWz# ~‰5½!•5¾w Æ[ ú÷G5xõ„m9c»/~!õ D:$ƒ:)ëô)¤Äm¤ÖRzò+n³1A*ÈÊTø‚!PÓX–7äò vïÖ¶lÙö·k ÇYé‡e^WÕc…½Zh¡'´pä£ÖòåËÁ®¡¢³þ©/óêõVöðúë¯+­ëìîŽ>²‘é¿1¥ ˜`a@{ü!†Ÿ¢·Ùa÷‘£xÈŽŒÆ€€eå•ş̹zAÐ À‘ã;Nl$Ž?î+Z MŽÇÕ?c²]åÅJÁñ3{ º¦Ù»pó?¿+Õ?‘¶•÷I±cQ¡‰.û¡íOÝŒ0¹Ö¯æ ™4Hx£ ËÍ)_S[ 5µUhʧÒ!¬ oýk…ÙHeêü¾Ð> ‡î«¥ú#ñžét¾ƒ‰£æðYÙ1õ$þC… -FiÈz$tkùåPg§ï°n¨Ûê*ü²O6NS%ÕÖÔ©¢ft:·8eaòTàeëîé*T“å©p"&1`j¹g³Es$‚šê:øò½å\ÌRì=wå|¥.”7LqîlNŒÖ2 ÈÛ½¡¡^i“—²ÓPùþ£[Né3VŽ;Å÷”çIætZ“÷nU5~ÇAJðxõ’²sÓ* +`€ö=¡Ü˜v‡ºoyºû*hÙôÄ];aC.ý¡áÅ·'¡I»¹x!Ls Í \+ÈÍø×ÕŸŠdŠ´. ©`g‡=¦I]Ú[ûžUò&Ó.àçpVÁÆÆV^.ÌЙŠJòá6õ· ÔgyãdÀ8 ¹®g>1F€ 'Ûh7…ƒqp´—Å´”R ›« Óö¿•ß „ºÆ’¿}û46`C›£~yççB<®ï™:&γŸOÏ¿ÂÚ‹l¼>;J3@h —ÂD;…e†œ—ò¸Ž—Ä[üÞp5§@&&×[åÝ8Õ¼ž'W[YJè.þ@Ô½OeUX™2°ë7lØñÈ# YrseºÇ)Cnç” Ûhm'å]K·ÓsLó‹)_Ös,–¥nÚÈ©t<»{7ÒkÉ`È+Óz›¾YÂiýX~£õáêÝ{,€3ÂÏœ9¼Ê¹˜½·•P ÊÆUBª?ѽo¼ÙbçsçΕéôHû¼E]e u®‘Ðhi sæ`OºŽ“öVË9{–V1+LoxF&ïàâ jþ·Â¬ˆe“5irwXƒæíì„Wu^ÎÆ¿7•UeH¿£À#79q*;{ሠoÞù’''Fx`ßñï;¼ aÔcàzaÓó]‹ö^¤ËMÇDtFS̾z^Ž×oû 蟿½yŽåz'WÆÙnC8:ݼUD‹â­»W ²gùa<3û]>+;~ÿG³£ÅWËÞBeCúMFXœÅÏš55æýQ\È©äÊ›q:SÆ;s&ËT”…UÐçIòôÁ±ô®ØÈÛwHW¡*?€¿ £B#·%–¾+›Ê¤†Â˜i)ÐwhWèÚ3N¡\Y¬ ÀÎÁÑPUY°±€ëyÅh¨NÜG_üâOVý´N΀ÚêZ¤O¼“mmmŒÎ.Žàà ž.àìÌ£çB¥t’0îÒõ6/êkjЄûNž‚°x !ð1r³OqžŽ‚Û³—i&ëLÆ„­*òöK}P rΜA¸%@ÂNSP”øãˆ°œÏO~åU>+;^8|ae ±¡­_›—~ ×2/+S•,£C+¸zûÈõzè!Y>°°mW¬ðï°¼=e\½±ŽÔ0È3 ‰àPÇ4ênI©¨,;:J¹¸·7)¯paï»_a× ln ]LŒhÊ,\(’1c€1ÐÌÿ®Ø,iÊí;Œ——Oó8R)-k6Δ–áçMºM_oìXÂÆ6þƒ ¹ÃàgƒþÉ£QŸ—O Ì€4UÕå’ßþ&ý{x&??ù‡KÖ7UaHPšZyE©ÿ°ü=yždztînÞ`eiäuõø š8ù¸äl Uˆ“[ÍP+*ñJ¸äDé±]ú¡~jëªäž´%7›”x…“žã³’Ç@|þÿXû…¤/FeÈí!cºCçö@<8‡Oìc°Ì;:*~Y»wöH…ãŽé†Ê®d_GØ@ mX3±ø­†zŽìíñò‹Ê l„2ÔqÓã:whüÛyÃ}Ž© óvm= ?|±5áÉgUIÓ³žs^˜OλW•*LÇÌЉ~xPl¬ÒÙû†„ òw7;£wvpQíúDµœÝXœù¶ô -þ½h¡ T,ܹ¨H¬^úe9ýÀþkÖVUÁcq1ðÝ‹/ÀêÞ‡7‡ƒÝËÕ3Ò¡x\Ò9‰k•o:qÆÈtVÜÆ;ª¤¡C‡Ê·²B⣻ ήÝütï$m|*ÃWÙ-lŽŽLª(\”1pøÄ?"¤_8&)?zj’wŠë‰°©úYŠŸïkï‰÷ŸyíéŲbKfÈåi’ëê¿/ï;Ò²7ý7¿üGÞÉX[‘ ¥íDN.`gë(×'ÏÓù׳áÄÙ}rÉØP&¢Â»`ÞSØpû÷öŸ¤Ô˜Ìˆ0*C.Ïs8¿òË?çÃÔYÃy‘Á­¬­¸Ø'â¤ÐÈ¥cõå¼…ÉX—¿ ç@çíÁh Ýð#ZŸaÍ0`O{äV§GnÚ©LDHïAMÈ#'÷ƒáãÄÞŒõu Hä¤þ3ÀP—ÞPùÖÚuð͹óðîŽðøg‹”6ãÒ´c5¯t#?O–%^¤ÂdïØü'”·)OŒ 6Q ˆT×׎KÞE¼ Œ¢1ݳù†…!ÜôÀ,¤²ö³O¦ÁíÆFx¦[Z ¿¼9_$S&¨ b·ºÝ ­À×éÑ£&û[u´nch†Vuª RxçÎ J¤Å$Ä‚{åÕ6‡—¸ìßÎpÞÜy“JvNNRb£—ѱ©IxuÙ€p4·™â6´üâ¨N»L—1`N ÐE„Ä+œþñ³8Öûà~Eº±í»"ÙÎýkåt|½D,L¾Œ?Ö*1ñ:æ~<ºM’‚šZñ&PDñrv:ÒSb´GŠ&¼<üÑlöra<Î]<ŠdO?ܼœŸÞˆ½žyä™óG_B@{ø Ëøü®ƒëø¬ìøÕ{[¦Ap`“ã"/ß¼ówؼ;&ôKÅ+<øá÷¢È¿¿(¬Ä ŒŠ£4äÊ¿²Z6׎ܒl:Mz`-a:–i~n¡HG‘àÑ à¡{æÃÜ©ÿ…×ÿœ‹#ý¢¤¨¾.ävžDÝôJQïeUf@eì°‡]¥1r9 }ò„1‰ë ÓçŒî‡(âÇÛO±w¢H™ J ÞäŸoh((3ˆ’&|8a*½v êkk!ùdÁ… ²uß7øò¶É÷tšð‹´áŽý |t³ò‡O^™Ž§›8x_¤ÒqàýØ[$ë$¾gÐ|ÿê+\Üulçu~|mŸmñHÿ¶ðÞÖ|EÛ6x)óm8Ôßâ³z?N}UunÈ`'Mš$óÊÉYߦ±_ñï'«¿bÅ •Ûþ¨\ÉÀ+o–¢9ºjÞûuÀcÀŒ¿+¶ÀÑóõn‡´Îgž’áüëØq :¢Ò# O†hË¿ËÑsª••µ¤÷]»ü5ãÒ QÛL€Ⱦ’´rÃ7ԔƄ%z(QÜ3t&šÕÇß¼€0ƒûN”Ëlmíåy’)¯äB*™yÚ}ào… dæ¦),#™9øc‚¥ù½Á!Yèžzø$ÚÄ…jI=µÉ&Œ|a)@ÞOèà—²ÏH©2™‘2`܆\'xÓIïÓaãqȈ©¥át›<¾v¥ˆ JÞô2[]U ×®Á²Åø+¯«ÏcÖΣGºpqoYÒ>^ØûçFáMíwªá.e䢜ñ)ô ô‚§^›.‹k‹”9@bݲÄÐt¨‚;ܦ|•eøÙNKF\2_??Ñ´Ï|@$ :ôk2¸ñ² ©‡ù¬Òcî9üàÃy²ª“üÃÑ:1°]¹‚d< ÆðÃëÖòPt<°z•HÖZAvvvk«Êë9ÔÞ-žgyG*dü#°!A…*0Û`+ ´/–6PaïK—.UØ¥0¬Qºž… * +pÁÊSÝÁß“ xèlhŒ£c@jYù±Ó»Ñ<È®ï$u <5O¥co7h%zžî¾ä€R4½¡Ô•s¨Ü…ÚL/lÖ‘ÏÊŽç„*=ÿáý/Ÿ…ŸW~ Ûö¬äŠ8ï3JU5• g»|Ý¢²êš $ ð AØ@ïîÊW//ûl?¢!"¿“ç\9ÊÍì=²Aé´•Åþ}Í"TW¯ð°v1¨Îîïœv4ltúwfï¡MŠT™Ü0JC®ÔÍÙ¹ŸýÜ$YŒÍî};‚ÓwÐ=ª½ÌŽœ„_ž3Ïc碹.ùPìݲÇqEê#·æÂP°¤}CñhA~±ö;ÕpçînxÈ7w7n6ù# mBâÚ ·äw£ç±Ëc@S ´õ^$2äjqIyÒ=cPìY;;°wvVJET÷î¨\Õ Ï®_¾Œê…Æ7ÅEEÂ@ÆÂ‰ãæÁ¶à³²£5å5K~—¿‹½P51äVÚy©QC¬êX[¾\`}¤§¿ùH¬UòïÓé­Âüùóeõ|ŠZWŸïôhìc²“ÔTév¼ƒñ wþÅ‹|U£;’ظÏtï ¾³Ý‘r 04É€Ôý™6ªL5GÖe| ¾×¥e¤BúEüþ¤ÈÃ78Pùs¥›‹§ä´b£º ùù˧¦Ù¨*ëJ:<¶þÚ¼¾Zö&Ý0½}Tèy qµ‚ÍÎxšÚj>ËŽwpuöPÈE8VÄÝ?ÓD„âçµ,fÈQé7«7|«ã£§w¡²çæüaEÀZ% Ôë'#(,æ§ŽyBáï?#Ì€q3À ¹Z<dC¶”I2ƒÒW\Lß™Q©7RO˜ÈK@cãm¡H”¯©®ƒÜÌk"9ÔÕÖKÊ Ehie”—¡¡Ð§ò8ÛaãÁõ¼*×5ÅœËø«Óãœûê4è;¸ Ìyn"<ûæý22‰Va˜1 7ò.à˜³þþZËøgŸƒ¥.Ébú~|à||à`‹}P›5•нa„5ÔÕ !¸Kx# 0uÞkriž/¤9:Ã#<EEEr9ɬÿü3„'r›A=°ÌvpÆÞ¶.ͯ®nzA<ªÚ} Jœ« „ÚøNP¬³¬‹§´qA•„q1£óÿQEU±·¡J |ö>|…*~oÆáC|‘Q7|ý<U”÷=™Ä‹Õ\Ø`ÆÄ€”!7'ßsûõ-›R _šZIY!|ñÃëH6cÒ óÀŽZŠÎËùcŸÒ±,;Æ$ó*²ã…»áPÖnY&@MÙEß7ß'E…&& 1„oßi~&KÔ=ÝýÐ,O¥ãs—°!ÞÍ,åŠ&žq°wâ6ײ–œåoü!’·G2eá,¢‰‚ãgö¢™¹p†ñ #g#ÙÞÔMóàÐñ|Vv䂱W ’)}’F**‚icç*,£ ⣻!ÑÁß*`À(0J 𢝢Fy$M2‚†B~~¾ä¦g]†âýÎîÞ£¨ƒ”ß*.†WôƒµŸ|,9¾Yï¾m1¦K6Ê„ŒÆ€œ©wÅÆÆ¦ðs¼’#·C“F=66v¢ÙGR¹tœaQlÛ½ÍpPïñÛ{Ó_/Ì%Á+Öd£‡ÌDX=d†Âbº…Šw øß9^ïTzËŽ¼.;6–†=<éÑÑ/ÃÒZÆ-õõÇK!NQàÌôøÁs '¼qŽ ¥PQ%Ee¨WwåK{‘2mb 0ØÕ/Ì/AØÐA&[Ùš3H±ÄÐ'êÞ‹üÂñ‹Û!*¶kgrÕå+¤CGTeߪUÓ`?“6¤#®Oë+Ã1={B­­œ ç"¦Âž.óá\»ápøHªÌúꃢêq}šâË“øA<€ÊV¼÷®hyRP*9OäÕ«WC¡k4gËUü®¤ Y‘}YSx€”””–T º¼[·&.—~oÓ8ozv’ÕôÑGEíÄ$coµâ«WD:†( ¡<6û-¼Ð«”pFj:FG÷ç/B¿)Sé"†Œ-2PV^‚î6Ö¶À{Ózy`ÏN)£*¯+5Äaî•Ëd>^ ËT-8G…yÖ#«6Í!•–áÕ8Ä9kêKhêׯ÷Óç°±j@¯¶­¦Aˆ¢6é³à¼™gN~^r´Ç( éaÎ)Š]ݳÛPðpÃïÓ·¹XÚ õâÕÏôFh´'¯2^éß$^×ÚÚÈG uÒ€žøÚß¾[±óŸ:í2]ý3`”†\©¯¬ú§R³#{ï@Ôà¦U{‚ï>]-„¢üÙã—D2} Š®cã¡3äêìTãÅ’NéÀé:H+;Ú³õ(ªÙ;¥É€„ 0 ˜àØX¥£ íÐAi¹> ‡Ìš…ºÝ¼Tì$TØð5ö@:ë!a±Zùño¼)Ó/ðë%¯WàÛ vwyÊí}!³¡.· [ŽN²ò Ï5/}6/‹% ›¿U>vy'Tf„ œ¯…%¤·ŸN•¨sÿSùuÿý÷ƒ[UG€Ø EV.ù€ÌÌLhhÀžrê´a(º5UUðD‡8Xýáû’Czñç_àí›Áža% `BÆ€ ÿΊnà+®‚ص-}”%›¢Y*ñº:æñVÍŒö”˺"í´óÓŠ¶õÚe…e¦Tpðè64¤Îe+:éóÌpkj°/ 5fâ`þ³_ÃÊoOÃwí‚ñÆO¬1ñknz·*°Ý".ª«¬qGû&o~¾§`N·ü»œ/’gå T&>ª;%xñ±E²–}©ð.ÇÓöµT…• FiÈmé†k$Ü+f—žx×ȪŠjI}ò%öà®S¨l.·É*ná]®+|îLê*&! aЍ(ÇíãC©29c@' ¨{/jׂ!W'ƒV³“è$ìY^ŒjéæÊ (þ®—,ªRâ7òjœ7ɱø¹°+á(s ‚"O/¸ +¶lAÁá`OÏuŸ}*oB•Lnn. <ê9O‹Ý^T¥ŠbÛ `_ fÏž­XLjJî»ï>Ùh½ŠO´iÔWý›¼¨ÿþûoQ;ôÆu7©øÈ¢ z”\»OuNôÊ ïœ_ŸM‡¸^½õ8BÖ5cÀ¼ ïÏS±!ÞÍ];ô.ò´®³³-’aeM…^]‡¡z»ˆ‰BÆåãHON¤íB“Í  ¹z•ÍupŸIhÎoÿ…{¨‡ºúÜ\]ðJWA¡ÉgÉßñ@÷öô‡ï}‚cL~Κ˜`AÑU…Í<÷hs¢ôÃïï!Ý_VãçÍ'ZˆÊU“îÁϰ¤Nr—ÁªTE:ñQÝ®ªRÆ)3`Ð 0C®Ÿú!„Ž/K†~dïn—æe5––Эwø$'†¾I£@Y®qÞ”;wì€<?™þÿ¨ävz>|ìô2×~›ÇYtþ?ËydÛs¸™JúôÓOÁêv=÷ƒ_XÔß‘ø'eUŽ9‚ª’É”¶Oqˆ)¡ž!ä#aIz$¤¤ÂpØfÇmÈ¥ +b’'Ê -)]TQ,ÀÓÝO$nÉë7È? Õ¡ãÀ’ÂÕ¿E:ÏÌþÂ7J¯!l.€?¿á!xëí;·!7‡tàîç,µ…;m©l´uÏ_:‰Æ>iÔ#rlmeäŸ0]‘Á Û~ŠÁÉÑ Zs ÚÛ9ÂËO| ¯>µ^æKxé‰OP»ê€ñÃFê_ÿ´aŒ“£4äò?ÞÆI¹ê£î”ƒ”OƱ“N9Ê x`nÓR“~C±'ËÕl¼ÜUTQOs9—z¢WÔ­•þ“¯«å^ íøûerߎ3ÀÐ%­õÈurwW8LßPà bw7-?øëYY|K¯_GØÙ£mKkkkQ{ª€JÎ3÷P‹°‹‹£»«Ã3PàVVp“ó Îóó‡ËžÞ0yòd˜8q"ïØI“& ðÑçŸC g´¾á»:>{߀>âødªŒÖ ÏÛ*}òIëÂé6 s^VÐ)gmÛ†ciµœÇ31 SxB'!„k—ñŠTh €<Ó¼¿{¼¾rµŒˆ ƒ1`ž ~®ß5°(c¢G’×µÕ¼ü}Å÷ð1Êï!~ÞÁ|7²#½![Ù­PU]Žtú&D˜€Ê*¬#R0qALd4÷?™ƒp¿£f@9öxÅã­Š›Ê+˜`i}CÔÔV¢™yyø#Ü. áÓ‡exùúÅHþòãúöÒoÓµÂ\„0N¬qØÂ›³1Ž_Õ1÷ìß Öü²C®ž~a\úé*yÉté+Ƕvø+)().Ooé8NòŠ,cÒ ¸{¹ÂÂær± oÈ//Ã1ž»÷é`ÈÃec3qZoÈUüûë¢xY§¾éì3yl]ºT>Œ?-ƒè$ìÍD nÁËì#ºà—+y*fˆ‘°ÕÉ‚«kç ç"§Á9a# ÜoI#;徭ü㌈`íÈyÝ:µ4—o¨„ÐÂC0|øpppÐRš­Ú-;Ö¬Y£v=ºÂÙv#Á.{TWWËy ¡6ÿ»v?Ñmè÷š0~ÿÙ<ú ëŸ1`î E¶íZèHˆÃÞþ¤ÐÇ3éð@J—/‡˜ gÏ7rxyK†\n•Nw ¡±^Ï”È?þæ%Tìáæ ¶6vÞ .f5¯´ÜŸº† ˜ŠtM RÕGazhÚ+0ïÝérþḭäe,Ó2.NîP]S!W$ZŠ÷,W6‘LQq>š 1nÓ¯CL¥¿ýW®÷×Æï ®}W9æ3‰›Â€ðXG?Ÿv²gþý…©«k*[å)¬ñ³>¥ÀîyÒ:'Þœ npo€'j­Q 7?·*©ÍŸžxõ^¤ŸDy-þºd*×5 c&WwüÅOXÆòÚaÀƒ3ä Ó‚R!4È|E96âšËß¿Až 6(üƒºt8¹â—aýÀ¨(!4¨üˆGCã9¾u Â<Ø· \<³m/OÎÎZ¸G£-gà{.ï¥=#.GL¯ô¯Ä“7oO“IçÎ+›Oá‘6ͫܳ#Üá<ß^ýuy;t(:þ²\Ñ20ûƒ™×Îc€0 |VÜJÅÇÚo²ˆ$b •JI]J‰E²¾É£D2›UÝTSÓôÌ{‡ púÜATý£·VÊp÷D<¦#'v"=S× ²Ñ”|¼°Ñ½¥¾^¨>Êp¡6ï»ÉrÍ-mØñ šrdh<ÂŒÔüñ€àk…Ù\(”f§"‹•0ì¹®“¥¥пGWó/ëz¬? 3`”†\sñÈ%çÚÚ{$]<×ä ÿÕûËÑ¥ ²¸¡éȉýàBZ6º—Ï_A]†Dà1*d@+ xxcC®1xäf_ÌC\øú·m¹6jŒÆ@+ ½=TmÂÂRñ-7,!AÕft®ç¢bˆ„òÅhlÑIÉ« Úµk§nƒÒïrî[°k¨€üѠƥÉÁX[[ ±—¿½ÍÍæú&ÃÉ“'QœeQ£\¼aCL–mñ7Ä ±11ŒœÞKb¦VR»´÷ïyhvÄÛN*ŒBïnÃEºR² ÑÚ2Ð?)=ZÖ1¦ý{ )TÍÁcÍ+2‰‚µ•5xy4ÅáíÝmª#µIR0rp…28цYkn³9kÚ»ÙÈ'­Çá;s¹ÂdކÜM;R?°a¤68ü÷^¡4kÊË¢zú |À²±±‡õT,_}‹õÛz¿U¶¾M­×äoÎZïÈ:<¦'ÅßËÿ•ἜB$ŸóŽ}B C"8o#A"¼õu ‰n³Y”A.4’ru{hÜâ¢æ0 º‹ªý9v©FÅît4PL–Özä*#$ŒŠªLWeôT²±™¶S—6†fÐöø”µš·ܪóež¸¾¾¾ÊT¾ìå—_KÙ¦gm»Ÿd•q±zõj9'Þ”1?íÀ~yË0ŒE ðïŠßþÒ¼ô™èz+¡@ÊœœÄ«fxã))×F;|jvÃŽ_exéo ‘<¥÷x9Œ”çI†„c0å”qñ$š^\T7„ ˜ûàÛ"p,©ÇFáfþH¯^k¦¡Ý. Br"c”;,ÄEµ-Ęd§­’$ïÎû ~[œ +¿9 />þQ+[bÕ …£4äÒ/”†B¦6ÆAoêt%ë:ÌŸ»ueï` þAÞHF€çÍË?Èð…Wsðf4¼\Çü+E¨›à0lhF… h…Ú[VR®•~4ÙhÁµÔ\brs,hTÀc@G hÃK/#×ÑTTî&(ÿÝ]>qÕÍMOCØÅ[|OB *€ÄÄD™–uE– Ú†£’÷„ì‡)S¦ÈbãÎÈ´3’„»Þä]2›–ý¶ºKk(wðƒ/¿üRÞDï‰ø#õ®ß±—Œ\‘eŒÆ€€þýgï‘M)À£3ÞDXœñª5G u5•ï–Ð5UX|êêj¡´ ;ìÐã¶â$bº…Ê!Aí…åU`Àß7iå!lŒàBæ)øð«ç`ÂìxX·u™Ò)d\Æœ©˜ÌÂÊ}l’Htõ™(TÕ{žüfÄGw'G½… @3 ¥!—¿9k†Ãn¥eì¼ÉÞò¨X³ >Rá$†OèƒÊ¾ù°/Z¨5õÀ5ÊËB+¨ÇŸ&´=<ñCjYi…&šÕZµÕu¢¶­mp¸‘0´Ì@[ ¹–ÜRtcLapè‡sqì¾óGŽ i…kÈÃØÉÉ ºäl@m2ˆ»¼" ö¨Q£€kÈãÕÔØfΜ nUÜË·Œ¹-éXx“á6;;[ÖLҨѨ¹ ©©3À` 0¤ N?õõµ\X…2TÜ•2œ £#:ƒ§»/Ã-·ôx̶Åy¶­(/圴ä—ÿ u/Ù&gBá°þS…þXó¦ŠKðÆS‘a$§çæâ%’ÇFŽG¤hp*ð A#+*6nC.Ù4ð•…Ó`ÿÑ-\è¦ÛðãŸÿƒ–¼€æ(‡ŽâPQcqø¡nïîŠC¯L5G¨ÊòŒ3`”†\©›žÆ™1míl”ŽÄÞÁüÅ7.¾Ò´‡GðYÙ± _ûËaQ‡pë&6s›Í°¤S<¼ñ²±²›å:í_ÝÎŽÄ^~Á¡MñÁÔm‡é34É@[ ¹í»vÓäPtÖV÷£P_§vþƒð…#x·î¨¤$TÞZðÖ[oSí è–öUk›ÐM½;r|ø•eÀ3Ï<$Ü€9¥3fȦë[ÔFC«½¯lÓ³eË–ÉÚs¦â3×UU™­l®ŒÆ@+ N?Ÿ~û*ªmcm/‹5‹„ðÂcŸì_†ÜÒãé㟔j'kaa ´wíÎý¡Î¹> ê;É.džFØA÷NDÓNê’"’1r|q»‚ÆmÈ={;Ùïã<õ§Ïí.IÄ¿×!ù ¾ÍaMPè0ÂrEá„:,Ïh FiÈ5'\rr}<žãÙϵì¶OóU\Pª°=m,xÎÀ_µ5qk—­pËÀ=r³Î㇇]Ù)»¤Ìr8m1äF÷Ù÷ 0xã{÷Fc¼qõ*ÂçB¸Ó€„[ ’““áõ×_—ÚBè{ê=€êë­mJkõ‚®í†”MqÉÆf&àl­ul@ ÛÚÚB`` Ääïhó¨n:ÃîÝ»eí8»»£öêkk¡±Á´ãA¢ 3À` ´ŠòîsàØTwñ;†¹ºCYÜ^2]£y@‰nÞ*‚ÖnÄ*jÜHOŸ'yû0¼šH¤À"|±Gn¡‘{ä–Þ,Í‘ªk*àþ§zp¡LjPyÙ-8©ó@TNƒ hZÓÆjÿ#¨S&0;ŒÒkN¹äŠ|ð©q’¦5·{éå% tƒ‡3„ÅZÍ×ÖÔÁS÷¾9—ð²˜àp­öË—fÀÇV(/«”V4)½[Xû@†93ÐCn¬„!7²kWƒ§Ó‚[¦ª,ÕT`ïþ hñƒ­²úÊʆ kÖ¬_7'H9·º§sk¤Ì•µ£é2Ç›ç¡ßÉw êڿзo_ø÷ß!,,LÓÝM{ .+²éYmÛ>ŸŠ˜"›sCCëÎÖÁqP^Ò¶öQc 0&É@NÞ4/KK+ðõB2C~Þí¥K‡¾¢ýNˆ²ƒ½’“çb˜2µt)û,š’§â÷'.¦ñŒI/ÀS-„ùÏ.ß\‰ê2 înÞH±ªúÂÆŠK”ü'¡WîÛ ò®7íÅPP„È!Ú!Žžÿ}ž¡E0~ÄC"04Í€ò73M÷¦¡öZúƒÒP7ÓLlB¸äXFNî')§…)#ñ×M«öÐ*ZÁe¥åðøä·¡²¢µO ñ¯¾7ÉÐ Äø/L·oßBƒÊßnÇZ$¡DXb è›¶x½´ï"6Ú¶7C.áÜ͇6É8tPv*ÚbØVõ\ºsž™«W¯† €¯e¤pÆÜ'Þ§Òt€Û ª6Óv½; à~ã”,ŒBræàïå+W®„ÿþ·É#·íˆ[ soS"ÆU.¹¸hwƒ‹ððp°æb@wÍ\ѦႳ¬~YY™ìhïÜ„ùFK¯+1ãõT9Ê9áBc´%µùµ¥sV—1À1–qÉ¢Ã;·hAt’»Š=nùîŸ{ô>+:º8y Ù¾Ô-›8{‡ë‰ŽLT:­É£…!ý&C÷Î)μq•’e…Ÿ}‡Ã« È<Ýñ³ìíÛðäë#lˆ¶jÃ7ˆ•ÞÝF ,⢺Bxpç% ~>Á%ûÐ"¥ËdŒM2`”†\sóÈ%óµ°´÷‰÷ɤ)ñ!—l˜¦ítýj1<ÿ€øáƒâ–®]NÎØÃFÛãaíûvœ@ƒöSb)2À0`¬$6;ó 3à7-iÄÈfÀå¬iŠãwæî2x¾Ð'/Ëãåš806nÜþù'8ØÙBRÖ H9¹óŒ}| i¢ É6š¼oßåB(,„Äœ5@ w6l€+V€·7ö^‘l  BZ‚¤þÜ<=‹«ÞÒí:ˆÎ^8nHJHÐþKíÓO? ®Õ× 9m@c­êcå5«®ËÎ%^^^2iÇ~ýùRÙñȦ· 4HV}Çmdîêa| @îz iþ|q KYû1ÀÐ c‡?«—ž…ןù¬8oÜÿH/ãP¥Ó~I£$Õ\ÀÍÅS²ŒçÎz•ý°ü}„Md\Äï1‘MaZl:bàü%üÌ4}ÂÓ\ì]#± ÙmǾÕhdƒúµ*‹ü~úŸ5°øðÍûÛaÑÿFm0ÀÐÖÚjX›íš›G.áÒŸÛÐìgåÓà{zˆm»|1>rzVVVÐØØìqr9ã DÆc= ¡M«÷ÀÊ·‰Zko|ð¨êãµÀæÄÀ™ãÑtŽâ®y–À@[´œ´4Y~ÿêUrÉ$¾am___ؼy3TVVž={àË/¿„ø¼-²¤¿ nÓ¬2§ Èw‹J×HKå‡ ÇhQy‚Ë.‚gy¸V]KÎ —$ö±Çž†#F€£££°ŠVóžžžðõ×_üyó SîzòOÔ½{w™'³UZ­:vìX™×Û'Ÿ|)$¦q+Ó·ß~+¯9`út8ðWó˹ަÍ{M^ÞÖ̺uëà7Þ‹³G!¸ø¨ZÍ‘sóþûïËÎjUfÊŒÆ€Ö ïŠd±äİú»¦{•Ö:kcÃn®ÒÆÚ)cžPÚrÏnØ¡§¶¶R©¾1_EÃäîç,1T` ¦¶ ꩘ú²°ðþÀïk¾€‰Zºs¯ê†Ê` ÌkHgCÉXÞ]òœ¬´®¶òr !0ØG‰¶¸¨× Î°o{óW©mëÀ±ÓÄŠm”|óÑ 8´ë´¨•~úÁÃÏ´üUKT‘ ôÆÀ¢…¿ƒ­ ØÛÛpG[¸ïQim ðò¡¾/—öòqÕFW¬MÆ€Ú ´%”€ØŒ àa$†ÜÐW×33eøêùóHß§ÂÚNNN0räHÙ?ÒOg\&Þ³gÏBNÎ º½yÔ ñºMèÛ )))z]– k×®• ÿ:ZàÂ… ——$ü@uu5k’|°%fb\ …:€_Vù·Vw̘1@þ•——Ù3gÐ8IÜ[2Vbd!aìì쀜CâÕLÆLæI§ÈÄ.HTuó&Âm®®®ðÅ_ÈšYôä\8´{ÔÛØAƒ•%„vî !ÜuOVFÙÛÛƒ‡‡‡lS·¸¸8­{b·u^¬>cÀœ0§Ÿ #nñZXXr¿©ÍƧòŠ›àâìÞb=¢0þáæß[bô^ñÍ)™÷²J•u¤T@m´®£žY7Bøû¶Pfèù+ù—Ñ]œ=ÀÚªù£þ}œwn (|¶ô¤'$5KŒCeÀ( ¹æZAxñÃZxTP¤R¾ÏÀ.È{æövT©%JÜ{¼2û#(.¿`=øä8@ÅéUÒ+Ò2$NnCC³w¶Twd4âuXS]+ûGtJoÜ/ÝSI|ea²±±Û曯°Œåºf -†\â‘ûÝEüp©ëñ·¶?7ü±±¾^ÖTI~jÒ?B3ÄxIþÑéÔ©SpîÜ9ÈÍÍ…¢¢"™ñ³®¶®¤+.·mcØrر¶†óÀ½cÐçÈßßÈ?CO$þlïÞ½µ2Ìúš°á «šNç÷íå®.$ùÇ¥§ž|Úwe9𿙵ÇÐ6ÆfÈõå6<zŸÆµWíw'Ð/ŒÛ¨©é£*á4ãÒ Hâ¼[J?­ø©4r÷À³G s|/$×7 ›Q “¯—úïÀÂú,¯n.^PV~C®\\r |¼åØ2t¼ÛÎñâç‘”^c!:¢<õÆ(Ù{¯p^$î-KŒCfÀ( ¹Ævs6„ †Ú0­º²Fcê¯k€'ï]äH§…_=A!¾´˜a=2àßήf_W:‚mkˆÊI8Žî}Ä‘¢é§°¡KWd 5a ´ÉkbüÔp¡ êï½ø©Ñ_^®Ïcgγ’ü£Ó3Ý»BÕÝMµè2† ‡?n#µ‚¬¦]¥É¨ŽnݽÆ×øk«ªP›Ìˆ‹è`€1`4 Û»âGo­„¢«vá(¤? SÆ<®×c’‘!wÏ¡-rïÀX·õQûÿì]mp†\Ñ ™@' øù´C†Üü‚£3ä>±qõÄÿ‡0ÈÇß§Âô¹ÍOH|ê7žýšWaGÆ€A2`i£jaPæì‘Û5 ‹IXF·?~èœB}u æ=ú©¤÷³_æ1#®:DêH70{ÕIu[p­D$ιœ/’iKpô¾62nmQÍÚmmŠ‘ÛŠþ ©J§»›CñcZ¿ès>k”Ç·7™Þ.ßFy"Zt×á#ÆîåËÖ8¼áoԌ㠣Ž` 0ÚÌ€±½+ºr˾ۇ'À¸áq´} Q\^•4nÄCHíÀÑÍKÃÇwÀmA8^gï‘|Ö ŽRc4ˆ™Á ü8qaÊ/ÈBƒÏ×Õ7­ªÔÉQñªRBaí0ÿÙ%ð×wéðû—©àíiø+Ÿ„ócyócÀ( ¹Æö•ÕP.«É³†¡¡üú5~iA…*³·¤/{ñôqƒï×ÿÜ<œUl…©é’–<¤IlÚºº¦%ÓÂq]åb3ë*ц²5cBëjœ¬Æ€¹10òQì%´í‡ïí$bœ"îܦiîF¦ÀÀhÓùp†Ïžƒú¼t4aM€åï,DÍL{c> 0ÆÃ€¹¼+ú…¢“Òx[yè4¢ü¿ÅO£:< «¨Í¡ø2}OžÝº ŠF˜í1@…°|þß_áƒ×€ÿ¾°æ?ùzÉáõΞ¸ÄgeÇ^Ä›É ô΀ƒÂ1ÕmÎÀ¯(¥<°éµEžq6 UïÐ%aúfÀœ ¹ÄkÕÉÃCá) 0cLnÞ-Ç7Æy™Ò˜?ðšNÚ^ü’ Õ{WaÇW^VªfsL1ÀÐ3ædÈíÙu0b;í¼t虥Pr³é~º`-™9éëÔÕá8§nÞúŽYõ-2äVÝ2ªùÓ+çȆf,1L£4äšÓÍYÓܬ¹ãP“+ÜŠ0Š®—ÂÉÃpît&d^¸ y9}1/–p,&ËTNÁfìlŽk@+æ_-¢EÅ79qwY˜B"„åzgÀœ ¹„|?é lÀÒÊJïç‡ À4ˆëÕM¬ôÚ5„Ûþ¦6í›õλmiŽÕe 0ôÌ€9½+vŽÇ¿W¯]–dÁdz‘<È?BÛḳwàÔ7Ô!=}úlÈu°W¾"H_ã4Å~E†\#òÈݱw5:%žÒϬH‰Æ€2`”†\æ‘Ûú+­S¾aßV÷tç¦C¢N¾ô-’J=°­5 &öJºHÄaÀÆN¼š¤èªfâöÑg¢““52fÖc€1 Ìé]ÑÁÞ©E’É&hWòñ*ÊWŸü\V/*¯¦\òóZlOÛ uu5ÜJÐfg!K K°·sÐv·¬ý» ¸ºÒ¡ŒÇ#wÓ?¿£ó8eô£3À0ŒÒËŒ;m»üœœñ0ƒóº¥ÓÖ5hŠòfCÞú?pü™{¦ÕaÃc@™G+hÀ#&ôE˜\õõ H¦)ðåÿ–‹ššúà0‘Œ Œý2ÐoÊTÉ„uÂ/ƒ’JLÈhtx…gáp ­iúìžÝ¨š‹7[¾‹a€1`„ ˜Û»¢¿o(:K[w¯@xÞ;â°!AM›—Î}`ÒÝ—ºa}€[•xO{ŒÕú§©öéä€C1ÖÕWÍTsó/ ±&vìƒ0ŒSa€rMåLª1Ž]Û#íý;O"\UY#3Ú"á]°}ýA¹øZ^±ƒ‘ŒÆ€¡0@/Ã6”qér$.bzô¤E 34Ê€£«+ ÷øDþoàÍ{ø2U©7 Õaã8’¨ÆcÀ(07Cn‡˜$t^n•7BÿÚô=*³·sB±qílíÁÉÁé\Í—Ž³‹”´Ξ?‚Z G˜Æ€‡ŽmGb[„` ˜ÍOÃF4+fÈmÛÉ B ÐF‰£r\ɺ7K*h±¤—§H‰ ‚K ìeKu1=­S÷hîÙró¯hnó"λ÷О3¨_[[k˜þÈH$c€1`H 46]%²kW]uÅúa È ÆïvÁrL2iûö"¬ú[Šnº÷©ÓÓe 0 ‹s{Wôt÷Eïauõ5P]S);)®_ŒNÎŒIxe)LìˆC©ý²êSTG× õä.Ôeï¤á3ÀbàÓo_Fâ¹³ÞF˜Æ€)1`”†\S:úš‹—;êZh¼¥—ÎwJŠAº¿.ù~þ oJÕgP¤Ã€a3 å‘{«¬éyûø¦x[RºR/¿|=UÄ«û÷o7‰Ôg?7Q$cÆ€!1 ‰ëßæÓš±ÄP›A¹xyµ¦V‡1 6#yÕYùÁû«¶}=ÕüÂñcuÚbºŒÆc@_ \ö8®iaq>¬ß¶L4¤{†ÌÉž|¼N¤µþ™¨ñVÒ(Üž]‡¶¢VÅÜhhÄaú$1Ç s»Ìi¾FiÈ5·¯¬Ú¸ GL胚ݻý˜ ?ŽäÖ6V0eÞpêBZœ9véÍœ;a ›k¥$„VVÍ?vöxiJIQ™Òú-Þn¼ _þïOQ,æ™ßt_-µÅʺf€rÞ7^Xö¼øó/ðÒ¯¿qùŸu}XfÊÀ€{ñ¦=%ÍKˆÕ¥äÀÚ5¨ÊˆGE˜ÆcÀ80ÇwÅ® ýÐÉZ³ù;øí¯Ï‘lâHü!Œ/ttpæ³òcyÅMy^×™Ûwn£.Øfgˆ]oÔÍÕkâÍÑ‘‚žAaqµ•5ØÚØ!ŒSb ÙRcD³2Ç›³¦OO—žq¨ÉìKù2¼æ÷H>fZ ´ óC2)ÀŒoR¬®,8Â_éàzPá¼¼Ý~~n!Âê‚_¿Ù(2âݼ|±§¸ºí2}Æ€.`†\'wwˆïÓâzõ†X.6npl¬.¨g}0d XÛâ‹—Ž5}ŒV—ž¼ó¨ Ûè ÑÁcÀh0·¹äD=|ï«è|í:¸ÈFgÂ4sò Bˆò!8íÞ#âUs¨&Í@x~®»”}Ö ç{:㽉*d€1` ¥!×oΚ¾Ö¤ fÜž!—S€ºê•’(ÃÓ…äBàòËXÞ0ˆˆj§t`»´Gå± a§¼Œ°:`ÇúƒPRŒ=zÃ"!¡[”:Í0]Æ€Þ`†\½QÏ:f Èè6|bb磌n߯_¤Ž›¯¯*U™c€1`à ˜ã»"‰“«,õé>ÅÑ¥uûöÀïzÛ÷¬¢Ut‚ËnáU66ö:é—u‚ÁN_—² ÛûçÚ¯ÐFÄ«wP!Œ`€rMà$¶v A!ø†ÿýç«E^’>þ²æ ï®°›ðhåFA…YÞ£6¼„x¼;»: EOv¯çß@媂Kçrá,ev÷p†ñ÷Rµ ¦ÇÐ;Ì«÷SÀ`æ ô›6 1ºa#ª€e¯¿†ÔÂ:uF˜ÆcÀx0GC.9[ŽŠk{à-¥'tÔ ûPyVn:º•UåðÄkxc³@¿¦=;tÑ?룙cóÈ-*Á¡F ¼·y2,Ç0AŒÒËB+hæJœóü$ÔÐþ'ŽŠ‘c:A‘çíèÉýåz,c „+1人a#.™‘µµ•hb­1fmþkj‡ËŽF€¿§Â‘Žž:@²ÌÉ?à]ÉTïá®¶¦¹M΄éÑ&¥`S5aË3 •Ö|Ä0Ô¹°q1Œ•{gìy¶o•êË€ó.^€;Thïvlu‘±^ lÜŒÆ@}’pØž—>ÙÍg•{vŠÊZ¡›\GNî„§ç­ uwõqÃDcb@7 ØÛã÷¾Fêž©›Q¨Ö˶]+‘bçxSwTÈcÀD0JC®¹~eÕô5gckN.’Íc¹ÞLDj9¾­d}&4^|ü¤¿Ø·Á/¹Ò²ÕšdUe Ò'^ÞN,î"…£`€râ4±Aš8“^~Ípë÷KVÞ™8{½R`€1À0*Ìõ]1¥÷8ÑyòóVWXa$^áLÆa±Vò_ÿ´Þ]4WÔvrâøñÓ="9è†kkl¸s»Q7·¢—Ã'v Zý¨xϨÆ€‰0`”†\æ‘«¹«/¦c¸dcÁ>’7ýý~r?ŒQ!FljW«(цü¼ÜBEª’ò‹é9Hî Ø+)2À00˜!×ÀNŽY2ÐéÿÙ; À¨Ž­ŸÁ nÅ]ŠK‘R¨+-u*ô«»÷U_¡JõÕ^ÛW¡N]¨P‡ R(…–RÜÝÝHBøÎÜìnö®$›díîþ¦ {gîÜ™3¿¹+÷Ï=3èHÛ¸7._îçÍe«àÊLþìSñ Åpß—cU¥ p(d½VLKMó›±›¯x̯,XA‡6=l»¶ï,Ùo}ÛÁÅdÌo©»9_~˜ð¡_ÍagÝ&÷ÜøBÀkQ¿ÊD„@zšÝY+?Ž…Ü5ëí‹p÷ïubD˜Ð(≀ÿ§}ñ8Ò#7Y—‰Äé—YÃcÎÝG¯ÃuoòšDzìt´< Z9À޽»³l¥MZ6°åÉ@À)r2SØ™èª×«gâW/6#ÝîÙºoß^¿.¾ýù]¿2ßùõ«Æ‚?gO´µvÖÉWÚòd ¨)äòåÞÓñÔ¡ƒ¤ÿÑݤzcOÜ3/:6¼ÐZ\¨TÅþåÜ>H¼doã{ô·‡C˜>iŽ÷î Û[7ï°í«à³žm'Ä9hþ@s˜˜hв¥_ÿ¹99~e¦`ä™§û‰'_ã¿ÀNÀƒ)„E€kÅÒOW¥ŠUÄ÷é×m;Ê+77/Gî9T|o†·nÞYÞ}þwIó‰ÉZúpd¸¤§—·5µo¿ýéJó{Ø·ÌðõøÑ¶ã"•9 q{whøïÔ¼I;ï,ÛHXÄÈMØ© }`=û*æ”\ιø™ý×"ykmÁ¸;õj],€f-í1 wîô¿3¨‘ k¶ØŠëÔ%¡ G@ÈuÔtal0bMfݺ²sS¡È°dÆ i߯ŸmÔW®ÕóçÛÊnýžŸXa«@p,_!Ò±‰áæsµ^í&²~Ó Oï³æM•#ûæÉ—fcÙÊù*â°Ú¯ç rǵÏÚÊÈÄ !7{Ÿ]È?ù³€Æ~öÍ«2øØ‹î gáî=ÛmÍ™÷}Å Uled ¨é‘Ë—s¢žŽŒ+šú ê,WÜz–§Ë†ëz¶ƒmT­n_D&g®88ø½w«–mðÎJÃ&¡/¼`; â€BnL&@ÀEÀW´ÿö[~lFžy†­¬Z:Ò¶O[@ qà‘[¶¹<ù˜ l ¼õñ“¶|i2o}ü„í°<×F$þ2¾B®¯÷íw?½Ðè½Ù{–‡»ðƒ/^°5Ù²)Ži6 dš€#…\¾œúœdpqLÀÜDñ£›½×ÅYß!¬XºÎVÔª}[ž œD!×I³…­‰Nàì;ï¶ qÖÏ?Ùò3Ç“¬;mew}ð‘-OH,\+–m>O>úB[;wÙŸ¬³í 13ñŸ¶š—½Ó–'jÕ¨o3jãæÕ¶üòÕö']Ü;ss÷iÈ…lw6b¯?üú¡­í;®Á»Û„LBp¤‹GnBŸ“ .Î TË´{å.]`ÿRdþþ}ö˜…uê× T28‚B®#¦ #“„@fíÚ~#ÍÚµËS6êÖ[<Ûf£e÷îR·iS[@ ± ä–m> ?ߘµKWÎ ØhþÁ|¹û‘ ä¦á§Éåÿ:J.¹å¿zûsü>|ã¯úDAÌ 4kÜÖfêµKùÛ×/^,/^ÌÏ÷”§hŒ÷ó†ðäÙ€—׊eŸÛ°5²7k§-o29¹ûeç®­~åßýüž­lÊŒlù“¹Ð–'Ÿš5jc3lýÆ‚kº]{¶K~¾whƒé×ó[ÝÜ<{X=ÛÎ2dL»÷>q‘_ ÿºö¿2 È)är—5‘OIÆæ5jVµ™¹nõ&[Þ;³iÃvï¬4nÑÀ–'§@ÈuÚŒao¢¨Pٻ݌wö/?Û†}ÁýJZzº­Œ  ˜rË>¯)’"éél ýùÏ[~Ùªy¶¼;s@E¾Uk»³’w ׳m6šú„¶dâ†@zZ†Íwh±Ùó·•תQORSS¥\Š]Z2‚o8ÓyrÁu½UDη5ûŸáŸJZjš­Œ €ýÝæÑòåì‰ÂÌ„%P¯a-ÛØV,^kË{g¶o-\tƔשWÃ{7Ûp„\ÇM'ÃN=-è(Ð;è¼ó‚îg X¸V Ï|ž}ÊU¶†Þúø?¶üÃÏ\mË{gþóòmVÖ7>nùŒJÞÕØv —F?`³ú”c ëW[¾,™×ÞD¶ï°?Ú±MoÔçÔ²4˱p,G ¹„Vpìù†á BÀ7º›'ú@`fë–¢0ME¶{så·ºJ¿f™¥h!|‡LZ¶SŽxño‘Ô9øÄák8HKûò”m~Ùøþ¸h»\÷å©’‘*S®é*3üïûoU¾ÝŸŸ)7õm(·lÄšèŸr͵bþÜ)7],´úO® º%M£ÿÜ(þ¼*¤Ã;¡™œÙ©Ž¼4e\ûÙb©W³‚lø·ÿmHE©ÒM_,‘æn•;4Ü’ž3}V‘χuúÕì+ŸGɺIb\+†oòëv”­1ó¹ºc×V[™É4nX°~F£ú-eÍçSçðÞ'™’ƒT¯VK¶íØÔâVÍ;Ùöuë8@fÎä)ûöç÷eèi×yò%Ù¸âö£Å÷{|Ä-£¤N­%i†ºHHŽrñÈMÈs‘A9ŒÀ!MëÙ,Þ´ÁÿŽ«[ðrWÌÈpäGŽÛ|^!`pÿ¨¿x‡,Zü±±JiþâÃGàÚ1‹õ¾ÈKg¶ökô€K\‡‹ù·¨añÚB FªØ¸dCÁ¸ÎÏý)‹o·/`bšÞ‘'«6gËÜ Áß¡˜0rüJ™³1KÞÚV2RÃûž‰‡ù÷f°lk¶‡«wy í•Û bÏïÏË·vçºÏ£@•ã ,åžÉ"ûUÜV·[£*2û~ù]ob4x`ª|j ÍÂ]&æFÀç¶U¯*Û.2 x{ï‡eP1l¤b…*ú˜ü={•gÛl˜E§Üé†ËFÊ#{æ^xæ-îj¼:„@¦ ¹ÁR£öůM½ýۄܺàYi„Üÿ¼t«lݾÁÖõ‘ý†H÷Nled ¬©ªð圬§+ãŽ'µëÕ°™³_Ž-¿wO¶-Ÿ––*©úG‚€Ó ¸…\÷8ú¶È”)×óÍÍ#Z¯/ý¶Îê*{ð¡þÑ2£Ø~ŽnUÝOÈ*ö x©^ÎÿY·G:«we$ҽ߭°š}~H+©]9¼Bn$ì-K›÷×LÌŸ;Pq6ÍbÄWìt×¹ùˆFbþâ9 yk®%âl]]~½º‹ÇT£=÷zÞ¾ ’ÙùÔ¯«­:£Îi#i(¹^l„N?áciZjÓ¢‹Ìš÷›§Ñ¥+æx¶ÍÆàã.òäÛ¶ìª7hÊéÖ‚›PžºQ³z]ï,Û `ŠpÉWu¶,îò¿Y³s£I`âªÝVwÞ"®)(§Þ¶ÞØ=š¦Ð,8ý„÷D:¤èGã}Ã/{ÄÙ~TªXM2ÒËû•SßjU·?émí¡í =±Ýå ê6qoZ¯ûs²lùP2ÓgýâWí¹¿ò+£ÉLÜdž}Æ2hÒ¬¾¬ZQøØËï¿Î’þGw³Z]2•­õú>‹£Ùv’€ƒø† ÅôM»s¤ÞcÈGêvjÇÚ2à¥Y2cÅ.ëÐv *Ëì[zHšÆPõM›÷äÊIoÎñÔ•Širw¿†òÈIÍ=UÍ#Ê~°@¾×Pâz »ŠÆ¤|ëôVrfç:žzfãIõ„»cÜ*É~ ¯Ì\»GNw¾lܶϪãëMç>ð‰_VË&®gVžUÔ­IUE'©Y)ÝÊO[¹Kú¼òÜ3àyb¡]fç xz½ìï™;U¹_ôÉÂÂGÜûQ­kÈØK:J% ë<2M6ï; yö“ÿLX-·»¢€±zª¾ª¤Wô)>N› APcä4éV»‚üusOóƒÔÆ :Æ{xÔ´õråWêµ¢}™Xº&>êGùóÚ—›/§©÷£‰akÅÁÕºgZ[>Ö^E3ÿóÇÓY)7šk\Öæu+ÉòMYòÅœ-2Dû %åèyxä«ÿÈ”¥; ª«igtª-Ÿ^ÔÑó(½‰ýz—†Up§¦OÎw4’ÿê%O*ïG'­•¿¯ï*]|¼[?9]–ìÌ 8¿UõQþl caæÌ;]ùÉ"5sSÁãÿº£bÕt¹`c¹ãÈÆÞÕd§ÎWu]0î™ã›Ééjsëgþ’\}ÕÈ,/ÛFœ_¶\™q:'Çž'™znìÑ7P•R•­Õx³t¼ç¶¯)^ØÁjÃÄçÎ|èwùϱMåúÑz.M_^ðy2@=À'^SàûçêÝrì›seûN Ó sðøÉ-üÆë6ʼ/.øh¡5×V™¾÷ÔHþ}Lá{Ï]×÷µNÅTíCäý,èÝ´šïnO~ˆÚòËê;Ma-ƒ±Ëxån^ÈÖ„^õ×FÙµËõ¤2½¸k]ySÃox§ º¿ÁãȘ¡í丶5ä0Y>WßS&¨¼¾½¼ vã÷ ·ÉÉï.ü¬\ëýõÁùíd¨¶çîþv¹<¦çdÞCýd‚ž·gëgê6ëWÓQmjÈO®›ÞÇ<øãJ¹oÒš‚Ï+GOûOúÙX­‚#/¯¼‡æøm„ÜðNaûV݃6˜ž–ayàzW8ÿôäÇ yybèÚ ÉÄ=ºu‚¯oR©bhOêìÞ»CªV®òXÇMøÄVwèi7Ž|6$d xär@¥&ÐgPá#”¦‘™,ô´5ÿïežm³Ñ¥W[ž ’‰@¶ pF¤›¡âbÅ»&É ­{´ 嫤ËÝN7ñ%}ÒFëÞ7ÅqÛ¨Ø{R‡ZRMGZ%—¼ÈS»¶.ìõý‚m2 Y5¹\…ÅcÚÖ”=*pœõö<™íÃw“ â"Ð#ÚF¿ÿÎKödmWʧÊ‚Í"aÞé®o–É_/“Ž5*È*lý_¯ú2Sc¢¾o1Wڪ—‰¹Bcbú¦­ÙÚŸî[m„¤bRÊ“äíé¤{ŠÚO=¬‚·9ö¾VȧÿlöÝHµÚ^B‰Ù6õ\²©¸ÜØbìòIFxé§ t™ø¯†ÿY]ëH+V®²ÎAvŽÎ“WÚ BŽY4íx%oÿj™tÕy0B¶è|að›ùþ ¾xnmZ1NÕ–™›ìáfV‘Jëaï/+u>Û«½tuRä®o–ËsF òJæ2çÎ:×õ«——S:Ö’µ+Ê'³6Ë¡OÿéU3|›úd¼üpé¡Vƒ§«—BœÖzc ¼Î¥qOP1íîcšHoט¶H¹…æÖ¬œî3©VÞÌeFZŠ£âº™ÿSô<öMVü^e:q™Þ¼ðJ¹ÊΜûýtžÜÉ,F˜rïo2ê÷õRMÏó3»Ô±ìÊÖ÷‚9·{ëùàrL,c}¯.Óq4S>WûiP«Bê]Ñk{é–l9Nof˜ãx‰õ^UJ½¹Ç,ªfÚÕ>ÜɌӔý£çq…»&Ët/oWSãӦʤ%;¤úƒ¿Ë[ú^êùì_RY?3Lø“ÌxÍßdDuó¾X®ïÓ¡ÝëÊ•}H=΄½èoê+&¤Ÿ9&!u¡ŠþÁRÓö÷n-×{·ŽŠÆîdDzz¡¡~6ž§¶œanFéygÆs›¹Ùᕲ\Ÿ«SU@®¢ïßùÛ÷Y¢«‡¿›¿Mÿßßòà+äÄWgK«Ì éÞTß»Êî¼ÑóåkŸ÷®õÙ¨sm>oŽÖ›Eåõ&‰ùÌå`> SïtÆè6u»ê{ðA½ñ2¬G=ëóý»…Û½«±#¹á_#HX„k/~ȯ³êÕj«÷­ýI¼ÿ;û6¿zÄ?ºµ ¹Ç 8+¨ñý{hÛ÷öGOÚòÅeV­]b«Ò¥C_[ž   7Á/g'Î6'"Zu .Ýc;p@/8]ioV—Ÿ;ï»8š»œW8€¯G…M3ž*åËÉ©ü½ŸüyµôTQkú Ý<ÃnªžºfÑ(ã5v‚K1;ëß_àiú͇ÊIíTPp¥q‹·ËÔå.OG-©Þ¹÷øx».ߺOZ¨7é5Ÿ/–É×vuêy}HÍÑê•v¡Šîd+2¢åjC«pkÒãj¯Isnía½šÞòñŠóì(ãÆu‡5Îheke» +5U„;[=‡>Qà]üžÚmRÊm¬×¥wô²^‹ûg¥ŽË/&í~äp£BïÛKÕñÍ?6H%>6ÀªãýÏ*roSO½.±Øx:‘ì”׿”9þí»êy¸ñþ¾R·j†Õåz"ªWéÍ_,•›4ò˜qâs¬í›ÔCùÙÓZzʳsÈYïø‹ž eØ0kƵVa½«Š×«wµ¹‰0\½@½“V±¥æ*~š´^ÇTß5&“¿êÓEòêÔõòÆôõri¯r® ªæÏ=ê{¢¶Š»ît”z–š´ÆKÀ4ùßVžûGÍ–ƒÎ×5Ÿ-6Uäõw§Áo̵Dý@±¬STŸ®ž¨“tQ®.±Ó}Üóê ,•Ò,iwY WãqÝêÑ?,ÏÒõêUZ_½á£•ÞQq³ã!UlïÏ”M”*È^¢Þí¯œÝF®tyïQQ¼ª¾Çï·Òæ•»Kák 7õh?¨ï wzE¯ÑÓõý0EÙlÑÏï¹q×q¿>sjKyVß?ææI»Ç§K«ú•d†Îg¦ µÞé9õbN Üs¾èöžê…­ª«Wººw¹m`áyovossàé kä)íË7=©âô õšýÅËk6å_ä7½™`þ¦ÜØMúº<…ëgmýÌ=Kùì{ÀîµmÚ©O,|©7/NÕ%î”r÷$‹é†]ûu~  ÿŸ+F÷Ì› =ß9¯à³É}\(¯æ=¶J?›šêRøp­>–î–N4T>øâ¿î¬çµW×#=ÛÞ¿RüM ïúlÇ'`BîÕÝÔà ϼY~›þgÿÏS¾ë/éÉ·±u‡~Ÿx¥†õì¿;¼v± ¤%€GnÒN=‡@Ù ”¯ Sy]„T¯™}Ù®G!ËÞ<-@ . øÆÈ5_ B£÷ßi¯«x(©§á^"®©òŒ>îlÒ#.ÑÔl!ÖJúȱ·ˆkÊŽUOÅÇ5³v›|E\SÖÌ% L]Ø;îÝ ì"®9fôY­Í‹41¢T ’¯ˆkL¨áöÔ Á´8“ïT/W“Þ9¿­MÄ5eoœëzd[=üÌcë¾ié]½="®Ù×£±zö…);­§GÄ5M60B —Èìîf‚ § É[Ä5ùŠé©òÍeÌfÄÒ„«ºXmø~…Ñ!XGnoÌšê1ì-âšú/ŸÙÆ:ìºoVX¯¡üSÉ%Ïñò,¿î‹%‡šP>Ô¯«(nÒé^! ¾·Õ* ´ ágœóç~¸ÀªãûO–×ãþ¾ûLÞxI×py±/»û°¨Š¸–=úýk²x§Ë«oeï×p$n×X7.Løt½Ó«¿¯³²_ihßôõ¡>V¯ïâR¾†¹NÃ<˜d<¦«ÿû717©ŒGtI’¯ˆkŽÍ017Ô36X*§‚»·ˆkêuu½G_×÷¶[Ä5å­ôÆ„ñØÝožN¾¾Ü.âš*/œRðÝRÃl„;•Ó› Æó»Ûsáo;ܶ:©=ÃÿlÞû$¿F+”¯,U*§âwŽ#P¯¶ýÆš€y¥¥¦‹ï¢vóíO;=PwäååZÞu2‹XpÍ»ÛH&ÁÅ1î²Æñä`ZÒ¨XÉîE²sûnÙ´¾àÂÙ £2 ¹Qðš|…ÜÞͫɾÇØþrž8"ðHõÑgÕl©»zÔ™´lG¡û¬ {¬²ÿëY×z õŸœù²`c–¸…¯|Ú @:¼¹Ý›ÞT1Þ‘V2m»Ò¹Ý úOU±jÌl]¥d´Ÿ%ê9燚Tj >2±w5Ù90Óú.ñ{Íÿ›Q-ôÑúH¥C½Â¸û(—øçYc}Œ;©Z…Tê:†hÌæ`é ããÕ»ÕˆÞŸ¸ÂcìóƒµeÊÇ^ØÞÚýΟ­ÉÌÚ˜-m•™;n²9ßM2§&äE½ž‚p»DC…˜´~w€÷‰zÕWÔ÷kQ©’zNëÒì²â߇Ióž#AmÐC¾¡‘Û¡RSŸfþïñÚ¼…u…ð0!1¼çËlÏq}}>ÇþÈcÇ —;ç‰b¼ÆM2O¤Þ1)¤Ú4e+Õ.Ïç@JµÜøhX¥À3ºg£ª¶£¬Ï_½ù,Ù²ÀÜ{ÿú´€IY^çîàC ÐsáCcÇÎÝ*f®a\« ™ôÁ_›¬¿@¶VT4ÔÔÇÄ#ÖôäÄ5ò„zEî5ñbõïZ p¢†!yð‡ò²z”>{Z+ùsín«î3^ šGÖMj@À´v¸ÿq‰iîl‰^õ&Ãï«vÅÍ£ñ©&Æ5 ³¨˜oÚæú¬¹ñs—§³oÍw¨ú ó9b¼Æ=©™TR¯\“v˜.öÁ¢|€æmE&ŒÂý¬11nË’‚½Ï­6ý1Ù•åljxÝäúê’CeðësäkqOÕWs‡Î„V0±rK•ô¦ÁÿúhµhaµRµ™Äq­™ÉÔç41 WåÌfê–+LG´×Î9õÚbí«U½¾lܲÚSÏĽ­‘érð”úoü:õ+[a‹&O†Ø É@ÎŒ‘Ëã2œ¹ˆ‡ÙÙ&äΞ¹DZ´.x¼ÒmeC{IH¾1r#1.c×$÷£êÁú0 i5×X¸&}0¬½g%vãÀZÎCÖÚÂ?ùAÂ|4¬ƒ|4LéW1í]lè*]äk‰Æ-5šw ôµ[/q¿z×÷Þ6]W6¢nÜ¥ñ~õãÜñ4½ë—eÛ,ð–îç­¬·e[ÍVL î­W–~ËrlfÅ›vûP–vKzì£'4“»5DE³'fÈôk Â-x·áŽ;lÆs{Ìzï/év%ãkÂkhìUsNOÔx­&ݹ¶† (om?7q­%ä^öéb+?Ø,PåJ5]1·éñE¦RÞˆÙ®ž¸54†óP]<«ŸzÀ6ÖŽK&dz2/Òð!&r¸’ ù±i„Æ~VÕU”ý Ć+ê¢bù“÷t]älÌÿ^À§Ü=ÙñCl&¬Õ¸?ÕÚ;½ìP+ûÈO+åßß­‹tᵟø®£šxW iûKýü>Mc]›…ÕZé‚oKnïÒqT L!70—²–ÞŒp[V„Ž<ÞÄ·9û7™5oŠìÛ·Gúö8¶Øqôì2H¾ùi´§Þ¿~$¡,ZöÙ7£<ǘPDcÛd $Bw‹ˆ# ¹q4˜’ôjÔô÷\½r£K³– my2€@ÑZ¹ÿ`NÑá nu­âþÄà·è– ö]¿Qo0+ùˆîönÖÇ¥÷ºC2‹ ¹Sm—X¶Üåýè.7¯9.ĈpE¥¹Ô WëéTÛ&âuLI÷™ÇñMÚ ‹‰JË\è7®b\5×¢QK¶Åö‘CK RòÍ:×Y*þù¦Ã\´£ÿÞ仫Ø|°Xª÷Qpcð…Ékå”÷æ[í¸EÜ™senh,uÍŸ[L6ÝÛ 7„_ð5bº>ÎnRƒª¥ót®®"óÏ×t¶Úh¢ñ`˜ÎèPÓ2{µ áN•3\—Ε`}·± ÊÞ"n°º‘(ôÙø‘>‘`RfP÷ÝTvhŒ`“ÌŽÒ¦ÏL(Ö—jŒá~/Î,m3§r9 >Ç~¦Ü~ÍÓòîó¿Ë§£‚‡WòîñìÁW{geêŸ?ØòÁ2[·¯·í ¶˜ž­$!G ¹|9'á™Êãš@ªÏ#Ù¹9vï§šuücõÅõ€0Eˆ†Gn/×B=›õ‘z_áq½ ‘n!Õí)æë ¸@…€¢Rs]\Ç·Ý›Æ.³ù^Wl–,IW(wž«X›¿¯Ýc ek<Ó¿1×]­È×<×#Ëu}BlÚXtµs/„VdË…;oî_pC©ùÓÚì45Þs-’e« Þfa+1Ü2viX¿•³oºÍ%èû–{ç_ù}½¼4¥`a+ïò’nïòBìð¼¿Ð4¸}7ìUw{Å-ª. ΟEO5Pºÿ¸fVñ«Ó7H~VžtñŠy:ùÊNÖ¾[¾\j½ŽÔÇù}S¹Ê*Òæ´÷ÝwêèyVÑ'ç·óÝrþÈV5䔎:n+;ê¹å´4\EH“Žv…N)ý_ð°³ÔÃÔ¤a]íÓÖvÝ,1¼wÊu}TöYØÌ,*§v½«Fl»Ê=“ekV®­ýÛ¾-g×l°”i>„®V?Pyš†ÅøÔ„ Pñ©Ëv37¤˸V Õ !Õ}(ó Kj·åR)W…:<êA ÔùÎà˹Ôó͈bàF+:„Àl 3põg‹ýþ¿ªÔ#ÈЛ#—»Øi  *¡dêŠr® O 5ob•štYï‚ÅšÎ{oü¹z·¬TOÉG~Z%ôÑÜ"Eõ,3í>®žµ¨Wb‹Çÿå›TüU±ðx=jÒ_köˆ kðÀ+e¾ sfQ©“^SO [Îï^.¥œŠ5Í#å*2V¹ŠõøûðïWH#}Dºc€…¼¬Æ}þ±ŠÒ§–_º^¾Ñǰ×ìØ/Ÿ¨\½û§Z¢†Ou+Û£n%ëµë³évÉÙ.Ñ(P]Svu_rG>f_]ÇþÛò–(zí˜År¡ò3A_óGxÕk#–åmkußMÅÂkô|3çÃ_/³æèébZ#þ_­!1®Õã|ü’ŽÉ:?\á|-§ :±™U\M±7U|5ÞÖ³5îñ«Ë6å®IÖ¹ì}Üí[ÙgtŠŽiø+üE`=/çèùhÒÿNoi½šÚk\c“Üõ-G´eºþùõâ‚ÇóM<Ó4¬ÂMˆ†”;&ʉÜAì`a0ï6ŠÛ«7?ªhÞyÚö½ß—Þ#³¸~"±¿«Ž¿RÕ ë}‘¢a"¾¿U«¨n8ýkìRëü2çOQi¸†0ŸF¸57E^Õ›ô³ÀŠs«çÄë眻î6·®am6y|ºü¾r—\¡ç¦IV¼p½Q´`Ý^1 Ü­ÖÏñ‹·K%=oÄç’u@$þÑs­öð)òŒ†’™¦¶Õù»užH¥4Ø¢`!´ÉúÙaÆûØÏ«¬ÏFóùx¤Æ¶5 ß]e>gÊ ƒÏ/ÒsVí01s¼ôwZKÞCyz3y瞑njtûB±«×Üt f᦭km»*T¨Œw½päbg|9N [ˆMZ4”5+74¥FÍ‚Åjî¤$àö*h^£àê9^ $¤©ØuÏ1MJ=ÂQç´‘½¹¬E£ŒP2ÜÕRíwúݬ\/] íã›Ê}?¬”ž*hºÓø«;Ë0GÖ«P(­¸½§ ~{ŽÜ¥B '©p÷p¡ÙM#nޯšùs§þ-3å½ ìŒ5Vhº cY»seà‹Âú Õ«g·±÷±Á^Í#êã®ê,ǾüœòÚlOµ{m*Ët 﫨ã›&]ÓÅxf©€}ØsþÞ¡¾õMþÀ#ý¥Â}Se—ÎÙá/x $ê·çÁ~q}Áp¶Æ }W¹Ñùe=ßÌŸ;í¸¿¯{3àkf°]©rÇÃÝûCyudž5uëù,$vï1MÅÌç c–È¥.´7§bý‡b+;¿{]¹äË%’»'Wú?_0'7ioÏè Ôãõ=]@ͤN žœ¦Îßþº¥»tæ/1áÌŸ; ëYÏZ Ê/Ëëöá}$ýö‰2rÜ*¹ò°†Ò$Ctß^=úëûvŠ Ü'››5ÞIÏ_OyïÝfû ½É`æú3½ùbþÜ©‰†HXzG/u0Õ‰òJ/ŸÕFÞücƒ5o}uC“Fég…Ióní¡7¢fÈÿi¼Yw:¾]M9¶uu–½>¯Ü;ÃüºGYtzæO¹Õååm5¯¡MòèçéÉþõä3a¼C)Õ¦†¼|VkO½Òn\_h˜…!oÏ“ÉKvŠ Û±Jc“B'€ÓO謨 HèÔþ0ùóŸ žæ§Í/ÞŒõìpm¬YgÿŒ¯[Ëþ{Á·>y$3½ =è$æ‹yÓ¦MR§Žý1-'[!OŒW‰I_º—-m;·ï–7Ÿÿ2à¡}vóGŠ>a¹Š™&†hóš ¼Ö|0ûVióÚ¢VEk¯ W¯AŒw¯;Ý®ÂíÔ wއIS„wªwªñz«¯b\móz€d^¡ýWP±óÍ|EïCV™Ø©9ùÒ¢vO¿Æ–§c2Qû oéº]ûe¯²j¥"ñö5av÷© m…uðî̵½QÃ/˜>ŒW¯©o’™Ã5˜h™£}[4Îj°y1<ÍcÝÚ)jŸË4Ï‹±ßežUnÆš¥ã ÔvQûÌÁf>Œ]‡¨íÕ¼DZ³/ØØ³µ¯ý¯B±Óç ólõ°®¬âhQÉœå‹X(Ì„×Ø®á _þ#ÕGÐónÛœ‹t.ÍvÀ9rÝ%zòl@…é‘Ë]Ö d ñ@ ³Fp¯Û&Í ýŽ;±á PR×Ýg0±Ðì/jŸÜ‹Ÿ¹Ûò}5B¯0•f=Š\ lúÖwçM\G+¶£» À«QÚºÂØm+jâòRö. 46#„ðã+1-˜ðdú©§…×óù2¿ ¤n»2”MqLM]Ã5X;Eís÷ã~õŸL¹k°¶‹ÚgŽõkSæNÁÆ^1½hQÖ}¼y5ÌÙì]Çl»6ßrwÞÌ¥ï|º÷ù¾w.:ÜmTu-çÎ{-8W4Œ@1©¸±›7sN”DÄ5fÇÐÔ 4ö¢l $âšv¬÷]ÙÆ~+̉©\ŠTS>4¡&só(Ø ¤@shÌØ¸û¶ÏˆæézS£¨d„lóWT27EBýl,ª`ûÌ|™ÐÎ=_&.Þ!Çš-?\Ñ)Xuʽp­èƒMĈ@ßÇÙ„Üy‹giÉoÓ¿·í?êð!¶<@ @á­è²¸ß"´BÜO&!så U¯U-@)Ep.Ò ¹Î1–C€@,1÷Ëÿ+ˆ™û£ÆÌíõßÂ:±°Ç)}"ä:e¦°3‘ ÔÈ´?A}ð`~‘Ãݹ{‹mûÖÝmy2€@!G ¹|9N [ˆí:6 hJ¥ÊÁ_ x…ˆs¹q>A˜@ Á|nâ’«ðŒ•»¥ýŠöjK°¡—j8\+– A ìÒÒìOi,[9/ì}Ð ’‘@ÑÏ Å)é‘‹ë3‹d!'N;ÿ¨8±3 9,v9¶´ @Å0ž¹î˜¹­Ú-ǾúŒ»’§ ÜärÝ$x…@ì ¤¥¦KÞ\!KWÌ•mzxòf#{ß[¾aýf¶<@ÀNÀ‘¹|9Û'‘ DÀÁ|óH € ;&fîçÃÚ‹”O•ñ ·Ë —gÅΘ8ë™kÅ8›ÌIjgrµmüo}ü¤-ƒ„ „D!7$LT‚ @€ ?Êé§_êh’^N&,Þ!M›?ÆÅЄܧkø8g°]È]´ìo[i3²åÔmjË“ü 8RÈ%´‚ÿDR@Ñ!pÐZ¤!:}Ñ  @ 8Ÿ¹ÄÜU›÷Iÿí"IqÇ&â~®qV“S ¤¤øKNÞ^¸c|Ç6´cžmË“ü ø¿«üëÄ] wYãnJ0€@Ò ´BÒL5… ài&f®K̲l§¯1s“9q­˜Ì³ÏØã€y?V©\ÝfÖªu‹=ù5–y¶ÍFÎmy2€€?G ¹ÜeõŸHJ @ :¼½¢Ó#½@€Š&`ÄÜ/†u©*?jÌÜ~/Î,ú€Þ‹›À“ËÐIàúÍmvÏ[ô§'¿gï϶٨U½®-Oð'àH!—/gÿ‰¤€¢C!7:œé€JF@ßä‹ó bæN]¶Kš>šœ1s¹V,ÙyCmDšÀ ¾ƒm]ŒŸø™•ÏÞ—%äyö‡½*•3=y6 À%äæçç[£àË9ðdR @‘'€yÆô@¥#`Ä\+ÌBFª¬Ú²O½<«t 9ø(®]$ùY*àVL“/Ô¸¹$@1$pX×£l½OžölÞºÖVÖÛ§Žm'@ÀCÀQB.^Pžyc€ @€€›¾Z*ÛwæX"nîƒý$ÕÎ%Aˆ!°õ>oñŸ¶<@ tŽr ­úÄR€"C€ï¢Èp¥U@(;«>_"+6gKíêå%ÿáþV¼Ü²·J €ÊF sû>¶Öm\nËWªXÕ–''|Wüíáâ9þæ‹ $¾‹’mÆ/ ø'pPMòþ‘ì<)_%]6·‹&ñ?,„™@FF…"‡×¨AË"÷³($€Gn! ¶ @Å@È- @ Ê®³ØqMLÜ=÷õrït@ xµk6Z©mË.A÷±°@ȵó @€ @À1n»L6nß/éê‰kb⦕#&®c&C!D†œxYÐÑÞûÄ ûØØ äÚyƒ I€…7‹ÄÃN@ˆNá’ÏËŠMYR»FyÉy7Jèé(‡zTÛ–]ƒîc `'ਹ\<Û' @€ œ†| 1q³òÄ„SØ|/1q“ó,`ÔpºµqޱX 8&€GnO¦A€@ü FnüÍ AH6×~¹Ô#âšp $@ñN 55MÒÓ3ü̬‘YϯŒ@ 8„ÜàlØ@ð#€ë‡„@ˆ"ë¿Z*k·dKµj’GLÜ(’§+@ ¬j×lè×DÏ.ýÊ(€‚pTh.žƒO${ xXªx¤ÈØr ¼HCÆ‘áK«€’‰@ÓšB^œì ÅúÉ"Ù·;GjV//[‡N!™ÎÆ D 0ì¬[å‰o´ ¥K>Ël@È@ ¹Åb7 G0Wy))r«yä’!-D^ãÑ¥Y@IE 5E¾¼¸cHC>ß%â7$\T‚â@¿ÇùYÕªÙ¡~e@Á 8JÈe±³àÉ@qo?ª±ôhT "ðÙ§ŸÊ',ê € PZCß™'r@oÀ†L8…,õÄ5"®‰‰[.%„ƒ¨@  thÓKÒÓ2¬x¹é©éR¿n“8´“ ¿%äZ!~O$,ƒâ‡À€™2¸}­ø1(Á,Yøåfùdѯrn—: 62†@Ñ$04ÄήýBcânͶÂ)l¹·yð†@À±¹k´cmÇpÄ„Üx˜l€ ÇàéÇL†Bp4-iÈ D²ó¬…͈‰ëèéÄx@€@X” K+QjÜ(¦@J€ï¢ hØ@a$pé˜Å–ˆkÂ)lca³0’¥)@€€s ä:wî°€b@!7Ðé€@’¸AcânÛ±ß7• ¸Iv0\@€@`Ž ­Àã¬'‘R@ˆ„Ü豦'@ÉFÀ,}vŘ%²yû>©S£¼lÒ˜¸$@€ à&à(!—‹g÷´ñ @±"ÀMÅX‘§_@‰MÀˆ¸CÞ/ˆ‰›^97±§›ÑA€JE€Ð ¥ÂÆA€ @€ÂGàêÏ—xbâfÝß7| Ó @ Cܧr꯳äÕÿ|Rí7¿~8¤zá¬ôÖ _ʄ劉Ôä•ÿ:[úêRÝpVºä”{Cn. ‡_ÿ¼¬Y±1$ŸxíV©S¿fHuÃY)Þš±>ûà»!ùæ†\7œ`ã˜ÑãeÕò ! ûÜKOj‡T—Je'/O‡ÜÖ¿¯ìÜ´)¤=?s–T¬R%¤º¥©tyë–!öÚâ¥!×uJÅH?R톓k4mŒf_ádTT[ñ8¦x´©(†‰²ïz‰»aÛ>©X5]vè+ÄÄuÖ̹´]Èñ†z]G9Ýóè…2oñŒz}ê¾Ï¤eÓŽ!Õ W%óÛêôËÚ‡Ô\,øÃμ¼£È?×6ÆûyhàacH§P±•.»mlÝÚµâ{/L—Ê•ªÛ&œC!×9s…¥€ âEȘ@a 0ìÓŲkç~bâ†%M@€€£B+—0ÑOGÆ@ þ äÆÿa! '0"®TL#&®“& [!@1"à(!—‹ç%t @ÜTô `€ÂA@EÜÜû…£%Ú€ @ Á ä&ø3<@/n*†—'­AHVÙ»i"ê'iåR’ㆠ@ ˆ‘[XT…  är@€@8TH+'œØ<MÑ @IBÜ$™h† @á!€Ž´@€ @%#€[2^Ô† $'€›ä'Ç @€ #¹1O·€ àL¹Îœ7¬† @€ àtŽrY)Üé§öCp>¾‹œ?‡Œ€ @€€ 8JÈŠʉ§6CH,|%Ö|2@€ @N!€ë”™ÂN@ˆ ¹q1 @€ @ é ä&Ý”3`@( „ܲÐãX@€ @(-„ÜÒ’ã8@HJ¹I9í € @€@Ì äÆ| 0€œD!×I³…­€ @€‡€£„\V Oœ‘@p*¾‹œ:sØ @€ @ÀÙ%äâåì“ ë!$¾‹a @€ ç@ÈuÞœa1 ÄBn áÓ5 @€ $&€›Ä“ÏÐ!@ ärKÎŒ# @€ @ ìrËÎ @ ‰ ä&Ñd3T@€ @qD!7Ž&S @ þ äÆÿa! @€ D$à(!—•ÂñdL€œE!×Yó…µ€ @€…€£„\.žå´c€œK€ï"çΖC€ @p2„\'϶C€@Ô ðtHÔ‘Ó! @€ („\N@€@ à‘[XT… @€ °@È J‚ d €› ³Ì!@€ Ąܸ›,‚ 8&€Ç“ƒi€ @€˜€£„\â&ð™ÈÐ 8„B®C& 3!@€ $G ¹\<'ØÙÇp 8ßEœ4L† @€ r` DO‡D5=A€ @€@!„ÜBlA€Š%€Gn±ˆ¨@€ @ €¨4 @‰K!7qç–‘A€ @ˆgŽryœ5žO%lƒ r“cž% @€ x#à(!—‹çx;}°€@òà»(ùæœC€ @ˆ¹ñ0 Ø@Ž!€ë˜©ÂP@€ @ E!7¡¦“Á@€@¤ æ'Ò„i€ @€@È D…2@€@xäC1 @€ D”€£„\¼ "z.Ð8 „@!7HT @€ °p”ËÅsØçŸ!@ „ø.*!0ªC€ @€@X ä†#@€@²@ÈM–™fœ€ @€â‹Bn|ÍÖ@€@œ@Èó Â<@€ @ J!7A'–aA€@d äF†+­B€ @€@Ñr‹æÃ^@€€B® @€ @ˆG ¹ùùùQÂB7€ Àrs¡€ @€"KÀQB.Ï‘=h€Š'ÀMÅâQ€ @€ÂO!7üLi€˜7xr @€ 8&€Ç“ƒi€ rãoN°€ @€@2@ÈM†YfŒ€ 6¹aCIC€ @€ P¹%€EU@€B.ç @€ Ä‚€£„\˜‰Å)BŸ€ àM!×›Û€ @€ -Žr¹xŽÖiA?€ Œ7ƒ‘¡€ @€"I!7’ti€Ž7nJ @€ G@ÈuÄ4a$ Ä „Üx™ ì€ @€ \r“k¾- ”‘Bnr8 @€ ”ŠBn©°q $+„ÜdyÆ @€ @ ¶rcËŸÞ!@Àar6a˜ @€ @ A8JÈe¥ð9ë à»ÈÁ“‡é€ @€LÀQB.^P>Ó0€ @€ @(5„ÜR£ã@@HFÜTLÆYgÌ€ @€bO!7ös€€ à „VpÐda* @€ "€›@“ÉP @ òðÈÚºh6žœ$@€@$÷]tõÕWË3Ï<#*TˆD÷IÕfÆ}S$wOnâ¹Bªyx\Œó“¯_‘÷Æ<#ƒ½X.;﮸°ÉmăO_)W.õë4vÅÕkö¾½rþu=-›>}~\Ù†1HÅ}?ŸuÖYòöÛo[ f'xc|߆‹7Z>öü=mlX³E>|ý;zÙ‰>{c›}ðÖ—åͯŽ­Eô>ñûòí§“¤MǦ¹EpŠÆ®J©Ò!ÄG­¢a}Ä–ÀÙgŸ-¯¾úªT¯^=¶†Ð; $-o¼Qžxâ )_^]Ia!ó@¿°´C#¡øø«­Êcǽ%Ûö”>Ý ýà×ükÎDyô¿×Ës}ážJÞüž¬]rÑ}Äa—| ¸øâ‹åù石@6)vˆ Pö£žþÔ:ú‡Ï“ùÿÄÏ*ï¯<ù±e×KO|T†ÑEîÐ5+6Ê›/|au°aíÖÈuDË€@ÈŽ?þxÉËË“?þ7djT„ p¸è¢‹,ñæ¹çžCÄ 'XÚŠƒ’›Wgö±®—½*PÆCº÷ñ‹,3V®](;wo‹“<6ìÏÙ'^ß[Ü똫×-õìgˆ ÓO?Ýú~~óÍ7qc3¶^rm8J–ÉÎ*|ý‰{Þœý±\É|éý>ák Lœww2wlÛ-ïÞz׎=qg£Ç86 z÷îm½¿ÿþ{•ÚÂYI€…!B€@ œqÆÖw‘yL“D °h™ÿZÝÔO—¥ÈéðöíÏ–9 ÿðØpóˆ!žíxظøfÿ°})LÃ$%AƒYßÏcÆŒIÊñÇë rK93Û6ïô;òÚsÒ»‡±]4êýW¿±Ùõô}ïØò±ÌäæäÉm—<ég¼H®¥î×€€8å”SdÇŽ2mÚ4x@€bBছn’¬¬,ùì³ÏbÒ?B R¦LÿÞ¯i³ÀØÕwçWÍ‚—Þ¹ßÖÝö›dÛÎͶ²Xenø÷)’½o_÷ÿÌÿݯŒ@ ².¼ðBÙ½{·üòË/‘íˆÖKE!·TØDÌ^îwäùòÀÍÿó+VAž ¢?}meæüµXöyyGË–@ýÜrÑã’¯Œ|Ó_¿ÀÞ— yDšÀرc%333ÒÝÐ> @ (gŸ}V*V¬t?;ÊFàÿ>\ G¼4Ëúôò,9ûyòí‚Ø?Jßî?3$å¶ e\œ=~r`ïµM[×Ȩwc³ŽINî~™0Õ?&î ÿ>9æ4ïûÏ¥²zý’€vìÙ»#`9…€@äŒ=š ‘Ã[æ–rK‰ð«ß™Xµl½¼ó¿¯JÙjÙûæ“À?ˆF¿4¶l —ñh¨þÆó‘½{²¶ôñ›?,§‰H +'°zv®ÿMŽD?c‚ @ :Þ™±I&-Û)Û²ódãÞ\ùZ·O5[ê´;~DÇšÂ^êWI—týKäT”øøÍÏïÊŸ³'F}ø£Þ°O»wÉŠ9÷E£ðÝÏž‘Yó¦íÊx2geﺟ€’Bn)g|c‹týòí²xþªR¶\úþxïç€Oùå°+G ðŽ+ž–Ý»²‚¶¼uwYƒÂa‡‡À'ÿlv´÷Ƴ×HÊ¿&Hå»'Kʽ¿yÆuÑ ¬qUºk’tî/O9€ @ Ìʧʜ[{ÈüÛzJöý}åÖAdã¶}òÍüØyæþzuÉy _™‡¯ ,ZV°^IQö=ôÌ•EíŽÈ¾q ÄÔøO_¨8âeßüôž|úÍ+Åö³cWìÎ×b£ (@È-ðm[üããú6ó裂z úÖ Gþ£7üã0y·ûÒ“yg£¶ýú³cdˆíÅögÂRâ“À¨ië%厉–Øhƒ3Ÿžucgw4RÊðßlc5ã­úÀT™¶²ô+ ïÞw@nùr©œÑ¹Ž¬º·ürIGk(K¶dËèåšÉªá}äÅ!­J<Äýû÷Ë9çœ#ÙÙ=ÞKÜ @€ °?¹…5¶·glðãŒU»åÌ·çÉ)¯Ï‘gŽ›º?/_žŸ¼VNÒ:üØqeâ²rú[såÔ7æÈ×ó·úõóÃÂmòÄ/«­ò¼åÞïWÈžýþO,½ðÛZù蘆¶ãÿ\½[ÎÒ'kÿýíoã“¿®–¿×ìÑEÅÊoÏ•«>]d;>™Ï¾y5¤nθ¼ƒoÓh¤ÿ¼tk‘ÝìÞ³]//^€.²‘Rì4á*U¬Vì‘cDZb±¨$ „ÜRLõäñÅ{ÍÑéú¡#%Z yýðy¡‡_ !͘<7ê^¹[7ïu«72ǯlýjÿb~•(ˆ:µ;÷Ë•/’~M«IÎäàSeìå‡ÊŠ]¹Q·%jê‚…æq¿-ö“ ê¹òµŽw†=èóüL)­”¼F9š4òøfÒ¸FyÔ²º•ÿz^ÁÅ͈c›Jãê奯r.i2Aè?ùäÙ¼™÷PIÙQ€ÊN 77WRRRdúôéeoŒ"N`òò‡”³õæ²w:í͹ÒKŸ Z´-[6eåʹ*–šX¶Þé—%ۥ“äÆÏ—ÈRõêý· °)ÿš¨+šÖ:Rcñ|q–¬Ôß>k÷äÈà׿HÿÚ¯†[)w~½Ì:(-5EFjþÆ/ìñQÀ{Ø%òûªÂéFîùì_²pk¶lÉΕ¡£çIë'íçÝc—ɳ“×HªÚõùì-òêÔõ…ÆEikÁ’™{jP·©{Ä92â–QòùëóeÌkó$55-`Ýp<˜/¿Íø®Ø&ñæbë„»Âé'\*ï¿ø‡|ñÆyçh9¬Û±»øñר8%4†B8„À»ï¾+ß~û­C¬ÅÌ’ˆü7GI¬qHÝ¿Í hi…ŠR½f59êäÞrÌà¾ÖÚ€Ã\øÉÛ?ê(¯_PAÚyÇ«2ü©«ƒì q­:Õ=ý™…ØŒ×ð”ŸfJÖÞ}~-ÑPšÕó+§ ¶&h,5“Æ\ÔAÒS îûœÒ¾–ìU3X2§¢^ÏE=å«[®\ñ‡b_š6S«rº5†“u¼.íh]”Ì[¿W:6¨\â±íÚWàmQYoôNK¶ú¿¼÷›í:®Ô ãÚ´i“GÀݨÛiiiR¾|y©U«–o3ä!@HFúÃg“ ªæá·Åú$Бÿ›%R)MÎîR(äŽ[¼]¾š³EßÕ[ZÕ©hQ2¿«RoŸ(ÏMZ#7 hd•õÒ?ºPj†ìQø;ðÖ¯–z¨š§¸~]¼CvŽì/Õ*\fnÙ“+uî›"ŸÍÞ,gv*ìÓsnÔ¯UAÞÔ'”Þ8·­§øûEÛ¬í'\Ä¿.Ý!_¨0»àŽ^Ò¶^%kŸÛÆ§Ô ÷¶A=Ǿ=}£œÜ¡–|}Ù¡ž²hnìܽU*WÊ”Ú5È'^&‡u?Z†^Ó]žþ±T©ý…f_zû®·l['[¶­·ìŽ&/w_Ûö’•kË´™ãä³Qsdú¬_dô§ÏÈÆ-«%//HÜx…@˜ 6LÚ´i#'tR˜[¦¹X@È-Å ¬^^ø(ÒÉç!GžÐKþuéSòÐ 7Híz5JÑbé9¨?²¾ÿlrH ,[¸FröçJFù*¤ƒÂT)--U.¸òdY±x­,׿§Þü—|úö8q{7Oøaº :±W˜z£™p¨R¾@¼5ž¹õªfÙlǧþ”yë÷ˆå¶š‘*Ó¯í"=Wõó÷Ú=Òí¿êÕúøiùøtY¶Yã&«@yRÛšòÏm#¶V{pªìÙcß­QU¹ëÈ‚‹Oƒ® ãmqÆ{óEÌbaÚÞ-3e‚Æ^s'ÓV¹»'ɇ絕1³·ÊÇ3ÕK\½?>q„»J±¯5+¼gv¸Ys€y´°Â¿“ÿÖR®é×ÐÓÆ }ä¯× [ã4…)ZǺzÒí&ýa©Üßý_9Q]mê£qsMZxKwiS·àâäŠOÉkÓõ³F=RŒ½Ø\î<²ð"åõ†yá‡Ù"£Î¹ñGéýÁ^‘ï/—”ùßK~~A»V£ü@ˆFÉÖ­O—~øázCµœdddÈÎ7‚#Ô-Í–„€†wªwßTÏžÜ\îðú=av§a Ìo(·ˆkÊÌÍñ*Õ2ä­¿6YBî õÔ5é« Û[¯îž>µ¥{S®³X¨(ëqÍŽÚfQ3½‘=ê÷ A…Ü{6–ëõØ}ú[®BzÁoÏŒ@œVNéK&©‹´™äqͶ±13³¼¼¥¿í¼…ÜzL¬D\c×§*B¦ð´}oÌsrÕ°¦JTÓ‰GŸ'§{¡òJSàTuH•›ï"Z÷k/~в%EÔ‹Aÿ/§ûb™~š4FjdÖ±<•ût?VÌŸI{³v[b´ñþ'AHv¹¥8ž{÷.©V½Šß‘¿õ£\{ç¹~å-Ðï²—?ayÿ¦èóÝf¾à.<\yù&iÐ(ðïˆÚTDãÆó¶SÖ’Y£ª\vóÖŸ ?1oÖ²"ŽbW¬ lQ Ç3Éëê%qiïúM±ïRÅtÒu]¥®þ`ùÓj饾­º÷0 %PÁ:Æx–áÒÔí§^­oœÕE^›¶AÞýs£Æ]"ÏœÖÊÓv¹{&‰ääË g´––µ+È©ï-sß_èÙïÞ¸÷ûÖãx£În#Ƕ­!³Ôcö4½iüÈ4Y}Ïaîj–È{ï+eÉælyíÜ6ò“z‹”$™ sÓ¯y¡…ˆÍxrŒÐê•ò\ãt}uA;1"öµõ¿§´°<}´È”ÕŽQ:þŸm—÷ÎicUo¦?&ÁwµÚúÙÅ¥g£*2Në\®!.6ìÊQNLûŒ\½¶ÈÍãEæ—{¯8K޽ö}² æÌjˆ @$ðÎ;ïȺuëÄxýŒ1BZ´h¡ÂZáwe»¦éP TL“ýú$ÕÊíûåˆWÿ‘»¿Y.§Z[Úºn[͘›ázýÐò {˜‚=Yy2×õ;ç=tMмˆßye½þVñmÇÜlŸ¾Ao8I×õoh ¹Ç¿>Ûs3~Ŧl¹ñˆC °l¿¶wî͕ݮãîÊíjüžrç£ýHÄ­®âää?¾‰Û¼q;?F°MOÏšÕëúí‹eÁÒ•sä¨~gø™P¹R¡sˆßN  6Hƒ ¬²E‹YúɘPHæ N’ó 0‹¥˜Ã@"nZÕdú$½Se!׈¶é§Ñ½)½`c–´sŨõ뵡0ÙŽWUÛæ£'4—“T`6O=®7®ë¸žè²UR'•¢l´Õ³LÎGÈçß½&»÷1ˆ“ë‹Ã„Rˆ7§Ÿw5®IñÀÇ—y@ñD!7L³Ñ¹gKÈÝ´~›ÔmP3L­–­™x‹“i‰«S?º1„ËF£½ Ü|3~lSi®ñmW«g쓺°Äíº°ÄFWÜØÌOö®òv5‡×Ë{|ŽyôN¯‚-ðånø‹¹"ñ!þî.²½¦›+‰R¦Šåäžcì‚sÊ=“e˜†x8O=i‹³­”ÝZ‡}ë²k ŸR–f8€ øxáôV–ÛùY]Ûà¶žÖþ«4Öÿ+SÖÉšû=1i}ìܰ ¬ÜYº.Áœ[{xvô÷&9§K]+¼Û©úä‘Y4-KŸªäŠuë©XÌÆ í ®Ÿ†¾?_¬ÛkI½öðCä“×ʪíû¤I ‘·˜öc½»uóΖ ›¶¬‰ ¡²À#7¾œ~fÎýMZ6‹Íu±>?è€@I ä–„VukÖ)x¤yÙ¢5q$ä,Ââèï2‹Ä}ÊaÑï˜ÃFÀ˜«îîm…ønávKÈ­¦?ÔwiŸ–~jWJ·„]£í¥ÅvUU“vh¸"ÒéÑcšXqåÖ¨'° áà~´0/Ì ‹™Ç?–Í’£‹±¥{{ÊDz€´@€@ÂÈH-'õ5†ì|]WÀÄùÏÐßÏiüýWþØ ú]éU‡Ö«,«õ÷ΗÖéAýýcnä›t›.’öÔ/«¥Â}Sä¸f™2~åN1á¨þŸ½³€‹*ûâø»»»]»»;×îX»uíîZ][÷o®­»®ºÖÚÝÝ(*"þϹÃf†‡šü?Ã{ïöý¾Af~ïÜs‰ËùkxåRœáG)öàÃT!K|ÊÆ«¤°ðú·³Š7mĪ&©_œ÷8ä·AïÒÆ›ÜΨ•~?ñœÒrØ®2<Æ<üyé Ï2Æa¼Šj,;Dd‹á¨‹Û»uç2ê×eºÍ‡C+¼q{Nuª´±9 @":ݶ }”‘`|öçUbÕÝw.ÂŒö›—£­õÙÃS ¡fã²¶ úµAY åãÅÔÅÃÕ6”ªl M˜MÎë'Ð~úâ¦A n¾l¦¡8›ËÞÖ°=~_,ˆÐ̃ãÍÉ·–Ó?u¯…F0J Æ…l„&vý%{&[`±bÅR¥$¨= @@¬M ztÝçwï8Æ=,B¨Ìbjû¼‰ŒéïV9¨$ohûž÷8“Ï4ß&—¢‘UÒÒ+N“R/y±e¼§&âJ¹i¼ië¡_òR6~ }ýµUåM`?O*¥ð.{%|›\šz—IEϹþ®;nôŽÃn­mžÝHÄ­š)>•ç±™Ú_msR)Þ +o~fjÚGsH­×<Æx#XãÒ™DÜ*ìÙ[Öï³”i¶¾vŽŸŸŠ¡ø"šG®·|‰z¶¾Mè@"<ðwc‹ðÂn€ Å¥+çî„]ƒ¡l)"ÅÈ]µp»šM¼Ρœª[“€=oˆ!ˆÿæ ÄØCynÄvT aiã¬êx”7årx˜’M8I/8n®xí~coÚ÷ra>/Óbf㌠æÐ¯Ljð÷=ráP ÞSK«/W^§M¼yZs²§a;P×b)(¾¬,¬J¼¯á»îÓ„êNÂW¿!2—n›oÓ~þÂ@;×™_zcOàu¼QÛܺ™(oh6aïCîû>;$'¥ùK‘XÞégéo K6ã1øð@dô ÕÓ5ëè¨ÛЭZµj´jÕ*2d]¸pÁ¨ .@@@ ¼ØÙéüBHgÏžå¿Ã¾Ø;¼`³Ý;å6[£ïCp¸[Þy£*§#ye¥X¾Ð»@PEhfíŒAæa1Öœ%äÏr‡øseP6‚½ƒå˜íêh~Ε·fz‹ú½iþŠ‘Öì2ð¾¢±xÿ-â„V?»›kœØ.9 Á"3fLúòåK°ê pä !7 ïS“vUiθÕaØb蚊H1r/Ÿ¹Mi2$Ý„PÛê–5ÍBíþ¼EŽõ}¤G>ŠK÷߇=+¼'zæ§¢sΓ¿†fgÖ0/°s Yp€¿\”ýý"9hí±xúiBIŠ=äˆQµÃ‹RÙç)á“x²¼L0ÄB. ѲLPß·_]8~Ü|öú0´×f"Ѩ㔘—*㱿UŒ’rZHM<$^Ž.NI' 0ßü¼ãó2rhË–-T¯^=ª^½:ÉÎá0°&ÚµkÓ_ýEšwî7y 0"P⧪JÈ}úâ¥L–Î(ÏÚv²ÙYú=½}ÿ"åÏUÊÚÐDi%J” }ûöÑÆéíÛ·Ô©S§(=ßirÑøƒ>i…áo[s ŸÞ…2d5Žë†]XÔ”ŒcЄv”-Oè¼-êì;…¾~õ¡ŽuGR«nµ©\õÂß)ìˆH@6¿Xi98ÖY\?×Ü8o¹zÐ+^æ–*^ GÖ´Œ›‡·ÎsÖ CB2|æM1âzºúåŸ~ô’9;Pj¿M-ÞñRºxfú÷ðò¡KëM–ôåâ˜iZüZ­w^Ö‡7U³3ÍÐ „òøã¿yò²217¯ne›ÈDÀ>2 6²ŒµH™<´jþßäË¡ ììl§•Kÿ¶4¯/Þªû¶=ëÙrè@@@@@€ \}ñIÏ!†½¥‰ƒ£‡îûJÓU×iÝyW]»±ìéÛ¸ê¼Ðœótö¡»:Ï•*]îSPß÷|R¤`EÚñßj›"Ðmvf[!÷ūNJAãÚ]mʃ€@d#!7îXÖ\éT«Þ{PÜøq¡Ëš´uh…=WMš"¡eF)p!0~ïC¶óA€¶ ¤q¦³½ H·$Áõƒ—qÇTMGÃ+¥%M<Á®ˆ¸{»æ¥ ™âñ2~-Ç’V£v™¼9Š© Þ{x2¤Ía“ÉêbäÚÖégÁŠÑjî1×&ït  y „îñkäw¸Ž<•߯^×.è6ú ×΂hÜÇǶœO¾L Ç b„Ȱ¯¾:1õËäR$¯·cKÐÀò©éÜ£Ô~Ý­ áé{]؄撨ú¼`_ÙÞÛnê("®˜,å‡X´zœÑµ5/"‚{çÁÊ›½¸5§¾@@ J€Gn8ÞÆ-«÷Q±rù±‡ ›þfãÐ ï>§ò5Š=Hä‚€ä{ÇØ=hÙ9WzÄñ× ¦ˆCSª§§ò~_,h"XE*,¼DÞº!öщÒÇ‹I½J¦¤‚©U;·_yP«ï|á‰é`G‰œìé‰ßœÀ°ªiVʘ(V`ÙH0%àÈ!Ää8¹Fš²ÿ1-;ó‚–6Éì~>zû¨:1ýÚÔxè0^®–§?{ùR,Çïûy~õ%Óöµ6"ë1cÚ\tëÞE› ?Eã¹¶ó’ööþB?½£ ¥êÛŒ:ÈJàû9#ëÌl<î<…²Ð«º'ѶŠ-C+|ýªûPW©¶n魠ߨA zÿƒ4j÷CJÈq׊±˜*Kõ*Ì¿Hœ~.ÜË.¾ö ¯ü÷#ÉXyù5šuŽê,¿¢úgwù⢽β°{âþ{úàåŸö‘Ïå ŠVFŽRæÄËOFiXj.·‚€€‡€¿Çìg~˜­ßAšì™Q G¼WéZb4þ|Vzîu™rÌ •·ýÚu\râ¹J—väuÓÕC]‹vXä·ó*ÍiÈaul¾æ†>$ƒòbáVêˆq´_R¬A‡)ɸ“ª~TùQ­|3ÞOŇÅTÛ¬ ´uŒÜÇÏï©[Y´@ŨrK1°xä†êú-+Ò¥3·ÈÇLJ¢Gg—>˜-7;ûsÉN5ãd)Ù`æè2"póð¦xNüü?øÖ£TJšS7“¾âñÌàõm×ߢ6?%Ó§‡åIóœ iq£¬ú&SM¡ø¼ÔOû˜êúÑ‹š­¼Î]j)–÷þ×Õ7ªpJ?oËk¢$€€€D;;å&y­n‘ÚIF‹Ùs6¯t ïïšAì±ûÚÍa¬´W¢Øæ‰žì’‡¢êivì+¶}Ï u´úk«ýT^¿Õ…Fk\»«Õ§A@ *0ÿW3*Ì,Ì!NÜØtâà%jغ²MFc«Ð ®Ïߪùæ.¨ó`´ÉäÑi„# ¡×FVN§^Ó<¦;ÐLZ‡TLC£*§%G~ða©)¯XŸoÔÅá loçÜTqþ%õ¥{›dŸyN¶M¦qUÓUMå}øâKÒl°±üôKšË¡r¦ŒCÉY †€€€@d%P5«ÿª¹Ÿó'¡:¹Qí¥Wèïko©‡– /;pW'äÖ[{3@.?àç«LéòÐíû¶óÈõµQ|Þ³—©ûŸ,IПã¼I   @È Ç7B2ï9Ž=Ý´­„Ü[ލ%L/è"÷‡%ПXawÞxK5_¦‰ûÑ^öº¨)¾ELÒŒ;¡ÊM©™!ÈòÒž“³uæ8má- ³&”²Ž–¹î¼+ÉK³ó} R>ra    •”JWM篫¯•«yÁz…q˜¶"iœi+o ûnX]˜¬¨1séÔb ×85C_%ïZñ-+ÔBß3ÑæK(VÌ8dÝ!,šC  ððÜî‡Cú ×m^A5òÕÛ8Fgè[¶¬ß â‡ZÖBÈJÞ}–AÄ ¼¨–E(’Ú™òñK>GànÑì«-¹¬Ê_ìWPœk¿ko†%Ùa­ýº[T.K|ŠòçWí '£/“KÑ¥~¼ùoÆ‘ŸwX~@†ï@@@@À¦.<û¨ú¯šMç©Sž~óg§í×u«ì´Á-8ö\; ѱXZUïüS]!j$ UÊ”.§šÍ•›§¬>+#×Fß_¾zDJÖ·úœÑ!€DrÃñN&H¤û°râ€m–ÌØb³³¯W¬C߆áHMGvw^¦\3XðqŒÞz~Ubé°Ši¿;­î[nÓ.þR±´IVÊ“Â2ïØoê«ë'ïïö¡°ãÿù‹Lî±é.oÊAÞ¾Tÿ«Z6Ž    ) h±iwÞ|K#þ}@å~¿¨BOÕË™H?Ÿø¼'ÀÞ›n´þâ+zäæIµ—]¥U~ûè ó¤t^­Çq±9çÕª¬O¼ßlJ»ýúªû~Ʋ³‹Nvüóôùÿ‚I2ôÅ£qŒÞo6­ðøÙ]5ø¶M…~h@~P–¹¦ý pÂbÚöötòð%*Y©@X4¬6lZá©jŒé³D­eƒ…%ðòƒeb÷£»Åâ/FS»ZÁ cÌž‡4ïÈ3S5‰·¬¥æ4ú8iˆFSj¦§ߣۯ<(sb'K««rÆ$r²WËÅyÁOà`u€Â    β&Ñ}þ©²ÐÏÉDV69ØQñ .tô—üF½¿^”¢ó÷&+®©t—¸Žä3®8EïȨœ4!&ž†Ý̇%)â;­ ežršªsh-½ñƒóæyýEd{Å̵¡Ë‰:?“&JM管¶d]aSyäz;X é¶—«÷Jt±a  !#!7dÜ,®•%gZzx7tË,î̤ -B+Ûb9Å GGÄ<2¹?üåÂϨˆÛìæjG~-Bi°8j¡ýïÌK¹ëewX¥´Ö"Úzå5{z\øeOêDwܧ,“O“ïÔ2ÁÞù`Û\TfÞö yC5²‡ßF OA@@@ šæKBò²Ô|Æ'O^‘äË‚Ÿ‡™óžZÚ¨zIޝë5¥49D7r4ÈLsëe2*+Rê.“ø»Ÿ½|U=­m­°l€+ýØ›ƒµ2Qå˜%c>:x|›Õ§#B®-6;“Î2¥Ëmõù¢CˆJZ!œïfî‚™éÃûOáÜ‹ùæmâ‘{ò¥Jo¹·¤ù‘#5*H— 왟¾M,,WœÚ¬Õyz;ò”bŽù j)yï¿õä%€²ŒjäHHå3êv†6ÊÀ€€€€ÀG £ß†gÇÏî¦b+[mþÑØ7ÚÚ1rï=Ô…é¨V®™Õ扎@@ *€Îw5~Bß®ÍG¨fã2áÜ›qóÖ­°wû 5€ŒYSW J[åv -\Ú8ëwÛÚÑ!—Ù2ã8´ƒ9kÂËåõ=ÛÞÞ|»ß«‡|¨I@¼c·íZn]!—ûkÚÌÅY>ŽFÑ£C‚°&wô õ|Ç,êMØ3rp´§›[½kk‡V8²÷¹Ä‹cõy¢CˆŒrf-LÏ_=¶êÐu¡|­ÚçËWO¨øOX¹iUèè @ J€k…ÛZ¢|~úâôRîð†µ=rŸ>t¥ŸJæ © Mˆr ç+GïÝ_[uó1µÙ™¯õ|t£Z[[«;ô Qš„\+Ý^GÞðì¿§­Ô›®k…VÁXB+Ôm^ÁªóCg      Ù /T•Dì´–©¹üýÍöäù]ÕMÚ¬Ñú(OB®•nqÆeÈç«•zÓuc-\ÙäL,OÁ̺Žñ@@@@@@À"µ+ë¼U½¼¬³§Š¹ß¾YÇ#÷È©]$ý¹8Ç·ˆ €MBnÐ|Â,·r⪭·¯ß‡Y›ßkÈZ1rì;§†’()þ8ïž @@@@@ $OšV]Þ|Ã09ÜÎuB®u$o—A­6/t     Q‰€ƒCLÚ}hƒU¦d-Ü›w/¨ù4¨ÞÑ*óB'  ð#€kÅ»œ$Y|ºxÚŠB. ¬ámNé–ÿä-”5¼»Bû      % ”*\ÜÞëö ï J˜ßoá¿˺¿æ« "Ã@@†„ܰáhQ+eªü¤¼W-*…¬ZáØþ #¦cŒM€€€€€€ÀI M“jâ_¼>‡;€h$›…ÿêÍ›wÎS¾%Â}>è@~$r­x·KW.¨zs}þÖ*½Z#´‚xä¦Í”Â*óA'       ÄqrQÓ:væßpŸžµbä~úìNåJÖ ÷ù ø‘ØÿH“µõ\%N®Ø±ÿ.Pݟˇûp¾ù†hy[£aépŸ :¨L`ñÔý”8aø;É é1—C„¸ƒÕsO‘S,ç¨|Ë07°:h¼¤"ü×TX}Zè@@@@@@@@@¢„Vˆ:÷3ˆ¢ äFÑ‹i€€€€€€€€Dr£Î½ÄL@@@@@@@@@¢(¹QôÆbZ         Q‡„ܨs/1(JBn½±˜€€€€€€€€€@Ô!!7êÜKÌ@@@@@@@@@ Š€Eo,¦         u@È:÷3ˆ¢ äFÑ‹i€€€€€€€€Dr£Î½ÄL@@@@@@@@@¢(¹QôÆbZ         Q‡„ܨs/1(JÀ>*ÌËí;ͳ’Ý}n4òQ«nuÈ1†ƒQ:.‚O mÍaVJœ,>MYÒ/Ðü¨”aŽC´hѨv³rT³Qi²wˆ¿RQé–a.      b¯ß¾ x. ÉÞºBˆ!¢"€@˜ˆô¹NÞ ¾­§(×Þ>:¥L›”bÆŠAÑùüè¾ Ô¥Ñ¢oaÆ+ÌA°]­áaÖ^pêPwdˆûvp´'ÓWg§àt%ÊÊÃy98êþ˜o[³Ÿ:ÖEß¾…îÍÖ‡ßËòÞðøä%8a     ™ÈçyŸ¯þ/_ŸPÆLóÇX <~v‡:ô/K]‡T5ÎÀ€؈@¤vt}þ–f]¥Ð ߎ²çÍ`„ñÑÝg4²×ïäËŒíØk2¢YhE¿ÎÇç«OH«Ò¢Í£B\7ªT!{ᦑúéˆv;¢ÇoôäÁKš0p1ý:µ“>/¸'Ÿý\o¯¯D±ƒ[åA@@@@BC ^ûìf«'Kœ†FXFI¥2›D°Hí‘;uØrŨ~ËŠD\ÉH“1-ß>ŽììЏ"¢¾{ûA½‚#¨zz|! å`‰½b¡ÙÛ›¹šŒKúr{mYZ7o^½§ÏãÓÇÇ—^>{£ Íèø•Ù¸>C?x¥uáþþy~ö PDX½za9ïîŸè-s νЩ_‚<#>£‹ºzöØ5°bäñÑ“çû–ŸîûZÆÒ ÷›WïèÏ     ¶šÕéAMùU¯ZGJ"3½xõˆ:¬Hw\ ÛŽ"AkuÛe£ÃjE¸‘ʸÞ#Ü`1 #‘Ú#÷õK7’ø¤µš”µÇ—/Þ4ü—9,ú¹ÕI‘& œÙÕ(ž®——7u®?šŠ–ÍC ZV¢í§ÕÏËLÙÓ¥y|üL¿v›£b-Câ¦.Þ2J]vm4–…È/Z–ZF¯]ˆè,öðÎ3?p)¯L-“9òe ãÚ¤9r•æMZKcçö ÛWЊùëóbǤY+«P’Ø«Å$r÷QŸoïUë[ŸÂÇû´šLõ[U¤D‰ãÑ¢éõ-I"@þ½îmYµOŸ®tØ„ —έ]ÒGwêñóªÖ $+—FtŸ«Ï“û>kå r‰‡vl:D–ïÖç‰g¶xh›³mk÷ÓÖÕû²ÒgN©„Xi3¤fo¯ûU²‹fülä‹§MýuݽùĨi{‡è4ãÉÙEçv»{Û1Z»x‡¾Lï–“ôçXâ‹}eoêñýÒ~ZÃ6•©FÃÒ†I8!&u~Ñ×lݨý{`Í_1’úiH[—ÝÐçý('ß¾…Þ%jß»Ù1§ëïÑâþ²ÖÖðî¿)7a’x4sÅ Z¸y$eËžîß~ªc­\HŽ¿MX­ª¥É˜Ì¨úô(WêéËМÕC(EêÄôÕÛ‡†Ó?•È¥î¼ÇÄJU* ®å½‘ˆÇ«Œß›ÝU"n©ÊÕéKû‘?(ØøÇn:´ûŒ®~‚€€€€„)*e›XÔÞ݇×èÑÓÛ•ýìù‰®ß9OÞ_½ƒ,ïããC×oŸ£'ÏïYÎ4ó1—üì®ir€k߯tùúÉ0Y­xùÆIv> ùªTÃÁyõâyŸ%Ï/–¯à4¬¯¿{ÿš.]?A_¼¾¿jÕÛÛ‹®Ü ôå%ÆiîBY(7 ªbÉR&2Š+[¶Za•¾oûIulÑ%à— jl¡6Ó:¼÷5l]YÕ5üñüÉkó[wJ^'$æ+’Mmh&O(e9’ä tãâ±Õ·a›æÎÝßû{ôjùâUjêͺoû ö¢ÍKú5ÒŠ©cÚL)hΚ!zOTI,W½0½÷¶­ùþ^€j5.«Êj?n\¾Ou›—§:ÍÊ«¤é P¢»°~Ä÷füü^J•Lé³Ct‚…þŽ}jMÐ×w*†mÜøqhÚ²þúôAÛÓ”¡|ï/ÝS! ’¦øþûé›ï7Õuˆ?xÁ›ê§Ç÷_PlÞô­ÿ˜¶ú¶å¤Ûàf$}ò‘ñŠw·x”k?‘ É{áÈÞó$1Œ´ª¬êiùrܸr·ø ½o%¯iÙ8oùœ­,4ê˰>ÎA@@@@BF@6@ Ì<¿|¦î¿Ö ×oWÍ¥N‘‰æŒý‹?ŸûOýwlÍ^2Ȩ9)3vàÿ(WÖŸôéOl§9K«×ô‰|’8aJúmÜvŠ#–>ù¹ëCê:¸ uk3–>¾Eÿì[©Ï““~]fP©ÂÆßsÿ‹Ç1˜¿#û{Û:Ø;Ò†E:§) ] Ù“çwI»Î™¥0¼BeIZš”Y¨yý^4ñ7/æ-K¯©yK¾a›Z{r”Õ®éªU©5è˜Ëˆ‰6®„ñ“ÑÒéômÍ^2„þ;¶E-'Ñ£ÛÓȾK(Oö¢Fé"ª·ë[–>{ú;j9ljOC{Ì3*‡ °5ã¿(¶M0úñôµ*í7¶Åµ.ž¾IÑ8^nåÚÅÔѼBwo= Ïž½$5WË,Y!¿:½xꦖ¤?Þ½ñX’CW«/áÄ>q¸sÖ¢k-½ˆ«åWo¨«%lXY¯æ“ÈôµzáöÍËZSW K-œ€a¥"¥t!.Ÿ øôZ¤Y‹R ÇX´jî).bÁä=”%C^}¹Á¼‚pMLDR9—W·6£õeääûk%â kÙbuÌ~2ªÈ…ÛûW*|…ˆ¸íš ¡ÿÍ>F£û/£”É2К-sT­á½êÇ% Ú¸†ô˜«ouõæÙJÄM›*+-›qHÍoâеj\#¦¶ à}ܬ[A%âÉ_‰NÞ«Ä^ñ„A ‰@¤õÈìɃ—*Žª%@}Ù‹R,f¬¤-_7¬'ž³™¹ÍÒb:ÅTÅM7êÊ[8]£Z™&Q¶<é¤Åˆp<†…¼8V±„ xûú½ºŸ>«lÙLÎÔ$\‚©%äð bÃÔ\8lÃë—Æ‚æöšûgã!£8È’¦õ)÷­fã2’¤98Ú+/ë÷n9LÄ^:øï²¡\õ"äȃ‰÷+¯§ÂC6°{íêdAeþ>ùÏÙnoÝUšxùŠ—. @@@@@ ä6n_ *?zz—NœÛÃKí=)®K"š4tQ£+6Ìà%ûŸ©^ÕT­œÎiÄÑ‘Cò± yîòaºpõ{–~áph1T½)óû¨ã¦ÅW”w¨\-PI½Þ»¿QyòcÔôŽêüYG(®sužjÞÈËR{x÷í»€—‹{•ZZ_+gÎTË é1‡f­ÉFnw®= m3Áª/aÄÌyPk •¯aþ©¶–oz” ÇXBiÌ¿†ãøþ¦6—3,'Þ»}ZN&SÁß°Œ%çSW³Ó‡÷N™6©V GXµy–QÍ.-GQÕrMÒäâÀqPغqÿy"hJˆƒ«·Î&>~øèF²d_–ø›Z\]˜7÷n,{Pü¸Iô"®aY5; ¬@ÿÝ@ÈM‘,½^ÄÕê´hЇoJWožÖ ¹É“¤UÙs8´‚iÈ­ž¥GùÞ¹`²ù½\,mC+÷â•î{b¡<l'L¡ òxì¬n,% U P® _»O_ÜÓç=­+?Á@ÄÖ2G÷_J=‡‡\˜ÖÚÁ@Š@À¿aÕr8·ScÜnåM³Dô´Ä\â;«bÞ^Ç6²¤KÊÈÆ[òºÉ±]gYI']¦‰ãQcƒ ÍkG6OÕëw•=n^2æÚÖXµH“¾cÓ!%âfÌššúk£<¤eðnì™Û·ÍÔp›‡oºæÁbîøß{RŠ4I´Ÿ‚ÅrP,§¼ñÝGåa,›±iÖ£Ùòüü…ãßÖäÍËüã0Éfl"ø[jö –ogi5”­Ën¨8¬÷Ý ¡“[Ò‚•£èÿÓ¼nµ&ß¹¿R§Ý†TÓ’ôGϺx«·ï_VB®¶Vü¸‰õeÌH¨±ì™ ˜ËÖ µO_<oÎáHëOÂ)h&Þ½9³fq÷”ŠU+!zwœ¤-ž¢û³xõX£M¶DØ•¶b)’¦ ðÊ”.Ê[Ž*û…?p{§}%.mP&qbÅâñRÿ ÌÇ'à÷Zsåûþ7~Ð Øm6%ˆ—”NžßC'væâAæš°Jš¡Ðšeã2±xq¸'rŸ å)Kõªµ×wñâÕcuV‚´¾aœ€€@8ˆ´¹Â¢CŸ4oâZÚ°ü_’x°Šæà'f:JcöÖ•4iÈRZ¼e4‰GcÖ\éè&§ý6n õÓZSBÌŸ²N]OZÜWŸÜ“¯Þ_•°l(‹öM5ãèh,ËS£¼ ÿ°ÆqÖýQw}þÖ¨û B´àBBH|ZÓ¾-¨ê"ñºlÈõÅÓ‹ 7ª›3nu¨ÛªªõJÒÞ¿O¨?•ÌEò ÀÐDÜwppп ó,9¯Õ¸,‰H-ñŒ5®†±•ù6ëÛö澎ÿwÑl³Ys¥§ó'®Óõ w©Tå‚Fej4*Mÿl8D‹¦o Þ#ZåÉ…„ˆÅžÇ0°# !òå,©âÝ8ñ7U(QO5¢‘ÇaõþêEÃz/øn‡‰éBHLÙ ,i¢Ô*ûñsÿåÿÆåuß1'°,Ô€q]ã«â…ª¼ÄCµMŸ’tCEäÉ^„*”¬o\0WòýÈ‹Ûe£%å+£y çfãvÍVLŸž&E&ºûðŠºúDœ€€@%`¬fEÐA6¬B%rRÝTˆ‰Qjoœy£+Ÿ¯¾ô郇Š1ëà`Ï››é¼ûŒnM]ŒæØº·©çÏ(}ÖTÊC÷ÞÍ'\LJrÌLñyµÚ1ç–ÏÙBÉS%¢|E²“ûûtêàeÕÜØyÝšÕÄÔžÍ'*úùãW4oÝ0Ê™?“*wˆ7Ð’pyÊJ»·#vEäýô1ôÞ˜¹ d¢#{ÏS—†c(M†äôâékúmÍP£ñ…×E³ŽÕU<Ù!]fQ¥ÚÅøžÙ³Àz\݇ðêSÚM˜$I|ßKgnQ—Fc(9on—·H6zÆ›=¸ýT Û3W "ÙD/¤–=°op8 Œenbò€á%Çqî×f U©WB ½§\áMÚbч÷ºjݽŽrÿ˜»•öýs’>¸¢!“:¨ Ì´ªDû9MDýnÇ*í ü–÷¯l¢&B.Â.@Š5vMs¬Ôš4wÙP½+JÜÖg/Э{—(K†ã0µìY”Î_9@È5 {ÜN¾²ç°¼ì£û;7}0m µgèàôÆí%%Œ²ý?J©NÛv/§ƒ,º["äfNŸ[ ¹ûl1òÔ•q8¯ÛM#Ž  `kº¿$¶E(ú¯Ó´MXЋ²åIO²±”Ûkwr÷‘\âÅ¡]kÑ¢-£ô¯18l¢ͣ(? xÜ=èÒé[tûêCŠÅKü»jBýXè54;öš_s^Žvv:t±ãø/‹)Í”EÊä¡çO^ÓN÷p”ÅÒØ,Øý:µ „.†MÓôå”àû‘Çq÷ÆcJœ,¾ÊOÑÉì,‚âå³·iÕ‚íäõÅ›Æql×,ìQ¬…ˆÐ‹éä¨N ½€õyzBÌ0¾ª\·ëYŸræË¨Ú½sýÅŒv!DLþæLâÉÊa¾ì½sÓú{ݾwÔ}‘òqâ:é«isuvñOÓ2cèæ&±iMMË3Mï3ªuåM礎Ÿ;Ø»õÂÉÊ‹ºi‡j$âº%¦Ë´¬´!öç’ú,‰É[´l^r{ã®ÒϰˆÛ¼S ê5¬…¾ŒáI\æV·yy5&‰ÿ,ïgÍsY>Øü¾~¸„?sȹw»·SǤÉP_“÷¯a»894)u7â]zçÿæÃ=ÚWŽ™ÑQ‰•¦=œ½tÐ()K†¼jߨéŒÒ÷Ú@M»êVäÅŽåÌ›œ%TùãçübTî Ç´½ä'ð6©cœgTð;‡Nl'-„ƒVôÜe]ÈÂl™òkIúãÛw®jÜú Oòæ(¡J®Ü8S_C„ï6½uéúD¿“Š¥ª³13:e»r˜u2/”ûúúo -•D˜•0 ï?¼¡½‡7µ#2où7ªÕE•ù߆©äΛÑi¶qÇ"Z½ÉÜZ:Ž  `KÑø‘eël9Jô       áL n»lªÙìÌÔöÚHóþÆ›¤‰CüCÃMœÛƒNžÛ£Š;:Äd!1=zv›>ø‰‚†my~ñ æÝ ëcíj¡¤r¶LiÒP]»"N6íVˆ÷wù¬ÚM–8 ½q{¡_þ?qÈ£ÍО»>¤®ƒ«PªäxäUGûñêÍ3ê8 öpa8{æZ“êÛÉE;MŠÌFérÛÉYå%O’VŸ×³ýª]©Ú¬íâµcôÞý5•)Z›6,ºd$âJ…E¼Ù[s ã«PÊ?~n¬˜±É9¶nu©Ê4ù‘,qjúsþ9ÅFm&Æ« Gõ]ÊBø*rqNÀc3^¹*¢ðŸóÏR•2ÍÔ¼e39{NëÙ~¢‘ˆ+Ý̳’ñœ¤Ž”«Xª¾÷4)3Ó–e×yc³r*ÿ ¾/^=bOçÜ÷#W*Mº†Ã0 QeåÞɽ\=÷4Õ­ÒN±K•<½¾mœ€€€- À#×–ôÑ7€€€€€€€€€X@¹@B°%¹¶¤¾A@@@@@@@@@Àr-€„"          `KrmI}ƒ€€€€€€€€€€ äZ E@@@@@@@@@@À– äÚ’>ú @ȵŠ€€€€€€€€€€€- @ȵ%}ô          €k$[€kKúáÔ·——7Ñ7Ë¿wó1µ­9ŒÖ,úDz (         `uöVï1 ;\³ø:}äj -öÖœÒgNh~DÎè×f y{}¥9k†9̳ǮÒòß¶RÝfå©bíb4ò::uø2)“‡º hd]Éôüì¥ÊxzêŽß­Ž>{|¡_»ÎTƒnß«å*9G€¦#:YcVÒíkIÞ+ŽŽä76žÝœâÄ2;ô…Ó6Ðå³·èó§/dïœ]œhĬnäÂõ`        ™DjÜK§oÑ»7î¾|||"Ó½0klg'úàîA[×ì7J7½X0u}úð™rÊ¢²uÚ¼v4-‘¯ïÝ|BnAÜOMtŽÈsÀØÂ‡€Üûvµ†ÓÅS7•€›» NÐýÒ~i:ž>òµ¯=‚N¸HÑ¢Eã™Xȵ§7¯ÞS¯æéîǦÅq     6'ðâÕczôô¶ÅãØ¹-Õm—N]øÏâ:(  y Dj\ ûòíã´Ó(süeH3Üi&ú÷ Õý¹¼Ùyy}ñ¦¯Þ_U^Ò Õ±CŸ$¯Èl³¦¢aÓ»Dæ)`ì8´û,ýÁÞäß¾}£¢ìAÞÙă|ݲT¦òO”,U"òù꣼ë‡NéHÑí£ë[Ÿ3n5?qzüµƒ¦/èK‰¤ %Ó‚vÌV6/¹¦Ž]UR}¬™w†œbÅùnÞ>VÍ××öNLw\¥~cÿ.:cÔÊ&ûwç„Q“ÀÕ[ghôôäåíi4ÁÉ¿®£¬ó¥ÉŽG×iÔ´öäþñ­QÞèþË)oŽbFi¸‰@¤öÈL7еª`™&ÌŠ‡j`vœ½ Åj6-XóéÁ‹4Üñ·¼ù†aj0çäøƒÓVpÊ6ݰh#°¶m”~îø5Z>g Q4"Çtâà%êPg„~4Ÿ=£‹‘ˆ+…›u¬®¯cz2vnw#Wòk7-kZ ×     `–€&âJæÒ?'™-c.±D¡ª*ùõÛgæ²õi·ï_Rç©“gR"®\ˆ¨+/GÇúr‘åäÜ•Ãj¨2~;;»¯8N.‘e*g°|ÿ ë¥ë'è×I-ÈûëÊ™µ05®ÕbÄpR½ ߄޻¿1êñ…ë#ê;ª}øäF™Óç¡æ úRl§¸ªÌÈiméõÛçFåq?(á‘Ú&bé„‹éë;å#¦£Š§©µ;ãJlÒ®-=n^¹‡í>GïÝ>¨*Ñì¢Q†,ìm:­³EM)›NºL­;@µ›” Pg˪½*­Z½Rú¼;×ÓÔ_—Qó.5©tå‚út9™1êtåì5G‰/Z·EyJ›!…Qà ñöÝg>=yðR%óßcÊœ#- žÔAÿAðü½[OhÁ”uôê…›>9eÚ$4trÇ@c˜ê ódãÿvÓž¿ŽÓ¼uÃhÕ‚¿éà®3ª…!<¶,¹Ò©óÕ ·+~ÞR×vÌ?sNÿÄêZûñïÖ£´yå^šù¿týò}Z:k3ÇTõ$»èvT¹NqjÒN÷AlñŒj©¾¯ï7aqÇZM”4¾ÖŒÑQÊž9z•ÄkZ,aâxÔsxsJ“Á²§ø¾¾¾4yè2ºÃñ`¥?±ä©Ó¯S;Qlƒx°ê¦´“«tUÈàÇñÿ.°Çë6ªÞ¸4ÕiZNŸ³kËú{ÝAòøøY¥9»Ä¦]jPáÒyôeäd%s=º÷<-Ø8‚Mß@ÇÿÓ=83çš5v•ª?ƒ¿øjX¹[“q3–#ÉïŽ9ÛÀ÷OÜ…›Fªìÿvœ¢¿ÿE#{ΣNý‘ðû^ŒëO†TPùáå©»æ²á1KÌÝI‹úÐlžß…“7T±¸ œù=Óž’¥L¤~w†tžE/Ÿé>h¤ÎŒÆÌé ¹Kgn)föD,nü8|ÿ[¨ßÿ…‘    á¸Ð9dËT€nÜ9G'Ïë¾{Y2P1ãÄŽOY„:{é ÌSÆlµE«t+K{wš¢Ïß²ôºþ<²žT+û3uj9<²ã¶@ï‘uéÁcÝw¦´)³ðw¨¿ô5}ØC¼ç°Z4kôrpˆÁ¿q©_çéTªH }™Ÿëõ¤~£Ò݇Wh*{°ø?}ž£cLêÚj4U.ÓX¯=4ªÑ‰f-LŽo¥>£êÓÊ9Çõåq?Þ#W„–¾­§Ð»·îT»YYjÔ¦2EgOÒ$ŠË"Y ²7XÊmé›ã·ñ«•Xæ'&UªSŒªÖ/I.,˜IlΡ]fYÔLÝæT¹m«÷(/ËÑß»}T¢žSì˜úü/¼i™——7oe¼\a÷yùÌmöP´§R• PÒ” iý²iݲ]úº†'{´c½QJÄ-Y±€†óÎF·®>¤n†²Ø±ñí»@‰¸…Y€®Z¿„áž>tU1L_ùyVöšsÙìJDÒõËÿU"n‚Äq)ÇþÂs›4d íýûßCªR¯U®[\ygÞ¼ü€¦ _nÔµÖ–ˆ‹sǯ¡ä¼”?Çöõñe¯Ð#´oû êÝr2ÛòÍNiX8•÷Ç€öÓYdõ5jK˜RVDÛ¦ªQ…šE8öï{%R¾xúÚ¨¼¹ é·KÃ1tëÊæÍ;× òÓóǯ¨;ǃý஦¥®/Ǿsý‘¹fy/TªYTŸ?}Ä´né.~ßĤz-+Põ†¥Ô{eþ”õ´—…qCûüÑ“äý´zÑ?JÄM”$žz¿yñ&|ÙódPåý¹t§au~áÔ %„§I¸h-b’d ôuËU/Låk¦G÷žÓ°_æÐûϨ~«ŠJXÕ2s"â¯ØÏü?˜)¦OÑ_LzSó`ñ^Dÿ_šŽS"îO¥r‘<ˆxÿö‰x+4$N¯s‘ÍåÏã{/ø¾Î5jJâøÎµBý¿!à ùÿ§Ø±h"?,‚€€€D[v.Sýµ×|^ö_….xúâ¾Åƒÿ•ÄVl˜Hot÷Á•œp^^žäúú‰r0¤áÉoß½¢÷~Ât€L“ù>óö+½3ñ4)æ—/_ÉœŒ¿[I'ò}ËõõSÒ„uK:–:¯Þ˜÷†þìù‰Ü˜‡%æñù£j'4a+¼½½Hâ{y±¤K‹ËÈ}zíö‚=Vß[\Gæ-ó 7»«q«—oN•J5¦‡OoQ×!:(iÿæ ôôÅ=%âʵ¼Ç E\Iëã÷C%ˆ—„ª”m¢qµ¼ z©Sù=øž‰×®.¥aY¹Ÿ®üþ0ý.oXF;—ß›ßi—8‚@„ %¾þB¢½½Í¿“’&N¥j\¼vŒFNkGiSe¥Ùc¶heÒÜžtâÜnæÿåÉ^Tåå˜ÈÆ6¡ûŒßí› ¡Z•ZµÑ¦wIä_Ó†…©i·‚zQsáä½ÔyPEUvó’«Ãø{¶—-{¥”É2м ;ŒÚÔ.®ÞÛÅ9ý1ó°º3³ ]¸ª;—„v͆RíJ­ôeµ“©¿÷¡£gvj—ê8ºß2Ê›óûú‹Q%\€@8ZI ‡ãI M_wŸ VWšˆ«Ur‰›>ò²mmY»–néQ<ÅûÕÔòΪ’D²Äª7Ò @Gö3*.KÑÅj7)g”nîB–di·ÚeÍ•Ž:öm¨]ê"úЉ—«&âj™ Zê‚ïÿµæ?-‰v²x)Ö¨me#WÒ 6 ]a‰Iˆñ6~4[5]¦D\)X ˜¿èhX1Uúd†—Fç½G´Ô‹¸’!Þ˜šЏ’Ö\[•õäÁ ­ˆ:Ї°XmƒPrmï`O)84‚'{{~úi¬xØŠ˜h(âJqã;«ô«çïÈ¥²Îý«ãöõþV$áÒÙ[*½‡ÏÐlɬMêt䬮Z’:Ê{¢ ¯G÷žåÉE†¬©D\IoTyÉ\´Ð’.O5ÅëX6$‹ådüÇUò-1ñ^ ÌDLauñÌMJÄ•Ðâõ˜ ÿEW~>‡üq:»8QÛžõ+®Dì¹k‡êE\)¨m(¡-4WÒ%TJBþ{úè¥: €€@ä' Þ¨ò¹6g–ŸÔdrg+¬Ž¯Þ<Õ‹aß›eôèöÏ%±*¶ÿèÖÅ{Ô}&mT«K€<ÓÛô)©DÜô©sДaë•—ð¦‹hÅzÿ° †õÚö)­DܹJÓÊßN²½_Å$ÝúïRöžnX”Dlň¸y²§ù“vó2÷•dÍŽ®Ý>C-X óøüA/â–,l¼ÂND\‰¡:¼Ï"’ßúuÑy6¯Ú4ƒž¿|hv8-XD·yýÞÔ¤¶Î!kÿ±Í´ó¿µÔ¢Gåñ;´çï”/G U¿{šze®Ùò›q£³¸9‹ÅUé[RëÏ¿èÞf;7HѸǰJÄÖ{ý9ÿõí<¾~õfµ’^ŒÏ“]Çõá“›µýOEÄË­ˆ:ŠCËÏÝ~R"n¥RhíïgYhÝ©8,eõÐÉT9Óíû—SïÛ"ùyÅcœøJŒNŸ:»*&lLmÕ¦™*©Y½¦YúkoÞ°¬@žRúëŲ́uã,˜¿§¹Ë‡ñ{ê«zÏé ˜9öK×NP9½;N6S"`ÒÌÅUâøA+2…ØâÕ㔈[â§j,àOR"µxs‹øÝªW %âvj>œêWë¨Ê/ãþ=¿|VçÚáSÛ*·tÑZêÞÉ{ CÚœ´ëÀ:­Ž `S•F›'d›óî3ÜÙ>$­º¿û¤„*ÍË.$mÖSB!HÜY1Ëd\¢ Õ‹Ð?,ÒmúߪÞÀß«Ïõù[Ò6D3ìÇܹô+&­©™ µ’åümU,m¦”$^ªæL›‡ä‰7¨X¢ˆ§é’±'å zpç)%äåùß3,‹”ÉmTŒCþ]¦¶nN@IDATkÆ·ÓNƒ½³á¥þ\B>,›½…6ò{³mϺ*ýöµGêX“ãò†‡M]ÚOy亳({íÂ]ÛX„ÝôYRÒˆ]t9uYöÈõ¥ît‡ÃšÈŸ‰ƒSŠ4Ihüï=”ÏoÓÿ;Rû‰ÿ"\›Z†l©ès}Í¡"Róû\¬6{bϳ’ÚÖFU9¬Gã¶U ˜¶kˆE`ÆÂj@™­X±‚•éøÙÝtõÖi;-6' YÅ^®•iÛ®åT¾„îó²4øÿ¹½wUm[VaþÿF©ÏÀ¥‹ÔT‚ TœüëZºy÷"ÉFQ¦öïÁõŠà /e¯ÉqI§©lñ,þ“…¿†rÓæ‹•—±¶Bt ‹\"„õî8…Ê«­Ê'O’Fy¸va/Ý®éï½+©VÅ–¦]™½–0âqlhâáÂ"¢¡]¹yÊÈë³—éúì…“÷°çjjýu©ÂÕYOȞѭiÕ¦Y4 ›NlÔàoJÿœž…ÊX*¹dáª,¨Ö¤…+GS– ùXÿS¥ÎWžF°Xwéúqìv‘´­Ùú¿ç)¡SÄWÍÄÛõËOÚ}h‹yRÕ²Mµ,³ÇæÝußÁV±€.±aÅäÞÉø¥ßÑ3:Ñ„Á+Õw„d‰Órè…‡ƒyÉ_AßÞíû—Õyªäõß%VnœÁ!<©mÓÁT§r•Ÿ2YzZ¿ðÕkŸƒf,ì§æ¢}·ÓûðÑM ÒN±âhIÌb5꜇6ïXJ5*´Ð§ËÉ¿uß%‹ó{>0Û¸øJ€¬zUÛ“¼D°ïcS“0ÿ[?•¼¾zÑÓç÷Ôï’”Ü}®ÞKÙ´Î’5¹=.ÏaMäý"á7º´Å^Ì™M‹ªë»¯ÒÔ(s:ž 6é’OÅáutŒE뜧|KÃmÌbaXz¬ýÝ_÷¸yç¼ÊïÛiª:Ê#7éÏq¶&%„\Sá%8P;÷oD §m )¼T¾]¯úÊ#oÕ|]M‰ƒR“?„"rÙëÿ $mÅç¯b†áŽñ’v±*uuOÕE?ÞsX†à˜Vþüñk$/sÓÀÛR⨊IŒZs–>kJ%䊀i‰%J—$ BhLÄ×ù“×ÑÙ@ÆoiÛvì­êcaao?‘^ŠÿoÞ6³µäjŒA­°Äë{ÅB}`m˜¾ß«püåѩוw²Ü¯Þ>êA„xöj&B¯X`íJ^ò”:¯9ÿž/—O½Ç±÷»&äNþ‡ªV¥nÉïUQ¾¶¹\bޝ›‘=…E¨îñóºë)ÇŸ~D³¥1jWâúŠIùô¼Ñ l(ñ‡Ÿ=r¥ƒÿž¡2U •7waúAȰLôèÑÕ¥öX.ò±×½„¢ø}òŸjã¨z‰‡x÷¡Íô ÛÀ9€€€€@Ä"påæIŽíéH†ÂWÚ•;~v7%Y2bCÅ«Õ4¼‚&ÒU+÷³%ÍОÃT¹?5*Ÿ5c^ö>íC«7‹šn«Êi"®VI>×J(‡#§þQÂVæôºï»÷x™¾ošˆ«•—cï“ið„¦JŒ¶TÈÝ{d#ÉËÔ´° ZºŒgîxó^¤†"®V>‡ »È¬9[1û¨^Ä•ü,rj¦‰¸Úu›&©ï¨ztûÞe½{à¸nÿ ‰ûjjÝÚŒVBî©sûƒrµX¯–@qµ¶´×øa€f㯠öýÊÐÆí‹Œ„Ü­,þ‹ ;ʹðbšˆ«.øG4~å`ïqi÷ÛKJ”@ç`¢å÷ï2Óè½,éòþŽË…Ë?W⨴!ö„V±IÓ¹ 2,øaNÄ•jæaû>cOÚøq“yûÞÿõèÌ›zx®o4¨ÞY/âJżišf󸽦‰¸’&ïwr?{~Њ¨£xÓ“7)ÏïäIÓåá"(!ä†äeŽ*&K h§{Z)ײ¬½mÿ§¦’Ä1aÅS/Ç0•eøš$›3ia,m¯QÛ*´—ìïØxX-#—Ø b²A”%ƒ=A=XØ´Ô4ñ¯‡uø©–š,³QS{tç¹JŠˆÐkZ>,®ûó½”±…Jä _†ø@š4d)ݼ|?,ºІ‡OÐlùöqÚi°ŽZ8‚t™SÒÈ™=LÍ5V•}r—p¸‰|âÀ%U¬ç0ã âÍ+B:6Ó¾EPŽŸÐEy‹·uª´IIÄl #¡ÍôNX_Ëû-=³ºû)8x9€kÚŸxÕæÌ—‘®²7ïù×-rMÛ°äZâ /å8»ï?§ÉC–Ñ9~ Ð‘ãéJì]€€€€@Ä% }I(ƒjåš 2#/­“µ_x)v ?¯O£Bf. ä.Mg.`ñt•ôóü\¹A'¼Ö¯ÞÁLÀ“$Ö§©™Ç{öÆ›¾°¿iqö®Õ­ »Å¦¹ZhIÒ(+ Ù2åSéâÕi©ÊS–ª–3öZÕ„BÃ6ìícè¿'¦kçK×Å?wöîOKm üç@„¼X1ckUÕQÂ#ˆ×±6GÃL-Öíë·þ¡ò¶ïщŒ·\6ËNê?y~×°™çÏ^>Pi"à›ãoZ!aü$¼ÂÒžnß¿h”uôô²î@²é—©™kWDR±7¼š©[⧪¦M¨ëò%ê±§õÿ8¼Á$Òüµ['œŽè»ÈlÐ$&N˜‚ Åüg÷д…}9žîHÚ{h#McOZS3,íÖY3³#‡™F›þYD«çž2-ÎÎLþ­–)ï ÙäÍÑ$®®–ozÞ{! Ôœ7o«Bùs–T²™ûÝ3­‡k°åÉZ=F°~Ží?O%*æ§v=꩘¸ZED(G¿eð!®ˆ¸b–.ýªŸª¼©˜¹û¶Ÿ z-*¨Ø ¯ÓR“eúâ +á"$ö¯¡z‘jéé8¤‚Ø©#W,rE¬–¿dÓ'ÍcRkKŽï>S—™²ù/1ÌsqEP4qãŸðjóå3݇/KÚwŽëO?}üLë—ïRÕLCiHÈ Á Þ£ÚƒKÚªÌÉy#»éô7o€WÔσ½Ç¯ÆzƒªyvZBL–kYbv,@+³¬¸%MZ&uúäôÇÜmWk8Ç¥ò%¾?ÚæsVB€€€€€Ít¬½.]?IÆ46‡x1ÊòðÖRÝ*íŒò»hÓdr¬£r/ß8®«ûOÙ4=¨kñ¤”ÇÞ¾zOgéB4ïT#¨*Fy¥+RBðÌÑ+Œ¼<ß¼zG³Ç®2*+"ÌŠÉòþO¼áÛ÷sŽ4$qb÷þuœ*Ö.¢¾$n±Ñï<£´¼™›%Ö©_CZ4}#ÞsV‰öò~1 ÁP¬\^åÕ=qàb:µ“%Í~·L¢¤ñX0®M{Ø‹·}³ÁA–ifªähÇ~ùú ãÍÂåÿƒ®/zþ¤CÚ$ê@˜ˆª‚ˆæL6Ó6*’žþZûŸ*Vž7¯[ ´®¤6›>âšÈ]03?…i©ßÁþÆ¥û*Þ¥ JÈáÆ)vL%¨Érw‰e+Þ°¿_äf[`Pã ®^ø-ž±Q¥æàåá–Zõ¥xŽ»é/?ŸÅ›0•«Z˜.Ÿ»Å¾'y)zj½¦µ'ãoÕ­6IˆîÍ&(¯\)÷—åß½õXq/áú-+ª*"^jBÕ/MÇSk®+âï¹×T ioæŠZóá~á;f,GòüìEë–îRáäþÏ·Z¿Xx BÞ/Ý™ÁjÞTëÒÙ[$ï5aáöú=ý³ñ‰§­ˆÝAYŸQ­hdÏy4ª÷ïT©N1ÊS ÉFi®ÏßЦ{(Iò„4tJG£&$«˜ÌW¬ï¨€µ©¢îÇíëHBO4ækÙ|N6;°ë4]:}3ØaäÞÊ{ñÒé[zoS±U (”?vl>¬:$K•ˆ™fÏï,˜?¤›«–“szÚŒþ¢÷;NrÌà+*ôC¥ºÅ)yŠDtïÖö> Ê˼ƒúÍp;Õ¥B³Èïl,þàä¡KJÄuæ0¡õô͸P@@@@ hg.TjUjÍѳ…wÎÇá<èã§÷b š­À‰5*´¤86èù+GX|ÒmšJâŸ&ŒŸÔ¨›¯]Ë…Kœwô•Úp,®KÀp †´x¦Ÿ™°÷°n¼É’Xou夹=Õv›Mšˆk8æð:¯V¾ý¶ì2»|$ÄBnò$iÕð.\;jñ0c;¹lÄ%‚ªl…wíöŽõ›LCEh Š XžVÆÒcóú½hÊï½hÞò*ü€Ô ° AG~—ÄRXöÍ;sVÒÄ)ƒj6LòÄ3ù·q“x ?çMÿ` èÖ$G„‘„` ÚFFÔÜK¼ò4Ó–Ÿ³îÄ^}ѵd½¸;bFú}Ý0š´°õøõgJ(.‰÷¬ˆ2š‰ø#¦-åÖÒÍg­¬–PåÉFô˜KÓ†/§¤)p¼Ü6æŠ7­b-öî䱋É8D@3gZ²iþŒÿ TÞŒOÝTbîþNQ…šEhشΪÓò·Ë€Æô>:mdx2Ç–• á6³hooO?×Ðæ¬B™s¤%OÞ¬kÁÔõ4}ÄJ4A{Ñæ‘yãjc×vO5lßô\+kš®]O]Ú_‰f»¶¡áÝ£ÙãVý¿½{®¢:8þ9C@BKi¡<ÊP(5­´¥<ªƒ(   !   0Š  T$`yÈÃBxˆ("¢©‘JmK§>¦¦-X*%RT¬P¯¬ö|çf/»{÷Þd'MnœüÏî>Ξ=÷wwáæóìw¤]§¯É¸iÜ*Ñ×Dm%Ú§ ø÷7jÜÐCuâ7½~tÄsÞŒu²æçÛäøÅÒHÏèyã-´n×R®jûÿûí»eéÜ'dÑÌ vb1 -kxŸ˜C5÷kô7ìVmZÄÔÑ «Ÿc&þj*§Ož³}zèîµ²Òôñ€ÉùÃNÞcüoλ7º6mîåÑsÌ}TeÆ‚”=ñáiyzÝK¶ÏÄÕ{±·™À,/ªç´ï!­Z·’³çåYÜÖÏAƒ¸ä×ÃK6Ææ Ó½eÿ=¢õµ=-î´ß6ùµ‹LÞ%s6Éüéù²ó…]&ðœ.+Ì=BA@¨½:ñ‘–ý.¿õ÷6³<%ÁêMsý»â®;£ ¯žj‚„¯æ={°Ù1rÈ]v÷²u÷xª=ÿ› òò®BÏ6]>(ò|Îâ11ûtÃŲO<Û#“‚}n‚˜÷{¶kŽÚõ[²Ûæ»&ÝòTª†•¯dD‚ÆNÞWç[_Xå,VËëõ½†Úvu28¿‘îPM]‘¨|9#’’@ý/zû˜ª:âUÛñ—åßíÚ³ÓæôýÉõÙþ*2¨ß»mÕÆÙ1ûtƒ“C8pgœ=ºõ·{þø—²wÿ«v2°Êæ’ÓdàæåëgJéÇbö½wô 8£ÁÝé86š¼½N€×}бGäDñQ»IÓPü¿‹~>úÙùËÍ÷ ßýõXG &®0êç5q¢ÚpŽOÍcÕW˜Güë•çÔü‡ ´i,gÊ ò÷qlVä/H÷äPe/Ù€¨;pã?ν~ñ“2;2W{)©‘ÐþG¯Ýõ-kj‡Oͤe©©)‰ªI¢ö/|TjÛH3z;i}Ü<Ñ(JÍ»ªïCëèèR'xÔ Mkpá£ÿؑǚ 4ìÄD}÷Ÿ¯2uõÑ{ ~^•ÖÈŽÀÖ6‚Ž Ú¦uÕ\ß“´ýE¯'M_ï/tmSí48ØðÊ¡-ô|NÿõšUÏ ~¸ûu©ì’}|?^Ÿœºú¾Î—”Ú÷§“ÛÅ›œ,ž‹ÓŽóª9`µl|q¾³©Z^í?¬ç?¶y¢õÚò§N:©¦¹¨÷­¹ÿô:ˆW>39lµ}J ­Ÿè 2Òv4•‚¾ê=SÑç¯OlG@¨²²‹2|Â5ö»}ác±£\^h®Ø[ÆuµQ¬ßïl®ðõæœËƒ&40;|`ðÄÆZOÓ#>VmSƒH·Žïj¿»g4ûª´ÿFùç»ûälI±Œ¾eºl.X&3ï|Tº/òĤ˜{÷urêÌ1˜»RÚ·élò°^+‡Éá#E6pæ~<_󺎞ÒÝž¯y³–Ò±]¦Ñ{JÞ2#Cõûì&È8aôÑþÄ[xnG¾ly~¹ øq¶Œùý ¨®2‡™<§)) ä¹µûbªèû;­·ù½½žÜØ7[2š·”_ýn“œ)‰©“ƒécïN™•—-o½³70•ÄÐÜÎÖÍý~õ8 N2“Yõ0)fLzÄiJ~ùÒ:ë©Z·ê(™{È93yÜ¡ÃoÚâ½w¬4Îý¢õƒΜ+–œé½í.MЩ}¦y/)rðÝ¿ÙÉÒút$wå.ôêO±-Ÿùìxê|f‚È·Mêf~·)5£ÁÓ¥mëoI3 ßÛïí—÷?8(JK<c¦ö²Ÿ£ÿ½{5+ó–þTöˆŒ Ö`qN5¤+˜<;ˤ'xÇö[G•76£Ož>&Å&•…–±#fÊàÆØeýã¾…£ìÈä† ‹æ’Norµœ,þPþ]ÄÍ2©Iœ Ú´~Ñ¡=f‚²Q2ÒŒ0žå½·4‡ú>±üui’vµVç¾tŒœ{AÓz\k&*TçWwo·×^ÿ>#dâíó¢Ç²€@²"‘Åd½†ÏëÏOù~ù$\ú¸¶¿èÄDA%l`RGJê»8]÷¶Ê,ëHÕÔÔŠQ'j?(ð•(ˆ«ýÒQ¦úS™¢AÞªäÂMÔwÿù+SWƒ”úã.AÇmÓcÔÜüXüד¿’¶©ùn«R‚úŸ¨½” ‚üαú¾š4­8gq<§}Ýóúû¯k·ŽîÍÕ²¬ê k8ÑÉ4Ú¨ƒó?6‚ÚJti?«rõm € €Õ'ðŠ Öh\Á$fõM@.=­¹ …éªf‘ ßš1t¼)ýÏ^Ö39oäO6¸¥ß›­š–Í+4iÒmàQ'bs— K^–-…+¥`ÇÓÇõµh`1w¤wä­æu}rÅnYô‹)& öW>në6MË;ræK·ïôµëýáä‡M»ªiªú]Y~)8ﮦS¸oÊjY¼fªIIñ¤m«c»kdŃÛÍÄS?²Á5÷ RS½¿ï¹÷i¾` Œû‹æZÕÒ¨¡÷w¢aÆK‡¶]eå†Yf2­CöGëi@|ð 9òƒÌët5ai–ž!ùù÷˜ ëßËS&ˆ æÖ—žß¿IÆe{ýµ1M•ÐÂ<ÆÒädÕ‰µüA\­£9m·­yCÖš‰ó~ûÊV3)Ûnû£ûÚ›€î´¡‹u1ZRRâ»D+™…;Ç.0ÿ¾vSö0îzUY^ò@l6ØéDGþu.ÚÔ×[v)¹yòͶÞ9ˆœ±Iž)\%;þðTùç9Dm&˜`êw»ôŒ¶¡ Nª‰ kJƒ±È­W?ÅsŒ³¢×¢Sô¾mðd)øõZÙi&~sʸìÙ&EÊ(g•W’*P§Fäú¥>ns‘êöÑÚÔà:S|NVåm5#7/ɈܛlŽ[ÿ±¬#€@D`òÈ<3 »Ô¦+pÒ`ƒ € € PûΖœ2# {™Q²Mä©Gÿ\û;L¨ãujD®ÿ³Ö\¤÷›± LžÐÍk^ôï–Y“Ž]ÚÄlgD4u„qµĘð' € € ðEØhFŠk™xûϾ(]¦ŸÔi:="×ýÉkîÙR”2)€lú® ÑÜõXF¯@ÉÙ 6Ý“{Ú»—5@@@Ú*àä‰-X·ßÌíœ~ ¶ö~!Pêôˆ\÷®“‘ÓÒ-Â2•¨L®ÝʵD-@@@šØûæköT:±AÜšRç<TM€¹Uóãh@@@@@ Úb§o¬öSr@@@@@0rÃhQ@@@@H‚Ü$ sJ@@@@@ ŒÜ0ZÔE@@@@’ @ 7 èœ@@@@#@ 7Œu@@@@@$ÈM:§D@@@@ÂÈ £E]@@@@@ r“€Î)@@@@@0rÃhQ@@@@H‚Ü$ sJ@@@@@ ŒÜ0ZÔE@@@@’ @ 7 èœ@@@@#@ 7Œu@@@@@$ÈM:§D@@@@ÂÈ £E]@@@@@ r“€Î)@@@@@0rÃhQ@@@@H‚ÀÿšÄ…Ä‹IAIEND®B`‚srt-1.4.4/docs/misc/images/srt-transmission-bad-signal.png000066400000000000000000003436051412557703600235510ustar00rootroot00000000000000‰PNG  IHDRtt²R«LsRGB®Îé pHYs%%IR$ð@IDATxì¼UÆ_º»»CJJiE$0i éînéRDº$¤‘FR¥‘îî¿yÎrfg÷îîÝ{ƒïyùÝ;gNÏf/³Ï¼óžÿ&4          0O b˜Ÿ!'H$@$@$@$@$@$@$@$@$@$ PÐå…@$@$@$@$@$@$@$@$@$@á„Ýpr¢8M           Ëk€H€H€H€H€H€H€H€H€H€  ºáäDqš$@$@$@$@$@$@$@$@$@$@A—× „tÃɉâ4I€H€H€H€H€H€H€H€H€H€‚.¯         '(膓Åi’ ]^$@$@$@$@$@$@$@$@$@$NPÐ ''ŠÓ$          º¼H€H€H€H€H€H€H€H€H€H œ  NN§I$@$@$@$@$@$@$@$@$@ty @8!@A7œœ(N“H€H€H€H€H€H€H€H€H€(èò          pB€‚n89Qœ& „EO?–‹ÇËË/Ââô8'    xëDøÏ°·î¨x@$@$@$@$@!B Q–Ljœè±bɸƒ‡È˜$lxúè‘D‹C"D }Ÿ‘ÿ^¿'ðŸ±àÊæÕ{ÌqZuû\ò}ÇÜGâò…2¼ût¹c„aˆ`ü£‘ „_7k.Ë£àȶ­òèî]‰?~ø= ΜH€H€H€H€H€‚H€‚n²yÈøyâoæ€ï•ÌåGÌEaÊ4Idä¬NrýÊmåµk6`‚H€H Ü€ÇWZ:z”š{ßêÕdÈæ?‚|÷n\—yƒÊž•+å¿×¯Íþr–|_juì$i²ûá:³kg¹h,ºfµ;—.Iÿš5̬"H·…‹ÕþŒÎäÐÖ-=vléµì7A\WW¶sÙRYÖ ðùX»¦Þå–H€H€H€M€ºFdž¡A`x™rø€meë‰ £™=‘½~’ö¹ÿ„ŽsäÍ$ú×w9Æóg/¤iÍ>ª,Sö4Ò}xS—õÙ«ÕxSô° ‡DÍ¡î¦UÊO?Ú^õDùš¥Ûe©±¨›Õ²åÎ 5´f)áyõ¢myÖ‰âÊðí‹Óå{·6æùòòå+å°UJóªRú£÷ò¹C$@¾&àì¡«Çký^óµÿF#FªWçu¶=W¬/¿¶\z^3< /EXlÃkoØ–m~¼TáIúmÎwÌ0 ðü³ _ª±ñK{ÿ%5DÜk×ël?[]®ætáèQéSÕ1„CGüÌ íu_ÑcÅ’qÿF–ic7”6oVûérç–îF؇–…BQðÊáÁ«½Œ Vªlx!Û⫆o~éqt^ÖÂ…¥ãì9z×&€"»6]ßÙC·Kù²rãœMüËk¼ªßrÂ$Ý$HÛ xèêã'O.C6ý!z/<0Öa ”§s¡BÒfútÉl„Jpe½SExÍþݲåüTÓ\tAÉO?“¯ Ô»j;»WOÙ<Çæ±ŒiM÷“>Oé¾h‰s±<{òظ^¢ûù¿^{èêóæ“.ó˜Ç‰üÁuëÈ¿{½W¦L)ƒ /xÍõÆ~kxÉn°yɾS¼¸´›ù²Ì“‡î¹C‡L!:*6nìÐ;wÏg¼¤Iæ‡Ï+òa`FVÃy»|ò¤¤ÊšÕš­Òš›;Ý&Ù³ª0¨üyï>Rư !VzžÿÚ†ü±E¥L¥wÕÖê¡‹ ÅÏxÉ2W„-ékæ'?éç|9tÊ   ð‡=týÄâ°E U÷ÏÍ Ý¹y_z~7έ iVt‘èÜd”ƒ˜›!kj)U± /—O’¦Hh¶8rð”tj<Ò܉Äî-ÿ8ˆ¹‘£Ø<¡¢FsüƒxÂV17eš¤jþÙódhÑ£ª©Þ¹u_ ‚;Ä\xk1·@ñœR¿U ùæ»êR XU_’f_&Gÿ>íÜœû$@$*¶@ÛT#n¥õ­ ïi;Ñ ­Õ¤).ESˆXuº÷ÐÕäÏ¿™éÀ$ò”-k6;á$œ¡`ã/?«r«øzöǰ¨p`Ý:U¿êõ²=LÔˆ‡ªÅ\äu[°È˜‹|L Sm{W­0…kç¼Mž)“£˜‹ 1×¹¾ÞP«¦)ææ(Q"ØÄ\ÝP¶ó‡oÝî Xj1ý"¤Ä¸ƒÝй¨Ól¬ýõû5Ó¦"Ë£}ب±1 ¾èÓ×lO_goÓû·n™åˆëʢňé¯8˜:û;ÒuÁB±}51ÂjX-‰Êd¨áÕmsQÞpÈ0³ÚÑíÛÍ´·‰kçΚUs½ÿ¾™¶&ÂzP¦Å\¤Å\䡾+1ežlÃϳL17S.Å\´O-›”ýò+³«ñÍ››iW‰Äà÷ÇV1õÒ±Ž“¤Kg6qÒ¬À øC€‚®?€X¶@Ô,TÒGî™«ÒØ°}ÃA¯¿Ø/œµV®]¶}A‚§ìÔ¥}¤çÈoå›–Fܶ5eÈ”ïeܯ]W*m,®öÇïŽÞ+¾¤2sìRÕ}±ryeÆŠþ2eIµý¾·ý ŵ+·dã [X|™õS'0¡•š§ eâžÒi@ã Y$ãUPGܧOŒWh 1†ò1³;KË.uåý H© JË®õ^ÂÚ†vn¼ìø ]Ÿ[ ð, J.Ÿ:e¦]%àá]ûMxWå©ßyÇÌÆBlV‹k,ü¥íÛW/f¦ó¼ÙâaAïå®L$L‘ÂAü‡g®+Á>f¼xÞ å¶ŽÕ«uÅãÜÖs.€7º¶;W¯êd·ë…é´56¼ý=YÝ=Íâó‡™iW‰Aë6¸ÊVye¿øÒ,óجÄ x @A×…MÍ:}&ùŠØ¿ü`–SG-4âàö’…?Ù=˜ÜÍ~å‚-fÑèŸ;)QÓÌx“ˆ'¦yuþÌqËt2D¶…ÞÏ-ÛÖr;ÖØs̲†gmü„¶Øsf¦‘ÈþnF™d»íû}cÍ–_§®2÷Ñ6nüØæ¾N@è.S©Þ5Ã\˜L @(è±ØöÐ Ão4 óÖîݸnVM•-»™v•€‡¦¶ Æ«ÒA±wŠÚÚ½Ìñÿ«7æûŸÖ‘´¹r©¡ܼégÈÃ[þPyøµ±ãÛYdÜ`ùPé[Ï„¨Ÿ¯ÂØ(;}ð/ô³E|Y¼Ê[4|¸Ùf ”ÓðÄì4Çö1 }ø².ÄÕè±üþ¿”1Ÿ¹Y@L÷i}õ^çY· ðÚ°ˆœ³ÅOf+G8ïò½+·/_v®âq?J=xU;{Åzì,…ŒpzħîTº”[ÙS—5Úµ7‹;¾_BÖΘaî%¡C€‹ó"lÎýêyëüçOܯi`õ´×õõ6]NÛçû·.]ÒÙÜ’ @ PÐ 66 m½Ðªû†W‰úš¢¦ƒEeVÎÿC-p¶k³ë/¨[Öî5§þŽ!xêÐf¦%‘.SJ‰%²™OÝ0x!7ëh÷Ôr5楳×Ìì\Ç÷Cxà:‡jزfŸÙ¶XÙ¼fÚ9QûkûþµËþz¥sÜ' à ߈±™>Ï»fWÖ…Šœ…³’‘¸uùй{éø116Ýý˜•ƒ˜Àœb½ñˆEˆˆg›=þ¹r¥™Ncxi–µÄðDl]mˆéªÃK «³Ý¿yÃÌŠjYÀÊÌtJX½Ko]ºèTøÝÆù}Ê?u“áés˜þ\µÁƒˆ~¿¯5‹pÝ5}'›t-_Îx áÞ«µœî@Þñ˜?°¿bÿc‹ææâmf§HDtŠ3í®‹ˆmá¯Pn ƒá®¾«üqìß] ÷®Ú0H€H€H€Ü  ëŽ óÃ<|E²Ëôåýäó¦;,2öêÕkc±¯2qØ|?ǰc£íP4jSÓO¹sFþ¢vOàsÿÌ#ƹ/o÷[á<™UXNœÌÑKËS;”!Ü‚¶„I<¿>#–Ý+ë°K˜F$@a…@÷EÆ‚_o<Oíßg,òõÀ65^ˆîÞ Ôôs•*¨vÖFMFØã”®˜ð£Y´s™ÝÛ‚×{•>6Ëv,]b¦çèo¦¿ìë7ÌÄ“‡Íò(ïb3Ó)/I3çÉÃ7ìÌœÀ'0võ¶ß›`á0-D›™á$Ø­XT¬—±ÐÞÞÕ«ä’š×–ƒgµÕ»Úׇ„pXDËêYýøÁ}Y8t°z q`](õõ\‚ÒòŒÕb~XÜMÛõsgeœ±àÄÝ+nBWàóþY×n%ºí¾ì÷¯]#XM±`áYܹli²É."P†B#¼Rß¾D1s丆Þñ—_%y† fðî)ƒ'i‹mÞÏËÇþ «&NP|1þøæÍ¤õÔé’»T©šN ÇALÜî‹–±ù_ËøÍäŸUá$zTü@oÚ,‰S§ñÓ…ú ?ð\ñÍWòð¶í­)„oønë6»ÿ€Ÿ6Þdx+Ìß¼pÁì.NBû½¡™É „0zè†0pç;ù gW ˆÅŠÃdTïŸÌ4O?5÷­áÌL§D’¤vØ»w‚Ï‹Éi˜íÞ»mŸG¼„±Ö6Ç'^¬ÃÊ$@$àk_õ`ñÀ9ï¾HQ¢˜yΉxIí^©çþùǹاû\uØ…çÏ«±¬ž«U¿keŽ_Öxņrü@ }pë–ÊË^¤¨Ú:ÿŠeY°êÅsû›ÎõôþåÿÕII•-›™®Ä{÷‹Ÿoâñ¼Aƒ«ëégà§µÍq>ïÝWFîØåGÌ5+„B×ËÄCG¤ŠåºÓ¨FCa:õw&Éã8²µ_×máÉšdôî=2|Û³ÚÃ[ùô›EøÌL/Þ ºÖî°° H€H€H€B›=tCû pü`%ш©;zvgi\½—Ùï…³W%MzÛ‚"‘˜²ÚšÁ?³Š¸ {Qà__ÁU9Šý?´ ÔÞôoõJÎ’#­4mÿ©7Í$QÒø^Õc% Iõ‘;©!hÚX¬¡œça]øè¥ñÚ|pÙ‹§þ ¨«d­O˜§“Ô°ð2üûw.„­H‘í·c6hh.ö¶iöÏ’1Ÿ=Nú§oÿÜL‡©„á^­Uk9ó×_rèÍ‚y7.œ—déÒ‡©iú7<„iÿÓlÓÛÙcÚSÛøÉ’Iç¹óepÛ}̦ٳ% ×2ÂXÀ‹M౞8uj·Ã=¾o»Ëm% @ ‡nçp¾'ÑÖºØB h‹3šNÊË/Í´»Äå 7Ì¢”©íÞ]ÈŒÉþñ±Æ¦5ø(/}Q3'í¯Íz3œµí¥sוP ±Ö¿oúf iÅkÖ’Èo¼r±jýÝ«ö#]Í%AòföþµkÍtPw®^ñªy­ŽÍzã¶/´…|H‘Å1tN”)E/Ô´}ñb™Ò¶µÙ.eæÌfÚš°zøn™û«ñÿ›{ÁúÄž=òâ©ía`”hÑÄêÝkí3¨éÄiÒHÝžö‡«k×tX.¨ý‡vûkgÏ„öÔøñŒEµEˆ`¿/Ñyoóט6OÞùºŽu[¸JUswò÷mÌ´«ÅÓÖpØpä–H€H€H€B•Àÿ×_¨¢æà!I±Ù´%Hh@S¥µñٱ頮âv{hßI³,}–Tf «·¯56­C¥7;ÖWk]•$/kÎtfõ“GΛio)ÓØEéÇæÝëMÿ¬C$@!M ïê5æ7/Úã\š™–D»Ÿ~6÷~4âw>}üØÜwN ¦ç¦9¿ ös‘ã¾QŽºÞTØíKåº!@ÃJÖþTmõ/,öëÍ+Ýçý#×ΞUEï/®«øÙâpÒ…]Ë—s¹h×ógOeh½:fû!›·˜i_$Êá#¬!º”/ë‹a‚¿OK,æÃÛ·ùéÿÞÒ­By?ù¾È˜Õ½«±øÙåIê§ãÚÛ½|™™íÉËÔ¬J‰ï‹‘c;wºݺ`¡ÕЏwêTú}µ«†à¢ 1µbuºw7«Ÿ>p@V1‰]ÙêI“äÈ›k ©VÝU5æ‘ @ˆˆâ#r@ð1xÞ¾xnÿr:ƒÝ#«jÝ2²oÇ5ƒyÓ~—«»ÿ‚|ÿÎCyøÀþe?A"û‚:·`ËÖ¯Ø%Î\uÎô~ä(ŽÛ‹g¯IêôÉÜö÷âù ‰ÕWžÅzḫoJòԉݶe „uIÓ¥“8‰˃›öEÝÍ‹Zå(QRŽlÛªª´|7·$ϘI Vª$© OÙ›/ DÔì7¼}m·ó–-' ’'÷Óeš90‡U~ó<¹$}îÜÆ›‘åüáC2v߉h £ 0ÂìZj[Jç阹zÛƒ‡ b¢Z­ü×ßXwý¤®]/­ß+ òo_¾$ßæ|ǘSÉ”/Ÿ<2^?±çO³¶¢5jH\ƒ›¯­×²ß¤yîœÊkø¾!„ŽnÔPÚLæëaƒÔ«)Såcž°Ñõ¿‘†ˆ‡Ð nÝ–%£FÈ¥ãÇU¼Ã=yC«JAüuóÂEÙºcž¬™6U2‹ëå-SNR×êÑ;d³ñÀ ÏÁò–¯`zvqHŸ4ǹþÕ5ztõÌSºŒÄˆG°¨ÙuvoùF#Fšãß¹vUn]º$Msd—¤éÓË»Æg1G±ârùÔ¿²ù—_äÆyÛõŒ%òä1Ûy“ˆd|V¿0Hfu뢪/1\ÖL*Yß+(©²f“K'ŽËɽ{Å*6Ú¸ÙX³0‚7ݳ øœ@DŸÀH ˜\1ÂÔ¯Ü]Föš%î?rÙ+ââvn2Ê,KŸ¯®Úo¾Óâ®^ žSF.4ëZ(ëÜÔÞOÛÞ_Z‹Í´u¶¹ÓV›ù:±jñVùeâ ½l[ÓÚw™ª“ÛGžH»o†I¿v“òÛö²Kÿ“ET<{Ÿý÷²C{î @X#0lËV¯…–ïgÌ”r_}mÂÕÓ§dŸ±2©u+Y4lˆì]½Êsá‘7q"³®5ÑuÞ|CÀ¤²^!Níß/' Ñ1@ÝýUýøÛfÖ.”§Ã+X r—*eÝUé<¥JûɳfÄŠ_FíúS´0¼†OÂôZ#~-Â;XÅÜš:Já!óê8޳Ϝê¡?6ËñÝ»Íý°˜ëÜoxã~"ü ÚµdÜ·ML1·Íô™’Ôx@àk+]·ž‹wüóOY0dû×ïó'OTYÖB…¤å„‰¾žJú/]ïsÕþ¹îãàúuò“áy<Ée°ï÷ÕÆCæWª¬b“¦R°âGæ8 )Q¢FUû× OuÄbƃŽÆ"{ZÌ3¦ü€(F,ê€ZÉO?•/úö3›=¾wטÛzYùãxµÕbnôX±¤ÏÊՒȇB#   °BÀÑÕ/¬ÌŠó ôÍú?F„Võ7ù‘壚%$cÖ4rLj“»o÷Q±†H€E§AüôÔehcéÛÖöjÝŽåÀ®còm‡ÚFÙòÊøR±cÓ_²fÉ6³]ÚŒ)$OÁlæ¾5ѰÍ'òCã•\ÃÖ,Ù.gO^’ÏT4<{Ÿ(±øÁ=›ðŒÈÁ:Žsºzݲ²ü×M*Âmý*ݥʧ¥%oá솷ÎKYöëF9ò×iU3vt‡æ¹òg‘x bË=Ãù‘áܰjùäËòR°x.‰#ª<{öBŽáwƒÁŸ[l+Áþ¹³jãÐwH€HÀG*·h)»–-“èqb{5Bä(Qeøö2ÑeŸ?}"…+WñØ®nžR»s™; Ÿ± Ôy|ÿ¼0Â@¸‰'Ž`‘±ß·“ÆÂKî,Šái8ùØ Õ°¾`‘³ç†#n\Éõ~)‡EάíSdÊ$ï–//ðºŒ`þXö¯]#‘£F“ØoÂSø×NÏ1K‚þUUå­ /â;†—ö˜Æäö•ËòòÙ3hž¯Â‚ë÷§~$ûŒm+ºì3cÞ|òìÉcÉ[®¼Ër™§L#´ÆuÝÄNPg«mŠÕu6§o_9¼m‹<2òÂ\¢Lb¡É¨Ñ’$MZ‡6zçÝråŒkóˆÄKl·¤Ë¬Û¼*ÈÕÓ§ýõØ.ýù†×êãsâúsYæË/eëüùæCë_ôé+Ÿuë.“Û´6¼àŸ9Û‚døÌ¤Í‘SZŒo¿ým"ÝvÂá£òçŠßdõ”Ér÷šízÆg/¾;øƒ¤D­ZºªŸm†wß5þ<“âŸ|â§Lg@0ÇÏâ‘#dϪ•j¡4ĘŽ;¶ >qõÖm Ï಺ºËm¡*•åèŽ=f,—å:o¤Í™ËxþJ Þú4    †ç;G’ ô˶$àÍj÷o ‹d„˜¼¤·[ ¯}Þˆºž&š1{é1¼©§*ұѹqõŽÛ:ýÆ·’¿÷“3Öª:ôè1¢9Ôß´êOùéÇå*¯mï¯ 9«C¹«„yhXµ§«"‡¼ÁSÚJ²~=ÌÚ7.·®ßu¨ëngÒ¢^5šß/Zîê3ŸH€H€H€H€H€H€H€HÀ7(èú†+{õ!ˆ§¿Íß,[×Ú_áÔÃʼnS´úDyªê|¨“*Q¢„.l»/À+ùðŠ­Q£†À›sòäÉrÿþ}I“&,XPvìØaŽ2cÆ S nذ¡ ÃòåË¥téÒª®YñMb÷îݲuëViÒ¤‰Šñë\nÝýúµ4oÞ\-6gÎéÒ¥‹µØcúìÙ³²bÅ ¹uËvσ¹W¯^]&Lh¶Ãqa âõ“–6mZ©T©’Ì;W.]º$ß}÷h^ª‚ñ ‹’¡-ʬ†<ħ…Çs³fͬE²k×.Ù¸q£¼xñBå#ÌÆAœc«a.&LÄFdÌ ¡eÏž]>ûì3kU‡4DïE‹ „pOq…á¹=vìXI–,™Ô«WÏìóNŸ>½Àks]·n:F<¨_¿¾ƒ'6<´­a:4»X±bÉ—_~©úÄ1€G‹-dÙ²erðàAu­tíÚUÅïÕãzزe‹ò(Gæ.ו!Ìæ†û¶øñã»=VÄÕ>}º)RDp}[ áI¦M›&Å‹—¢E‹Z‹ÔÄpþûï¿U>ÆÀ|‹Újx@‚zýõ—ʆÇsÅŠ¥@Öj* Oùyóæ©8¿ÈÀõX«V-‡ÏŽaLð™ƒw:H€H€H€Â# º>:k?ý¸Üì¹výÍôÿs¢sÓQróÚ]ôh1\/ ãPÑigî´Õ²fÉv‡Ü+ú;쇕U ¶¨©¼xþRæLZ!¾¯V¦ævGž–)£šåÓëçðê°Y ÿ‰´ýjˆÞ“Á“ÚJ²T‰Ì}ÿ§ŒýÛOò¯šYŽ“Ù^w63ÃIâÝBöEd¦Y,}~hNfÎi’ xC‹¢A”„uþüyÉœ9³jA?:–.2á QñÚµk2{ölõwÓ… ”¨¡L„1—‚DJ´]¹r¥KA÷÷ßWMá쟡?x#ƒdïÞ½.ûtîgñâÅòÏ?ÿ(Q,iÒ¤JxÄÜ!bBtÆa{1 £>.ŒÃØÇWâáǬòð B&%ƒa^º>öÁâ·³ø7hРŢŽã]¼xQ‰ååÊ•s!Þ‚!ú‚è¾àóæÎp,3gÎTu«T©â®šÊG ÌãX ¢ãõë×eÀ€J\Å5ƒº7nÜ¡C‡ªԴЈã×±xчfgyÑlÒ¤IJÈıCLÆ5aô .ØÂ[\†ÿã?Ê'Ÿ|"¹sçV}è_Žq]‚ DsðG^¾|ùts‹cGWÜîÝ»§Ê Ø[ õ‡®²0'ÌýàBÙ²e¥dÉ’ª y£FRi €"@ ‡ˆ­M/B/x\{8~}ÚœúæÍ›Å* nÛ¶M•Á+T„;°Ë™3§òÊÔù!\bA»wß}×AF/8!î1ÎúpeáQ kÓ¦ÀK6°û;tè VôOe0€7ª>VoX_ðºugW¯^•Æ+Ïaë=ш#Ô=DYÅ©R¥RÕûí7µíÖ­›éÍŽ œ7 ¼mÂ^ ··€ðáÿšGñaâfš |Ïö%虫7îÞ0úuê*³Zxx]}È”ï峆¥AëRýó²æÜ­ Ô—Î_—};X³C5Ý¡}s|Ìïñ#ÛjÝfæ›Ä„ †8©­Ë F:¨m™J…dÊÒ>&.´{ÚjPnôqíRæ –ýºÉL3A$ðvÐâ*¼½µöíÛ›±V!¤ycx•æ,²!Ü ¯ïÄà)š1cFõÿ2DEOÁöõ×_;Tƒè ‘¢D_o Â&¼4áª=yÑB«6VÓûðî…ÁÛYóÖ\t}„€È CØgƒˆ«Å\”¹âð •€{˜víÚ9ˆ‹Îýy³~ ÎZ=¨?ýôSÕT›7ýXë@F˜~À ¦ÚCØ*梘&   Ð&@A×g`Õ"û—…ܲú`„ðßeåOí‚Öª…v^ÞÙæÕ{Tµx bÞ$Þ}éõ¦__Õ‰Ÿ0ŽT¬QBJV(à«!|ÒoúÌ)%}f›× Ýçg—ãôh1Ö̇çiŒ ÆŽhx4G6b+{ú1 § \#Ùþün[¿?œ§M$àŽ^'‡ÅŽÛ]‡|©Ú;Ò¡ÀŸ¬Ym÷p¨yøða%–AX ¨Õ­[W5Ù³g[OUTÐ⎰ÖÄÆ…áuwo ž³0kŒïKÄAÕLQG/^<ì*Ó^ɤ]YÕªUUö©S§ü#¶®@ý™‡ ¦¶s½=¯®úÒy±ÇÔB¤>>]×ÛmþüùýTÕ‹ÜiAÛ¹ÂPÀwW›öPvå]î®ÝÖ›-â?Ã\ÅÀµ¶×*pN­×Ò…aú¼#­¿oß¾bõîF™³áåÛ1ÎT¸O$@$@$žxÿ^x:ªPžë‘ƒö/ ‘Þˆ6¡<¥07üՊɯSlÞ?‹~Z'V×Ódïß{(ÏŸÙ9©×øcÙ°Òîê©ËG óà†òm­¾ªñÉ#çäÞ/A³3,JÏbØÏ¿hÞˆkx©c!¹Û7¼÷àó®gÖ"mZLr·ØTpÍa Ã;¯ÏÃã±Sá«=/:ú,V¬˜ZmáÂ…‹yé¾ pÂÆPî, ‚Y¡B…TìÞY³f© :.,^ÉG\^üœ8qB b#v* á´iaO¿~¯óõVÏ«1½Àš³˜¾ükcõRö¯®åÚkÙÝõ $X½©õ±ú×w`Ëõ9HŸ>½Ç.t¼àM›6 ~\™>¯(C¨x#£îÏ?ÛBÃÛ·L™2®š2H€H€H€Â5 ºÁ|ún\±/`R¾ª=®›Ã\>w]~¾ZîÞº/¯^¾6¼£IÙ Kñò~Ÿð¯/wå/_¼’E?¯“cÿœ‘†(5zÉž'£$Hèø*ZŠÔ‰%Wþ,îº ¶ü\ù3Ë¡ý¶ðgÿ½lxƒÚ^ô4@ûú¶4P§Ðû¹$èž;}Y–ÍÙ(·®ß3yn| Ž"iÒ'—š_W„‰í^>žÆwW†/Ï‹goP1g —Ì-Ù,±“ïÞ¾/+Œ…Ñbž«5¾(ovñðþcÙ¹ÙkÙÌ4ë–ï´îž½ù%º‡…㶬Ù+[Öí“'8´Æ—lxÈÂøý 8¼*éЩ—;Ñ èk¿/+ß,ìÖ­ù2î×nfëŽFšé¶½¿2Ó¡‘€§ë©¥DÙ¼’)»Í; ÑM¹P._¸.ŒIS&”&Æ¢tYˆ!TVÌÿCç ÞÃÉR&’O…“$wÙÛc¯ý͇jn¨ÿ÷Þ’§ ½ù½eÇz$Ö hDí©êËù¶lÙR#"Ö—_~)DaÖX´^›;vì“'OšÞ°Ö>´')òt|\k¹N» ] Ëœ·ZüÖÞ¡ð2†áuù?´ð8Ö½;¬Dy¿¯ºk¼`ÖZYõFt®ƒ¾;6”4’KïÑÍÍ×àëyÚùâ¥4û´¿` ƒW¶UÐ0t¾œ8tV•YÝ¥†¸¼a…_ã9“Wªºú"Á•¯ZTïšÛ¹SWËš¥®'9uì‚Ì»TÃÙÓbffg8OXÀë™!‚?zðDöí8"Šå=Û©<4M™&‰äÈ›ÉC/¾/š=i…<{ò\ŽýuZMj#?OüM6®Øí0ðÅs×äÛÚý¤Bµ¢oO䯢}ΆkfïöÃ7~l6­D5 Æ —Êc ºxÐ@A70ن£Gš“Š?¾™öU¯ÿC Ó^ÁYA.A‚^Õ«WW"ê‚ \öƒ˜·5—•½ÌÄ«õ8xçîÝ»W-§E=Œ÷ðáC%4Bt'¡ã¤º ©€áu(Äc ŒÁ›¯ôïß¿_°øé ¦ã9:tH°øœ³aQ1XŽ9Ì"0‡°Žó îVÓžÙÖæJäÕs:räˆ_­}YÓ2dP×¼Ïý Ï`m‡ÏÎUÞ¼y¥_¿~*<Ž'8¯UëxL“ @h°q ‘ßÒ1­á$rô|u>ä놘۹É(SÌ…Ç„»j–T钩긞W£] ÃÖvHãx¬†Z<¶ ј×Õ«W•`m­Ï4 „'ôÐ æ³aP[L‡ÂëëÚº i$Ys¦×»Æ+Ý oÈÇÒ²î@•7vÀ/X¯Ù¿÷œP}Ä1bvŽ™ÝÙQ«^Ny¼6ù¤·mÜÿ ±nn7[:„~×kú±ü2q…mÉìõoPw¶Õ+‹3š!<ÇtWÍ!_ßÀ×mRIÊ\ÄîCƒñwoëõåËW*¬C'v¾«3À,…×jË®õÌ}ÿ%øÕ¯Üݬ>yqo3í*°ÏÚ¯³ç÷«?uEzµ¯²N:'Ç‘l¹l_ê¬õ¼M#DÑk—l‹47<’µ•0BBXãêêüÐÚ^¿b›#Æõs'‰o‰ù;¼ÇLAØtÃû½x9×!MFöœ¥êàW‹Îu¤`‰\æ>¸Vú·ŸäÕL‡†nvt\h7ÅÌ&ƒ¶mÛ¦f ÂÔùóçMqªFAòµ. lܸQ~ýõWHŒ×ÇáM©_[G]xÓ"Ù²eªi‘"Þ‡|²ŽåœF‡iÓ¦9g«}„ ÀâSð¤…W¯^=µÈDµuëÖ UëÞÝþ›î1Nÿý÷_§ìîÝ‘¸ñ"@IDAT»¥D‰ºH’'O®Ò:F.Tm¹sç–?þøC–,Y¢²à}i5ˆwèkëÖ­*,wË’%‹wÇŒ£ªÂ«Wam4ºž§íü¿›Å½Ç47Å\ oÃüEsÀGäÑÃ'‚ùGUû|[¢\~SÐ]kxMºt7­úÓœK›_˜iÿïXP‰¦Úع~lƒ1ÂYìÜô—*:nÄΙ/³s5—ûÝ AþÙ›Ú —Ê-ßvøÌe½àÎ\1ï³KijusQoÝo;~&‡ÎSu‡uŸ)S—ö1Û&ÑeP#ióÕÕT ¸Æ¿iY=0ݹmƒsý‡Ø}òeyãsPÒ]±™?aA?±‡ÛõýZTé¡êÀ“ו!tƒöÊ] ‹1m2eO#S–ô„pŠÅKÛXhÎö ñÓ'ÏüÌ7(}³­o ÀƒL{¾ùv$öÖ@‚€é콈y¾óÎ;R©R%A„à2Ä…H‰Å­ðú8 ÷@V ¯ÈkA åÁ5>D7–XÌÙðÿjëÖ­Õ¢hs ç¦ë¥H‘B'Í-HÄ5ýå—_Tú· ºèS‡r@kŒ\íi‰|wLJèqS=Cáq\­Z54’u‹-dܸq2sæLéÑÃöJ:õ¢±Žá Q?E½tÑ5bЂ Äwí©¬‡„øÿá‡ê]µEÝÚµk Âm@œÇ jÕª¥Ä^•aù&#GŽ,d‘Ñþ«¯¾òsm ¬Aƒ2uêTõ0Äz® ãˆ6ˆË˜Â3€¹Õ0OíMŒ|x³CxÆ6\OíÚµÓ»j«Ãx@ˆ¦‘ @x%î]Ä.«X±¢Š¯–¡{s1ïYãm^4HÝÒýŒò•‹˜‚îž­ÿXÐýsË! ¡ÌÝÂ_™ßI«]Tºg,àÝŠÀÖÒw¿!ã•x,öâù ¹~õ¶$u±ØÔbÃ{W›5>­Îó´Å ¿'ËS «)èÞ¾qÏSU³l@‡É¢_“·P¶s­Bgú,©Tˆ sRN‰ÂÆ¢q×ñÚeñÊð<¾ï‘Ä5¼´kñÆ‘ìÆboXTO[ecÁ4kè”-„Öׯ_¹íÞ±ž ç{âž!t}”A¤ýgßI•uúø#G]¬¶Ó¡íû>_뤟md#|IúÌ©üä$ ØAÐ…!>´§ÅïÒ/ëúž@éÒ¥eÞ¼y\ž¾Ÿ1G.7TWð …°d#áÙ¾}{áÒ¹sˆNX„ ‚.b˦OŸÞO\S´)\¸°ò`µŠaÎ}¹ÚïСƒƒ]•!Â^Q·z뺈Œ¹!>)<”á¹ áU‡‰ÐõôVÇ5…g&úK•ÊïßPô‡° VÁZ·ïÙ³§Š£ke¨Ëô¶L™2‚ĆE¬]0†pìÊ0Oçsb­‡ãsu~pŒú¸­õÓ]ÑÞùX:uꤼ™ëãÿ¨Ž;o ù½=ÇyÇñã¸àm2»uëæoø,’‡°Çµ„csÅ_Ï âm¯^½ÔyÅùÅøz^®˜áœ`~豎ñÀKÇÖuÅÇŠÏ®™³FXH-ý®Ùëºßv™é—/^É¥s×Õ~Ê´IÍü€$žw³)r›Âp¶—†@­-$ Òåy/«¤Ï”Òwa{åñÊŲn¹=”YÙE"ºÑœº4¥JçN]-x…?^ׯ‡ºè"HY'ÓñYÑ&a"»·n@ú@Ýþí'›M†Nk§b½–þè=Ù¼zÊÿ¡ßléìE³“°š°Äãóô®é?ydË ÒÂ>Äp·<#¯\¹"®€²Öcš|I fÍš*ž(_#÷%eöM$@$@$@$@a‡@¸Q\ÅðB ðúº+ÄSwfõüƒàX¡Ö]ÿÖüöžPb.òÒ‚0B 8˜¡fÌšZzŒøÖ!;¤w"GŽ$ “Ľ(¾Ê”=zsAܸÀ„ èÞr¬y(ÚÖ4bðæ3÷uâð½tÑžÌå«‘õo<‰¿ÿz¨LYÒ'Dâ#ÌÂcyª¦n8X\ôQ¸ÞÞ0™Ó–*ÎýyÌX¸Ëga’$K ºüªyUSÐ=~è¬\½tK±Ñã…Ç-®CmXDÎ×öä±í\b º¾¦ôþÿúë/%Þ:÷”2eJ¾Å9Ÿû$R°HH€H€H€H€Hàÿ‡€ÝU4 3bç¾xá:Ω5 CX:„—†‡®»˜·X˜J qz&_Í}TŸŸU×ù g—ÞcZÈä%½eÄÌ2`B+>½½L_Ö/ÔÅ\}ìŸ7¶‡Ô˜=ñ7‡ðÕê•ÑÕ¼Þbåä;7ï«ú1cEw)æzÝ™SÅÏ›V–´mÓ¯ØÆpªá›]ˆÞÚöï<¬“n·§_4Ë’§Nb¦½MÀKutßÙfõ~ã¾3ÓÙ!’këß~’N†ÛmÌØ6¯mÀ­ëQá-kLähÑìq£½mÏz!K zu÷o @쥑 @H‚nñâÅݲ8uê”\»vÍmyHXRzlÄnug‘ßĵE9^ù÷•!N.LoO Ÿ¥L“T%/}…ßWóD¿ù‹å0»Gˆˆ)£™û}b Á`fx‘xýÊvì¨?¡ûž¼©= ÓkT3Ó+÷Öõ{2æOÕ=–y¹úçåÌ~~<×L»J 6òý»UQ$#ÌGÜø±\Uó˜7yäB³¼°ZÂyžÅÊæíqþèÁcÙ²f¯Y?<&°:v¬Ø1Ì©[q3Ó’xñÜõƒ&KIĈ†áïõo‚ÇF, §OŸööÇ:çP Ö2¦I€H€H€H€H€H€H 8 „yAwÇŽr÷®gO¹,Y²'“ õ•&Cr³½ÕûÎÌ|“è?Þî騧ÍG¯'s÷ú7Äàú•»«Ÿ™c—úéB ¶ÿ<.íê“Õ‹·ÊÎMÉ¡ý'åÄásræä%¹së¾)øúéàMÆÈÞ?™ãX_ãwW?°ù‹ç4›Þy³XYÒ” ý‰f%‰ˆ–x³î¼-q,£Œc Œ!ò€[™MW/ܪxšH@\׿',†.0¶¹ógˆŽ0x¯^¼M¥A¤nß`¸™Ý¤]-3ímá/þÜòY½iûÚfZ'à¥Û®ï7zWfŽ[&Ö…ÅÌ‚p”¨Û¨’9Û'‹ÃBtoJ์›|ÒǬ«]šŽVŸ•UzÈÓ'öu¹«-b3ÓÂ6"EŠxœ â¼Û[C?…2"ñPãÆ­j<{öBZÖà \Ï7_ëØh¤‡ü/Jž*±4þÞ.–‚§ö†ö¿µ½F^#$†¶Þ­ÇËå ×U?3Ç-•ƒ»é"µmÑ¥Ž¹?úïÒæËÁïXmk–íFÕzb¢mÁ­ä©K¡÷óèb¯·ý,!¾1¤ƒxëʲäH+‰ßÄÕűO6ßU5¯óþ2>Œ6B…xúùeò ¯û hÅâåó©EßÐîµ!Œ7­ÙG–ÏÝdv³ÀðÄnd|^ì:jæYW/ÝT»`qößKÖ"‡ôÑ¿O›û“v `ѳ7nø;A,ÔI#      _Ó‹¢í߿߫/Ñ€ôÞ{ïy]×—Pß+‘K ¦cõ¢­RÃòмó¸ýÆ·”-ÇÉåó×UÑì‰+?î,vÜX~Š^¼xé'Ïš.YÊDríò-k¶Ëô¥³×¤¡!N\Ø3Ôh‚#f4yòØ&Fb¡¨$ɺœ¯7™}h!m¾¢ª"„kgkÖñ3™0tžs¶×û;°vÙvsÁ¹fµû)†^w`T¬^·¬ÀÃW[·f?褤No÷úFfþ¢9ä‹o+›× ÖkYw YßšH—9¥ôÕÜšåUž¹w ÏmmïP@']n;jhzïÛqDÅŸµz»ldÍ´ˆÅ7صøñdðRþ¼IeOU‚T6|F{iYg€ÙÇ’Ù?Îö…‹Ö:ÿ½v¿8"ÄxmÕŒóO »2gÎìÕäΜ9#.\4iÒxUŸ•H€H€H€H€H€H€H 0´‡nþüù•—"<ÝôÏ Aƒ”§àkC(Á¢W/_¾T ¦]¾|90Çìm"G‰d†xñü¥GoMˆRxe¿E—º†h™Àå\°˜WÉ d䬎.ãÀ~Ó²šÙ®h™wÍ´NÀûV‹¹'¶–+úûùùaNWI!™j®ÓÆ,ÖÍÍmºL)miÃI³êg¥Íü€$bÇ©ª#T'«ÿ] ³¸NÃÌ´«DÊ´IÍlŠÀÌ0ñŒØ¹¨sô–#ó;ieÌìΆ÷jnÓû4vÛ­}@TÖ?a\tØbÁ¹‰leð¸¾ôF¤G¥dFÈÿ ±iGÌè ñ8Æú+„Yp¶r•‹ÈsºHö<‹Ô~êtɤ}¿o¤÷hCÌuíXë²Îœ7Ã.6öÛÂä£Ë·o­^ÆíKk„*ÑÞÔÞ´‹eY¼L××a ¢ZΗ.³n­ã$|sάåH#Žî´å}B¶«ë*Ï{Yk§‹”û¸°sSó:@¸“t™Sù)×—/Ø<>Ñ?bÐÂ.G™ÿáÿ"ü„ -™2e„4H‘"…›Z!—]¼–%jyñü…l_@µ©r€,#½~mw¾Þ·ã°”þȽ¨»uÝ~³%âÓH€|G`Òðùªs„eÈœñV}Gš=‡4ß~ûM ‚.ÄŸ]»v)¬F {¨ë1ÃóB§/ÅÎgÏl1ß­ã" á1f̘’0¡ÿawœÛ:ïã Þ•‰'v.ãÁ»:¼-œ×§OŸJµjö°R‡V!¶ P¦NÚy& g­ A7yêÄR¤ô»²kó_ê:X9‹àÇañ/,¾æj(wm˜O$0‹~Zg6ðä5oVb‚ ?ÿüSÍ´råÊafƸ_È—/ŸOçóÎ;ïø´ÿ:O›6­àçm·Ø±c+¯Å·ñ8õ}Ì‚ ªŸßÿ]vïÞ-&Lž={ú»8éÛÀí%J”P1¼ÇŽ«„Ýð,è¾ ç„Ç@$@$@$v JÐ…W¾ùôthxÒŽ›3w†Wàú*™~šï®Ï°”ß¶×—2¨óT¹}ó^¨M«iûÚR¯q%Á+Þ‡œr9H‘#I›ž_H®üY\–3“H øýë´êìËfáûUÒà#~{ ƒB÷ĉj‘4ü¿›;wn)_¾¼Çî¶l±=0Ô¯ã^á—_~‘$I’HÅŠÚ›wáÂ…ÊûÎ9LÀÕ«WU I„QªWæð믿Jš4iäý÷ß7瀘™'Ož”zõꩼY³f)O:jðxÕ÷8÷îÝ“Ÿ~úI½²ÿî»ïJÙ²eÍ>tbñâÅê^žÀ°mÛ¶©¾tù²eËT!>úè#-èñŠoÞ¼©òðŠ}íÚµ¼}zÀKV÷%Js'¼¢}ýúuóXÌÞ$6oÞ,4Þ x­Â6Ô¬YÓeøÌá 0l§OŸ.OžþøcAˆxîjÓìp߉r«ÁÃ× Žå¸Ö]- ˆk6^¼xR¡BÙ±c‡ 'Niܸ±µ»`Iã3ƒ˜Ö¸W†Çj¹råúÅçråÊ•ê:FA† ”7º¾v*»ÙAÌ^ÄkEh´Ë“'Ÿk|Μ9Šk­Zµüô‚k Òø|§ðj 5àü]ç‹7ê9ã<}ðÁ~æ¦3Î;§>gèç ŸyOßMt;|®Ê£N:‚Ï›Õ0|^Ð'Êð·ÊäîÝ»òǘq‹ÁX/H‡^®<ú­ý{›ÿø[ëLJ7ÜýÝü矧qãsúù矻œËüùóåÒ¥Kê3h$@$@$@a€×‚.nú²dÉ¢bµá0pc‚ù ¨£Ò75ÈÃÄ8Ó†`|ІUÁ‡ ¦wÕ—oO‚nÖ\éeÆŠþæq†V"N!R!l@„)‡,;ˆ‹WØ1« ‹W÷Ïœ9£Æp©ï-ðÊ{¿~ý÷)J§WÛÖ­[Õ+ÿ-Z´ÐYj ‘sÕ‚.„>¡Ú ¦jÓ‚î¨Q£”Š|´…0Q´ÿþjl–0ˆÃ¿µYûªT©’ʆgô‹/ts Çæl“'OV¯ðãU~«8p@íîܹS°P”60Á¼ºtéâð¢8bþà *?^ôëóº}pmqŠs†ù€^e·ò°Þw"†3„lkÚ¬u­‚.{ô ƒ†¸Ë ,Pi¼*o5ˆÄýq½c>0Ì¡F‚K¤³Ž×¼ysùá‡טUÐÅgêçŸVU!(âúÁ‚„øAh ÿ<¶Q ÌaÞ0ÜÓ㳂kp®õç>`¸·· ­ÈÃg å¾ ¡?›sÖ×:Ži\¯ø{Ò­[7u-¢ž6„Û@ emøŒáûþ”)SFgûÙêkçÚêx‚¹Às× åx€ð*©R¥’F©|ļ¶^oÖk·3GÕ(€¿ðyu^ÀLJ¿-Xûä‹/¾pèóÆ\q<±bÅR×/cÆŒQ»86\Ûø{ŒvžDsÝž[   %ൢ‡¬âÜ¡Cõå 7 ð°Þ,cêˆS‡/,zñ Üàà©>nºpá7SV‘_.¼5ë·mXÏ‘nÒ)æ:2á €¼·ðí÷߯<¾«Hx£~òÉ'~Piï\xrZ ñt!ôAôÁCamˆa¸7ÀXZ âÃÕYÌÅC£ø»¯ÇÔíôß <Œ ƒ×6 ^úZÌÅ>®ý…ÕËó†Yªa\™õþ®ûçñíªæ‘ øž€×‚®³‡âEÁðTÜjÚ«Dçé'ëúK ¾Xà&7 Ö×|t}ÿ¶®¾|ø×†å$@$@$@Þˆ-šé¥[àÿmDFVƒGÌUÌQía¨ÔÓ‚h«V­°ë á5y˜³À¦2}ðK‹´ºk«ïb«é}^ÂZœixÄÕt8ˆW® çæ|.‘‡XÁþ„@[xů»‡WÃ=¥;oëèÑ£»<,wñâÅ]–…D¦áJdÓ‚u<\x;¼½5|ŸY´h‘༷k×ÎO3¼9Ó·µ‚þûhÍóeZ é ð3 ªáœY¿/éz8>„ƒpgúš‡Ç1H€H€H ìð:ä‚ó¡è'ÂøÒ oœëX÷­'ðê…gnÜðÓ¹sgu#g­ï.m½AqW‡ù$@$@$àko³‡®+vø²W}!Z=qW¯^­ª»vàÅ ÿk :À£L‹ÈÓb/<äBË Î@¸v6-Ú¸*s®ëÍ>„P,ÎoÊྷq~{Êy>§qN{,`X­3fÌP±†±èî+Ù*Š;–öuüXˆ—:,ƒ7ó íÏ=B+À<ÍÙêMíî˜pîáÅ ‡ ݧ»ºXcêÔ©*žjƒ T5x²Â¼y *zø…˜²àŠ¿1ø{E¸ÃÕÙ0g,VOZ„º°šõ3¤ÇUÖ6Ö4žû{ç&E‘öñ–œsÎ9‰ ˆ(*‚Y8çç™õî»3ë™söÎ3‹9g=QQÄ„ŠŠ’s\rF ˒v¯ÿ=[³U5Õ“vfvzæ_ϳÛõV®_÷Ìt¿ýÖ[ØÎvŽeð«­úÖVÛHE "}nñÂFݛߧølÂ/ø+¯¼âºÀ4hPÈ‹ ¬Ì€{ÎjJ\_°¸‡Tè§b~ìƒH€H€H zq+t¥…€¹ìÈ«kÕ7lðy…?,åƒ_:ø€²Y˜í©7lfe  HÛCªú.~¤AUØá79//Ï]¢¬¦«ãƒ¢ÊC¸cÂfT°„“Ö®m²X «T¬èÁË_éæ@m#Óârs'(J`…¿›¸ž>üðÔLËÇÊr CÙƒ•Vð­ å,6ñ‡Í©Ô¤R2¡8:‘ ú–-[ºJ+[ÒúÓ–—Š4i«º¶÷ÁW_}µç"mºe66ÄB[˜?|Ï¢^ÖH¿¸jãØ׊ô‘ŒzPpBaêõ¹WëGŠ{¹@Pë¡OŒßCàÿ°øÁw,OÕ ¿«äQÍóŠÃ56ÿÃ&ƒ=zôy&‘Ÿ|Ÿ©¾zÕöêÔ©£ŠI‰Ëó®q[™þýû»¾ŽáC q(Æþùgm<̾Óñ}ýÙgŸ¹çîà7Ýf™n Ì#  H>¨].Àɾ°9‚y³‹ Ô ßxKºjâØl7Ñܤțªhʳ @Ù Èåùr¹>Zœ3gŽÛp¸¥ÑÒ×(–ç#@™… ¬Æ ì•;ÁCñÉÖoPBAI·°”ëÚµ«ëÿ4Qó–K§½ÚÃfQ‰P¼ÂýüvöéÓÇmóƒ>pñþ“–³ñÖ¶ž¼—„5'”™¶¿H£í+Þr°ŠE›[!.Ý"ØÆ+ÓP.\À’zÜOèâ²Ë.s7ù‚ÿ]Óß²ÚFÏž=]åP"Èϯ+$ùßûï¿ï*sáçþ¡TŘ¥› µ{égWúäVó¼âPðK%9,ÏMe°ô7 ×’³yL„rÛk|2}ÈkW¦™GuU¤š‡z8çØô ~Êñ=¤º×e¡0‡aéÊ]   ô#µBKpŽ>úh×_Úå—_îî°Û¯_?mÙ%¦‡›Ü`# øÃÂÆ¸ù~Üpãˆ@<ÐÀ”ÜlºÑÛ[çhê± $’@¤‡êDö•ʶðÛ,—,Ë~¥J¸Z–È“úX".@1†ûX¿!à~A*^àjaôèÑnZ·nÝÜ£ßÿy]r9|,KÕcUÁÿ(‚M¹*]<à…|"ºò<ýáp£^Ê$Y.ÒQú*ÖÚQ*Ú¼”wª{³o\Ëùùù wyaö KYXRã©Ö‘ؘA~öâi[26WÆI߬¶6q?€¿¦M›æÆ¡PMUÖÁ¦ÒY¾h²«lÁ+ó'œp‚[Ï=jÀ 95Ï+.¿ß°‰t"ƒ|Ñ0~üøfW­Z妩VÝ!…œ¹ÂÖ¸^ýÀ-Ÿ½¼1H€H€Ê—@D…®|ˆÀ,:°#êk¯½&à+O>˜©S1b„»|7~·ß~»èܹ³ö`¹X^Ù®];×W–üà¦4Ú‡ ÞT¨´'  Ä€"òñÇw«Ñ2~û_~ùe·©´ƒ }JB ¢*yÝ‚Æ?X¡"À‡#‚ú›¥ûðù ŸRâŠò¤ØÊü‹²zÒŠI&PVãÞEú–5Á)”ܘ;˜{iû'Õï°­ÎСCÝd0;yïeÔðáÃݼóÎ;ÏV5ª4¸YøòË/Ýó¨€öå²wÜ÷©¾8ñ'_ ¨y2WP2âZ“ã™íȲæ÷“¬*¡ •.%+J©„” /¤É¥ó0@ؼy3’Ü€ó‚òð KÀ9Å\Gu5($q^ð‡kóÇx`ýŽÏâõ×_¯µ·%ðã:aÂ×â]ÀÜ`É)À ã•by½WÀg×4Æ‹>ò¥ZÜäùVÓ?üðÃÝf¤Ïn¸fLÅ«ì Vï`¹~ýz÷…òs|þùçe±#® ¸‰ÃU–p #¯µ§Ÿ~:øý‡ðùÅçL^c²Qi=Œï=|FðÒ!Z t¾˜ßiòs/`ÆŒ\%>áÖÅgŸ}¶{Ä¿_|Q@ù+¿ðù×¶#¾Ï±ú¬dÀ54±ø"–uy$  H>ˆ>táËNÞÀï]47æ^~àpã îvM{j95q  H5/ ÌT#Ñýaœ_~ùEüïÿÓš†å¬t•€ ùR&E ¨'•ÒÂPÖu¯´z2dˆLŽx„‚Šü™áª«®r}ûšé©’± JNU)tÏ=÷(t7nì*8áKW(Ìp´dÉ™áÏé©§ž Z…ÄÛë‘ @< ´€2Ê( 3%àvoP„A)€eÂS¦Lq}åCi&—ùÊùBå6ÆŠ&ÀrÖ´PÔ©J'ô‹ÍŸ`¹†¼XåêNîæ¤ÿµk׺J ¹4å`u‹LleX‰bþrã6™/ùÀ÷¤Êå1f(3Ͱ{÷n×ÒÈP\«õ0vXÔÁ2Š(‘YZñªíAYÎ ,,¥Ò–„’«ZqŒó„ò×-ëhó3ÚÀ8ÕMleô‰±©y°Æ„r ¸f Œ–c’õпTdI%ªÌS¸‡ÄFM°âŦS&{”Eð÷ ‹nóA?Pš'æ(•¹²Ô…µ#Î΃z-ȶ¡¼ƒb× \ ` R¹&Ûò}á°X‹‚#6–’¾nmådšyýb^øn ç&BÖÅ8Ð×Ì™3ÝsƒÏÆ­^c( ‹Z( mÜpmÀuðG¿˜#d“ÚÂ5$•æ¶ó‰Ï8¬Wa]mî©úf@y\×¶ëÝ,+e(1fŒ× Ž˜ΕÍÂŒ§OŸî2‚`l@¨^ÿø¼áÏ|€k×εy]c̰–Ç÷%§P*£oõ{MŽÌð„ò|°Û¿Ì³ñ=(_ Øòå÷òPç/$Ð7\sàº5?(‹6ñÃg×I[gCJóúÆ5¾ðwŽUx‰‡¶öÐ& @ùðB÷æ›ovoÃÝì”/RöN$@$É2U¡Ë9ƒËx¡5 Ø`Y÷ĉŵ×^«Y ÚÊú= Ë鱜Ê=u¹»ß祎 sn@‘*Ýz¨ùpoòwÜaUnªe'   (ˆ.¢mo†±ë-¬L’ðö˜H€H€Ê›@6[-É%ü—\rIyŸöŸÆ Ì…5 ºü;‡[¦¡AÑ «ÆLUæÎÈ‘#]FrSC,E¡Ì½øâ‹©ÌUÁ0N$@$@$@I"0….ÆwÌ1Ç$i˜¥ÍB¡›ÍÑ¥$#  ò!кukwi¯m‰qùŒˆ½¦¸y€¯ÐHþJÓmÜñŽ®/äfUñ¶‘ÎõàÞnp.ý®ªãŦ[°NÆÞ $@$@$@$@É'P…nò‡ð—Š~Ø „#Í/ óHÀõ Ÿ¡Ù®¿þúŒž*üÎb35ucDuÂgu–*2N$@$@$@$dTè&0›'    ?À&iýû÷÷ó8v   È(¡Û§ùôèC7ÍO‡G$@YB ›-t³äsš$@$@$@$@$@$–|©ÐåCtZ^K dþeÕéædI€H€H€H€H€H møR¡›6ô8         H!*tS›]‘ dZèfιäLH€H€H€H€H€HÀO¨ÐõÓÙâXI€H€H€H€H€H€H€H€H€²š€/º´ŠÊêk–“' ´ Àߢ´8  d_*t³î,qÂ$@$@iG€ Ý´;% d*t³â4s’$@$@$@$@$@$@$@$@$@™@€ ÝL8‹œ @Ê ÐB7åÈÙ! €CÀ— ]>DóÚ% (oü-*ï3ÀþI€H€H€H€H€H ; øR¡›§Š³&        ÈvTèfûÀù“ ÄE€ºqac%      2 B·ŒYH€H€H€H€H€H€H€H€H€REÀ— ]ZE¥êò`?$@$@^ø[äE†é$@$@$@$@$@$@É$àK…n2°m  GàÀá²™G$@$@$@$@$@$@I%@…nRñ²q  L%@ ÝL=³œ ¤7ß)tÓ'GG$@$-¨ÐÍ–3Íy’ @zðBwÿþý¢Bß ;½Î:GC$@$@$@$@$@$@$@$@$àK¾ÓŒÒw¡/¯3šH€2Ž-t3î”rB$@$@$@$@$@$à ¾Sèú‚*I$@$@$@$@$@$@$@$@$@I à;….\.0 @y …nyŸöO$@$@$@$@$@ÙIÀw ]¸\àCtv^¬œ5 d;ß)t³ý„qþ$@$@éA€/Óãtùé—%çG$@$@$@$@$@$@$@$@$`#à;…®mL#  TàËÅTg$@$@$@$@$@$@ à;….].ðÂ%        ÈVTèfë™ç¼I€H€ÊD€ºeÂÇÊ$@$@$@$@$@$@qð¥B—ÑqžmV#        ð5_*t}Mœƒ' È|¹˜§‘“      ß B×w§Œ& HTè¦ÃYàH€H€H€H€H€H ûP¡›}çœ3&        ð)_*tiåÓ«Ã& "Àߢ :™œ øˆ€/º>âË¡’ ddØŒ8      ?ÈñÓ`1V>Hû팥ïx¯úËC¢°`Wp€½phÞªqPf„H€Â …n8:Ì#     HßYèîß¿?Y,Øn–س»H›ñ²Ü5šLH€Â B7æ‘ $‹€ïº°ÐåCt².‡ìi·¸hŸ(.*Ö&D§ûe•þã?v¦uðÑÌ@$@ÑàoQ4”X†H€H€H€H€H€H Ñ|©ÐM4¶—} ¶Z'·!ßšÎD        H¾Sè¦4Ž!s ¬_›—¹“ãÌH€J€º ÅÉÆH€H€H€H€H€H€¢$à;…îþýû¢£<»,f%°mK5‰ë×nòÌc ”7ß)téã´¼/ÿ÷¿rÙïž“Xµ|½g3H€H@%À—‹* ÆI€H€H€H€H€H€REÀw ÝTa?™K`Å’µž“[µÔ[ÙëY‰$@$@$@$@$@$@$@$@$"¾SèÂå ”…@îœåžÕWÓBד 3H€t´ÐÕyP"     H ß)tér!5F&÷²xþJÏéñúòDà  ƒºŠ$@$@$@$@$@$@)!à;….¨ð!:%×FÆvR\´/cçÆ‰‘ d6ß)tiA™ÙdyÌÎ|A@…oyœöIþ#`~wøo1 € øN¡Kº~¼ÌÒgÌËé¢Õ©WS4oÝX`8— ZA $@YM€ ݬ>ýœ< ”ß)tAŠÑåv½ø¾ã“æksèÙ·“èе¥–¶pÞ M¦@$@$@$@$@$@$@$@$@éBÀw ]º\H—KÇŸãX4Oß­{ï¢}çVÚd–æ®Öd $@$`#À—‹6*L#     H6œdwèö©ÐM4ÑìjoÓ†­Ú„[8îvîÖÒò·l×d $@$ àïJƒq      T B7ÕÄÙ_¹Øš·MëþsMî¶-Z $@$`#@ ]¦‘ $›€/].ð!:Ù—Eæ¶à€>·J•sDƒFuµÄ‚í;5™ ¤ _*tÓÇá/û÷í·¸BEß} ¬ó`" @j ðåbjy³7      ßi²è»—n¼&›£UmÓ±¹&S  XP¡ ­Ì,ûñ¿ÿìÙ]\Þ©ƒû÷ÕK/fæD9+     ´"@…nZ&™Æ|1AkþØ“úåŠ9ƒqD¶çïÐd $@$@$`Èi6í}:jrN¥ÍÚ»E{C—Òj•( d5¾\ÌêÓïNÞf¡»w×®´³fÑÂÐñð­e(¦ €P¡ë£“Å¡ÆO`ʸ¹Zå#ë­Éª×¨ª¥mÏß¡ÉH€H@%@…®J#;ãûŠB-tA"ϰˆ-O:ïß{oH÷´Ð A    ð*t}uº8Øx ˜ÊÙ][‡4UÍTèn+ )à  lºÈýâ ²H¹Wçæ†Œá€ _¡(L     ðBw¿³©­¢|t…ùh¨õÔÖFûûªMšLH€Tü-RidgÜK¡›;a|Z)Ú³Gì*Ð7ÅÀì§B7-NA$@$@$@$@qðB—Ëã<ÓY\-K6ûœœŠš,…ž‡v’Q÷¸`Î2M¦@$@$@*ýûö©b0ž..–Ïž“¡BWçA‰H€H€H€H€üE€ ]/Ž6K¬ÒjµnßL“¥Ð«¯¾QÚ‚YKe$@$B€º!H²*ÁK™ œÕDE»w—;篺Ò:¾·ba" ø†€/º|ˆöÍõ•]ºPWè¶éØÜ:®vZjé›7mÓd $@$@$ @i.,›=;\vJòvlÙbíç t­\˜H$@$@$@$@~!àK…®_àúyœyyybwórrë@IDAT ÖE;vì›7oNË)/[¸FW§nm4™ ÄC€/ã¡–9uàÓ?\øà¡Âe'=oãÊ•Þ}P£ë͆9$@$@$@$@$àTè&ð$a cqq±ˆf)ã>Çïþ’ Œ<êò©,h*t»öj›ÊîÙ @†ˆæ;>æÌéXD²Ð]3¾¥–w\8„sãà]Óž3sìö 'õÀðÊhÏŠÌ     H 9i1Š‘ÎÒ#G޳%–|°8óÌ3=gµuëVñÌ3ψ*Uªˆ[o½Õ³\¼/¿ü²(**U«VuÇo;™R¯¸XWœ×kXÇsjU«U»wí æoݼ]ÔkP;(3B$@’-t%‰ì<šº¸Ì{”}ÎKÞŠ9ÑÝjýkà‘¢Ày![µf-Q¿Y3ñÏg‡‹f:Ä ÷«çŸ÷®K— Þl˜C$@$@$@$@> àK Ýt}ˆ>ãŒ3ÜS>oÞ¼°§~üøñnþ°aÖ‹7³W¯^¢I“&¢sçÎñ6‘µõš¶h¨Í}ÍŠõšLH€H€@ D¡[¡‚¨Ý°‘gõ‚è¬t¿xöiW™‹Ê»wˆu‹‰ßÑÚŠUØá¼<ö ¦âÙ«ÓI€H€H€H€H€Ò“€/ºé‰R(ša W {÷îõæÔ©Sݼ.]ºx–)KÅÿøÇ?ܱ”¥L¨»|‘î?·Va§Õ´¥¡Ð]¹!lyf’ d/t}¹˜½g$µ3?`q›Ô¼S'msÇÓd›Pàl\ö…³jÇ ¿ŽøØLŠZ^2mZزMtÃb& ¤9*t|‚9ä·ÅwÞyÇÚò–’§¡ȱ,Ä«„eË–Å´ÁØÚµk6%‹6`³3ô‘ŸŸmq¯_¿µ*| /_¾\DÝg" N7Gk¦ï€îšl -Z7Ö’Ö®Ú¨ÉH€H@ BW’ÈÎcˆ…®ó»~Ü_Ï×`üôÁûšln>f -Yl\±ÂšMâïê÷ 9ÖÃj8°Ÿ ]•ã$@$@$@$@$à7Ñ9vK£Y¥û2Áã?^Lœ8QüþûïVjÒÝÂßÿþ÷`>æôÆoˆÕ«WÓd¤[·nâÏþ³ÝãÃ?ìúȽîºëÄÓO?ôÙwÍ5׈zõ깚ac´{î¹'XVÃð­»qc¨‚òÈ#'œpB°¬ùõ×_Å?è«tíÚUœsÎ9j1Ïø’%KÄûï¿£,Ø¿qòÉ'K1idz–im÷îÞ*ºU»¦ZùuTèj<( Ø6Eë{≞ü/B¿yõQ´§Ôo»VÙŠ—¼9•*™Éå9?ý¤•9ì”SÅ”Ñ_*iTè*0%    ßð¥B7­¢*V¬(ªW¯.vîÜ)¶oß.j×Ö7Ôš9s¦ëš>ne@9(s¡ä0`€¨S§ŽX·nxûí·Å‚ \«ÖZµjÉâÁã³Ï>ën¬Åè† DÍš5ƒyfdñâÅJÞ£>Z~øá¢Fb…cýóÞ{ï (™:ê(wÜj=¸€2·_¿~J_(ƒ?úè#‘››+à'¸Gjñ8,zÑ>˜\z饢yóæbÛ¶mâ•W^“&MœÍ^:ËSC)cBþÝ"¸I³úa[lÓ¡™–¿nÕ&M¦@$@’@:ÿÉ1ò˜<…ÛôU.ÕßnÛ5±»°PTu~sͰ×Y-3â?ÿ6“5yÊW£Å€ÓþùµŒÂ.ǯºq„¦ÐM÷—ãêØ'    % ¯Á ÍO»?<„ 2ÄåöùçŸkü  …¥lÓ¦Mµ‡>(paM Å,âP~ÊMÖ~üñG­)@QzË-·ˆÞ½{‹« Ja¬x`U{×]w‰Áƒ»Š_ùä“®‚5''ÇíJg(fc ð\à<†s!­o¡ðmذaHH«ZµjHz"¦þ6_k®u{Ý?®–©pË·ak0eÅâµ¢]ç–A™  q¹P¢ÐmU²òE‚]5üû\}£Ó ÎKÒÿŽûÕ-rs`øÚ­åï%ÀûŠKWÏ íúÍš…¼” ÷ûmöO™H€H€H€H€H ýP¡›¤sÒ²e@¸páB·‡/¿ ì.MÉ̹PæÂýÁŸÿ\ú Wè<>öØcfñ¸d¸{€µ,67;AY KÝgžy&¦6¡ÌE§…»(ÿò—¿¸ñTÿûñ«‰Z—Ç;B“½„.=Ûj ݹ3—P¡ë‹é$Åüðr1‹OOÒ§îe¡ëv ŸµŠ_ƒíÎê˜Ú΋Ìß>ýD¬[´HÛù÷ݯ)\»ô?B,œTúûõÕK/Š3®½N«ã%L5|îb£6óZ¥B׋ ÓI€H€H€H€HÀBMAÒ|Ü~y¥-6C€Û…Ù³g»qéƒÖJþÁŠ›%+äææºMcS´hƒ´²UËK÷˜_$w ¨Euy…å‹Öj]qlÀ·±–h:÷h«¥Î›±T“£&Œ%n¼ô1ñÊ#IJEk¢­Ær$@$@> êC7°)†ÞïÔÓ´|öä®üÖm·jéÍ;uÇœs®–6ôª«4ù—>ÔäpÂG=¨e_ø`@Qè ÃÁ¼V‹ @º B7‰gHZ¦Ž1Âíå°Ã³ö&}ê®]«+ _ýukùxëׯïVƒÕ¯ p·ðÒK/I1äåùsÏ=§ùð}ùå—ÝríÚµ )o&´mÛÖuËðÑG™YnúªU«BÒ“™P1'ºË½YËFÚ0¶m X$k‰Qo<û™Ø¼1_Œÿq¦xà†Å÷¼E-!ð SIæ—qsœ‰!°-Ͼ)Z?þ¢‹µNf+î<ùDí÷nýðZ9-:vÒÒvæçkr8¡ØØ¬´ï 'Šdüþ)ÖÃáÚc @z Ë…$ž—ƻ˱قÍÝÒÏ>ûlñÔSO‰?üÐÝ@ î –.]*j×®ì„Xÿ¾ûî»®]©h]¹r¥ëß››ÙÂÉ'Ÿ,ÆŒ#xà1I‹]¸Z8ÿüómU´´‹.ºH<úè£ÖÁ÷ÝwŸ;Ÿ5jˆõë×» ]lw饗ju%í-Öš‚âVÅÑ„ºõkiÅvìÔäh„ß~˜!Ì1äÇ©ަ?–!H=*tSÏæ4Q3dA ð;/»Ü:…Ó¯»^äTªlÍ“‰gÝx“ŒºÇ‰ŠrWËPÓn—Ãûs }®àžhA4Œ €/ øR¡K«(_^k)ôÌɹZýŽê©É‘óáwïÞ¢HUÜüy3—¸þ£*ÌB$@¾&Àß"_Ÿ¾¤~Ø•W…ôQËqË`K7 ªþxÍ<›<ùË/µäŠ%d˜.ør\ÃEH€H€H€H€|GÀ— ]ßQæ€SNÀôWÛ¡k«˜ÆÐ¹G[­üÌIº‚XËT„ÇïzK‘%ÈdTèfòÙ-ûܰ±™éG÷þÑ_GÝ0\6¨á÷eKUQ‹}ÿ]M>ñ’K5YTÐwE£BWÇC‰H€H€H€H€üF€ ]¿1Ž7%ú Ô-z§N˜±ß©¿Í ±Î­T¹’V~t5HÀ—¨ óåi+—A¿œ»H¼ºx©xqÞñÀ7ߊZõëG=Žv½ÑÊ.?^“UaÙŒª(úžx’&›/x kx( €ïøR¡k>˜øŽ:œT¦unN%ïÆ½r˜á¢aîÔE^Eƒéoæ)­Üåÿ}\“!„ü¦í?R† $@$@$@$@$@þ!㟡FJ«¿±ÔwõòõZ§íã°Ðµ)j`¥[±bè;ÕËÖ‹;vi}^~Ý]¹y«FZzÞ†|M¦@$à_¶ï‰TÏæ§?krsÅÞ]»DñÞ½bÈ…Š}ú¦zì/IjÔ­+ óK7r+Ý®ýö¶ÿ~Q´[_Ò±¯íüë.¨Î "d„H€H€H€H€|I T;•æÓ B7ÍOP oí C¡ÛE·’vˆ5kén¶æm³V½ûšáZzóÖEÍ— UªUÖòŠ‹t?‡Z& ˆ‘À/ŽB÷§÷Þã?ýDLþr”X0aBŒ-°x< Ðä +Vh›qV©^]T­YS+Á´Ð=p ¼¡˜@$@$@$@$@$V|§Ð½t°ŠJ«³ÈÁhöîÕ•¦õÔÖò£jÕ­¡Ý’·]“!äÎÖÝ; íÁáWãà†ªU«È¨{,.Ú§ÉH€üK ~‹ ¶lÑ®œ;W“)$‡À^Ã*¶Reý»>Q½ö>n°ÖÔò9³5ùçÞÓä¦íõÍÐdfȵJ]‰†G    ð%ß)t±¼RA N]ÝÊiÓz]q‚1$@…®NZQQ‘Xe<Ìùhø)êòEkµ~jÕÖ­lµÌB§m´KèÒóf.{÷ien~øRMÎÉ©(*T(ý˜ÁeH‘aA¬U @$à!Vå0ò"céÿÖß/‡Qd_—!ºU’c¡[¹Z5O¸ûŠ‹Åã%w«®]­åºBWˆÔ*t¯ìÝK\Þ©CðoÕ‚Öq2‘H€H€H€H€H :Éqú]ßq—J‡‡è¸¯T¼ï¾û)…òïÄO‡~xˆk‰^xAlݺUüñâ¨£Ž ©Ë!~ýaº†¡ßÑ=59¡[¯öbÔ‡?«,^°2GäÕ'>Ñä}:Š*UtŸ¹(¥îÞ½¥–å{öì•*ûò£§Í— d+ÓB7Y.À·vÃFb{Þ¦ êÉ£G‹ÃO;MÌü~L0 ‘zÍši²*¨+G~`jºE{ö¨ÃG~&Zw릥Q     ˆž@©é`ôuʵd¦mŠåt5ÇPæb~ß|ó¸ÿþûC8ׯ_ßUò¶k×N˃¢Êá]ÆP­P‚„¯¿þÚíkÇŽ j1±ÍÌž²Pk°ÿѽ49¡k/óÚ•ƒÕ7¬Û,ò·eDþyó9š,…Š•*ʨ{ܳ[ßLGˤ@$à™òrÑ7ÀÓh { º9–—y‰îYÿú—ÖÔû÷ÝëÊ¿}ö™–Þç„4YÌk5•÷R» Åþ}ºÿøq¬q      øÎL0Ó|èÖ­[W\sÍ5ÁÓ¶Ïyèy衇\ÅîìÙ³ÅÁÌ;ÿüóƒq5²·dÉm±³ü2ÙAö±Ç±¶©iÙI;ÙýGj¿pÇn­HýFu49Á´hRë>p˪(Úvj!jÔ²/……®vï¤BWåÁ8 ø•€©$K—y¬Y´P´ìÜ%]†“‘ã(Ú­ûÐM¦…î!Ϊq[)ÆÛ·¹ÂœŸÆ–&:±!\¤Éš`øÐM¥Ë…ÃÏ3Ƶ« t“Qm¼H€H€H€H€H€Âð…nØÙd@fÅŠÅÿøGw&Ë—/OøŒà‡wûvûƒÃpé€2‰°âM¥%ï~g 鮺B·nƒÚ‰˜ŠÖƺUEáýaþú{.ÐʨB˶MTQ¬\ªûùÕ2) @”Ìeì²Zîĉ2Êc’ìÝ¥ÿÖTJ’] ¿z-ýw Ö®» Büç6iÛÖs¶æË‡TZèþ¾|™ç¸˜áOwŸzŠxòÒ‹Å[·ß&¾xöN‚£&   Ÿð….BÌŸŸƒáK+ä*Æâ—_~)¦M›&n½õVÇWk1kÖ,1räÈ`ý'žx"¿òÊ+EÆ Åš5kÄk¯½&.¾øb1oÞ<1eÊ·L—.]Ĺçž+Ð×§Ÿ~êæ+;0†å0,ˆ è}æ™Ò›öáÇ»éøwÑE‰¶%’hoĈb±áÉ¡‡*†¬“Œˆ©Ì­X±‚ë¿¶,}¡}ûJýßît¹w]õ¬ÖdÇ ¸vÝšZš*tîÙF,˜Uú@»4wµ8r°¾s¹Zžq (ïߢUóçYA-œ4QakMk-&ÆB`¯a¡›S9Ôz,í…+[ÁyÑ[Óq¹´cË–`±·î¼#G¤YÇŽšl æµêÜJ¥,Lý¥µ¯-Î~õÃøýµVbb¹À …u‹¹r0ƒÏ¿@Ô¬WOŠ<’ ¤€€/º)àR®]|V⣅ :t‡v˜˜3gŽ€ ¸g¨\òP »j?~¼X´h‘¨çÜpÃU,¾úê+W™Û¢E qúé§‹5j(Ž¡}úé§Å=÷Üã–«U«–Û׊+D^^žèÙ³§¨Zµª›×ºuk÷ˆO>ù¤k•Û¦Mñç?ÿÙQ†îPþB eü°aÂe)Ø^¨5™S©ì—w‹6MĪe¥»Æ5ÁQ‚ëOÂÿ}íF­_SèܾxÇ“—.\Œ3B$à_¦’,Õ3Y鼤³…Ík¹ ÀÆ%‘i{vîÔš«\ò{¨%&P€ÂvñäÉÁ§~5:GdØÕ×h²)„\«)ÔèNùR«Ûêó©Ð•0|tõ\é }9ìí›ó¨Ð•0x$   (»Æ+E•ݤr™ ì3™ÇBg³K6«ƒU«V¹ŠÏ³Ï>[˜JYsPÌžæìt½dÉW¡{¼ãgŠW[€2²°”UÃ!CÄI'$*UªL†"V½°îݰaƒhÒ¤‰céšãö5jÔ(W¡;hÐ Ñ Aƒ`D X†‹…öíÛ‹ .(uApÛm·‰ÿüç?búôéîB,µVâ~þvªVùÃËîCîT…îÈ÷~Ôúh×¹¥çk…;u/UxCVÛƒÌ@$@ñX1g®µZþ†Ò ­˜Xf…†ë¢õ«YÊܰG¸êjñø…¥¿«f±®ýû›IºløÐMå½Tq‘Ýoü2g•QïÁCôqRJ{£†ë«”0àí΋þæ;¥ýØ9@   È$¾ó¡›Ê‡Tœhø­?¾û·xñbW1‹~ÇŒ#víÒý´–e<­Zµ Q梽jÕªiÊ\ÙÇ!‡âF ?}ц_~ùÅ-ª*seÝ^½z¹Ñ•+Wʤ„ùFWèžtæÀ2÷aú¿5 ç;W–­dX gÚ5,çÉ# dd½œŠ–ãÊys¬Ea-Ç\;·6&“½Ô¬›ÜåæÝ)»²k7hhM—‰!×j -tåÌãg僿ÀÝ‚cu2h¸Ï`    H-_Zè†<˜¤–YB{ƒ øª•ʾ×_ݵŽ}ôÑGƒ.d~¼GéãÖ«>\6ìt–nq|ôáoݺunQ¸Lˆ6ÀÏ.|èša[ÉÃ/\6D‹Y7Zyg¡¾IM»N-¢­êY®yËÆžym;6µêÔðÌg $“Àö¼Íöæ- {A¦ÆK T¡›\ ]Œ÷>¶‚­{ôˆ8 ó¾ÉÖNÄFâ(·fµg­e3gxæ1#= ¬6öG£\“›+£<’ ¤ˆ€/º)bS.Ýà¡ë²Ë.s}Ñnw–tΜ9SHkÙd èÙgŸu•¸em_*±ùšWèÞ½»WVZ¦7oí­Ð½ò¶¿¤å˜9( Ô0•d©éµ´—}ÅE¥c)%PhZè¦`C¨6ŽŸüŽ›3\õü‹fR¨ìÜ[¨á€µ²Tó÷r ’¨öÙNj üüчÖWç.°¦3‘H€H€H€H y¨ÐMÛ2µ ?´PæÂ÷m2º/¾ø¢«ÌÅæjðÇ+ÃܹsÅ'Ÿ|"ōިh J]¹‘ZT•ThÃ:ÝR­zÀ†mem¾a»ÕÒ6‰~‰m… œÍÔö‡³wO‘¨\¥Ôoq0C‰lÞ˜/4¶÷¯c”H ÅReÝiZŎ˯°ÉñÇÞHÙ°Ò«Óã#P¸]w¹P£nò¿«{5ÐªÐ­ëø¹B^>¤ÈŠ{ùìP´:Ö}ÎÞþjãéG`œ‡Bw•‡ånúÍ€#"   Ì!@…nšžKéò’E P"`SµX6=CP•¹áÚ…¶¾ê:´›7oð»ëµ9[¸¶Ë’·zÙz­zÓ–áý j…Ãr¾f‘»Ÿø§™V®S¯¦Øºy{°Ì–¼m¢i ï1nuòo¼ô±`ù6š‹{Ÿ¾"(3B$Pþ¼¾R52(üÂÄ/>Ü´’C`ç¶ÒïsôP+ºýN9UŒ~þ9mBu7œ—©‘ÂA°ÐÑ@wùìÙ¢RåÊ"§J÷Ø Et.–8›¡† ó~ýU|ÜqáŠdlÞ+7þK,>Mlq\\Á7íõo¼)z <:m绯¸Øêò.,q¹•¶ƒçÀHÀO`‚ÕÎfƒnp¾ç…³BÃñ»ã§Yp¬$J`ùr!÷‹ŽÒBˆŽÛB\׺‡VöAÊgŸ ñå—ÂÙIˆ¾}…¸á šCô;ßmŠàåý쓞ŸŸ/6n ìR~ì±ÇFì®eË–nlªk€U-‚jqKÒO?ýÔÚTƒ Üô©S§†ä}tàAäÕW_ÕÚ“mJ`™WÖcî<çB mÿ¶É õÖ‰Ùw.ºjزI·îRó¿ïú´¤•K>µD $@åJ <‹ð ‹W˜ôå(¯,¦'€@ˆ…n º-»v y×#„¤Ù*yñ+ó(]1"Ӽ޻vìýñLqï°ÓÄ'/ntŒWÑtÓOî§Ÿ¡•™ùãšœMÂìy«W»Ê\Ì{â矧õô§|5:­ÇÇÁ‘@J`eÌý÷ Ížá¿üÎ;Ê›DvÞ¨Q@tâ‰BàU·m›ÈØ ¤žö rV ‹Áƒ…8é$!:›—×®úq$«Çë¯⬳„³’ï½'Ä­·&«'¶KßYèbô•2hy6 {òÉ'Ý“‚tlN¶·dí™gž©,/áDçÇ~¶c=óí·ßºÇ]»v‰¿þõ¯B*_½ê!® ~ì±ÇÄ€ÆWPô[”}úôß}÷˜6mš»qÆzúé§‹V­Z‰Þ½{‹_~ùÅuáðð˦M›:÷mÅúõëÅïÎÈ………IsÇ0gê"mš‡ ˆ¼IŒV!ŒðèkÿkV¬¿ý0CLŸ¸@<ô‚óƒc¨]¯–V#œBwÜwÓŶ­;´òÌZ*ºõî’Î ì#P´[ßÒ$° I#²)Zä»\°Mæ/w8Ê„(BÈˇ,t'v¬MŒ°Óññ_=бЫ!4Ñ÷„ÅåÈ`k«ÌƳ-²«@·ò^5?½YLYzÞ²í\q¾iJÀy!â˜Õp–,ç,Y;v¬«dE¥š5¡è¡²³TÒÎ;ï”ý§ž*ÄŽûºž=c¯Ï$àEŸ¸0ÃÙg›)”I )tm]RºH|£Òglâ[Nm‹·ÆhŠ’³<¶0þÌÐÈY¶i“2¯v½êAáŒ?¯× Òý‚W™d§T!½üLµhÝX›òúµyš,…GnyÅYF½OŠÚqù¢5šLH { ìuVb¨¡JõbÏÎÂ`’k‰¯¼zƒ%‰‡”WjÈ©diª–)ÏxÈob ›¢-œ8!dèG~Q¡»tÆ ­^½¦ÍDcÙ°©×*d™Î'vy£ÀõnZ[×kÖLluV_ɰaŠѬWI<&™ UJV4j=Ýr‹o¾©%E%Ü}·½Xÿþ¥éÎFÕž a†äxâ‰øúÀËÃwvøÃ}ј1B _[¬E*Ë/ήðjŠwÝÅûo¥$pÖ“ø/”§U”ÿheþˆ7oÔ-¥*æ¤ßem*t7¬ Uèæ9óX4Ϲáð{÷8˹H€Ò†@yþ™º••jØaX‘ªyŒgƒÒc±Ðµ‘*ت¯Š±•™öÝ7Zr¯(öÐ*d¡Î/vyâøê¥µî›´k'·i«¥­[û>ZH ×]g/ýÖ[öôH©%îïBŠ]pAiR8ËÎ’=FJ 3–ÆËì¸úÀ Íã›ÍÅðr3®¾X)³ Lš$Äo„ÎñŽ;BÓ˜BI" ßá'©“D7›)º‰æ’­íM›èø¯RBC:*RzD[´n¢ dý:Ç?—îºòY-¥jõ*Ž/ãÒ(6«+ܱK+CH ü”§BwóºuÚÄë4l(ªÕÖ]»l—;dk%)d%ÃRû€ph£ß¿mWŽÀ‡°i¥l6·dê4-©ÛQG¹rEc„<Ç=C€ÀW/:ÖciÆýï#mTºéfÑÄðWºnÉ­ H*k¯õn>VW.^¿•-Z„öÑÑòŒáüþÆåæ!´u¦¤’|ºÂÒ›þ‰SI=³ú4(t>‡*D‰ËËÐL¦@â ”j‹ßvÒZ,χh9©½{ŠÄ‚ÙËœª–‰ùÎfU‹ç{[VÊ:<&‡À¼úCDϾ–›­ätu«µêê¾uöìvüp)aÌÄî]Ê& NÞýÏ\)ª×Ðý(çoø[Vª2J$…Vçê/²Ú8»mר£+t7+Ë¡³§¬0ô¹ŽU’’&j*òÔ¢» T1$¾Å¸þšµkï–é?ìZÙÞy[“³Yó¦ÅÒ§œÀÕÂãRÇCÛ¶ÕF— $2Í›{wõÚkÞy¶œ#l©B¼òJhº³‘´X¶L8›‰ÿâð›É›=…HÏ”‹.â¶Û7ß,Ä… ál.ªW?^lfÿ§‰°ü ßs3À«¯ aÛÏâ×_3m¦œOšÈIóñY‡—ºË¯Þþzp|°¦|Ꮏ†”ø}µî¾ mGËõ”*ú÷ïÛ/Þy´VJéFMë‹ÖšŠy3–󦎟'Z´Ñ­}ƒ™Œ ¤”@y¾\\½ W›k›ž½D%Çíºť>þr'Œ½,¾ÕµŠ²‚@¨Ë…ýQÍ{ýÒÒß³Â:G±Ñ©_?39(çoÜŒ#"€Çüù1þÓO‚yãþ÷?qÎm·ålˆÛ|:߆~8 ·å;«’Kßà{¯–³Ñm£–-µS¥úÓÕ2(@²Üs÷ÝÚú£:V÷‡¦{¥x-öò±ê¸øcHØììðÃíãÁ÷×СB|õ•=©°¼^»Ö;Ÿ9$`xðA3% îÏì…˜J‰#àK ÝtPèæ:Ö¹jؽsÀ’x†ÔØ´^÷åצC˜·ö©^Äø—î›n¸×yÓì„“Ïèå¿GO–QI€²˜ÀêÜÚìÛ8¾ý²É'P¹J¥äw’€ðÀ›¿¥@¬XâÜD(á²ëÎrGòž}:)9BlÏç.º $PŽÊó·h½á¯²u÷î¢Û‘Gi4Ö,Е¾Z&…¬"‡>WLûö6ÞSìPE-®ZtjŽP­fM-ÉU`Ê_­@ [=º˜ªéÞ ¼§?Öp‰qí+¯¹CjÔª•6´­†[-“ $ƒ@gÅš—¯Ê{ï®Ç×_·—›8Ñž>s¦Æ 1`@àñiº¿p{E'uÖ,!ðâµbE!ð¥,ÿŽ8"ú6<W2 ¸„Ëøö•}È£c]/Î9§lÖ¨XIpãBtèÚ>úiÛVˆË/¯@U†›²hÓ¦B„s .Ñø~?ÿ|!ÀS²•Gp?ï}¾s¡ðÅüèöïooCM…âèC„;6T7i’‡&ÄqÇ•M‰ƒÏë±Ç ÿÂï¼#”RfÀw­ãæFÀe ”Ò±øŽEÝ“O¢V-!<àKØl2,áÇãèÕËyKe±€¶ÕKE6s¼ê*{Oᔽ²¾çÀ /´ $µývû2Ü^Ùü¬ÊöÌãM7 {9¸ ™?ßÌ/óŸ|2ðR>ƒ#…çž ( ¡|†ÂÒòîn 7fŒüc l,ŠÝáÃ>ˆ_zɱ:Ú®/ùn½5¯r[¶øïæ|Ë-v.P‚c~ù‹ØxÕë…Œl¯°PˆGâ矅Àçïûï aä#¥üÔ©²tàøÑGº„k ãÃg}üx!l« `Œw5ݼyñôÂ:YFÀ— ]óÁ$]ÎÙ_9_ )%0ök}yL>ÎÛâ4 Í[7ÒFöæð‘š á¹ï I«]W·fZiXô†T`‚ï ämUn,[¸Æ÷óâH€ʇ€éC7š]ÑvãG ‡rª³zD¿mÜ‘ú]…*ß¿õ–RÓy¦=G±lqrª×­«åçoܨə.x¹\À¼ÃmD—j.‹¦è÷Xu7IõØ „'…•-D»Úf] ÀDXòÁŠ£HV{øŽgÅ”¦pñË/‘z)Í‡Ò +&¢Q8®_°FýöÛÒúÑÄæÎ¢Aƒð–±Ñ´“È2Ï<ãÝÚÇ{çáw /##) ÕÆ‹Ž1Î9”Ä=¦Ö‡’Sµ*UKãºs\qy*¯Õ²fŠ]ø$Žî½Wˆ«¯ŽTJ¸ c(·-Š\Ö,y´nØìÎÌó’á* J÷/¾ð*á…+¬u#|NUKn•pnZ°"\N…qeÒ­j’ÉÐïÌ}B%Ѻ«—¯ÓÆÏsg,‹ç¯Ûô‡ËÞ=¡ (óó7SÍ¢”“L`Ì´Ž;åpMN'¡ykýAhÉç‹] =ût°Ð5CÛN-´¤…óVh2…Ì#pßõ/„LjüØ™!iL “@cGçpŠ#³.åè˜>NM%gt­¤¸ÔAz‘,t7›ÃÀú;DZ*iÚ®½ÖÐ*ë‘¥Ó§iå?m¨&7k¯¿|ͨÿ–k…3PçraN4ŠŸ1ù壵žºbi* ¤,{·…Ï?·¥–¦y¹%‚¢*Q–xX’K°Yí…«¥”¦ñÔ…%m¤Ð¬Y¤áóK|o‡/”¢\,kWVh½Â¢ÔÀ .>â °5|LJ4Ó¹³pãkðR°£=ß稺xÁyÁŸW€¢Þ¶!¡Wy¤OŸ.מ7p P„ÆN?=º—jÛØ/Ú«ãý+ <Æ8áÂÁf/ÛëÚUÆ¢?Fûb*úY2 øR¡›h ÝWŸüD ø}ñø]oЇo~Y¼üøˆ°§zѼ•Öü}ÅÎ6CJ ìØ®ß$µïâ¼ÝLÓТ•n¡«×ô¿¸XM Æ»Ü.GdþÌ¥šL!ó˜×5f8î;¾0J·3èߢD̯Yýft•×k":ËÒ6vË «ÕÒ—ž§#óZ¤ÐÍ5,šÂW¢Zví¢Moa”›ÈÈ Ñdåú“ŒºÇI£Fir¦ Û6…±HŽÇ:/IÀ¦»ÂŸyýõZOæuµ7K?­ $\`o~LÃ…³Î²çßOöBQ¤Â"1’Å,|h–5y¤½…¾}¾rñ‚?øÿ5|_»á6àí·ím ^8wNœg(AŸxBˆK.±·T´‹²Ì»¥Ää doÇæB%=Ô^®9à³X2†Å·Mù çÏ>³·tc_­ |¹þõ¯v×6å?ü¯. ó¬7%O=%ÄÍ7‡+Ý‚m(A×V¸îš WOÍáèc&x¼ìÅ‹OÉ|Ó&ïkÏðÙ¯6oCùm€ëXTÃu,±z(`m«¿p¡ýÅÎi§ ±m[`.xiå5\jHã ÛçÕÖ>Ó²šºÎéß°n³v̾X“MaѼfRPŽô,ÈHB˜ÑÕ¨Y-!í&£‘º ¼úÿ靈=»<ôˆîZÞ‚YË4™Bzضu‡€?ííù…b÷®=Îý†sCXëøV¶ׇnlMÙšaZ ˜ÊŒ6¶)s)|Nå*ÁòÍJo2aõ‚ù2Êc‚ìÄÍ·ªÕvnêÓ<„¸\ˆð½ôõ+/i3ø§ÀoT?ÃÒvúwßjå ì3}ô9/,UŸ¹(cZ쮘= ÉYvy=$—Øž—Wî,l>q¶l¥«U÷š¼4 ,­ $‡¶W‚e¬ù}$Kâ;жʳDøCõ °¬ÄàÃGl‚OÈÏø5ëÂ×-”‹ªõmïÞB¬Ze– È]dOÇË6/%YpC_¤xÙƒ æ0—3Ï´·%£ñBÔ^0©^Ê~ÛK)¸³€2Ü pMÿ«p' ^Dz½@8Ûãç J>¼8}÷Ý€«ð…]°IšàãþWmJaÔõU!®½6°a~k~üÑV:å°~ûÍûs®èŸ;§L±»&0Û´É_šŠ h¾‰e€¿[\{W^)SJ¸vW¬(•c‰ué"Äûï Ö† ø§Ž¥¾ZÖæï–âðÁ-ýÑC†oëë®.¯pm¥ÑŠu:Œ§_*térJ’X7ÖZ±d­çYܲIÐó,ÈŒ2ØQ°Óù­p~,JBNNEQ¹J%)¦Ý±n}ûCõUEÿcöo£fõµ¼¢¢bM¦^î½öyq㥉kÏDüóìÄ·#ŸÂ÷£&x–ž9%×3ÙC`›áo´VƒÒƒ®µ‹ !†„(4º±Z€$t4Q6†%(?Jjit½aÝsÂÅ—¸™}Ž?¡´ûÝbU´ÖJJ¨áøË5_~TÀ.ïYvì;û^VIak%6óc·ðfëºöØäI ‹¦:ï $j°f3ü{‡0cF0ªE` e™lгL´²—Oø==ñD½•{ïbùr=- ËÊmáë¯m©4/KQX:šÁ«ýï¾ ,57ËCƦZPLÙ 7ØRSŸæåÁfñ:x°}|° õ o¼aÏ1-€±ÆKa%²ñÛí6 %+~Äa5ü·¿éýxmÜ…—ýP ÛÜ‚xYh?ýth /…1\R`£85`³?('cµØ…Öðãï6‹Íö¼ÚzôQµçÒ86Œ5àG®óÌ…MÖÎ9'°QÚO?ÅÚJiyÛË#c/ÒÂ%1XCYÍ@øR¡k>D˜cØìü-ö¥Ûó½o¶W,^çÙæïkÊߪÂsp–±Í8wÕÅh:/…î#/9oµc TêÆ,…Åó·l×zûuÌtMŽ$üôµ÷ñ¸1޵CÚHäoQ,“Ê7–kתWªÐmƒM0”°˼J`çvýÅmu,»KóòLF£k¾0P§Í5Ÿgøß­]¿Ú„g|O¬~&=[JÿŒ];ô{Ïö}úhƒNŸÂ“F}¡éXcc;d†(taÅ@åAÀKÉtùåöÑÜy§=ÝKi/íj*îdIìpÔQRÒmÛ 1k–žI²½üñj_¶¥Z7Ê4ay©X{z…HGy)¡K‡W¶`SòÃ_¬N:ÉLÑeXHÛ‚iéûÊ+¶RBŒmOWS§NU¥@ÜëzäÖÈËm Z…¸¾ùF•Jãsæ”ÆÕn@ (ŽÈ?ÿ©¶P÷²¬F 馠´t öâ‹fJx–Ôo¾¾L¬¹ª·¬‹ï›Â\æóHQð¥B7œ…î’«Ä%Cï÷\óœf½éÅã½—FY³fMñ~.Øî¼iòáÜ1xTarœæ®1Z´igK©©V«NŽŽrˆ¨]74Ý,ؾ³þ¦{âO1Þð™ RN ןq¬ŒàåBÁ(æŠ6ß¹j9úOVi¤|¿cuñÙ“Oˆ5xËŸÀ`*Üj*´íÙKëÉ´´Ô2)ÄE p›þÒ¦š/ºúힺºÅ„ðËǺ5K—þGhEªÖÔØ«ë{î¸_´ò¦ÒOfšþž—Ír¬‘²$˜ºN?C›ù‚ ã5¹<„åÆù8ì”SC†Ñï(aÓª•ŠÄ( ¤À¥—Ú;óR2½óNhylÜåeéZ:|Ši©(K*cöãÁΊ½X_6—Æ ¢ŽêÔ IrL+‘#íåþñ{ºšz챪”~q¯ ÈÌó 6 ÚH®9¼,+ÍU-ßogsÊ)öôH©^Jx¯MàÔö¼6½4ýjÇ5ë¥$Gp=Cÿ“U­GSñB±|.ÔFc±|‡õï'Ÿ¨µ÷r¿÷ ½œûvºVH ç,mÅùtù/xYˆ@™ûÐM/»Zµìwqõy;«iœ% aÂŒ‰öíyqn<5cÒ‚0½1+‘~ûA_F5øgiB\·¯zP\q˹¢«³ÑY½†µÅß®ÿST#îwŒ®¤™øóì¨ê±Pj LþÅòßÂÆß·D51†»…¶Zhõvï c-¡•¤LRæõ[„¾÷;V7=PŒ~þ9qï°ÓĬ±a|”Å8Ø|Ó傢ЭZCA¥2Cb ºÕ gb{KLkæµz@xßÍ3FëôpCqð AZþÔ¯¿Òäßõ‡ Óò¥ÐÊxÀ\êµ4ZVÈ £éC÷Гõ‡÷¼xwõN£bGI$¿çd“u-;¼›/3¸)š¤ÅcÊ X®ÏàÌɼ>_ñ,ÍvbD~øÁH(xÀžOªÍší XªËH۟גuSɉžl!]Ü&ØÆmÚJ—O•+ë-Ø|ê¢,Âmle,±m®Ô`ói,ÛPËEß±Ã^ÊTRÛK qüñöuÕ×êÛFpöÖ¢KÅ&rfÀ¹l¼ŽfXe¸ŸHF€E·×y€8î«0'øÒ†_mˆ@F)t¿ûMmê…»D¼H-Í]¥µ%…꠆ œŸ֬ؠHŒ&“€ÉºßѺÒ3™}ÇÛ6¾«ûÝSÜòðeâ‰7oŽº™N][keÍü´L åBÖµ^þ¸ßyQ_ºê5À_¾Õ—Owr?ç÷]ÿŽ™7c‰Wu¦§˜€ynÔîo:ú(¡îdÿìßÿ&LW jùXâ›×­ÕŠ7ôòU§•¢(!›¢ùÀBWè_#ÿ{@VÍÓ_L™˜õx´Vs±±ìs£a Óã¨Zy)t4v Ï.…®nå]3’/= -EÇþGë©Uš,óeÆ^>ˆJ4<–ÿþ×Þë3Ïèé^þM ŸÐz¥%Ã×z°¶Çg)˜KÄ´¨¥®­¬©ÜàñLÎÓÖ®™aSH³xRä?¶7k¼‹ÛËÅ›Íܽ,¨#õiS‚¢Ž±·‚g3^–Ýêu`øÈ¶•ÈûP¯ÁÎ|;6òÀ±á\G/Ò#Wd‰l&àK…®·Ëó‰ÅqEf¡µ+=~¨œ+"oC¾õº˜0v¦–Þg@7M¦@É Ð²­ãh^ Ü|O‘&Ñ—÷¸9tÆ·|¡®€ó²éÓû¨ãûŠ3/¢ï¥(üji5(”ÓcH”ë…Íku?î۴ѦXѰ¾ÙáåËO«E!Z¦B×>tõÛ=ÓúRÎ}q±Œ¦¦éBÁt¹¬!ÒíÈ£´K Ű–™á6‰«Y¯ž6Ë¥Ócó¿®U.£0~ägZ ½û^M–‚ymyY³É <’@2 üýïöÖÍ%ÔÿÑ_X¸•`á›ÈÍm:ÙGêì¯ÖëÔI‹±(˜iÓ͈q/Ìör ,‚ˆ×wS1ióŸ[–áuï¹¶i%¹F „—BWYŶ)¸±õEÍ¢u°éZ¢B¦®,ƒïjÃE•'2øPörÛáY‰ÙJ@¿Ã÷ o…nèÂÜ9Ë=g5Ö2Ï<¯Œ©¿ÍÓ²úÙSdXéî,¤©¼) ‚¹i]ÅœŠIè%}š¬RU_äõ0ž>#ξ‘Ìæý¿pÇ.Ç×þ°PÌ—E•+Wrž-*ˆÇöÖêåm´¿lÒ e°N×~8 ]Û)ˆWñe¶µyí-©q›¶šÜñ°~šlú4Õ2)ÄL Äå‚,tC®Ue“ŸVãѾ݌ÐÌxpÛ¥¸ ØeXxäµÄ[¦¦õ@IDATÐi³™ñðhnft›ñbÿaÐæøó‡hrª„}Ž»…Æ&8¦¯\9¼Á•4®«¹](ÚëëZðr­PVç‰TØiŽRðÚ°ÕÿúW½Ã×{0óÅcã‹s1Ï1 «R%Ø„gdûvϬ°Û³£U{)>ÕëÀKÁ åc¢‚×õ{ÝX®i”…’úhÝMT¢†Y¦vàKãûÑyöSæf£yyBxù²6ËRÎZ¥Ðµ½é.Üîm-knTtÂé´ á»/~ÓdÛ µ´úêˆÎ=ÚjiófQ¡«I‚°$w¥ÖjëÍ49…£†ôѦõé»;£*¥¾ûb¼¸üô»Å?Ͼ_<ûÐûb˦ì¶îTÐ$4úÉÛc´özÚIœ}ñIZÚS÷¿«Éª¥»ù}tØQ=Ý"xU¥šn¡½r©¾ä^m+âÿ±-—,‡‰{½\ÄPX,¯¶xùSÆ¾ÎØù·OF(¹è®ýf¿Žz³íioøA[»paHLˆŸÀÞ]ú •ª5<Üãï"á5:H¿Ý“ Xµ#¤åK6Ûô |©åoܶ­–4­ÄºÍô§Û¶g/­œ)T«¥[€­Y˜kɹ©aùlZâ§ ÄÏ>£uÕþýÞCËtÓÅ ýèš„ÂË>úhøÌÀ¿ÿm/ÿ}É=󫯆æydhZYSúé+e‚Íýßÿ£eŽxYÐΜYæ¦Ý:w¶·óÊ+öt5ʪt XErÁö‘Ù”·^V¯‰²H5^ˆ?ã^0ØV´‘çž³—TLò†×Fƒ2?–#\z˜/PßËÝC,m§[Yl†Í½|nc¼W^™n£æxÒŒ€~‡ŸfƒóŽº¼K-S\´OÝø^Çšm¿e©³mó¢Ãê‹ç¯ÒÚ+**Y6]½FUѱK+­Üêe¿k2…ÄȳBk´ç!59…¡:F›Ö¯c¦k²MøèÕ¯€Äî]{Åô óÅãw¿e+Æ´2X²@ÿ®¸ôÚ³Ä Ð_Ù¾sd·Û¶è :¤×kPªè8é ýaãíç¿U³ò¸ÏY2™JÝp Ýý`…ánØJÎäžúKC¼Á·)‡ÃøzÆnß[ †„0VU¼,±ÖcÙ ½VC¶ eöª¢~]kyøÞrÑEÚ ¾zéW^il¨fúÛÕ*9Â!ÇÑ’&Ão\–ós ×¶ï‘dã™ôÅçZ—{Y4–”ªl\ÿÛi©¥$3£<øàƒ¾]y’vgÊ ›¥"Ü,ÀU€m“«»îJü4Î8ÃÞæäÉöt™zÅBDkQ)ë˜G©¼6Óc•O?Ý^cDè‹æ‚°`µ…ÓN³¥¦& \MZÇŸ~ª‰®`S,"ã³ÏBËÆ“rÔQöZÿú—==\j8߯‘¬ªácÓ&{ëá˜É¨î»–²~(KG>¶kg/¢ÛÞRz¥âåŒ×j9sÒk¬MÚð¥B7ôÁ$ÀU}QI‡*z'ý<[-"`áÙ¨i}-­ _°Þ»§HË—ãho(t׬ðÞlMk€BÜ–/vÞf)¡§c™é¡^Ã:Ú½®wYè¿w¾!Lw [6‡*eyã#ðó·SµŠ•*åˆÚujˆǯsN%Ý·óïkì7KÃù@k£GýÅé¬å¯_“§ÉÙ(ìvnÌzè!Qì壭œ¡ì³Œ ³Ha·áƒåçþ:.R5-¿®¡Ðݱu‹–O¡lÌ%åþp¹à(9”`ûý˜ðùH¥„µÃlÈÑ{þ´qÅ ·îJÃj©cßCµ6M¡÷qz; ~ ]eÖñ»lcï5§Tû£5_V`\uy,ã-t§ÃÓ†¿Øp»¡eR°ÀKʇ~Øqíêí&ÎZ‘‰v^V°ú“½üÉ'ÛÓË’j|.‚MÁeÔ—_E-òÄB¼x9¦¥‡lKàá/8Vºð«ëÞ~Û+G8ÖOB\u•=ßf!m/™ØÔ÷ßÖ¥^~b¡´´ÍåÙ,_ášÆø½‹kÀC‡Ú«=ù¤[ÂÜ»}î¼xƒåÔpÒIªT÷ºöe‰‹/–1ý8@7Nq3=VîcuX°!Üx¼–1#ÆKã`ö AÁhÆE¼ünW­šqSå„K Bb›KMk^º^½Ã礦Or|)áÐÝEz5u_•ŽE.,|e0ý¶Ör”6p» Sñ«æ1ž †q,¥3=Tªœãüxë³ô²ú,ؾSÌŸ¹T/ìH»wî IcBÙ|ý‰®lzαÁ[¶mŒ#2å×¹š,Ó_÷ ÷](³Ü#^™/ÞMÞZ…,Ú;*A™[žJ]ùR/ZÜ‘ºÅŽõMºñ ëvy>ÍAÈÍôd~G™}Qö%_*tmÑ^–o8+ó¦/ 99 fé’}ïê–ÁfFjØUè|•„Ÿ¾Ñ—ɘ>we9“K`ï^gYG\÷uêÖÔfnú]•™7^ò_å1É6¬Û¬õ ºZ8ýÜã´¼Qþ¤É~7¬mqžm/­Z·o¦Õ5y¡&g›p´³É”ºéæƒÐæÏã„Â6\J1³ÌŚǫm³Ž)ï+.}9iæ¥J޴ʹÁVÂ}Ã<,S”2é5­ýárA¿·±Y‰š×VG{âÇœs*UæÃ-ˆ©Ì f†‰46†Û7n S:3²Ö¯p” JhܺMP:ýêkƒqDL¶ZfÂíÇ/®:ä`qeï^âʃ{Š_->¹ÕfL²iA­–•ñ®G8ÍJXa¡<ë,!à:ÅÙ„OÜr‹°˜„;SNŽix _­+ãPÈy…sÏ pnÞ<àÊ ^½@»R¡gÖKä†pfÛpѶmà÷†˜×M7 aø…7«¹2\xYG¢Àå—[«¹‰PVb5ËÙg  kø¡E¿°b(16(vUeÖpÞyÖd7±woá<6SÃh©³š Zü¦zù¹…Õ6Î9\’ ”ëøœx)sá[Úæ¾$¼À&š+,K½”¿Þ3.Íñz™ðôÓ¶¡$½ûn!àÓJ÷O (ðaUkaâDUiË©Ikk\'`ƒ8ß}'6ÀÃfyxA‚Ï‘-`“CCÀùû/غ_Vrê¬&›-úëüx)Á´¢mU¢,iѺ±X¥øÀó–Íž¥qªX©’&Û³Œ Öäðß›©Áü 7Vüv7ü)F²êÇð[_§yçî»ÄÀ?9”až±›öÉû»GÉÒäf%/ÖdÊvìÊÍ3(uñ9ë,ã~ÀÙýfÇB­J•*1·Ã %žyFø£°)Q²Â!åú^¾I½ú…Å6”<Ñ(J»w·—†" þ^#ù|õò߉¥òpWà¥t„…g¾ºÖǰ–M§…&Ü&Dã>h½\ àw–—‘| ›ÊWÉ÷Œ`.xØ\#Áu~C½¬X£¹¡Œör…$^V\r‰÷ˆ½¬P±jÇØü׳0…°ªÀ–…aÝ>˹çÀŸ^àx¹ÇPçߺ^..ÔrŒg5çé¿`³^Ë­?䪳2-ÙL«Æ  ?ã¼ÁÁ8"ß|úk©,µ>%)ªâfÀqÎ["%˜~5•,F #ßûQÌ›±D¬^¾ÞUêþôÍT£„.NùÍùñUBßÝ)³£ï«Mð·ïue 2‡?á¦@k!»øÒÎÛ˜/6—ü}òÎ÷1ùö3Ýßã·žR¿F-çm¸ pWÃן(ß1N†ù$Ëž0L·‚2Ý4ÈrÙv”–º{œ‡‘ûï¿?-–«~÷Ækq†å†BLmd^‰ÝõX6§„&ŠuŸ’,:~¸*Š™?ü É©L%–ìIšùÛ|û®;\·ÿèÞM<ô§³Äƒ°l2‚i¡[ÕËƨW®bémN`ÆýÌlc©}[XpE]ú—¾ÔFÑW®¿^«qð Aš­7™Ö/e) ]ÛËX7E”ìîÿÃ0 á~ÅËJ×¼®Q©®Í:+Ø#‰&p衇:Ïî==Ì~÷%%}ê–ð9çD®Ü¹sÀB6rÉøK`ûh­…ÑK_ç>?V߬°Ä;6þ1¢¦ñ}®5KIçþ*îðÈ#BÀýA"B,,½úƒ²Ëö¡ˆ†s4Ö _}MIï2Úó0'({ãyãe Ëä(^ÌZ„—tÒ¢ÔZÀI¼øb!bù‡%:^öÅú›‚:Ñ(ܽƙ.éÆ‹Ï¨†U«–Ql¦U[,”Ñ*øqv¦¥ æ°5o›çTÌ¡F¼í˜¸+á(EIÖç]9¸¥¤]ÓšE©îFû ì©%å.´L Í›ò5BÁ¶Â4™`nÕ±‹n=%Ëeâñ˜Õ¦µt¡s£¨„Wž¡HŒ†#`³ÌaÓ8X䛖н,›ó­|¿`<¿ü?{W/Uñýä£ûÑðÝÝ *‚”‚ÂOL DLT° ±ÿŠ *!RÒÝÝÝþçì¾»oÎܹ»w÷mÜ»{ÎçóÞΙžï½÷̙†àÔyÔj 04’”#›)WßT!A2 £.~Nc`ÓHÁ û.±N)<¢vÇWù+åv¿á1>!{6o’³A=²n6îÒÕHz^ÿ•hHA””ë×iGúzèsÚüXeÞîs8uôˆi*ªáË•” ¾s ÞåMûú+²Î–=C:ÜÝ´9sâ8Ñë¶¿†èVJùºt£R ÎfÕέùûMݲ¾¥\!<ŸræËïÓ1±¹ƒ”kÖ€êIntñÝ ÏIòúû'ÝŽQŸ4`%, Q× _@NÝh}Ÿ…eòNê@hDò'5‚úëÛ(ãüèMhŧkÔÃׯÄçð¢ErNz:ÐZи†^‘ÒQzã)lcÅÅk4Ej ŒÑ©$píá<=…üáMñ ±ƒÖªÁÄìô‰4ˆ±Í“Q¤KôL}ÎÏïĺV^À¤3¡à}…ß VÁ÷›=[ÛµS[Zëx!ýÂ&ú;Ó²¾¬¸nåFâ3M|yçèÝŒ ez@÷ïL+o]ÄTá¬÷ubõ~ v~¾$¦O×׳j†Æ¤c`al ä»ÊFQ¨¢zè¢QãÒ%ñAaSÖ­ØJj¶íH=™H¡PÐH°o—ø0‘$w^ñ# R.Èrú”øPf±…ÀåKâËQ¤º°’ƒûèvñÒâK+AD½÷ÕeÏ™¶”da€®¼ ïî½?Ò ”ÅsטV{AB™pdÿ>(X¬¸\…ÓA" sê>/Œ‘È©›±ØGUâýäù³ßÊM#–ÿZúRï_P ÃTq å{˜†÷tƒT :yÿÂÙ/ö…÷Vƒ5®Z̓”‰ï¾¬ª†”Ÿ/À#xÿu€uýòó×ßÏèÁüÉ'Þ?uí”eä=f§]ÜÁÀhøÇ„ Laê'ªÝ¨&hü“å/@=f ¯N4ÔÊ2hp•¹p±ÕãsëÆÝ°NÅd)[žþPÍ,úÉ’5³\ØK—À¡UN?¥Í?~TŸYñ*Ò6ŽóÌŠÕÊ’®X´¶‰{ôÈÁã$ÿÙ·îñØ(ZRü0 º,b“WCuòõ‡bÇ=€lZ·ƒÔ@HÙò%Hž¬ Á]–ñßMó¨{w‰~Išµ?TüH …ÒaçV±óÍB0ÑDùòàÇ£=öK*Þ~¯ôºÑ×ëOñ¥1Q©Qc¢Rò¡ÝÃ{÷jÂå6P9um6ãjŒ#À0Œ#¸’rAõÐU¯SÎ\IP¢l2l^¿ÓW´eãN¨Û¤ üßç“|y˜¸¥¿8.¡Hó¶µá7ÉëwÉüµ&ãOº”VµV†ù3ÒËÿL[7Ü~©g¤#°[ÐYXÉÔ s¡©¸²L›~dóÛ_×\.Nˆtuqïýòõ_¾µ.˜½Ò—6oŽd$¡hqq,H•Z*J˜ä꥛ü®ƒòåñc<]¾`=ißûîŽDW•r•J“¬cGNÀǯÿ@òºönCt+¥TJQ=sÇŽ™,>gÚk«ã1ÍD™~áìã‰påðªJ›Ðúæ[à—·ßòµ8…¼^9¢@räÍ]î{&ú©¯öžDÀ$q\M’< HMæA1ŽJ H'P¢bEZ) ÚªÙ³È(É)©°ë’·fî¨Ú´És’‚†úžƒŸpÒ”‚š‹ê±-sœîS®EÍ ŽÚWoÞ–jè4’rÓSR&›¯H2Èï;v@™ªæó@ý¸¡\5ÐfÉ–5à´ÏN½ñ:€ŒQ ³ ®½Ð`Žòì/ãáÕªøzÀ–5‚ƒ²jÓ¦°P‚e¹}¸à B -Fj×/Ü‚›sxò"E¦_&x9‡ ™• »DÄ…×Ì0Œ#À$:®4èÊžhx‘©PjÌÙSÒ“” ÇRÐfíêÈÍ=iõˆºÎ“OgT¾Jp¾ÊÝY“4è.ƸÛ÷A¾üy<Á«*ˆ G\)Qdù"ê1-¯{óºtƒ¼‘¿eý.#éymÒºÑA)—æykµV4‚gËžþ€˜;ONRù¤]&þH7TvŒŒ»€õ)<-t1àW, ºË¦y)FŒ‰WkÞêµo~ò±‘_‹ Ã'OñéNKÌ?ÎÕ]+] ¥J¶¤j–¥Ž×ñ›¡ÏšÊUÚS%£€¸ßeƒîN,a ºY³)h”­Y¶­XáË_>c:4îÜŧë+DÕ ¿Ï‹Ã}U‘Ú¢Š’´vÎ_Þg?/MùËôÙ¥fó5°H”ª’n(Æ**¯E³²õû =uQV®\)b- ƒAƒANüŽ`aF€`„EÀ•]Ù˜ºc åé2¸*Ñ‹Q–«·›9mÅsr(¢>Ä} 1V–㊱Y.3Ò¾öžI†ŽF¤þõ4Ô¸]¹RY¨ >yâ4¨I¹Žl¸”óã9muÿáš±¬ÿ£ôþÉ)‚üÉröLbz¸ÈlßL?7ðób•0â‚›AVݱ_R£SÇî-Œf~_+îãu‚C'™³d†…óêŠLy¥S©'rÄ¢§•¿û¢5CHP‘ºÃ‡‡'Ÿ|²*œ•ÁBcE!°M§m+VBÍVÖ÷ ݺÂöÕ«|]ÝþÒËвgú±l_A‰­+ÒO®`ÓÊA¹ÚuˆAùÑsQ=zäP«~£N»XTÊ ‹òýKé?Ô«oÍ`$Ÿð×I»ÛnÓe[æÕÑ6KQÜ—ŠÀ^Í®»Þ²¾› ̺fƒ.zÍÊ]¤ÅðgÐÅϤwSŠäÎ͉Q¿%A.݇Ôóåà†ÏÂ?~÷é˜È-x¹URA£”®R•äîÞ”þ}J  $ò÷™lÔ}ýõ×='O²gÏT¹ F€`F€p#®çÐ]½Œ6ŒÈ!* F–_¿r«œÅ”`Qra?ŽÔ€hr»`Òö!Æ\l»aõ¶`ºp}]Õè]¯)}(xãéѾ5ž9M=K3eºBf\¹'á[S¨ •#ÚègÀà^FÒ÷šËdÐ¥8ú*&Hâ²0dàF,÷ýàÞþ4&ò šŠD•W¥{ØdÎ’ ¶ jX–3w’QÅóšèºÈ_«ŠÎ ü’&Íì©‹Õ¦P õ€2UHËÐ`4ê>òüíFÒÖ«º5wú2[í¹R aÜ,W®œ ½¸È©Q º+fÎ ÝÖrh°Q½ÝÔcÑØHõb­P¿§¯ë~„ôŒR­95ènX°À²ùÿ½ü’©lãBj 6U°™¡RQ(æõ2oÜ­éáµ›o"ºÓ”_ßÏiS }>ÿ]ö³W)Š”¦|ßv¨Ü¤‰j~ë.UŠ”«÷ )t±rQဵò°¯¬%Û·y³ßUzø!R^ :¹÷ýtÙ¾¼›ŸêKÛMä)XˆT=ö,ÑY /È©[CæÑYé•[8¼¨roŒ#À0Œ€ûp¥AW~ˆ>°÷A½¶LfˆÁ§kèó¦ÓcŸWv²~©T-ÅhfzUÉTC‹z´[®»a•Þ÷ÒÅKrµ¸M_Ô¬³pr@Ï[C°ÎÑÃ^ÜÖM»lÏk¡ä|DO$¥N#ê ƒkç«'´¨†Ä‹ÐCH[5!2?ñ Yg³´À{ÅKS/Ù™“‘z¨¨mï|Ð~4xl_¼Ta|1I¾ÁmN¨AØ&þ8ÓÔ'g˜0l‡N§Å0Œ#À0aBÀ•]ÙC×ý‚Qçò%ú ¨–õðµ|k/•” %äª$]² }€Þ¼NéwñÜ5¤¬üþÓ,YÛô®­ûÈÚ %çÞlWˆ pÔ¸5s²×KìßYÔúQK³7*é0޼?GOý¹0HZEÁϪó2µ‚à?ᕨ²rñ²ô[îéìÑ[_ãõˆ4 çΠ^¯PQ504mkªh´×½6lYÓ” ȩȨR3•d'Ê&Ytˆ zê"ý>c 4'¶Ý®MF2Å™¯°×€ï n&uzú¸ùä†j(Ì•7Ý[¼<å„7ºJÊM?+ü`_ÑÀ|ö¤wã,ضêÏG7PRj×&M²+A}ÖΛKÊ£©¨Ü¦x}Éï ±†ÁºdəǞ‡¾Ü&&i ®'‹Xˬ)…DrJŠ\ÃvºRƒ†€ô"åêÖõpð6êÚÕv[¹b^…wÇëßIr;7¥M] /Z\“jìµ 46öUzâ í­·„äµéúMÀRâ:²¸zõêy€’R–§Ž™=t•&DmxíµD7”… ÓÈ÷÷ª”°îðž=,›¬œ1òÌNÁÔ1_j­{QZ †%Ë/o½)«QMŸ?s†Œ‡^’µ”ù½yûm¤NîÂÖ¿HÅ+Æï&c¸95iåXí3l¸QÔk©ÊUà¹_'À“?Œ…Ǿüú¼0,¨öFåòuêIÏk,ûd"aT.œ§^òªÑVªyú[hò£åbOú§7ß0åÝôô³¦<5#kR.SF͆F¼œ¦ê=¦û¬±Ñ W ôÔEú”p}Ÿ9®Î0Œ#À01BÀ•]ÃcFåÏ­Y¯"±Ljq¢ËŠÎSN.Ç´|ô_-³Òk7ªL欧Fg,k5 <ßóg.÷WË £.z4…Ê©+0æŽGF¨Ø°‘OON¡Õ{6R/q•WÓ×0-Ѹk75Ë£—µaÐ-^z÷nYNïô öÇÏ9õË1Ú±ífÝGObTjØ4½õùˆ¾eõŒ'…VTÞÏ,Âèõ¿ï’QO£ÞÕyý]' c¬È÷*N¿WUäÔZÔ{:ÚS./<|e L®ë–ôÅsçÉTýt›\G)}VϦ§·ðúMt ²t}ð!m5¹Ž‘~lÌWFÒ÷ÚºWo_:ØDÑTú9§z³Û×·€L¿ð /ÀYæ0¶×dF€`\Œ€+ ºÆƒÉ꥛ ô5ëSGɲEI¹¬4hVMVµé¦ÂûQ4ÈøÕ³W=¢mÇ};tž~†‘ ðøô©ÔSˆ4ˆeÛfjÐEêC’/èY“Eüþ»”˜Ý9Ó—ôÊ–§ô)Ù“²‘ò…ÿ¬òè{v ù¨ÔmÚÑÔçÞ/¼w? ù ¼òñæ~íf¨ñØ k¹ôz†Q9uŸþù –Yô2ß cGß@©‚çP–íÊQòÍŠS¥X(š’"7÷¥ítñ(¼,[–Ñ÷ÀâÉ“äbSzWF¢Õ £¡Êœ?™rUçÌ›×¼iÙ4úišT„2ΩºÙ²AVñ—)sfËóòÒjXVpHñ»É˜ÎÅà“#LôFÿ¡¼–¯[4Û¶ÊûùK2]®œ:v”¬ {Nʯ-&—.#«pâÐ!¢Oüp¤‰Ó¹Ó€{I ¢»êŽ;áæ¡ÏÃàヌ÷/…\ùóûkâ·¬DêT±cÍj¿õ¹0¼ ýrêâszê2§nxñåÞF€`'"àjƒî6%H–ê±V²,峕/@ÕÚåeU›¾ºKSS~(¼­*GãÌI I¿÷‚œ¹’HÞ¾Ýô‡;)ŒeÇæ½d%å+§ó7WŒéß|2‘Ô-f\ŠTbŇ@Á"ù|iLìÙe6P’ qªüõ¥Mè~ÛÕd¥7Þy Ñ¿ü`¼GÿåÛ¿H~õºÔë‘FIQO$$Â&P$ 5ŒºØ7–ÑmÂY+ɶ­¢Æ‹J’‡nŠbÐU ›Ó «²J}?wÁ‚¦i¤T÷³5H¥«Pƒ®jLþìñARm€F»ýØýDF9}âü'ñ £7¢Î€ÕöÊùùǨƒ&luÏŸ¥©F`ªr €<`^Íu‘Ë“V9t•‰UmÞLɉ¾ZA¤d9¸}»¬ÆEúÀŽdÉeË]VòN ÒuCrƒÆ?§ÆxG®ï»ßÄ»K*h”ÞO= íÄû¯¢àAΑ'¦†ý¬é÷¢úYc¿'®**§®“ƒL†ºFnÇ0Œ#À0é¸Ò ‹Þ¬x_•Üys’,GòU*i˜¦,L`˜ÝÌF¤R©Ô3xùÂõ¾î—Î_ëK 4d6mG:þüÍT£8n_Ïž9GÖ†žÊ†\Û£¥‘Ô¾V¨B=W´•8Ó‡@jÊ'½uƒ™ ÄW9j€Ãúͪ“ÕŸ>å¥?Y8›zŠu»©-©+EöìÇ9lRxc5/·+uƒ¡_ ºjP±R•ÓéwRjÖ"¨Áž¶¬XNÊuܸ×?ò(©ƒJáÝHJVJŸÖÝ·u‹¯É)ApQx'Ër÷›o#ëV¹eåúþÒ'&ÅÙ’rÝP®ìs»‘ô¼n\H7=Ia•óg(ÝQ–lÙ=£u¹ÿ~ËQ󤾳¬àù^ÕM©Ežºì¨æ]!~ÛÅ»¨]+ï{‡,bD–3b“eÜÛâ}* z‘_70ôRW!'‹*Ô2vÄŸA>dp¢ØPæÔÅÀŸÁlRFqš<#À0Œ#À„WþzÆ“÷_ú–,¿r¢ûSò´ç…€bõ!¨hÉÀÇ+kÔ¥ÇΖHFܱ_N!S3ŽMw¿õ*’¿j±>˜©Ç R[ø \W±šµWKÃòÒR+Qƒî% `È»¨¡4,‡T —âáÔÍt…oUÈ£‹2ê‘Sî¿vû抉qŠ'1)dÅ/hÔÍ;·‡váÍ7ßô[W-œ=v,ÉÊ""×Ëß¹òÑÍA•3÷ðî=¤½ÎÈS½y R'”ªL©ˆömÙêkòýða¾4&JTªä¡ÿ‘çŽùªaóìÈœ_~"Õ*7¡÷«QX¤túé #oëÊ•F2j¯f]/KõÖŒnáÐ…ô5-žµÚ:c“J½÷üÔ.Äá™Át )ïeÿíñzGºY‚¡ZÛ…3] XqÒÝñƒ‰ÎJô@£nrr²ˆ×q S—…`F€`â×t×­ÜJ®Èm÷v!º¡¨l˜¯³6ꪯø`ñÄ+}áé7þCGÜÃG>…4^»j»ŠU©÷èÎ-^j4 íÚ¶T7Žx«Ü»Xé¿Ëÿ‘ºñ¤\8‘,G:UèËoßÍúh•š©¾zœŒ@Šâ¡« Ö¸wטó÷²€zMÍ<ÚøžÏ“r¾=ôKÒ®Õ5õ‰K¥iêÙìÈÉXNÇÕcÏš5 Nž< Ùg꣚½au‹3ŒO_?û4)¾ã•W‰H9¼‡tuǰ‘ïÇK\§Š‡â‚¯2‹˜k )X¼©ræÄqŸ>oÜ/¾4&z<æ¥_¨{5¥"™:æ RÏ®2ýÛïHÕkîîOtYi§x鎼o€\•´ê‰lP.ààÅÊéišò¸%(š‹®?Žà¨/ R¹ISI˜ÿ«—ö†dºX9¨x­ª^­êÒºYþüôóô“r–‡ã¹ÛCI^,”ÅŠ‘aÁõ6jYS»ìVhóÌÎ=[ɘ¿¦V, ͯª #x>ÿíEO°µ˜OÊ…˜={6lÞ¼Ù3ó'žx"¨œ;}.^¸@ÚÔ½ŠDI¡F9~rZ)£?0jýFxÉRx}ælxñ?A68jºõ›µcíZ“çyí¶íVSIèq-Z+V¬ð`ðä“O’Ó" /ž`F€ˆC\iÐݼn'¹…’óYþ`©Ý¨2©‹Æ_ÕpJ*„AQgÒx8ÿùk é}Àà‰^A¡X¶`-)'EæÆuU­c6¼—(£7ÒÇÑZKVAaòûO³à‰þoè·è‘wÝZJ÷LÄòõ*èªA·Þþ)æœÍü<»{`@úL³s7mÚäi8dÈ pDÌ—LBEÍì9)Ÿ;©¦¨4rÝɹ<£i{D¿»I7ͺw÷éÅËÑÏcsõ5RÁ¶Aï>ÙãÛïÞ]ê¡“ ço^‰·±,ÎXn®|éÜïFž#_ý|<4½î:ÇL¹\í:d.hØ'9&¼&e)\²”¬†”Æ fN¬Ù“àÓ ›</Mù n~n¨¦•Ps@ÏÜ•it5ƒ òPè$¼XF€`F Áp¥A÷ˆb˜yâeú`*_êµReræN²åeK…A9qì”)[ÆUHÏÍÚÖ&úüˉOÊ– ;Ér*+Æl£°L9ÊɆù…Šä3ŠùÕ&v<Ëmvåˆjø~º³óÓðãèI°OxÀÏ™¶^yâS˹©ŸXÑÊh†žóVÒ¥w«"Îw!†17{öìðÜsÏyè‚YtÇ<9„4¹úŽ;‰n(êÑüûwE-Vžn^,ÿ{ÝK eÝõÒ-ø&£l¨t¾z‰]ë©G¼º~]³š­Ûì‘÷ßGôH+*Wp¾"…}CzŒô &X˜Y Zåkà°Ä~(Êשë˜ÙªÇöO9☹Eb"v6ârš+¹çÝ÷HC«zœÿ 1=sñžÑTÇ_ÆÊ3H±]áZï÷?"ýDR9qè0é^æÐÅ‚|EŠøx~ÑèX¶f-HÊ•‹´qªbµÕû©g7å\è‰miœ Ž›h”&¤FÃasäÉ µÚ†ÿ³"JKâa„€Ì™ûØcž8aaF€`ÄA€ò8|ÝkÓ¼ìŒäÀôG·Ëå,bMÑêkco‹«êÁúU[}S_˜¾©nöÙÁЇë>}ê äÍŸÛul^¿ƒÌ׿`ؾ™r‚¾2ä3¸¹'h¬PtàçF’ÀŸ(”ÊV(g…gýÙ3çÅß9h{­ÙÈë¯.sè™»YD‰GÎÜPiäU]8{d:“7ô”‹IºpéÒD78t·.£\éUš4%õ¡äV drŸVÞ ;u†Ï䫺--jº/#@é(d)YÑÞiš óÆó5]?¾/(qéâE@Ãó‘}û Î•WBñrå5!å&Ê…ÂEH9*­ZcÊssFÞÂ…7ý¶Âzüˆw|óšþÝwP½EKŸžh‰R•+›–ÜgØ0Sg$2ÍÂã?4|b¡Å«e"ŒÀv±I/x¬á¤p É– ©rêÕ‹ð Ü}Ü!pI8¼a f¤sÏ*žûH<·Ú–âùØ1  œ<v?°k€ø- ¹…} %…9úíbãz®2èV©UÎWõÚ gÖ$hݱaŒá³>§ˆ:o%½ûv´*‚Ê5RHÙŽ­{‰ Çd1 ôr§Ã@RR6bÐ=uò¬+ º³§ŠL’”¯RžÛ¾ú=ü;k¥Tðí'᧯¦’¼¢%í0†¾s/iÇŠ»=sÃaÌE4JV®û6m„ËâXÉ*Uü”\¦ )?”íâJ«“#OR/Jî|ÖŒ·½ 7 eÅ’å¿ÿþ ±%x¸|ñ;Aîcõœ Z³æ~ûtHîÑ›FgŽ5Ìùœã(\eÐ5K­T^yEü€ ý™Ñè*b¯™³d+YÅ åò,[¾¸ß1“‹$厗“Ê.QæÍ¤i5ëWtÉÌÝ=MÕ+õôIñ#È…òÏ_KȬ{Üæõà0¸7Ô¬¿>{çgR~NÙ@ðx/RÎJü#`xæâJÃÁ1hŸ7²dÉ„gz‡ú“ä2ôÇþ¡Ýb\ˆÚ.)‘É…¹^Õ±püâå­½X³‹¹ ¡Ô]ÖƒOÛ•3gM<¯É¸Ë„´ës;ü5æ _‹qï¼í× ‹õë×õÕ7_=ótPÝ“‡)åB>üÇ’Z»¶#W—Ý%4Á‚§rW#mB0‚ ;‰ ‹øÇ’¸Æ\D=sc*âÄ '»øŽÐRSƒŸÎ‘#è݈žiyÅ{Â"`ÀŽ7nôz¦¡Wzµ[7vÄjÑÓ¶úóPÄ{Fþýõüóh“~»D|=Vcé dŸ àZV­ÒOý¯¿tÔ·µ›ku¯.[Æ]»ưž+ º¸#íñûÁÉ’3w“A·GŸö§Œ\§È3kÈîíû¡Dñ…fCú_?.\¸9„‡0{áýû…Á!³–Ñ«2gÚR2XÓ6Î|¨$“Œ%)õ¸;åBƒ.Òä÷^™)KJ–- />òñða<±¡@IDAT“/_éÔb²šééÓ§ÇÝ:Û´ickM†g.r âÃ/`Ã%èIŠÉ:Ï e"J#{öÀq¤ ‘†\!¾Û²åÈa¨a}Í,Ö¯t¯äQ¿cÔhÕýù‡¯ÎÂ?þ´eÐUùvë\y•¯;‰¶·ÜJ º[–Òï µÏ?nZ›Qgô'àΗ_1T¿¯†¡Þ¨¤ç2òãåõÆÁ‹É%‚ï·_SG…áJ’œyÃï/u·ÉDþ>“¹ƒ„ÇÒÅDðX¹Õi;Þ`ƤñyNWìX€=ŒZþ_'MÐw”Hž#Ëþ[si¸¨V `Í}o·Ýðå—ú25Œ+'š].Í‹ÔwAœn°Sðܯ…ïÉ6ŠXø ºQ3”¡Ð“øÈ\ñ…"‰oÔö×5‡?žík5ú½qðÔëý}ºUbÏŽc.–ŸÜŸgN€/ÞwìnÕ$&ù[Öï$ãÖmR•è¬Dä…ݲÁëˆ#Ú/¼*\&‹ç®&3ÎWÀüå—Z±$|òósðÀÍ/yøoåÙíD"ʶmÛqÙ`x憋37# ªÿ‰ã×§ÐûD’ì2æâŠ…}[·J£\y[¢«Jõ–-‰Awý‚ùj­¾} }ŸVnÜX[Ï*³˜âå…†Öâ!¯ˆÂCŒíÑ(>_âÜUûœóÓXÛ]µm¼ëÅ‚äŽ&Hwq\ò8Ù»e3”¨àîÓ<+Ïõllé–JÔï3Ø‹<‡ ’¡€ž!/7òw’èÖL§¿‘›‘ôäÉzc.VêÝ[|¸Óޤ­¬èŽ(cy¤ òØnJ£á3ŒÚdéçu”•1+~õ•øR`ÄÒL« ·¶]ñ0Óîýcw,§Õ3 ÜèÏXtñôC ‰Ôçt!ïˆrWtݹÚw`q|¬Zº V-ÙMÛÚóDíØ£%1èn^G  VwÎG¯ÿ`*úGð:Í «NÒiÄêüâE/Q6ÏKß=ß·;ƒÇ3bŒJ·Ð¬]í,²dÍþø, èù¢' VBÏwôàM$yî¹çân¹/½ô’xÎ ü gsñû"\œ¹*˜ý.2t#øÃ©aç.0ñƒ÷}žëYÅ¿$«i ­Ø Y²ÝÀh{1 ƒ$e«U—4{ÉfÝ{ÀœŸòUÖý:±`‘O7“Gn$=¯Y÷ïE|¸L4ÿÒpèýäSFV¾"«!j8#ß)¯…K—!ÝÝ6¸Ö ‹Ü¹5€ÓÊNN6èu»¡3Þäý÷ß´…‡.kÁ‚°zõjωGpæ'%u3ÿA<‹Ø1èŠïrKÑy]ZU>pÀª„óE€P(¦9•†'ƒü]Ã`‘S¾óµÍß}àwY Æ@Y¿¾v(Of49t­gþ4î)ß%ЪÀŒá‹{Œ.V§ØC7º×Á…£±A7‚-g®$hÓ¡¡ç/˜a°,ø0péÒe±/Ž&YÈÙ3çaûæ=ÚÒó‚Ç7›àóuªdò³.§ÎÙó*QŠÒvìÝø!Âië\)6FdiÔ²¦¬šÒ#x~ÿi4kS æ£W&€â4Ã0æâòœlص~=¹t^¤FèÊu üCÚ…“‚³0köÀÞêÅγøÃ†ÈU¬ž_x+7>1„tц^ÍHK!˯#Ä¢$Ý} ’rå„/¥@AS…ÑéÜ~d_Zf†“y Ìp‘ì œà÷ݼd±oˆµóçAƒŽ×út·$&|8ƽõ¦vºÞ}_›Ï™Œ€ŒÀÂ… =Æ\ÌCšÇH¿~£F™§3q"À‹/šóÕÞ•X=>«8Á‡Çêu¼8h n¤£Ð ÒÕ‹ƒn‹€~a¤gP½z‘þ!‚äa°©Æ\l?wn°½p}'"`uJÏŽAWœÜÈàØHU¢J¼¾Ôuº\§OF.YŒ[(B…=V‘W–}»ü{SŽzëG¹:IÛõð%"¤?*x·$ACsF½Ü¤î8镇y¯ =t/œ§§R*”ô³bt¸:ÝЊ¹~QНBØ‹4 Ï<󌈋¹Í¬`?»ÔúËÿžFÀ¯Õ¶Ñ#¡`p´|EŠ@μ68¹495Å„XWn7/·8ž™Mò°AOÛm«W‘æwí4qç¶¿«/´èÙ 3X–©c¾UN;ºW·'3\9c&Ñ® ]Äã­[ZsoéepºQÝé'ÂüfaUZ ä€ÏaõÀ 0^}U?ê’%ú|9=Dy&ÿñ‡ÜBŸÆ l:¹óN].çÅÅb“ƒè…SR =„'LÀ{FÄ'ošpŽÀ}1ÑA@úÍK´C¹@„ Xfƒn`F¿‰+ ºêCqôa‹üˆÉÅ×$Kþ]+iæäâ¹b'ÛB&ŽuÎ1ŒÃ(gdÞüfT‹epv(QºéaÿîÃDwº²d>}8ÙëÜéXÆëüdc.Ò,Dzó/Øï¢¢©åôóî×ÚQ0è’ ØPÊT¯AjÍÆ`5~äÁY+K™´½\(]¹IRåëçž%ú˜§(BÕæÍ=åxÝÛõéCêþðòK>º RÀŠ#¨Ü¨™×Á"PŽ 7þøäx¤ic8¼{·iÆ%*U‚OÖm€–=o4•q# #€ž¹+…+òDú$Gsq¢þ(C ~OyAr=kɇªá=¯«u­û¼ùuˈ˼*U¿¬râ·zecp¼žt ÿĹGF@BÀŠ7–ÝÔlÒì9C\iÐôCz ¯‡o讽ÛúÒ˜ø}ì,¢Ëʧoÿ$«¦ôÊÅô˜º©B3ì¥FÄ|lÐúY³Q†|ðt“|7Jã“äú[®”4N&:†1q@O&'Jé2e«WwÜ´¯ºýv2§?F}LtU™ <„_}{èžR÷HÇÚº|9n휈~ýÀG|z¯'žô¥ÄŸÐþŒ|~eÂÀÙÓ§a@õªðÓëzÏÅG¿ü ^˜øGÄ7šÂ±î#¶ 1×ðÌu,mñ-UJÔ×_ëóÜûï7RÖ¯m<»Œ£oï$Ofý 7y}1p #ÀP¬ÿyÚoÏY×_ïý“Ç—;ôÑô:X·{w›ùr7Óè«\s>?”ÜÙ³°¿† OÄXÙüµì¹„Û·÷â)cœœ ЮÅÈÀ_Å ° ï5œŸ<¦[´Àyø“¾}Ó碫‡ß;òü0ýÆæš'íáu×àé'üÃë. :ú`°BAËEæ‰q^]®IÓÿþë½çåµa¼Ävÿ~Z7†1x^3ñ}‰3rŸ˜Æ$<ù5|x žÒËñÄö׸±×‹{ïÞô²)SJ”Hª_~™^®K!No n|œ‡:?\7^çÏ?×µôŸKƒ®U\6èú¿f)¥.{™T iû¨?§–ã:åVäŸU) þµBp^ö-!S¦+ ~³jP®r)¹s§OZ{Š1–ÕË6“Ô¬_‰è¬0:NŸ: 2.¾7 %ç×Uå¼CÀ0æ"g.Ò,DCäÏå`Æ+Y©r0ÕQùve9{ú$àF¢ÖÈ ~ä^R Z9ýË•;¶H7êÜ…G›üù§PC<(ï!X$ip­xˆSä†AäQŸør1 Üd ­ýwùòŒÄ|Б${xIhÄ&Y¡^}X;o®oð•3¦C‹‚0ÖøZF.±kÃzx¡[WÓ}#f<Î÷üj¶i¹ pÏq…s‘f?_ñ¤ ~¯9ZШ2r¤yŠÈij%hØ´+hD“NU'ÆériÞôé^ÃMý ŽC† Tg$“Û÷ê%khl|è!Á© hßD, ªOÁ6ï¾ëSM ئMÀMÌ”4â‚Æ¿¿ÿ64ý+®_•»ïøî;57c:^ åÔŽ§Cä¹9 U«Œõ­Ñp®Ê?âôŽ?®^¼+Z×ÁÍÃ@Ú5ôã=†~« €8׺upsdüxu%ÈmÇ88nm‹úcѼѣÍcà˜ÝºÌ›д)­ohh”Å“oƒ 6†7'_Ñx©ãÍF£'Kñ¾G¬°­?A|40òÞÆ÷r$cà[üÃÀŒxïZ Ìûê+ZŠA?øÀû¾V¯#^¯÷ßPh»|¼ý6À#øTS×ïküëß_±þÝkØ6UÔdX}Þ[åkº0eÙý-k\Sµ4P³8W^%탤ã¡~‚h”•eќղêIüú$¯u‡†ž‚ÍÚŠ/yIæÏX.i±KªÁÝ*×H‰Ýdxd× °yý2×ìIÙĦ¨Ø©eIh c.>‹–17#€çµúÁ$:MÊ'#]G¬mVñCÿ|"~¬ž<|اʉã"Èlì6µ•+ÛLw¹ÿRsµx@Fãëi%ÒsŸÄtEð3¢Cÿÿ‘Ü_Þ|ƒÌÑ(<¥<ü…4Îè_3Ž€Ê¡¼jö¬Œw¦ð>ÿhàƒðܵµÆ\ôÿhõZ6æ† ïDèÆàÌÍ,<Ð0 §ã¹xQ¬¸OˆUVÆÒ`Va ++™8Q_R¶¬>ßÈEÃRÛ¶Öó3ꩯ葇Á¤±³gÐãS5æªý:ýszÆëÖ­FÊû¬G¤ÑZÐ|„]Ðë×®ô"… 7ª•ïü §‚÷x Áb4¨Ysåö¿þ л·œãMïÚeÎ 5ª`7­Œ¹r}¬‹Á³Œ †|VgÌ•Û`-&OVsÓu4ã}Ș›Þ"=Õ¯ªƒ\³jÌ5úÀ UpíèîϘ«¶AœÐ{<ǯÑÎÊCWœV Y¬>£Õý<Ÿ¨UYw®4è&Š!§VÃÊäŽQƒB-ûw)G¥Ï½b‡OHË«ë{^;·†plÉhÁ×D¹–„0¨®3g¦oùóçÄ1ÈèwéÎsç^­]0kžb$0Œ¹8Æà@;ÿažˆl´ ¦ë\~~X%—-LWQ­›=g.2Þ^ÙH*9"cù¹ JCK)]ÚÔðµ›o"y¹Ä89-8Æ®ø0©{A–Oÿ›ä¡b6èæ5ÕáŒè"Z³pÏ&ñ î9°c‡‡+w¡Æ „¿i^1žüñ'Ì”§àdÎÜhŸe#…êÆ×ŸÎ(‚…:#-zV­êkêKø3ðüßÿùªùø+o@ú Ò>ðÚkj®}kxD>A˜Î¦ë^v¸ƒum<ë7êè^-¾CuUƒÊÛ¹S_7†'MÒ—E2·|ùðôžÇÆ&<Þ£v¯=Î ë£ÁY+#Ÿ\'#i|?Øá´–Ç@*¼¯ƒ4lZmj q5#r×]çÏÛïaÃÿkÖ9 !ÅI¨›‹=…‰ÕµFÃy¨b÷}ÍÝPvD;jÝqÄ”O"QöåOT>Y¤(±¢à@,Œ÷&nb=L|ä¦ú’– +ƒ®ËN”?Ž$¤&t nS\iÐM¯ÎäââKC’KWîîíûáÔ‰3R)À€Áô¨FÃ5Hù×M z´œ³,*°\ÆéÈ P@1èÚ§9V™¡CîõÈ¡ã¤í‚':Q>ÈÂY!$‰>hÌE£n´%dƒn^kƒn Ó<- ®óÌ5.`ñ ~ËoÝj´J-V Dðë#Ü/¿œ^×H}ö™‘¢¯VØ}û¼†8ZÛ«!…îûótÆgl…îX+©VÍÛ©ÐøÛo)·ª˜Ó×u‰¾¤61p 6ÛQ™ÈY+¥åÉáqtùs]NËõ‘ÖÑ€è<Æq,]0¾ûèï[SBcò(ßxczu¤’BO`' R ¬_bW)}VÈëkÅlÔÂû¶gOC¸új€ï©ÂW8gŽ/IhH>.œwð;V÷~—+7i"‚N$É9Þ4Î=ÁÍ ô¸GÇ ®öï¿^C²ÑÒ¬('¸ƒö :*eʉI’ Gï…ó!ìØI}d$¹eƒøP”¤lù ~Jí8:ª‡îÁÔ+-ôž#×òïI Hç [:×ðE&ÊJĈ¥1ª‡®?@R¾PucQ¦n¤b´hÊÈå+!›­wÀû@9«£œÊäøèc%G!þ}¢/Ïdе2 øZp"Æ D’¬šó¤E6‰\Ë÷ÖªÛW¯ÒôÚÌÙÐïMáÃÂdÇÑ;Ðíbõ9Œ^v²è 2cåέo[£€•G²¾…3s•梇ªÎ¨)x­[Ó;œÄF å· ‘ô+z¹×«ç¿™ÕgÄ=÷X·C#¾îÚþü³u,±2貇®ܸ\iÐU,ãù:6RhvlÙ Oßû.YrRŽlP¬¤8"¢HfáÝ«¿wnÛ«ÔŠžº{Ý,B ÎÑ›I⎤t>áx0¶mÜMæxUgºÉA Y‰{†ˆïX{2E «/wÚ…,Y¹ ™Ò¦%ôaV5zåÑý˜%=§dGѨûø·ßÀ÷GÂÇk× ª…¶;A߆ ¿â×Ï=ëkoæÐÍë+ãDìhÖnVOÿö›¨L樈ÿn¿»AGópí€{áÓ › `0ž‰Q™5â6îÞšÙC bå¤ÅêŽãüTÞI÷,òç‚ëUAMô’“E,­{w¹Mÿò Õ ÍŽÇ%\Ò‰•¡GW×ʨ§««æ¡Aì?ú¨uP)µuô´¢u:4}ÆñFLï%ã)ßk0FGœr¾ú3>ZØ;s&ãó¦†Fô¸ÕIÓ¦zc.ÖõsM×UÐyá¸G¶ÄЇZžú†ev ¬:ãìnú<+ãI[}dd½vTÞ`Óä8ÃɸҠ«) pFçVJ1z¸] íÐÖG@®¹^|`Iòñë?JZt“{ƒ.S.D­@Aj¤8vD•q°œ:iþQ£ øçà%ðÔÂŒ€>ÿ3bÐÍ”‘faÆ2˜îRjQÏø5 Ù:<¦&Ij„<Ž+ ¯äÌ %ÞíJð4ôÊݲ|™gÖg”ã®9‚}p“ÖÎÉð!ÐðÚN¤3䲆¼Ü«§i˜üE‹ÂÛó@÷G„a……q㠂ƨœ¥x¼YäÇ4DÙt3²á¯¿|IO=U¹ÿ~5']×€±ÔÎç<gÒÉüùº\s2u^ŒæšïR‡R¹9Ñc¸|yï±pRèåË/­'j×Ñx)ÑõmÅݬ»ŸûpŠUìÜHtÙ¸޽ø"ÀwàgañÏŠ %Ìn¾Ù^íuëÌõÐc½®ýýíÜin(ÇÊnÇ€Œ}ë>ÏìR.Ø#и<&¸Ò 7?€l\ò@Æ«¤Ù¡h‰B–=õº‹z0íÛÝc²òÄŽ¥ÆÃ⥋ÈÅœŽ ‹–’;zBÒœ—üãçÙdRUj¦F dÄ [¡žôð‹É‡8fƒ×’–jp¨õÿÒ‡ÛŠºãФ‡è+IâHkåÆÔÃÿ-äïrN1äÔý0Žþ”~ÄÜ óùÓ§£‚É!åa¬çCàÙs {±DÄeXñl.]ê!ª¡B}ýAƒÒó­¸kņKP‚Æ=+ŠÜ‘Bûâ+:wΗ [â¬#ƒlÞ €ÞИ„ܬnôVµ (gÐb¨÷D8ׇ†AUC…½Áñºâ<÷ìQkšЙk—cE³\/ñS¯AÕªÞ{ÝÈë‹üH±2q"rð⟆Ï>b D’ÓÙî¤í:‚è~ŸØ5èêhAìÎëÅWtà¡Í+W¤xAËáúôsÄ(­•Š×Á}G,û‹TÁÐüU‘ûÕ" R.w¸‡îÒùkÉ:Zµo@tVX ƒn%M¤í¢©åb±Œ Æ¬Ö¬©¯¼ÖÌGÊkµnCt§(÷*QÐÏœ<»…'ÈYÅ ›#w§L9¡ç‘£FKrAwö<Üß{8¨<¨¥S‹…{(îÏ SÊ…ÇNÙh»*»¶í#ƒ×l vlY#ƒnA·|   1^/EO¦³Â0*KI ²á@É•/?WŽ9¾Ðµ³Ù ›— ºN¸|xß©ÁðNŽì¦ô’)“ÉÒ°L–F Vüô“~äï¾óæ#e€*:ƒîÛo«µ¼úŽÞ× Ìå?lÎ ”c—§Ôª^$=ÚÆXµ  zàgû”ôÙö–[ˆÎ #Àh¹påâ~ðj†AV.Óy¢Z*ÂÀfVß½x4;9>p+äòÔIåʺÜðåU«°r¥— gOÿôMš„oÜhô„ž¸V¤?é÷sX§óá‡}\—H€÷‰ò=\'\ÛÄÖjC?Ð[iä¿"E,»‹Jý•çc'-b6@'@;×™ÂFÓ¨‘w3O|ÿ=€]Ê…_¸þz€š5êÔ0 Kí0¦LäùF£zݺ5jx¹Š·o7Uã g"àJƒn¢yèâz¯È$¸’é~ËUJŽ^ms 5èb`µHËÞáá>¯™†AÎßQã†B®Ü☠#ྠ^Re+”ðS›‹w YóìhÙWL¾a‡Ždžs~ùÙ£¯P¢A)S†Ôs¢R®ŽøÑ*É%ãXpZ^Î<ô4ƒT•“QF FËVdÄŸHôp+'"]6½N< ±0Œ@`tÇ{gÍÀ “:ã©.Hòñ¢G¤*ß|°aƒšëÕ­8|Ú u‹‘í™—O±HLŸ®/y}õ5—‹Fq ÄôÐCú>£Ä-®<ÄÜÔ7ŒäÚ½÷Ò1ш…\¹))45ô"FïìÕ«½T本(8p=Õ³gÇ(¡÷¡ópµâñ}”ô–-…³\ÄÍ 6Ü«WzY >_ü,~—£7ù’%H1bµq¢ëë‘G/özüÏž Pº´®ç9Í7¦g©L)Ñz:ª‚FùbŶl1{\¢1òéºA¬<×Ý0wå;×7åk®ñ%cšÐ}¦á„GLçȃ'<®´¬%¢A÷¥Âè Ãà㟞ƒgß=ïi»ÚäfŸü«ˆùøàÇÑÔ³‡iÙ¾><õzûFèÌ» »»= Üô<~÷[0ô¡‚kœÁÚh0;^x#HR¢´ðÚ`a€€ºYÌ”tûp\bÐ-[]Ã’d/FÛ²Sy8¬Ö¼¹TË™É<Âë¢,+³6èZƒìòŠ7õi<º!™úÅhÒsózF€ðƒÀOè ïºËœ?dˆ9Ïȱò~}õU£Fúë˜1éi«Ô 7èKÐ@Šg+™4 `÷ns)z ¢!0ÜbÇHÔ½»~Tô6UÅÊÃÎÊ+Rmi}éÒHÞ?GWƒ¡Åƒ\uèÁíFѽ¿pVž¹øÁQ£&V²‘Š6èUküÝz+^_;‚ŸWÈ·k´Í•Ë»Ya§-žRCÚ£-¾Ža§%×q®4è&²‡g¶ìY!µbIÈž”-¨Û§y[ºƒ½bц ÚªŒŸ¯ƒîzæMnþŠÜq_7¸ëÁë•\Vc…€Ê£‹ëT9tà¨øþ¸ 'Oœ†{öM{i4¢%ö!›Ÿx¿'åîžÖ\yœÄC #]ü±ôé†Mä/‹Ý€1†:ŸÂ[v)íâðî]dfÅÊ•'ºS•þo[ÿXÍŽÑÃY‹À]ò0Ìvõ?☡$Mºu“4N2Œ€_Ú¶Õë"×ûãŽÇ£¿:Ù¹Óœkǰ‚¼’VR° €z„j¦MèÐAßêÉ'õùÉÅãýè…›š pø°uOƒ[—©%È¥©ä©Ý¶ÍËIŒTkÖøS×G8òj g#7v8úVû¨REÍX¸ÐkÀBOæ”ôÆDÎÑ»ïO°9<ö<¢ú¾1O(97Þ襈À{iƒtÉè>bM• µ¾qtž»htÄúÇûªEªé믽ïÜT 0|8@ûöÞÏä´Å÷:S~'k;üê+m¶gsA_’ž‹\Ï‘eËô­íœ€Ð·äÜ("àJƒn"{è†zo ç|ÖlâƒE’ÅóÄŽlä‰þouUyç«' d™d5›õ#P¢ 5èîÜj6ènݸÛ4Ëp{u›2þøi–¤‰MÐ{ÄŽ9 #à2Ä¡ë5„:ZíÚ‘¦¿¾;‚ènS^ø=2†A·áàôùÖ»FxžH2#ÞGó'ü& žÓì¦H+VG@G¯ B‚†™@bÅ])·Ã(ðvåÐ×JÐhвöï§^njýŒNÔþ =+eAj™—_özØ¡±¹gO³'±QyV‚ÆZ;¢ó¢¶Ó.£uJ–°;ÇŒŒ…†ÎŒ|oìÚå5ì¢ws,%Þá±\z¯ZÉsÏ¥S .°¢UÛpæ¿ñnäè=cq3¹¶ï¹7ë?GÔ÷´®½œgE'uà€\KŸV6£õ•BȦ7tÓã&^\iÐeÝÐnßn;F’|ý!}x‘Šl'ÏŸ»‡#õ ÉŸýú"ä+›ä³â T#ûÅ ‹ gÏœ3MvýÊm¦¼Hdà÷Ò=È‚õXF ötì/~°J2ùsáÉ"I)ݱF©ÜiÉüâ¸gþHp!:m¡.ŸÏ5}ÅXI6.Ôx©Iå¡$¿>Œ4ëõ”.R“F€ð!л·/i™@I ±âŠ•Û½ý¶¬NãQôŒ‘ò=5#!v¨#tã¾ö@:ºoÒØ…RÉN“°Õ™:5l]ùíéüõý6N+ìØÑKi`§n$ê`Œ‚hÑTDbþjŸèq–`W-²Ô›4±O%`ÙIHƒR&ÂÏ¢Vœ×v>üyã¹TRO-°8WtÙC7´ûªa‹¤áñ£'‰в`öJÒ,¹xAxsô qâC|@³8¥©‡®jÐýw½¦Æ"¢å•¸~ÕVcHÏkÎ\IDg…ˆ5Ñz/ÄzºñKZñ¥U¾öžºfŽÎúÛDÏü®<è•Y½U+GÏ7'—;~Ó²OYy³˜jÚË8¥ðVÖ¹ò*{ ¹#À¤#€'4Òøôd $VÑŒvèqŠqGï¿ï=šnôè èm€Þ¤ÁJ ,ŒþðXÿ§ŸÆÎ¨Ç¹Ñk4Nh š=ÛheýšÏO¤¨Èˆ``X]Ð2£O4à#:ÌñúåŽFÊ缇jãÍ7½œ¹Äï ‚…˜%ùyöxì1:¼u(>^C †?Ù»×KËᯎnNVýêêúëËBicÕ'¾ñ^¶Óç‹/Ìkÿ}bÕ§VsÄk…|¸H­`Õ§U[¼—ì­­¸Ã­¨äñ¬Œ¾vÐV-|P…ÓEÀâ“Ä¡³M›{è†v} Ê+è’2ù¼Ñ òØá¯`žÐ:­TƒnãÖµBî‹FBEèƒññ#'ÉÀc>OtYAz†R)Å䬰§çÏ\Nú¬Y¿"ÑYab@"sèæÀ"?âF# ßœ;ò¦›]~–ÉE1@ zËV°jÖLßÈ?¾þ*Ü1\IƒÙ»ä T˜y,YF x0ˆr½.] €/ÈQŠ4 U«4jd¯?4Ê9€¼¦´ëÄ o;ô°lÖ,4c®1ò}÷ ñnÝ:ôòECªz<ºMô~­!œaìÉÜ·ÏË—‰ýâü«WhÙÒ5ðkß¾ø‡ë9Òkx–[¡!éÿ@ÃO… r‰ÿ4Ùñ¸úwß<ó t„ã/6#§kÊ—X±àŸ¼GÍÑð}õÕþ礖¢Ñï›õ뽆3£ùÓG+±‹9ˆS¯1~ÆãqvÇ›(6{;w6üÅ£G§çc?xͦO÷®ïu¼î(AЈŽ|«óç{ïuô¼mÑÂË•œÞ»9…óÅ@~+…8ø!½nº#URt¨‚)ÈM‹óñ<\ÍHy€ïK‚¼µÿí¥$É› n]€@Fö“âÙÇÁûi Ðûôª«¬GAOP ´;gÀã{¶FmÜ0øö[ôŽ6¨ð^ûùgïõà _ÝuÂöèQsÀ¹ 1éKpÓ׊à{eØ0¤(AÎ\ÜÂÏ7YpŽ7Üà}¯¢±Z³!-W÷¥1P$~‘¶?C0 ÞgµlÚVv©e7î¸Ã{/øð“@zÄ?ð=”7ŸîåÙöÓŒ‹œÀâÁô?gLÅÞ,ИûˆøwóÍ7ÛkÀµÞù:>p̗׫oèp½øòÐȈ¿†sgÎÁ?'¨^xï>±)%~LHr÷uÏÁ¥‹âƒ:M^úð!(®x€eüê vï8O á›L~aè{Œøò‚´}»>ë+Sh°¿gÐjvXõ{{ 9hßc/ÞÕëñÃ5¬³áÎ3EtÛŠâG³Ë¾>Í 1ç¡F à>hk䓵ë!S°žš~8‹PX>ýox·ßݾì«ëõ™³}zF¿¾ÿü:â_ESRaø”©>Œ#À0.E—ºÍhÜp¨]Ûÿ¢¬Œ½î2Ÿø_#—2Œ€« Ö9—,…=tC¿P·ßÛ4þqô$¢ʽG`éüµ°fùfؼ~'ìÚ¶¶nØeû^ec.f²1×cI9ÄN§$΋Ñ4 Ä“»qÍv£jD^÷í9DŒ¹8s#5wšÕk@V ¨ÞK?›ðbbc®¿†ªM›‘.`£0ÉoJp¿Û‡ /F€`÷#`õ]áÏó×ý«æ0Œ@‚ àJƒ®ê%š ×*,ˬհéçòe½ƒö´ßç‘z¨ {ì’·l8 I¶ìY$“NE )Gv2µ ç/úô©æúÒ˜¨T½,ÑeïnRdOô›ôÄ›7V‚€|4Û!SŠê4Zßt“v¼‚ȃÇÂD¬Ùéwsƒ…AÔMšJv…‡alî‚`F ‚ ƒN>úH—›ž‡”:AUF€`‚€+ ºläÉØÝ“+wÒÁZᅫʤ_æ¨YžãÅ'O~–4ùõ»¿¤çµó­‰ÎŠ3ðç¡»hÎj2éþQz|è=/è7"!z (eáÉ×úEb(î“`2€@ËžôsÁè*¥V€£‹FE~eBDàÊ>}HË7o§:)´©¬œ9ƒÔÌS¸0ÑYaF€p1V|¯È7ŠÜºß°c‡wÈùuÖ’rʯéœ{v BvIê5ñCZ/Õãdl@wySi :ãÄqÁ1¥H¡"ù<ôäìí›÷ÈjXÒo<󅇫Yî¬Ó­ BÕ2r§G èº0J'-1 #Aº>8ô~hGÆi€þÁÀ*’ÔjÝFÒ8É0Œ#àj0ÃÒ ÃSGeÄóòå(п? Ì&·Ã@Xvƒ\Éí8Í0Œ@„p¥A—)2v7\£Aûç¯%¤Ã“]V ¯Ü‹.š¸NK”)"Wå´ƒ¸"“øÑ"ÉEaÐý}ì,) bÝB'Åóú‡Ñ’zUÆ}û¬Z²‘t“R¡$ÜЧ=Éc…p êñl§Ì+šó@¾\U*7n¢f±Î„œ"ÂöRpV|/Å(çEDhIÚßÕWÒ8É0Œ#àzÐün j]蹋Æ^F€`„tt1¢5•ÔŠ%ÉPªqbÚÄù¤\UvlÙ GŸT³‰×§©3…@&%jëÅ —`Á,±K-I»ŽìK›˜3îÒ¤C?>NKa…`xDObà_¯^ñ¸:^#À$®ôÐeƒnÆïкMª’N¶nÜíÑùvÉïÒ«-Ã]ö¤l¤+ÎF t¹bd‚_»Ï’tUhŠ—."•"í‚Ù¨O*Pž}à}“1÷öû»Aɲ¡‰ ÉÅŒ@Ø`ƒ.@.¤ZóPµi3¨"¸sKW©6|¹#F Y²Ñß- ÔD[¾kÝZ’ÏѬ0Œ#À0Œ#À8Wt‘C%cJ6{Sá ”]Ûö‘Ž›¶©ãÑoêw-É—•üóÈ*§]€@¹Š¥üβåÕõIyÓ6â(’$3…Gw¨òùˆŸaÏœ@’õ+@› ¥N2ÎE€ ºÎ½6<³Ä@ þ5ÈBgýèv”Ë—/›ªåKN6åq#À0Œ#À0Œ#àDØ ëÄ«¥9•,C\>ñ“Ék²H±žÙ´¾¦å¬R+ù7Z6䂘!¢Æ“'’9s&(X8Ÿœªwë†]¤Ü®²ðŸU0kÊbR=¹xAxôù;H+Œ€“`ƒ®“¯Ï-h©“]0!BÊîú¿xr©šR‹n\’BVF€`F€`‡!àJƒ.S.„ç.ºûᤣ¦.!zÅje|:R*Xyâvº¡•¯'Ü@ªƒ®0W”5›™n;£ÖÇoüHB®Þá>DòXaœŽ€Î³Ïésæù1ñ„Ò|Èrþl:Ç»œï/=÷—ŸIñݯ¿AtVF€`F€`'#àJƒ.S.„ç–*Y–z誽>2ôv’õÐ3·ÝPÊW)m$ùÕ%)VÐr¦÷=)¢»j$¡¼$wÕ’MD¤œAÕ.^¸Hª½óÕ%Kzð5RÈ #àPBÙÌpèRxZŒ€kHÊMéžfk{-»6¬‡ÿÊ…Â¥ø´‘m¹"#À0Œ#À0Œ@Ìp¥A—=tÃsßdÍ–råÉ¡í æÙsР#ºcúÙ²gÕ¶çL÷"PF ˜f¬¤žHïßY+Œ"[¯Ç"õræJ‚<ùr‘ I¹üŸü3ä Ò®f›6»€7,)`…`F€`F€p0®ôÐeÊ…ðÝQù P:£ç†-¬ {F~?ºõÖòq¥ºÀhÁ pôÐ R½z½ Dg…p lÐuË•âyÆ;ù‹%Küõý÷ˆ®*ŸzÔäÅûà'ŸªÕXgF€`F€`t‰"?ÁÒ©ÅÈ …’ó•øD {åHÆU2Ú,’€1ær¢³Â$—•`J‰°f^##àDùâK2­ÉŸZóè^¼pæOêwø°–^ŠTb…`F€`F€p ®4è2åBx冷½Û@ó+ëB“Öµ=s{ô¹:¼poŽD gî$2¯¦mj]§\ÛƒÒ$Løa†®š)o×¶ý$Ï*©Ä #àPØCס†§•p/O©€€ çÏkqÞãz“wn§÷jër&#À0Œ#À0Œ#àt˜C×éW( ókмàKb!pã`Åâõ°mÓn8{æ<´½¶Q@jÖ§Ñí?° VØ´v©W:…z…“BV‡#À]‡_ ž^ €üù’“áØþôMà BÕfÍû¶m…kÖ¼A_}LáE a…`F€`FÀE¸Ò Ë?À]t‡ñT‹@“6µÿ‚‘B å™ÓçàâÅK%Kf¿Ý¬^¶‰”W¬^–è¬0nB€ ºnºZ<×xG·óÆó-sê˜/LÝá=ºûÊ1‘·H¨Ü¤ Éc…`F€`F€pL¹à¦«ÅsebŒ@fa¸Íš-+™Å‰c§ˆ®SV,ZO²4­NtV7!À]7]-žk¼#Ðsð²ÄeÓþ"ú’©Sàô±c$ï‰ïþè¬0Œ#À0Œ#À0nCÀ•]öÐuÛmÆó' %ÓÀh‹æ¬ ¸¼Ó§Î’:eÊ':+Œ€›`ƒ®›®Ï5ÞÈW¸°i‰§÷åzäa_åëÕƒä²|J„€Â #À0Œ#À0Œ€ëp¥A—ƒ¢¹î>ã Ç=ooOV3þ»¿‰®*lüRaÝí\¾|ÙíKàù3q…@jºd=}9Æ£ñœ?s†”=üÙh¢³Â0Œ#À0Œ#À¸WtÙC×·Ï9^¨×´YÊÉ㧉®*«–PþÜÂE ¨UXg\…oR¸êrñd–={’UþûûD¸xþçv¦™¹3÷ÎÜ;Û™ù½ŸOÍœsÞó.ß÷ܹwžyç= €N  ë´£½Ä€@“æuûHàË' ;.Àuíð¤ùq)P¦Bñõ ®Ó*-•.¸ .ûL§@@WÀ‘]_°'îÒsbCà'«%@IDATðáŒ7F›5íwÆQå<@Øp¤]GŽs\))’'þ ½¼ï­±ö±@@§ 82 Ë ]§_v´?*U=ã3¾[à±­_|ü£Ç¾+Ú6òØf' Ðuâ¨ÑæD¸ÊkÙ…s4”eË&B×é# € €@‚ ¤8±¿ÌÐuâ¨Ñæxh䆬^¾ÞÕ­ù³–HóÖ \ÛúdÇÖÝÛ5.¬â±ÍNHKKsb³i3q/кÛ=¢ÿìć/¶ € €ñ&À ÝxQúƒ@„Ω^Á£¦uÿlöØÖc^¯|òfÈÜ&@Èi#F{U€ ‰:òô@ˆGtù=þ/LzûÞëáØwȣѻwî9~rWî<)’;·#¿p².>zá|ójVôèÄçï}ïÚž7{‰ë¹>©Tõtm6pªkè:uäh7 € € Ž è2C7>.>zá|¶.óèÄ´/~qmÏú~¡ë¹>iÖ²¾Ç6 € € € €Á 82 Ë Ýàš3‡@¹ò¥=ŠM=zrÉ…=»öy󾉚ÇA6p3t4X4@@ˆCGt™¡‡W"]r¤@‚ùÅýçQ]û÷td_h4 °†n RäC@@‡€#o9Ï Ýp\ ”‰@ö ) »wžœ»eóvIú/É£°ÂE¹!šŽ  ëèá£ñ € € €€ã9C—€®ã¯;:GçÖ¨èÑ›Ÿ¾'ËþüÛc_¥ªgxl³€“è:yôh; € € à|GtÝ¿âíü! 8[àš›šytàgÐ]±xǾ*ÕÎòØf' °†®“G¶#€ € €Î  ëü1¤DUàÔr%3Ô¿bÑ}5jWöØf@@@@ {Ž è²äBö›³—@JJ.¢<ì±]¶|im6p²3tÛo[Ë”-á÷p¢7Esâ¨Ñf@@@ ~ÐeÉ…ø¹éI|ÔoRÃoG\RÓï1 àD–\pâ¨Ñf@@@ ~èÆÏXÒ¢&Pú´â~ë>¯f%¿Ç8€€˜¡ëÄQ£Í € € €@ü82 Ë’ ñsÒ“øð÷syj¹’ñÓIz‚€  Ëe€ € € MGtYr!š— u#à[ Áž—](\ôß'°‡ ÐuèÀÑl@@@ NÐõ70NÆ„n àH—øè:²34Lèf‚Ã!@@@° 82 Ë ݰ_T€@Ðå+–ÍpÎY•ËeØÇœ.@@×é#Hû@@@g ÐuöøÑzbF H±‚ÚÒæúK2ìcNHKKszh? € € €€ƒRœØv–\pâ¨ÑæD¨qaIÉ"yòæ–@o~y‹° € € Iº‘Ô¦.@Ç ðmÇ!@@@-@@×ÑÃGã@"-À ÝH‹S € € €€»]w ž#€ …Ý,€8Œ € € VGtùškX¯ G@€€nHdA@@›€£º¼‰Ûu@Á € ðáb€PdC@@‹ݰ°R( € € € € z”о£9C÷—Ÿþ1/Œ¨sc'(_(3½ýÊ$™þÍ܀м«OixIÍ€ò†2S—Ö\\4 tY6¬ù/ 6>÷ÆýRêÔâå e¦X7Ô¾ÒÆÐŒøóŽ•¥ WTØ£/Ü-•ªžP^2å\ š¿‹Ü[ÿ@£†²{Ë÷]~Ÿ¿üû’¿`A¿ÇszàŽÊ•.âU]×ÃÕÿp•J²H¶1’u…Ò(³²b±O±Ø¦Ì 9† €$¢3tqÔé3 €@¶b% ›íp" € € €€£ÐeÝBG_k4ˆ ºq1Œt@@p¬€£º¼‰vìuFÃ@¸àßJ:‚ € €8R€€®#‡F#€DK€£%O½ € € €*@@—ë@ èEV@@@ Ð 9)"€ijÝx]ú† € €ľÝØ#Zˆ Ctch0h  € € €tpÐé2 €@öèfߎ3@@@r.਀.wÏù€S €@Îø]”3?ÎF@@È™€£ºÌŠÊÙ`s6 €@Îø]”sCJ@@@ȾÝìÛq& €@ ÐMÀA§Ë € € €@ СÁ ) €±/@@7öLj"€ € €ñ,@@7žG—¾!€„\€€nÈI)@@@ ºA`‘@º\ € € €ÑpT@—;‹GóR¡n@àw× € € €@4ÐeVT4/êFP~q € € €DS€€n4õ©@Àqt7d4@@ˆ+ºq5œt@ ÜtÃ-Lù € € €™ ÐÍL‡c €x Ðõa@@@ ¢t#ÊMe €N  ëô¤ý € € €€³ÐåÎâξØh= tãaé € € à\GtyíÜ –#€ñ"Àï¢xIú € €8S€€®3ÇV#€DI€o‹D žj@@@,º\ €!À Ý °ÈŠ € € rº!'¥@@x  Ï£Kß@@@Ø  ûcD @bH€€n MA@@PÀQ]Ö-LÀ+”.#€1&@@7Æ„æ € € € &਀.o¢ìꤻ €@ ð»(…&!€ € € $@@7›®"€ä\€o‹äÜ@@@²/@@7ûvœ‰ €ÌÐMÀA§Ë € € €@ СÁ ) €±/@@7öLj"€ € €ñ,਀._sçK‘¾!€Î  ëŒq¢• € € €@¼ 8* Ë›èx½ é à~9g¬h) € € tãqTé €@Ø膖‚@@@  Y@°XþÇ–à@@@ t£¡N €Ž`†®c‡Ž†#€ € €q!਀.³¢â⚣ €€£è:zøh< € € àxGtyíøë €Žàw‘㇠€ € €Ž  ëèá£ñ €‘  iqêC@@p  ë®Ás@²  ›‡@@@Â*@@7¬¼Ž otãmDé € € à,ºÎ/Z‹ eºQªG@@\ÀQÝ´´´.º mºÑêG@@[ÀQ]ÞD'öÅJï@XàÃÅXÚ€ € €$®ÝÄ{zŽ >\̧ € € €„L€€nÈ()@ è&Â(ÓG@@@ vèÆîØÐ2@  ƒƒB“@@@  ›@ƒMW@r.@@7熔€ € € }Gt¹Möš3@B#@@74Ž”‚ € € =Gty½Aæ,@Ð ðábè,) @@@ xºÁ›q €@ ðáb>]G@@b@€€n M@pŽ]çŒ-E@@âQ€€n<Ž*}B›ݰÑR0 € € €@t@"  €¶][‚G@@@hІ:u"€8V€€®c‡Ž†#€ € €q!਀.w‹kŽN €Žàw‘£‡Æ#€ € €ŽpT@—YQŽ¿Þè € € € €ä@€€nð8@ ñøp1ñÆœ#€ € €±$@@7–Fƒ¶ €ļK.ÄüÑ@@@@ ®èÆõðÒ9@P 0C7Ô¢”‡ € € ŒÝ`´È‹ ðtþ@@ˆª€£º|Í5ª× •#€º\ € € €ÑpT@—7ÑѼT¨@@ø]Äu€ € € MºÑÔ§n@Ç ðmÇ  F@@âJ€€n\ 'A·Ýp S> € € €@ft3Óá €^,¹àÂ& € € €@DÐeVTD¯ *Cð!@@× »@@@"&਀.o¢#v]P €€~ùa7 € € €@DèF„™J@âE€€n¼Œ$ý@@@œ)@@×™ãF«@¢$Àò?Q‚§Z@@@K€€. €A0C7,²"€ € €„\ÀQ]fE…|ü)@ HºA‚‘@@@ ¤Ž èò&:¤cOa €ÙàwQ6Ð8@@@ dtCFIA €‰ @@7F™>"€ € €±+@@7vdž–!€Ä Ýš„ € €$ÝlºŠ sÖsϹ!% € € €d_ÀQ]ÞDg 9@@@@œ/਀._suþG@§ ð»Èé#Hû@@@g ÐuöøÑz@ ðm‘ƒS € € €€‡]6@È\€º™ûp@@@ ¼tÃëKé €q&@@7Î”î € € €  ë°£¹ €Ñ  ]jG@@]€€n¢_ô@ (ºAq‘@@@ ÄŽ èr#š>Å!€-@@7h2N@@@¡€£º¼‰áÈS €@¶øp1[lœ„ € € "º!‚¤@ÄàÃÅÄgz‰ € €ĪÝXÚ… “tcrXh € € 0Ž èò5ׄ¹.é( ³tcvhh € € Ž è&ĈÐI@ gœq†,^¼85Å_tãoLéDGà¼óΓ 6D§rjE@,਀.3t|¥Ñtˆ)}]½zu)[¶¬Ìž=;¦Úë! ë#Dû@À)Ë–-ý€Q»+W®tJ³i' € uGtyõë… €@œ lÞ¼Y5j$ àÍt€cˇ‹B‘ P@»çœsŽ”/_^6nÜàYdC@W€€nâŽ==G\´ÞL,XPþþûo×~ždàÃÅŒ&ìAB!°~ýz9ýôÓ¥D‰²gÏžPI € €@\ ÐËa¥S €@ööïß/•*U²¾;oÞ¼ìçgÐó¦{ u;vH‘"E¬¥Ö¬YõöÐ@@ Öе¿æš””$‘þשS§€Ç.ÒmÓúÆŒpû´/ÑhcÀ 4£Ñ¾?- ¸‰*TŒJn`” uÜ‚IÑg§´ñ»iÓ¦lРAÐ×cV…ë»uëÖ•SO=UæÌ™“Uö„:ÍßEî?3›7mؽP¡BA_#îueõ<à†˜ŒY•åÄãáê¸Ê ¥q$ÛɺBi”YY±Ø§H¶)«ºôæ*T*UªÈŠ+²ÊÎq@@ a’ÌL£ãNêm¹råbª¹úÆ>9ÙQqñ˜ò;žv\’’ƒ ÆTh Ø´iS@-¿è¢‹dÖ¬YåM¤L±ö»(‘ìé+Ä@ ¿‹Ú¶m+&LˆŸŽÓ@@ ‡Ž èæ°¿!=]cáý»½$O¿v_HË Eaÿ¬Ü ªœŠ¢ÂVÆø·¿•²g”–F—]¶:(| 茱ÌÒe—]f½yÖ™$@Â!Õ]»Ê‹/¾(ùòå Gõ”‰ €8V€©¥9º…¿.—7l“Þœ’ƒRÂsê÷¿ž‚CTêÏßÌ“¯?!?OeΑR !èСƒìܹS¦™%憄”BP`ýòå2â®;°çtÐôìÙS:$£F"˜RJA@8  ›ƒ}}اÖÙßN˜%ËþŒ»Â~þ«]£žû8½ ß©Öü'c_™hUðïÆí᫈’@ `+®¸BRSSå“O>‘¢E‹|cG O“F’vìXL4hæ§ãeÿîÝ1Ñ–H6bó_Iß‹›ÈãmZIí-"Y5u!;wýÜK/½$yóæ‹>Ñ @@ ts zðÀa×ÙÏõKŽ>êÚŽÖ]ÓwÎô?­êûy‘õGq´Úâ«Þ];öÊ€î/»íÙµ/æÚèjOHzõêY?ƒß|óäÊ•+zŸ])iæ&_ÑLŒùÊ£úaóØŽæÆÑ#©ò@—ç34áXjlÌ(ËÐ0v Ç­[·–]»vɯ¿þǽLœ®Ù³aÿùc¡|üô¨v|úÇYõk€yïŽQmK¸+?´¿ÄíQ»V†ÙÑ)yò„»zÊGÀñ½zõ’ÈgŸ}æø¾Ð@@ ’t³©½|Ñ?Î|X:vì(úž|‰œá4Usçúl²z‡thïóX8v.3³sõælÞi÷–-²~y|ÌÒ­Ö´© =Gò8Å»›Û ¯ië±Í € € JºÙМ9mA–gé2Ýoxʼ¹Ì ¿¾0+Ó6Í›¹$â³t·oÝ%›ÖoÉ´]öÁÍë·ÚOyD öîÝ+ãÇ—­[ù ‚-®²þ2i¢ßþü½ðwùôù“ëëúÍ‚úÜâ·”‘÷vó{ÌiŠ”*%¯üñ§Ô¹ê*¿MopõÕ~qx8zô¨$%%É\?.Å[é €  )±Ð§µaÞ¬%>›œ/)Z¼°4kUOš·ihýqë3cˆwŽgªh9«ôTß12`h׬²…ìx‰RE]õé Ûtñìï—ûe¨ã/³DÅég•ɰŸ à_`‹™ùhrÿ37iJ17bÊ›7¯”(QÂÿI‰;YŸŽÏ´Oߌ-]{­”=»r¦ùrrpÁÔo帟¥´Ü­ëÖÉžmÛ¤pÉ’9©&¦Î½í™çdþ”)>۔>÷³@@…3t³¡¸þŸ]gµêØT^xëkûÉWzÈÓ£ï“˯¾(bÁÜãiÇå›ÏfºÚ“Ù“¿Wl#‡f–%lÇRRrI§»ZIÙò¥%—y>üÝ~Ò¸ymW}Ó¿ëzÎL L™2R­Z5+s½zõ¤\¹rRÊÌ$%–@ è ¼êJs“´¬— Ê®Ü}Òfv~ÿæÍ2;ì¸c½êÖ±Ú¬³uóäËç¸öÓ`B!púé§KáÂ…­¢7n,ùóç—"EŠ„¢hÊ@@ÈD€º™àø;ôÒ{Iá¢gß|òöT¹§ßõþN Ïþ$‘×>h“’“Ì£XÏok3@†¼ÖKN;=¶‚;:·zÊR¤X!¹ý¾vÖ?]–bé‡Ç‡Rˆc äm3³5ˆ»víZ)_¾|÷–®ùX0uª¯Ý®}e«œ#µm+U4”ü3þÞreÌÁ“%³fÊ‘Öp>´¿l\¹BÊ™69=½Ù÷AI=rDžùqºµžîÈEKä“g†ÈÔ7ß”rU«:½{´€Æ'›6m’ÿýï2pà@©X±"Ý€õȈ €d_€€n6ì|s‹•(,sg,‰p@W×,ËÇ÷0¦™Ù»±”öí9`5ç®><š¥3v5ÈKBN`ÆøO\'”03å*Ô¨)ó¾þJJŸu– ùî{×±p>Yðí·ráU-%ýCÅdëCÅß§}'æ#Æôý¹’%9Ùì7³'|.ú=Îæ„½ì¥³fÉ/¦­»w—’ÆÜNê/µ/¿B~3þ$E Y³f¢kèj@·E‹R·nÝDé:ýD@¢*à;Õ&9³ò–í›Èû£cëM\Ú±´˜Âüáë_­ö,T ¦ÚEc@§ 44³o{½þ†Gó‡íÙ-ËgÏöØÎÿ=ñd†âû]z±YS÷¸työÙ Çœ¼#íØ1vkg)[¥Š´íÕ;CWήSGô @@§kè†H·Æ…U¬’¶lÞ¢s^LZ&7¨ÉyéÁ— 7“+uj±àOä @ŸõZµÎ°ÿân}ý?–ššáX¤vèŒ\‘Øú–H(úþ@£‹¬b›ôe(Š£ @@@ [t³Å–ñ¤â¥ŠZ;ÿ^¹!ãÁ(퉵%ôfrvà;J$T‹ĽÀ™çŸoõqËÚ5Qëk’ èr³¶¨50üôS²wû6ytÂDssO¾à” BNA@@ Ð dŠYVײõý‚•˜óbôë®±’8d5¥uÇKb¥I´¸Ð;Škú÷ßã¢?t"çöº®Ë~‰Ü² Þ­Ö€n<¥5‹þ”ïÞzKš™uBϪV=žºF_ȱ@®\¹¬2víÚ•ã²(@@ 0øzÇXŸÃ–«xÉ"²xÁ_a+?Ø‚ci Ý÷FO¶š_´x¡`»A~ÈD Ož<ÖÑ«®ºJ¦L™"µjÕÊ$7‡Ià»±c£ÖÝädà9;*æBgnw­.YRn8('Eq.q)¾ÄŠHß¾}­å^R£¸ÜK\Ó)@@À‡](ÙÝuýmWf÷Ô°œKkè.š·JÊW<-,ý¤PY wîÜ2aÂÙ±c‡´lÙRjÔ¨‘Èôý„@ÍËšËÖuë¢æ¡žøçŠ<Öê*Ëñ¹ŸgFÍ“Šˆu«¯¾Z.\(:[W/‘@@Â+@@7„¾48×*íï±±Žn¬tSSÉÞÝûå’+ë†P›¢@ÀhÛ¶­µ^©Î$7nœ½›Ç¸á‘G­Þ§9…¤\æÏ‹8˜¡;yÔHÙ´j•Ü÷ÖXI!H•k‰J!0iÒ$Ù½{·Ì›7O˜¡ëŒ1£• € àlºa¿9?ÿ†Rƒ/2Vº;·ï±_»azÀ;øžp €@0%Ê•³²/™Y¥ÉIΟ¡»eíZ™8l¨44˜TkÒ4~ò"… –:uêX³t€N#€ €  bì2eKÈòEÿ„¸Ôì—#7Eûðõ¯­)Æú¹ÙIÎB‚Ð%r¥¤ÈŸ?þ܉!Ê3tiÑÜÒ¸ý¹B¤B1 € € €@hè†ÆÑUJ¥ªgȆþsmGóɱ èþ>g™œY©l4)¨H82*È⟧G¥ßöM’¢Ry*}öƬeLF-^*’”‚)@@ÝÐYZ%5¾¬¶õ&0ÄÅf«¸´ciÙ:/”'>”¾~ãÿºµe±”… …À¹5’í7f‘+<‡“ô¦h]C÷§?UóæÊC_”Üyó†ˆR@@@ÐÍž¯SÏ­YÑÚ½táj_‡#ºïxZôº‹ÿËês¹3ËD´ïT†$º@ó[nµÒŽ‹8Err.sS´ˆW›ã îÛ'ï  5.m&õÛ´Éqy€ € €„C€€n8TM™Ÿ¿7-L%^l,¬¡ûÓ”¹’/óYN9@œ ”:ã «ãÇç¼° KкNŒèöº°¶ÕÓ¯²ÇdG@@"'@@7 Ö•Ï;Sþݰ- %Wd,,¹°xÁ*ÖÏ nØÈ„L %Oùù“CV^ éºN[raÄÝwŠÎf~aö/’´·äC@@"+@@7 Þõ/®!û÷”´(/yíú>jévéym”)@ +ó›4•më×e•-äÇ“r9ëÏ‹¹_%þðƒtzü )ZªtÈ=(@@¥€³Þq…²ça,ëœjgY¥ïÝ} Œµd]t´—\øîË_¬F–)["ëÆ’@ ä5/½TöïÚeÍ< yᙘœäœºÇRSet¯žrVšréM2é‡@@@Ø  †q8ýÄ À–.L¿!Xª¨ÈcÇ¢{S´¹3IñREj+™@B/P­iS«Ð£‡‡¾ðLJLvÐ ÝÞ ë‹$%IÿñŸfÒ#!€ € €±#@@7Œc1áýïÃXzÖEò’kWo–ZõªfÝPr €„E øie­r¿÷°”ï¯P]ƒÖ kè¾Ñç9`f0?ñõ7¢ëþ’@@@'ðî%L£TãÂ*²õßa*=°b£¹äBjê1«‘—_Ý0°Æ’ @ ,¹RRä‹—^ KÙþ MNÎåïPÌì_þË/2gÒD¹ö>Röì³c¦]4@@ÈJ€€nVBÙ<ÞîÍ­3™;fG+Eó¦h½1Åêö©åJF«ûÔ‹ `št¼^R¦ß¤2R :C×LÑTuÙªç…Î7K¡’%¥U×nÙ:Ÿ“@@@h Ð “|©S‹[%¯Zº.L5d]lZ×ÐõýïRú´tƒ¬[J@p 4éÐÑ*úÀžÝáª"C¹Ö’ öÆÎŽ~—^b-±0lVúÍ;c§e´@@ÈZ€€nÖFÙÊQà”|’ËÜfñ‚UÙ:?'¥EivÔ1³Ü¡ƒ‡åªvCÑ Ê@È@©òå­³×-]–ƒR‚;ÕZ6J¿ƒ²jéO>!Û7¬—~}"ÖLâ¬Nà8 € € ctÃ8 ‹œ"s¦ÿÆ2/:ZK.lÙ¼ÃjXõ:U2o G@Â.P paIΕKLý6ìuÙ$™4%W\ظj¥ü0îiqûíRé‚ ìæòˆ € €8J€€n‡«† hnß²+Œ5d^t´ºS&Ì´V¢tÑÌÈQ@ˆäΛWf|òqDêÒJô¦hÇcp†î V-­Y¹ê1 *B@@B-@@7Ô¢nåµít™µ•z4Õmo䞦¥EgzÔŒ©ó¥$ÁÜÈ 45!€Y4¿µ‹=|8‹\¡;œœœºÂBTÒ 6­$))IF-^¢)@@ˆŽÝ0º/YØ*}ÎOÑYv!7EK5ëçjºãþöÖ#ÿ!€D_ õ=÷ZØ»#}Iœp·()ÆfèNùªlX¾\z½ù–¤äÎîîS> € € VºaåóÆ1E~¥€nZZ˜{—±ø…¿-·vV¨R.ãAö €DE@—\ÐôÛäÉ©ßºÙØñÈÿòÕ¹ƒûöÉćIƒkÚÊù›øÊÂ>@@@ÀQtÃ<\UÎ?SÖ®ÞæZ|%fÿ°PòÈ+yò0Ê÷¨°ˆŽÀ)E‹Ê¢é?E¤òä䨸ó⸠*÷¬]Kr¥¤ÈÏ¿‘¾S  € € nØxÇî^F±üêu*ËÞÝû£Ò‚hÜmá¯Ëåô §F¥¿TŠ à_àŒóΓUóæúÏÂ#Ð…›¢={ã V;†Îž#fÝö¢@@@è Ð ³}ý¦5¬ìµeÃ\Gñ‘^CWëÓ7ð×ßv¥G;Ø@ˆ¾@WÈá"Ò¤\Ñÿóbú‡Ê_óç˽¯–‚ÅŠE¤ßT‚ € €DB úï¸"ÑË(ÖQ¬DúѾù|fÄ[é%¦M63 LªtÎï+"€d.и}+Ã={2Ï‚£ÉIæÏ ó_´ÒÑ#GäÝJåºuå‚ËšG«Ô‹ € €„E€€nXX= Í'E¦|6Ãsg¶"½äÂÌi ¤pÑ‚èU €+`ßí‹—G{jÐùõ¦hÑ çê7Ez×»P´¿ý>ø(è¶s € € ët#0Bš] ‡‰@MžUDz†îƵ[¤nãó=Á €@Ì.YJæ}3%ìíIÖ%¢4Cwøí]äÐþýòä7SÃÞO*@@@¢!@@7êuW“cf}ÙƒûE ¶“UDr ]¥3‚7¯s²øQ 9ådå{þ99¿IS©P£fÈÛL € € €€SÂ3…Æ)½§ €$¨@é³Î’•󿆥÷Éɹ$Ô·D›8üEkÝÜÞo K›)@@pŠ@’Yã.Ô﹜ÒwÚ‰ €@ üðî»’;>iÒ¾ƒc ôfŸáZÎÁ14@@^€€nÂ_ € € € € €€SXrÁ)#E;@@@@@ áè&ü% € € € €8E€€®SFŠv"€ € € € €@ ÐMøK@@@@pŠ]§ŒíD@@@@„  ›ð— € € € € àºN)Ú‰ € € € € /@@7á/@@@@@À)t2R´@@@@^€€nÂ_ € € € € €€SRœÒÐÌÚ¹sûþÄ»²nõfl.«%ï¹FòäÍí±Ÿàº´~ÔïI¥N-&Ͻñ€ßãñtÀ—CRR’\}ã¥ÒºCSIÉ?Rñ4dô@@@ˆ+ÇÏÐ]øër¹ÿ–ç¬`nJJ.)wfÉ—?¯ä2Ïg}¿PºvxBäxì™ok3 * »£ícÙ®;wžñþW°P¨ô#š•ê‡ú/wžô &}ðƒÜyí 9~f«NNB@ˆ¬€£ºÏ?:ÖÒj÷¿æ‚¹z |¥²2vò`Ÿ¢LݽsŸu¬H±‚¢_›$:pX<,ÅJÎ2ûVp.Z²°äÎæ×ðµ»vìµf3åš¶oÝ-NÉ+ù ä ô”°å;v,M¶ý·SÊ”-‘¡ŽÔ£©²c›ikÁüè,ß=»÷K3+6_þ<å©•ÖSÔŒK Þûöì—#‡SE]{ Ý6ôÒ0¬«ÜÝîqÙ´~‹Ûϧö’}{H‰ÒE%W®œMŽ×þªÎ.T˜È¯§4[NXcƒMÁ;.UZÚ?ø \tm;ïCáÙ6?K§`ò\hø3n^½Z\ÙBò*,//ø=Ó ï¨\É:>zÙ óM—Ðþ‰ðð¥éÜG>û\*Ô¨™i;býàö¥ß%M34SÍZßÛ]Útï‘áØÃÍ.‘£‡K÷×FK­Ëšg8;Þ8@¦øÕµ¡¿ü*EJ–ôÙÍ¿æÏ—gnèhk÷@iÙµ[†|‡öï—îµjH"Edļ®ãË#/ÜÜɵm?ÑÀÿ—·ö}ûJ©3ÊÛ»3}Ôë?yºž®¤9"#ÿ\,y²tõ×÷ ”ŸÞ_Jœ~†<ûãOþ²…tÿñ´¯ƒaxëßü2Ù²vßöÆÂ‡-~Âök«w‘5ÍkÁ­Cž–BÅ‹{b@ÈT ´ïÖ2­*ô5€§Á¸6×_pᇕ÷Ž­ÿîô8§lùÒòØ‹Ý<ÖÛ=r䨤kpI ¹î—˃·õ8Ggbž}®ç›„ûÊ#÷ŒHĞȭ몾>aµÕ­Ã“rÈ„íä¾&«|^û×&yªï±fiÚÍãyµ*ʃƒosÛ#2oæyõ™åÉWzȪ%kdܨ/]Ç œ’O†¿ûµD‚îìuó3²gWz[·}Õ­ûs’4Ý»ó³Ò®ss)Yª¨Œú©«8íŸ"¿üø'™ðÞ÷®ýö“n}¯—zM«Û›²oÏéqÓ¹êºÆÒðÒZ2°û+®c:îÃßí'…‹”¯?ûYÆê:¦3µuƶ¯4éÃdâû?xªP¹œÍI`7åDà#9É3P{øÐyþ‘·dõŠ u¦äÎ%ÃÞéë ÆN4[>|ýkWžûþ÷Œë¹®O¬ëkJ5³«Ÿê3ZÖ˜kÄ=µ¿µ…´jŸ1¸àž‡çÄ’Àê… ­æ*QBN­PQÒÌrûwí438ÿ–·ú>(ó¾™"=G¿KMvl[N­XQ’’“åàÞ=V*%çbvÇÖ-KŸ©¬­PsµŽRåÏ”ÃXãm×éÔÇÿýg5ý”¢Eå¬ê5Ìóãr`Ï^YgfàNzi¸ÌüìSŒ›îѽrUΑí›7I¥ZxìïVí<+Ы3ÅÕ>œé^=l£#æÿ. þAqvÛdsõüßOÚöºÏgQËæÌvíÿ|è ri§›Í…\ûô‰þý É{ þH-)\ªÔ‰Àíq9|à€lÛ°Qæ›×ýWÿêkäΡìó3û/ÁBrpß^sô;;vßÎé?G¹ÍÒK!æjÛj˜ÜóÌ7*Öª•YSsì€yÝÑtV ý9ñL9ù»Ë³$çlU¾°®yµ8.iæ["[Ö®•?¾Ÿ&½ëO“g§ÏeËf»#ú“cf‰™7Vþ•í28@œ%àØ€î¯Óÿ´¤ËW<-`ñ43[´[û'¬7g]Nºõëh‚«&àöèXÙ´n‹Üsý`x}܉=‹Ü¼~«ÌÕuy{ ¸Yf|7Oæš@êSޱ‚ŠEŠ|ÓÑÇ}õkó÷?q‹œ[£¢5kt`“Èëo»B6¬ýO¾Ÿü«UÉe­ë[ÅMðÓNƒîi=½µG[ixqMÙ´a‹ ~`´,]ø·|úÎTiK ;«ëq¤ ênÞ°MªÕ®,—µª/ï¼:É *ß}Ý yëËÁV¾;îk'Ì[‘¡îS°îîûäóqÓ$¿ *ŸÁÙ&ð¼ØjÃß&°©ÁÜBEO±,Ï8ëT³Öñï2nä2ê¹=ºvçþY¹Q¦|6ÓšÝÕ}G?ÿ‰5;µïäL¹ÖúÉ5«e¼+ÉØ—'Ȳ?þ–ׇ}*wÞßÞ.ÂzÐ=}Y!ûè w›™ÁùäÅÇÆÉòEÿXãW>|Ä#0/yßÊ^¾Ò©§ ø¶ÌÕ@õõ]®´‚ëÏ<ô†™É»U˜õðqý¬üuU“-›·ËOßÌ3oVI“Ëk»>\(iÚk%ó^öžŽOZþ&-êHçnm¬1îßõ%ùôªæ@IDATí©&8\@š¶¸Ð£~6ˆuó.j$w{ÑÕLÁ¨®?øA¶®_o4g¸Žñ${4Ñ îæ¿þ’É#_•¶÷õöYÐ7¯±ö?úùDŸÇsºóéï=?LËiy±pþUÏ•Þou5EƒŽwSY¶›å%tFi¾SN~ƒâÑÏ'¸ò¹?Ñk^S0k»ŸÌó#&Щ)õèÑ`NËVÞM«VYç]|ÓM2ýƒdò«¯ø èÚ\t]{™m‚á#î¾Sú}ð‘½; GõÜùÉô¿wìölÛ&÷7¬/¿~1É òvõš}ÈçcÃvíä‡qïÈÐ[:KÿOÆûÌóëäôÎ;<Üßçñœî¬qñ%2ü×¹9-&æÎô3ß×Ì54Ì ê÷¡çu=ñ¥eò+¯ÈCfÆÿë9ÆzÐænP< €Ä€€çtÂhP M˜7{‰•Uz¦ƒß·‚¹MPõ±áݤôi%DÂ/ÐßZ²Ai½yr–¤]îÚÕ›¥± °½1ñq©^§²ÜóÐr]çË­ÃãM Í=i0·ú…U¤º ¬êMÚN-WRÜ×½äªzrs×6®Sô¹þky]×¾ÇGÜk-qñJž|¹EƒÏ¯}:Ð:>cÚW>÷'Ì}âåîò€ $ת_U^4B}¯Zt­aMÚ._u_Ûé2÷¢2}¾g÷>ñþgÏšq?ñûÉs̬ښ2òãGåÞ‡np-}qæÙeeÄˈ÷–Jçœa-/mYO®¹éRëô/?ùɽë¹\ÛvjfÍh­rþ™2ôí­@±Î~]gÆæ©Q½¬àmãæ¸¬çœøÛ…mß²ËZãV—×xá­>R´x!ké†~OßnÞ÷ï=(ÿmÚngÏôQ¿š¸tájëß·g™õs_‘…s–‹Æû<ÑÅã\½VÞúòIÑÙÇÅK‘BEN±Ú[²L1Ù­ËiœHºôƒŽ^3š®ëÜÂÚÖ}IÉéŸ0|úîT+˜«³qoëy­èÌo-gôçYçŒ1Ñ5ƒÉÚÁ8P@g(êl:M† õÛ]T¿öî+ékÒÖõëä¿5kDodhÚ¿{·l2AÏì& ÈiKÛhÒÀš®+ë+eÖG_ù3Û÷ȧŸ[‡§ô¡ßló¾úÊ:V®rå yÔTgO«i i£±8r8ð<Økf¸.]bfº¦Ïè ¤‰¹~Ù²l¿öYýúçoÙ¾Éó[Ôí+þÞmrý Ö!;ðç+_v÷í6J½¶}%=¦¡ìèŒyýY°g"ûª/«}o÷ØÊróc§¶Í5ôßš2=­Æ%—Z³ÉWÍk} “iæ6K<¼0ë+çÂißÉÞéù;µýƒ}­Ck/ò—E>ò”u¬á‰×)ïŒ[×­]æÄ×ßFÞyu[ÿ›þJ~û:î½Ï¾fƒùYÔkbÝÒ¥9 äëu§}‹TÒ×ÍϦ¯´o×.ëµBgüšöîØî÷µY¯u}ý$é5ìïwO ç{繺G/kWf×Ë~c¡¯:{<i˺µ!ùù E[(@²/àØºÿnÜfõZd¦?æ®°‚c-®¾(Ã)ï¹Z¾{¸L8[n¼£¥Çq ²ÝޫǾƗ] ŸûNþøm…Ç~ÝX½|}†}Áìð5ëXƒwšö›e|¥›ÍlÍ3*xÎmÙ¾‰|5þgÙaÖÔ-}Zq_§½¯W§“KØ'ë,c÷@±î×7µw=ÐÁÎâzTK_k¾ÖoR]&}ð£,š·JÚt¼Ä•_Ÿèš¸×ÜØÌcŸÎ`Õ±êú`G){F)×1]›Vו=b–ÖpOxÕtïÃ7ºï¶ž_s㥲ìϿͬëÅÒºãÅŽ{ïÐeìõ›ícpön£ÓÀ±¯¤Ë<è’!Á¤¯>ùÙÊî½´‚Zëlp탮5ÈKBÀÉ•ëÖµfÔm3³íôÁ“OX3çF.Z"ƒÚ´’-'‹÷¼:Jj·HÿÖÂáûe`«–ÖìHû<},zê©òؤ/ý®Q¸ÙT^u¥Gð·Î•WI·—_q/Æïs Èî¾fæ1Ð~é®;eéÌ®:õ‰¶ï¾·Þ67†jì±ßÞÐ`Ï#-š[ý±÷Uª]Ç´é{3ÓGûT‡ëdƒ €º§×´•;žÁÃÀýx Ïí SÙJ•<²÷iÒHvýû¯ëmÃï¸MOŸîÊsoMc"ÙË/L4Ë7L~åeyÉ>ÛéFÙ´r¥•ã–§Ÿ‘&í;ˆÎ@Õýÿ™`»{Êe–üÍT³ÌEyk÷äQ¯ÊÄaÃ\YîoPÏõüÙé?›¯z—³¶5@>¨Mk¸;y]êf;ËMÒ?HÑ2e¤çëoÊë+VÉß|+‹—Õ Èg/<ï‘×ÞèùeÖú£-»u“²Uª˜ošä³exœôòY1ç©}Å•òÜÏ3EoÆÖ¦GO+èüb—[|Î6ÕÀ³úi`ãþ·ÇIï±o[ÁêÕ æ[_•ÏP‰½.¬cs[Ü~‡Œ1ýzÉÜd«´¹Þæ˜öÇÏ<íãŒÀvÚ·O~4I4ÈTé‚Ú™žÔÉÌ\m×çAWž«Íú²ºÝᡇ3¬¥ûDÛ«e³ ôÕ¿újs£¯ËEƒ”š^¹§«ì4³‹õ&bz£°WþXdMEåO_ßÁUvÓ×{ÔÕêž{­m­¯øie­|ðRW æ¶í}¿u-›ó›õ³ñøq2Õ¿M:›PÿŽiqÛmÖ)Õš6µÿnj¦EèØê4,Õ õ†n¿—MyV²3-Éóà·ßií˜úfÖ}èðÐCV^½9™wúá½÷¬]w¿4ÂãЫÝî6ëéæ“;†¾h]O$/inĦKo¼ð¿›=òšdk{ʘ1V0÷<ó¡E#œ/~Úi'òÙýô<íî·~{ŒyÃúY|òÛ©R°x ùǬGþ‰Ÿk¶»¾6™kôF|ò©èšåÛÖ¯—nÕ=ƒŸž5ÜzࢆÖkL½6mD?diþ•?ïò5y²][Y»h‘ì1³°ô+äv²ƒzîë´ÚÇ|=êú¢€˜ýÙgrã£<²ümš*Ö¬å±ÿe¸p¿9UÙJg˰9¿ZëÄþfֽμ“.s¡ Œ·»¿÷aíkzö²ÖUÕ¨t߯•+d 쯚?OªÖo`J4Þgœw¾<6q’kfœÎxÖ›-3 ¬Ò§Ï?'G j^k—šN1Ä!ß}oÍ"þÎ.;ô{ÈjVei¹_³K¬ÝQ³¼Ä^3c699—õ‚{Ÿ|•£ëC·¼»«|~"0~Åm·KÞ?¤Ôsõ+Þƒ§N37”«àQTŸqïe˜ëš¾T·g±ë zÝh]Ì Çôz½¬ó-RØ÷ÜÓÛô·è]ž}^µkgÒ<Ï™ëôs-bf2^Þå¶ ×²{öó§®K?ÿò[»Ø»ÌM±jÊš?ÿ flO77ˆó•´mšî7³WõCŒg®ï(:¶9MÁ¬ÅÝÐÌÒg,ÜgOÛõûÆëÖÓ N|8bïןw÷›ÌéŒç§§}/wšõ”—›,|¥åæZíùÆ›¢kæ’F,07²+TØ•õ´Š•dØ/s¬ŸÅ¹_ef?ì:f?Ñ@æÓßÿè3}zWÕ*rÔ|f½žx]öyú¨3‚÷íÜ!UêÕ—»† wÒ´ý.½ÄšU¯3x½g³º2z=Ùi>XóNúÁ—÷ÏÉš?ÿ”~},•ë¤`nŸs—™ «ué9vÒ×4]²F•;6o¶·ö1}Üa>ìhØöZ¹]gÝ›TqèPYýû|+¨ýË„ÏEƒâê¨é)óó¥¯Ù:;[?ÑYîšôƒ=é|y—Ûåúþý­} 2ù¿³^/~4AþÍìu}½ $Íýúk+ÛúåËdö„ æÇÍ¢7ª|zÚ§_ÿÈ£¦Ü>úö3Ëú¬4AØŸ¬åUάVM*T¯ný³_GôçÜ;ýôárÀ,)¡êuyæ9ë°¾4ù+éÝ ¾,˜ú­uCµ\n¯ÃÞe° €±'àØ€nËvMd¢¹¹–?I…Oܸìè‘ÐÍVõW¯Þ Kÿ­0³O‡?ñ®üúó"³~jQéØå §¸öïÞ¹Wõim~µ‡G€Ngý:=}ýÙÏV0W×Ïí3øVÑÓšvnÛ-÷ßê{¶W(ú\ÀÜœí€ ê>5²§”-Ÿ>³)åjuž'ù ä•Ý;÷Y7k+~bö°ëqãsã½ÃfIŠÖrYë“A ½i›þM)nDŒ<8ÐÓȇ@Ì l0_!ÿÒ|¥\¿N«ëêl!M-n7Á-w¿é±A‚¹šÉÏéÁÑÆæ¦JÞIƒŠÐg}µÙ3 ›bÞ´»‚¹'NÔ`«Þ½þˆ¹Óýî­[­Ù¶ÞeÚÛ ¾ýÆzª_ßuO¨ÕÙÁúõ`}#}ŠYrÀ=é›w÷ÝtV±ì`®½]Ï|­Wº‹Lí@›uÌÔáþ•Z;¿¿G ÌjuF]/Rƒ˜ššµku¦ò…-[f8Õ=˜k´:CØWÒ¯sÛy|wßç/ÔÈÌšÓ€®.IáÐÕ²3KP¸'-§BÍšòÏÈk×H™3Ïr?ìñü{sÓ+Mv0×ý`íWXÁ Ë—[3Ýùz®×ív3ãÑ=?~L†˜%3t¹ï`•{¾`žkàÜ;˜«çû›ý\öìÊÝ@êÒ›‘iò¸Æt‡¹Îª6¼H–ÿ2;=hæ5“P³¸']‚D}‹óè[3Y—]xùî»2,Cà~¾>?«Zu9íì³­ùi°²nËVÞY‚ÚÖ€Y I—&QWõ¯3$Ï®]Ç:UƒÍì+}時˜îÁ\»÷À£½Ïýñ³ÄB Á\=Ï=˜k—cÿœí1¯S¾’)Ý_gôzlÔ¾½õz¨f}mñ—¾;Ö:Ô÷ý2dilfyN2Î?úI.0ËY’lÒ(C6]&aàDÏŸe]öÄ;˜«'úCý­Ýý{vgèêë‚̵+¿íÙçä¹›n”Ko¾ÙÌÕcêTؘÕr·ùð¬ø‰™Á?¼;Î:ÕæÚåè£^—z}êR jÔp?ä÷ùè^=<Ž]y×Ýb¯Ýì~@_«}¥º­ZY] k@74íí·­l¾n ذm[™úææÃ°ÙÖ2”G@ˆ Çtõkïyóå±–4øú³7óE«k«j:h¾ÒäÈQë†Xîù–˜Ù¾štýÕP¥sªW—Þ}Xînÿ¸èM è.šŸÞ½ñU¸f[ê­ö,˜Põ5Ðr>7ÍÊÚËÌd¶ƒ¹ž›“|`×¥Öüµ)ä]m×Uf½bíÛ«C>”ÃÒgGèòÌÕä̵vøù/WJúušÙ2˜Ö5 xØh¾R­ÿì¤knÞ<èqëkåö>÷G_{mD VسªÜÏÑY`þ’{°Ã=25 «_ùÎì|ûœ:ÿÏ~êzܾi£õ\×Ýôèz·Sg|ùKEJ¥µÅ*sšZÝÛ]& Qt¦a»ÒgϾv"ÀÐòîôYÎÞuèŒÛ&`¢7Óµ8S±²è×ã}%]¾"ؤe¯[²D¶¬_';ÍŒµf¶&½Yi†d~‡ùJúõa è.5ËPdÐM5Ëdhò5f;NŒ™Î‚Ó¯–g•ª6h(}Þ}Ï•MM^ºãvY2ãgk&d0wW!>ž\py {OîÚh>Ñqé ›öšY•9¹¹Ÿ/—-'nf¦ëD—Èb|Ó?8¹eÈÓ'hžÙË.è2:ß_€Î>©—YZà¡f—Èè^=¥ŽY’CËÙMÍRÁ¤^o¾e­×úÍëc¤û¨ÑÖ©ö#íû>ä³(ý»J—"Ù¶qƒå¯³À3Kt6Y?‹sÍlÔ›d“ûÏ¢ŸotùúÀ¤V³æV@÷/ÓÖ̺W¬°šçëzÐŒiZbÖ¾4 ÛßÇ:¿e+Ÿm•ãþßÙæ[™%]Fa½ùÀEoìµ{ëóº±ÔÊ®7¬õN¾^ßK—?ÓÊ–’'}Bû9eΪ`t˜¹vÒ¼4ùrÐ×*M:Û6Ѐ®¾&è‡ó͇o?ÔO¾1K.T3ˤTmÐÀ*Ëû?]Ëyݲ¥ÖR(z“B{æ½ý{Ï;¿¯m{Ml_}س-ýÀ•¿ýJ@×û@ˆaÇtÕôŽÞ×É«O(ãÇ~+º^líç¹þÞ×?¬W.^#Ïg™,[¸Zš´HŸdlÕ¡©u“»1CÇË}3tY‰üzm." ëÞ9ìŵVXš²šL%ºž¢.ïpÀÌúò—ôM¹<Öú´wšÇÂ%KYë^ºíŠêÓK;Ýlt¿~m”+ »dÆ «M¾˜úõ`{F™Î\ôÉI‡4(qÿE ¬µ+u¶an3329WФ¥ ºØ"'Ö•Õ ¿¤õÙhú3]—ô¼Æž3®ý•ç½_û KØ7&ÒYçºr¸’~XÐëÂÚVŸ4x§c”d–|8rðd@*º5Àj§Ì\ÊV®bgóûøNÿ‡­cï˜e Æ=úˆG>ýÐEÇ`¥ Jž×¨±Ç1ï ½yWÅ Ì,÷ß—É#_u­Çë/mû¦`åü,õà]†5+×üA 7R³Óló}MöMíýú¸Ö|ñ¤YçX“õs’;ÏÉ_úÖÞœÿ7ßÌXÕý« œü,æ+XÐ*c߉ ¬¿–ÙÜdv=覊ææm9IzÝô¨]ËZþ@¯#5Ð׊Ô#éÐä¤lçê4D×”™CM·oXø+Ë}¿Î×¥=t&òC—^ln~v‹µF®ûßäÚßûê×µ¾ááÞ_]"ؤ3æ5eÖ‡&×ßl±äG@( xFµ¢Ü˜`«¿°ÑùÒöæË¬¥t Ós³³Bæ†XÇRÓd¿¹Y™jsçN1_KŸõØûñ[¤ëu›µwWIÏ›†H…sN·fìþ½bƒ5 ¨ZÊR¬¸ÿYRYµo¶ Ò1AN;½¤Ôª®¹IÖ>ùmú"ë´'_íîqºTíÙéi+½yýVyõãGåü Òg+üln„¦ËÔ¬{ŽL4Ûº˜{÷ï;@ð(0ˆjµÏ–™Ó~—®íŸòO“7n“—?èD ÙÏzã-­õfî:\.¿º¡³hýŇ엚õ™%J]ÿ÷Ïy+¥k‡'ä4s¼šõ«Ê¦u[dͪV€ûÅqýľÙ^Ö%fÌQÕÌȶoò¦}Ó¤4ügÖy~àÖçäŠkYß¹3KÁÂùeïîŒo¸oé~Ð}û•‰òýW¿ÊÞ=ûåágî’eŠÉu/—Ì> îßÓñIkwEs ëõ»ÑôCº,Çq\Øÿ…MN“ýÆ;=Þdf¹Ï>˜'ozßtíZïdóÞß{PgéfnN§çhôëñ?ËÂ_—[³ªo¸ã*Ñ { Én—w^-CÓGoLqÒ5{\RSvnßcíŸg‚¹îj%½½Ù•ÇýIãÖ¶S3«Mº>´^ÏöLf BŒüd€>h–rб›:q¶õXæ´âr¿×õë^.ψg{f®ÎBÚ¿kW†®êÓ5å/˜ñ»cf”wÒ™ý5ÛÒåË{ö¹mç÷y0ÆvÚ3¢xÿ]™:öM«u¿Oj=íbÖœtæÚÇCñ¨kkjoÖˆµƒ¹Y•«{_Ë=h@DÓYæ&A™%¬I—yWúÇ55Kˆd•ìå7ìYtYå·k~]ûX“½œ}Ìߣ¸Õ›bùK{3 Äú;ÇÞ?É^5u}ùUk}gýz¹÷?=®íÎlö»æÑ¤kY7éØÑz>Ö|==;I¯ ]‹Yÿn«~ñÅq׋é}ÿìÓ2íĺË-»¥Ïu/dÛ†õV[g¿²<‹û¹>ÿãǬ¬ŸâÌÍêü][þËåËW^±öeõs¢ëÛjò7«Ó:¡ÿôg^ƒ›šÜƒ¹‘¨>‘ô¿ß·šqGÒªia–j±“þúM_¯Ív>÷Gý°Ê_*S¡¢u('K±ø+›ý € =”èUºšO;½”ôrò¡ÌJÖ€iϾƒiÞ祘ٽo}éûS-Ç×lÈ®vý—UJ6kúyí>ŸÙJ›àœÎõN=éä½KªÕ®ì³šñB³´€¯6&%'™’uÉPVf;|•ã+A|Í*ïÅW\(úÏ;yŸ§tï}ö9—\UWôŸ¯¤AT©^Óê¢ÿ²›üµGË;³RÙ íÍeÜw÷é`ýó®Ó_Y×ÜØLôŸ¿tÃíW‰þ#!€ÀIºææ>s¿š,úÜ*~>ÁÔ4êž>›éQsGqï¤_kíÓø"ynú × œ¾|õëká‹÷Ξa»…™5¨7”y³ïƒrïÈQ®z­ŒæMùÆU«¤\•*Î ÕŽ‡›_&§Uª(=G¿p‘˜;kšú¦ æš6j¯Ø‰¹bïÓ5iõ&evš:ö-ûiŽó™ »ÞhJ|öM€Ø/Ãnéì·l vÜuNe1ÿw±oFõÛÿÛ;()Н‹?2ìD(" ˆ(˜A@@r”œ• ‚’vAÉ %#( $‘œ£ Y”$Y¢‚€HN‚ß»5[ãäew¿¿ ÷3ÓÝÕÕÕÝ¿îž={ûÕ­ äªf‡BŒ,ê…w—É;ÉðæM¥Ç¼^V=2é€\÷ Gr ÎÁt3G†lN9¢éß-ÝÕ‚—}±Á’ì`}žçðTá²sÕ*“ XÔ£‹u5oñâFGY>?>©xñ?WÜ?ðƒ~MíK0è ìÒê½zNí¢/”)kšØ²`¾>‹ûä¹R¥>؈iÒ¨mÉ3ïù…kرh#BâžÄ ,D ½et¢ÎSjÉð„Z‡R1û¼O›g¿—‡Úa„CÔ t?`}LFîB…dïúõÒâ©'¥DÃ7õeÙó;ªçˆ—9±«V•ùŸ0/\Ìï…¾„ÊSäUÃ>ëˆèr¨òÞû2wØP™9 ¿´ÑƒˆË——ß~kÎ÷5ÍþÆÀfð;Ojþ¶™J._/W¬(KÆŒ‘ú[’*•Éh¶Ç•ïµâjíW­v™ßÚ$ú7 _‰rX_$Ùß [×¥IÎ’ üÇ ÄÿH€Hà>$JE<ƒ‰)L5"ÄŠ±¿ì;H<'!æ¦TµóÌYòþx‡µ€ÝêãƒÌ·‘?ïÒÊŸuй«¸5hÃFI£¢˜kø²lÀú1{÷I µ ÐuÔõ³FÜ…˜ ñããEîYzøG:PxfÁ¡nHD—_ÏãÁr¼xñ™YQ Û ꇅùÜqƒ7n6¶§ÔSbnFÍ\¢~ÁYµ;¶ç±BÔ žõQ·^×0iÔ Ù bÄܺÝÂeÈ–­¦,iòP3µ_¸þÈêóË>œª¬ñ…Ä:ˆ²=—,“:ˆ–kø;¦~«×JãÍ=€ áíêK f‹\›ð9Ÿ%wn/A ûʪv­>û\o¿àÍuã¤úÂÀW<ùr©Ù±“Y1çb­ì}ï™qgÛ ×—e›57]þ‘)‘ºýWS¤ÅP‡]€­g§‰'‘Fý˜Eˆ¹;ŠL…÷ý(õ øáîX¶Ôˆ¹Ï-¦¢è یϩ̈́­®u À³—'®ž×©Óg0›ø³,€AÊULOÛƒÂÕkºeM"{3}¶lR¤NéªÙúc÷¸'1;«Ùá#³O|Õ×Ìn_ghȦ-&»>»ðûM¦¿W}W­‘—ËWðÚ$TÅ6DòÔ¾{Øß;ÈêšgQ÷gý·C˜›>{v¬eÙòæÓ{ÍýÙC¶{|å€gµä[oÖsñ2¿‡/•uÏ ¶–*¡.ÛØo§ofȇ“&›{ƒ@BPFh–§òh;³Q%Òð÷ znhÏ·¯øð‹ÉR»s³j•f^ã¥YÓACÏÂfø›ýÂoE²”Žìi[†©ýb³û]×¥J÷ˆY´¶,v]Ÿå+Íà¸Öømß¾x±4ƒçuŸ¿ÐV 8Ŷþ¢Œ¾ŒÁú+W:«4Ñßņ={™å5S§1·aÏÞúf­)óìP£]yµ–£‡ ì)2dÏál 3as¾“wÇŽ3Ù»75ówëÂ…FÌ…ÐÛuö·º\   ˆâi×ÅâÆ¡ò(I€H€H€H€H€H€H€H€H€HàÁ&À ÝûúóìI€H€H€H€H€H€H€H€H€â ºqèbñPI€H€H€H€H€H€H€H€H€ltìëϳ'        ˆC(èÆ¡‹ÅC%        x° PÐ}°¯?ÏžH€H€H€H€H€H€H€H€H   ‡.•H€H€H€H€H€H€H€H€HàÁ&@A÷Á¾þ<{         8D€‚nºXS3múA Á'.GŽ'•nƒZÆåSà±A`ݲí2I³ËÿùçyE3Ê[xd”OŸ¸XŠ•yY2<šVîü}ÇdÛwÐL$Làl}x¯©²cÓ/òný>âù;é±t>¸…$ÒL^ÓÆ-’ås^íÇxÕ·u8%       ÿ28¡û_ëylªYE)¬@‹ŒU±Q³•êóWÅwyDõø£Zß÷Æ`iÏ9àñG¥­¨Ôõwº1ц¿¶ÿGå?nÜ+_ Ÿ#O$q’D²iíNiZ%Üy4ׯÝ%ßn?NÿiÊBS$“°Á-ÝÄ\¬¨×¬‚sÏ™ž#Û¸‰¹X_¹îkžÕ¸L$@$@$@$@$@$@$@qŠÀ}‘¡]âMût'çÿøËd &IšØømÚvOêhD'»ìôÛ¯–˺e?ÊÅ —Í&ñâÇ“ì¹4ûôÓA5Q h^Ù¼n—Ì›¾F*×yÍk›9SV˜²òÕŠ8×úå„ ì:Q´¬$E˼è,ÇÌà“e÷öCæá?Zõ’5{&·:® ÈþýøƒQròèS¬=×%gž¬Ò©_SÓݵ.æ8)£L—³§/8WeΚNºôoæ×ãÔY1Š3³&/“åó6ÊgӻɔÑóeí’m¦…Îzl¹žyÜÌO³Àð»|ñªYޝüs>­Çß·©Y¶_K¿Û ß~µB†Lî(¿ìúU& ýV=WoHüñ¥L•BR§q9SuÜàY¦ ÿÝ»ÿÆêÅš6}jÛŒÛu·mØ#È¢F¤yä!iÖ@˞ѭž¿…»wïJÿ.åúÅbˆŒY‘®›K¨‹_lóêKÖM¹g[Wÿ¤°s¥Bí¢R¥nqçê%sÖËüékåÚ•ë¦,EÊPy£eEÉ_4Ÿ³f¾R®VìѳÂeì ™²qµãÂ'Ã[ËОSÌö£fþ+ºnüN^’4YbÁ³ã+fêõƒ;fvw³zõ¢-òåçó¤{ÛϤy»Z~ˆÈ<°¯^ö¶Z0úùºuÃq=|­n­Ç OÞ~c?az~?mÞgª¥z8…Þ3M$Cæ´æÙéÜb¨œùí¼Y—%{ùdx¯ævn;`˜Á‘*ur½þo˜çß«2 H€H€H€H€H€H€H€H ø ].¾5@þúó’T®÷šÔz»Œ$P!å§M¥bY&IèÒÅ;X¶#zO5¢YHò¤RºJA)WýUI©Â¼;»´T3U”4õæN]éUÝÔ/^¸bĽФÎõ7up³[·në Q7œe˜é¬ûܵí f,&”"¥_ô™ÓÈŒ‰KeúÄ%nõì¼I›UëaÄÜWK½`âgóç–{ŽÉ;µ½-.ÍZ'=?mÄÜü*D—«^؈q§ŽýaôËq_Í6¸JWzŹ~Pø$™>a‰Þ7I¥ZÃ’R¡fs¯Œ0CV¨@îׯÜÜOSÇ.4bnÚt™ûí–Ö÷T¾ìf@½o&,vÝÄÌÿ´eŸÄËæ_¼†èŸ.ÃÃÎm‹WÈ/%*æ—ãG~—n­‡Ë±Ã¿Iõ7KÕYÉÇ D`Dýæ}¬õ.‚ø€Pï×Tćøßºn/#æ¾\äÁ ‰‹^ˆ¸x±ßkʃâÅω#§õºŽtk >¿Cz|i~7A\SSBB“I_}iÄ        è¸/2t‘©éÏÈí7{Òµn›z½Íâ`ÍÎD¦"¢BÍ¢Ò¨R7¹pî’|2Â;ûÎTŠä«Ôë¥e‡:F@µU‘éÙªVOùýä9#ÆXOGF tB†æ¥¿®Hʇ’Û¦Y¥ˆ2Õ¼…)g¥ˆL§uŸ~G~ÓÕ™]»_Ã~Æ{V7Ë+C¾üHÒ ED)gkÖñÍî\¬Yžå«½jÊ!‚Íœ´ÌÌüº«É^5 ú…k3MÁŽMíY »ë lÛAßü„‰þõNµåðC…g0O×(S¹W&n½¦¤¹ŠÔ{vv­êœ_³x«|ÐãM#æ¢b4ÄÅ)£‘Ù¨È:E zïÏGdꘅҰÕë¦ _€‘Ý[µA ©R¯„³¼n“òF GÆó¨aÎr_3}õšÜVÑ´}Ï·åéçŸpVÁËËct <¬C<õlvÙýã!Yµp³ ¢L¾°ý_*DÂ?6$"£ƒÎ¡.„æ5ËÖFͷʘcƒp[Rï[db»„Þ6]êË‹…ò8‹}<½lX¹Ãˆé87×€`ŒðôÄu­3þ»]Í|ÃV••ee¹võº@½*hDUˆæ'Tø¬b.Dý<ÏæÒ• úª.'i}}~ †OÑŒmÜ«x®üY5@ÔMš,‰ŒŸû‰¹æh4üÝ‘râ×ÓÖf„äΗM>êÓÄì í6­Ú]EèÓæE8bî׎ ‡~ÕÉ,ã«¢þ¦0H€H€H€H€H€H€H€H &Vcbÿm@0ôü¬]¶-J{¶b®Ý(åC¡rE»sÛîî¶<Ø)2‘ ëÏæÒA &*ÔrAëWþèV]Ô•ëüÛ•Þ­‚Ë‚˜Ðå±'Ÿy\š}XÓ.:§Èzµb®]Y£ai3;oÚj[$‹UÄDÔjTÆMÌE™«ÐK‹`Ö Èvÿt÷¹éãOdòsQñ…‚ÿŠ®>š-ƒë¢Ûüûá b.V ;Ó†«˜‹²ö½™U'ž¶UÌÈÊ.XN˜(¡dRË„šY|ãúMù dÜBTtsQ9Uê¦|ÏŽCÎm[´¯mæÌXë,ÃÌÎíÌr5µÕ°1~èl3Û}h+[d¦¸'^ŒàuüÈonë°ýÉ,nb.ÊŠÎÅZZ ËÈBÆÀeÉB’ (ÊlVQë¸!³˜ Ë dAû‹°ÖŽú£Ô Ç™"eˆ4j[Í_u#füº‹SÌEE;È ,/¬˜‹rX¨¤Ñgqêø3å Ä6oÅ1¶÷ íûÊöƒ ¸ô×U#XÙ¬»è´…m!fÂ"Áfž'犔¬P@ªX7{òr©Pãß,¿?~ÿSìÀi‘ö‹€€ëž‚-ÖïÞqÐTËúDfAÖª¯°çuÈE¼ðŠõñ šYyZŽ:%i´Û~dá²@±¼nÕâaô,Ñ¡wc¥ÞE°|¸|Qzµ3ðdðöÃÅ}tçï;*Pº¿û°Âø•TÏ6­§¯ky"õŒEØ,P×užó)ô…‚/öž÷cr(ahdåÚ—ð2F/ŸßÙ4l4ë–nw–Ù{>ö56#¶Ó°A-\󰂘8lŽÌÒ{³QÛª¦üàÞãfZI}{c#Nhg2t/©8»÷§ÃÆûo¶\™%|p+¯]œØ^3tïÊ•K×äÚàÅO߯I¦ÇÒIïÏÛzÕG&¸çoG–ˆ—°=#{îGå¬r=§Yô>GTÖÌìaŸ|%^ï&åÔî£v£rn/R<Ûà2 D…À}!èz 0QТ}-óéL ]è¿WÝdèMåðÙ„O潺…CìZ¿Â=³6ªí¥VX„«íÂÚÕQ¶ja3ìë¢Ú5D%lý÷ >¾"©Kö%|Vð°õÙžÌl]™ÁDÚt©öÑ ˆ°£úO—í~Ž?ضãköê +ßÖAälLþl®u›B8M’Ä[´•àç‹8«‚½¿6<ï÷²êϼhæ:Ùòý.“­Œëñ÷í;æ…2}m@ðEøkë2f~“ ¢PñçÌ=¾N³á­ ;(l’Ù¶lU‡GP E¡’„îõßÍ¡™Ã¬ß­ßG~=pJý©KŽÜ¹µß_êgÓ 1P ü‰;þ‡¬]ºMŠ•}É­¾¯+vûZ— ãÅžwÏi>,*>ïÿZpl0dŒ·éR®…Ä) À=¸/Ý{>{Ýp—zŠ"0U‡ÆŸšy|¡»{£wY‡ÎÂ(Ì|¤ž±ÈÜË£§èžoE! âdí‚m®V£²2S»ò/šõ½é^ïP’ &’hfè58ƒ +VT»ø«è~ŒQÏ8~èwSêGðõ¬ËíõZÂCö¥Ây¤uçúÎ&ûuž ûwýê\ŽÉ™Dj«`㋽ìl”¦Ö¦àñœ™¥ûïŒS_•Sa‚îxµ!€—ð¦5;Mµ¶Ý¸UGv/^ Üë±¹5¦ –S§Ii²ƒ‘}ýhÖôQöö<<·‰éeÜoÙ”Õ¯Oɦµ»¼]Ïý!ËöéçrÈÍîݱ闠]Ï6‚Y†ßðõá=ñëïÒ¿óDùQ_,4S¿]xó2H€H€H€H€H€H€H€H :Üû‘G§¥8ºí«vHáRÏËØo{Ȱ)™ÝÝ9èÔ½žÄ\,¬˜{¯m•‹|lå‚M‚ìK ?Ï`Ý÷°‘ð ׬R»îqµZ@lY¿ÛœB´F`p(_qì°Ã—õ‰ÜY|­Ž•2ˆ¹]ÅÜXÙQ,5zæ·óA·œ"•ãúbƒ«W®ËŒ/–˜m=-6`epÍ&5ÑøêÜ¿™Ùz¾”·có>3ÿn×zÑh1ê›ÆWË         ˆJÐ3u¥,œµÎÉ ÝÒ;6퓽?–ĉéÀSÉ%q„ש³bÄ |š>q±l^ëÈ€ô\o— f»¶;üimÝ`¦È¬Äek–íö šW fSS§hG·ò!é¶Íù³ɰžSÜʰ`Ztû¿ªÃEU”2UÆ|:ÃK,ܹÍ18Wüøñ%åCÉ#k*FÖÛì|éu§Ž‰‘}øk$gG—ÿó6ú«i9|¯«=űC!<Ò ´Bóv5Mµï—o7½¸_<­ ÖÔéÛq\0MU'mú‡T8O`2P§ŒvX•<õlŽ ¶J¥ŸT,öõòYá‡ÕQ¸ÄsÎ&÷i6|s=m슸' sðð¬ÝekmaÛÁ +ºã%ƒH€H€H€H€H€H€H€¢Cà¾P $ú "f4€Pó¾^mª•ÐÆÐ%BT·J›Ç…Oöj"ï‹9å½ð†Îï÷íüÕøa¢b ]8!¡I°†nððºEvìˆÞSÊåu.åÕ'uꘅ2nð,SšG»jÑs\&Gµ[úP¬©x¹üj5q@V.ج]Ô³81ÛŽÿÍw* ì!ÚÔëclPïí®øÀ ÃYÃÕ:„\ˆ˜¬.ë@U­ëö–·t[Ø+ü¸i¯±—@{C¾ìh›õ)ð¤ÉËë·dú„%Ɔ×x¯©ÎAÃbë p¿´QSuð­Ûî5°¸pî¢y™€ÌÛѳÂîþƒoJ÷¶ŸI÷?—ÒU J¾r Tûã÷ó2ûËå’.cé2À‘k‚G+ç‹ø°Ç[fêúUëí²æzüå¸À’¢¶.c: 0¶fÉVÙ¹u”ípmq/îÜzÀÜã)5ÜÕzÂuÿÑ™_ôí÷æåC†GÓ*Óü’6ýÃrpï1Y<û{ÓlF-Ïš#“s«mVOáÝÆ¢tÕB’1SZ9rà¤ÌŸ¾ÆÔÁyz† ÝÃLóê=Œe žÙdú;°yÝNól¤P{/‹îa7Ü„H€H€H€H€H€H€Hà%§] xÌz†z^Ãì¹²8]›!§ú“ N œU­È>¸¥dȜֈR½Y‘¢ÖIDAT§ŽŸ1â)²i!ÎÀ a»x›?_°nxÿÍþ²A0ÃñXö Ò E%éûÑx?[ù/.õzA#ŠŽ½„ã€æ+l±çúÁ“;ªGð ùyË~óÁú’• È-_—F•ºyµ^ˆÒ¤g©lí'M–Tà¯ëçv–>šùyHÅÂÑÿØÑ¬,‚ÉL´ÇŽlÞÈÂÖõWoà„öÒ®Ñ@Y2g½ùˆ7DérU Ëgý¾qÛ,P[Ö¡Ïõ¡É“Qô]Âqÿ¸fdCè,_³ˆÛ¾}-<–=£ôó¾u—ÏÝ(øØ€ X©v1»èœÂÖŠê°Èœ5sëÌç3¤cÓAröô5`ºs÷z®@nç²™ñ<9÷µÎ¥º¿iî!„ésÑQ­K:6$gN—ic9wg¾ÁÛVs–a¦ÕGuåÔ±áòûɳ2#BäF9Äþ'óf—޽aÑ+²ç3…ÑÂÕîáõ߆Gïžn¬G&ó€ñí0Ë        hˆ§ö*>ñ·v·Ž§ba‚ÏÍÝ*¸ ê>ÙˆAEʼèB'Âu)ø×B˜sp¼6t)€çí5í>Ÿ\³4%vèçè’mç]ªF: ËØ.À"PjÝÐÑFJͨµÂ4º¡ʪ„/+ÎumjE,_Ç»ƒ+—®šLä7£š‘èØ=÷L]tÉ¿~í¦¤Hj2²Ñ†¯í|•¡.˜ãœZßUFî'ØøúPm‚DÂd!I£ÌmØãÇ= ž¾ŽõlܾuÛˆçþŽÉÖÃy]¾xÍœÁó7ˆ™?.¶;müz˜™8¿§-Š•)~®®^¾n|¤qoù„Ïsǰ ¹‰çVŸ?Üþâ®zÜ¢}O« ÔôŒøb„v±Ž)ž™È®›¿cb9 xˆÓºž'Ù²g–èшÁºÐÛ30€‘¯ˆª@‰ÌI|\ã^Ä\lÌÕĉ#Ï^ Ô¾/,˜‹ý"ëŸ`bot¼r»çþƒ© ±×ðµ¯2læþ†=ï'×}`mÂ7:áëøµ—(±ßn‹ó‚_tdá‹ëvÛ6ì1ÂeÞ—r¹ÇÊ<„j_÷p AP â2Ø¾Ú ôŒøb„ãŒÎsàëXF$@$@$@$@$@$@$@ ¹:xsÊ!@Á*`ÕÂÍrâ×ÓÚMû3hR«ZŽLúMËßÇxj$}“?›kw2ƒH€H€H€H€H€H€H€H v */ #include #include #ifdef _WIN32 #define usleep(x) Sleep(x / 1000) #else #include #endif #include "srt.h" int main(int argc, char** argv) { int ss, st; struct sockaddr_in sa; const int no = 0; const char message [] = "This message should be sent to the other side"; if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } printf("SRT startup\n"); srt_startup(); printf("Creating SRT socket\n"); ss = srt_create_socket(); if (ss == SRT_ERROR) { fprintf(stderr, "srt_socket: %s\n", srt_getlasterror_str()); return 1; } printf("Creating remote address\n"); sa.sin_family = AF_INET; sa.sin_port = htons(atoi(argv[2])); if (inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1) { return 1; } int epollid = srt_epoll_create(); if (epollid == -1) { fprintf(stderr, "srt_epoll_create: %s\n", srt_getlasterror_str()); return 1; } printf("srt setsockflag\n"); if (SRT_ERROR == srt_setsockflag(ss, SRTO_RCVSYN, &no, sizeof no) || SRT_ERROR == srt_setsockflag(ss, SRTO_SNDSYN, &no, sizeof no)) { fprintf(stderr, "SRTO_SNDSYN or SRTO_RCVSYN: %s\n", srt_getlasterror_str()); return 1; } // When a caller is connected, a write-readiness event is triggered. int modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; if (SRT_ERROR == srt_epoll_add_usock(epollid, ss, &modes)) { fprintf(stderr, "srt_epoll_add_usock: %s\n", srt_getlasterror_str()); return 1; } printf("srt connect\n"); st = srt_connect(ss, (struct sockaddr*)&sa, sizeof sa); if (st == SRT_ERROR) { fprintf(stderr, "srt_connect: %s\n", srt_getlasterror_str()); return 1; } // We had subscribed for write-readiness or error. // Write readiness comes in wready array, // error is notified via rready in this case. int rlen = 1; SRTSOCKET rready; int wlen = 1; SRTSOCKET wready; if (srt_epoll_wait(epollid, &rready, &rlen, &wready, &wlen, -1, 0, 0, 0, 0) != -1) { SRT_SOCKSTATUS state = srt_getsockstate(ss); if (state != SRTS_CONNECTED || rlen > 0) // rlen > 0 - an error notification { fprintf(stderr, "srt_epoll_wait: reject reason %s\n", srt_rejectreason_str(srt_getrejectreason(rready))); return 1; } if (wlen != 1 || wready != ss) { fprintf(stderr, "srt_epoll_wait: wlen %d, wready %d, socket %d\n", wlen, wready, ss); return 1; } } else { fprintf(stderr, "srt_connect: %s\n", srt_getlasterror_str()); return 1; } int i; for (i = 0; i < 100; i++) { rready = SRT_INVALID_SOCK; rlen = 1; wready = SRT_INVALID_SOCK; wlen = 1; // As we have subscribed only for write-readiness or error events, // but have not subscribed for read-readiness, // through readfds we are notified about an error. int timeout_ms = 5000; // ms int res = srt_epoll_wait(epollid, &rready, &rlen, &wready, &wlen, timeout_ms, 0, 0, 0, 0); if (res == SRT_ERROR || rlen > 0) { fprintf(stderr, "srt_epoll_wait: %s\n", srt_getlasterror_str()); return 1; } printf("srt sendmsg2 #%d >> %s\n", i, message); st = srt_sendmsg2(ss, message, sizeof message, NULL); if (st == SRT_ERROR) { fprintf(stderr, "srt_sendmsg: %s\n", srt_getlasterror_str()); return 1; } usleep(1000); // 1 ms } // Let's wait a bit so that all packets reach destination usleep(100000); // 100 ms // In live mode the connection will be closed even if some packets were not yet acknowledged. printf("srt close\n"); st = srt_close(ss); if (st == SRT_ERROR) { fprintf(stderr, "srt_close: %s\n", srt_getlasterror_str()); return 1; } printf("srt cleanup\n"); srt_cleanup(); return 0; } srt-1.4.4/examples/recvfile.cpp000066400000000000000000000056321412557703600164710ustar00rootroot00000000000000#ifndef _WIN32 #include #include #else #include #include #endif #include #include #include #include #include using namespace std; int main(int argc, char* argv[]) { if ((argc != 5) || (0 == atoi(argv[2]))) { cout << "usage: recvfile server_ip server_port remote_filename local_filename" << endl; return -1; } // use this function to initialize the UDT library srt_startup(); srt_setloglevel(srt_logging::LogLevel::debug); struct addrinfo hints, *peer; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; SRTSOCKET fhandle = srt_create_socket(); // SRT requires that third argument is always SOCK_DGRAM. The Stream API is set by an option, // although there's also lots of other options to be set, for which there's a convenience option, // SRTO_TRANSTYPE. SRT_TRANSTYPE tt = SRTT_FILE; srt_setsockopt(fhandle, 0, SRTO_TRANSTYPE, &tt, sizeof tt); if (0 != getaddrinfo(argv[1], argv[2], &hints, &peer)) { cout << "incorrect server/peer address. " << argv[1] << ":" << argv[2] << endl; return -1; } // connect to the server, implicit bind if (SRT_ERROR == srt_connect(fhandle, peer->ai_addr, peer->ai_addrlen)) { cout << "connect: " << srt_getlasterror_str() << endl; return -1; } freeaddrinfo(peer); // send name information of the requested file int len = strlen(argv[3]); if (SRT_ERROR == srt_send(fhandle, (char*)&len, sizeof(int))) { cout << "send: " << srt_getlasterror_str() << endl; return -1; } if (SRT_ERROR == srt_send(fhandle, argv[3], len)) { cout << "send: " << srt_getlasterror_str() << endl; return -1; } // get size information int64_t size; if (SRT_ERROR == srt_recv(fhandle, (char*)&size, sizeof(int64_t))) { cout << "send: " << srt_getlasterror_str() << endl; return -1; } if (size < 0) { cout << "no such file " << argv[3] << " on the server\n"; return -1; } // receive the file //fstream ofs(argv[4], ios::out | ios::binary | ios::trunc); int64_t recvsize; int64_t offset = 0; SRT_TRACEBSTATS trace; srt_bstats(fhandle, &trace, true); if (SRT_ERROR == (recvsize = srt_recvfile(fhandle, argv[4], &offset, size, SRT_DEFAULT_RECVFILE_BLOCK))) { cout << "recvfile: " << srt_getlasterror_str() << endl; return -1; } srt_bstats(fhandle, &trace, true); cout << "speed = " << trace.mbpsRecvRate << "Mbits/sec" << endl; int losspercent = 100*trace.pktRcvLossTotal/trace.pktRecv; cout << "loss = " << trace.pktRcvLossTotal << "pkt (" << losspercent << "%)\n"; srt_close(fhandle); //ofs.close(); // use this function to release the UDT library srt_cleanup(); return 0; } srt-1.4.4/examples/recvlive.cpp000066400000000000000000000131201412557703600165000ustar00rootroot00000000000000#ifndef WIN32 #include #include #else #include #include #endif #include #include #include #include #include #include // assert using namespace std; int main(int argc, char* argv[]) { // usage: recvlive [server_port] if ((2 < argc) || ((2 == argc) && (0 == atoi(argv[1])))) { cout << "usage: recvlive [server_port]" << endl; return 0; } // use this function to initialize the UDT library srt_startup(); srt_setloglevel(srt_logging::LogLevel::debug); addrinfo hints; addrinfo* res; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; string service("9000"); if (2 == argc) service = argv[1]; if (0 != getaddrinfo(NULL, service.c_str(), &hints, &res)) { cout << "illegal port number or port is busy.\n" << endl; return 0; } SRTSOCKET sfd = srt_create_socket(); if (SRT_INVALID_SOCK == sfd) { cout << "srt_socket: " << srt_getlasterror_str() << endl; return 0; } // SRT requires that third argument is always SOCK_DGRAM. The Stream API is set by an option, // although there's also lots of other options to be set, for which there's a convenience option, // SRTO_TRANSTYPE. // SRT_TRANSTYPE tt = SRTT_LIVE; // if (SRT_ERROR == srt_setsockopt(sfd, 0, SRTO_TRANSTYPE, &tt, sizeof tt)) // { // cout << "srt_setsockopt: " << srt_getlasterror_str() << endl; // return 0; // } bool no = false; if (SRT_ERROR == srt_setsockopt(sfd, 0, SRTO_RCVSYN, &no, sizeof no)) { cout << "srt_setsockopt: " << srt_getlasterror_str() << endl; return 0; } // Test the deprecated option feature here: //srt_setsockopt(sfd, 0, SRTO_STRICTENC, &no, sizeof no); // Windows UDP issue // For better performance, modify HKLM\System\CurrentControlSet\Services\Afd\Parameters\FastSendDatagramThreshold #ifdef WIN32 int mss = 1052; srt_setsockopt(sfd, 0, SRTO_MSS, &mss, sizeof(int)); #endif // int64_t maxbw = 5000000; // srt_setsockopt(sfd, 0, SRTO_MAXBW, &maxbw, sizeof maxbw); if (SRT_ERROR == srt_bind(sfd, res->ai_addr, res->ai_addrlen)) { cout << "srt_bind: " << srt_getlasterror_str() << endl; return 0; } freeaddrinfo(res); cout << "server is ready at port: " << service << endl; if (SRT_ERROR == srt_listen(sfd, 10)) { cout << "srt_listen: " << srt_getlasterror_str() << endl; return 0; } int epid = srt_epoll_create(); if (epid < 0) { cout << "srt_epoll_create: " << srt_getlasterror_str() << endl; return 0; } int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; if (SRT_ERROR == srt_epoll_add_usock(epid, sfd, &events)) { cout << "srt_epoll_add_usock: " << srt_getlasterror_str() << endl; return 0; } const int srtrfdslenmax = 100; SRTSOCKET srtrfds[srtrfdslenmax]; char data[1500]; // the event loop while (true) { int srtrfdslen = srtrfdslenmax; int n = srt_epoll_wait(epid, &srtrfds[0], &srtrfdslen, 0, 0, 100, 0, 0, 0, 0); assert(n <= srtrfdslen); for (int i = 0; i < n; i++) { SRTSOCKET s = srtrfds[i]; SRT_SOCKSTATUS status = srt_getsockstate(s); if ((status == SRTS_BROKEN) || (status == SRTS_NONEXIST) || (status == SRTS_CLOSED)) { cout << "source disconnected. status=" << status << endl; srt_close(s); continue; } else if (s == sfd) { assert(status == SRTS_LISTENING); SRTSOCKET fhandle; sockaddr_storage clientaddr; int addrlen = sizeof(clientaddr); fhandle = srt_accept(sfd, (sockaddr*)&clientaddr, &addrlen); if (SRT_INVALID_SOCK == fhandle) { cout << "srt_accept: " << srt_getlasterror_str() << endl; return 0; } char clienthost[NI_MAXHOST]; char clientservice[NI_MAXSERV]; getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST|NI_NUMERICSERV); cout << "new connection: " << clienthost << ":" << clientservice << endl; int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; if (SRT_ERROR == srt_epoll_add_usock(epid, fhandle, &events)) { cout << "srt_epoll_add_usock: " << srt_getlasterror_str() << endl; return 0; } } else { while (true) { int ret = srt_recvmsg(s, data, sizeof(data)); if (SRT_ERROR == ret) { // EAGAIN for SRT READING if (SRT_EASYNCRCV != srt_getlasterror(NULL)) { cout << "srt_recvmsg: " << srt_getlasterror_str() << endl; return 0; } break; } // cout << ret << " bytes received" << endl; } } } } srt_close(sfd); srt_epoll_release(epid); // use this function to release the UDT library srt_cleanup(); return 0; } // Local Variables: // c-file-style: "ellemtel" // c-basic-offset: 3 // compile-command: "g++ -Wall -O2 -std=c++11 -I.. -I../srtcore -o recvlive recvlive.cpp -L.. -lsrt -lpthread -L/usr/local/opt/openssl/lib -lssl -lcrypto" // End: srt-1.4.4/examples/sendfile.cpp000066400000000000000000000110061412557703600164530ustar00rootroot00000000000000#ifndef _WIN32 #include #include #else #include #include #endif #include #include #include #include #include using namespace std; #ifndef _WIN32 void* sendfile(void*); #else DWORD WINAPI sendfile(LPVOID); #endif int main(int argc, char* argv[]) { //usage: sendfile [server_port] if ((2 < argc) || ((2 == argc) && (0 == atoi(argv[1])))) { cout << "usage: sendfile [server_port]" << endl; return 0; } // use this function to initialize the UDT library srt_startup(); srt_setloglevel(srt_logging::LogLevel::debug); addrinfo hints; addrinfo* res; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; string service("9000"); if (2 == argc) service = argv[1]; if (0 != getaddrinfo(NULL, service.c_str(), &hints, &res)) { cout << "illegal port number or port is busy.\n" << endl; return 0; } SRTSOCKET serv = srt_create_socket(); // SRT requires that third argument is always SOCK_DGRAM. The Stream API is set by an option, // although there's also lots of other options to be set, for which there's a convenience option, // SRTO_TRANSTYPE. SRT_TRANSTYPE tt = SRTT_FILE; srt_setsockopt(serv, 0, SRTO_TRANSTYPE, &tt, sizeof tt); // Windows UDP issue // For better performance, modify HKLM\System\CurrentControlSet\Services\Afd\Parameters\FastSendDatagramThreshold #ifdef _WIN32 int mss = 1052; srt_setsockopt(serv, 0, SRTO_MSS, &mss, sizeof(int)); #endif //int64_t maxbw = 5000000; //srt_setsockopt(serv, 0, SRTO_MAXBW, &maxbw, sizeof maxbw); if (SRT_ERROR == srt_bind(serv, res->ai_addr, res->ai_addrlen)) { cout << "bind: " << srt_getlasterror_str() << endl; return 0; } freeaddrinfo(res); cout << "server is ready at port: " << service << endl; srt_listen(serv, 10); sockaddr_storage clientaddr; int addrlen = sizeof(clientaddr); SRTSOCKET fhandle; while (true) { if (SRT_INVALID_SOCK == (fhandle = srt_accept(serv, (sockaddr*)&clientaddr, &addrlen))) { cout << "accept: " << srt_getlasterror_str() << endl; return 0; } char clienthost[NI_MAXHOST]; char clientservice[NI_MAXSERV]; getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST|NI_NUMERICSERV); cout << "new connection: " << clienthost << ":" << clientservice << endl; #ifndef _WIN32 pthread_t filethread; pthread_create(&filethread, NULL, sendfile, new SRTSOCKET(fhandle)); pthread_detach(filethread); #else CreateThread(NULL, 0, sendfile, new SRTSOCKET(fhandle), 0, NULL); #endif } srt_close(serv); // use this function to release the UDT library srt_cleanup(); return 0; } #ifndef _WIN32 void* sendfile(void* usocket) #else DWORD WINAPI sendfile(LPVOID usocket) #endif { SRTSOCKET fhandle = *(SRTSOCKET*)usocket; delete (SRTSOCKET*)usocket; // aquiring file name information from client char file[1024]; int len; if (SRT_ERROR == srt_recv(fhandle, (char*)&len, sizeof(int))) { cout << "recv: " << srt_getlasterror_str() << endl; return 0; } if (SRT_ERROR == srt_recv(fhandle, file, len)) { cout << "recv: " << srt_getlasterror_str() << endl; return 0; } file[len] = '\0'; // open the file (only to check the size) fstream ifs(file, ios::in | ios::binary); ifs.seekg(0, ios::end); int64_t size = ifs.tellg(); //ifs.seekg(0, ios::beg); ifs.close(); // send file size information if (SRT_ERROR == srt_send(fhandle, (char*)&size, sizeof(int64_t))) { cout << "send: " << srt_getlasterror_str() << endl; return 0; } SRT_TRACEBSTATS trace; srt_bstats(fhandle, &trace, true); // send the file int64_t offset = 0; if (SRT_ERROR == srt_sendfile(fhandle, file, &offset, size, SRT_DEFAULT_SENDFILE_BLOCK)) { cout << "sendfile: " << srt_getlasterror_str() << endl; return 0; } srt_bstats(fhandle, &trace, true); cout << "speed = " << trace.mbpsSendRate << "Mbits/sec" << endl; int losspercent = 100*trace.pktSndLossTotal/trace.pktSent; cout << "loss = " << trace.pktSndLossTotal << "pkt (" << losspercent << "%)\n"; srt_close(fhandle); //ifs.close(); #ifndef _WIN32 return NULL; #else return 0; #endif } srt-1.4.4/examples/test-c-client-bonding.c000066400000000000000000000203111412557703600204120ustar00rootroot00000000000000/* * 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 #ifdef _WIN32 #define usleep(x) Sleep(x / 1000) #else #include #endif #include "srt.h" struct { const char* name; int gtype; } group_types [] = { { "broadcast", SRT_GTYPE_BROADCAST } // Others will follow }; #define SIZE(array) (sizeof array/sizeof(array[0])) // Note that in this example application there's a socket // used first to connect to the service and then it will be // used for writing. Therefore the same function will be used // for waiting for the socket to be connected and then to wait // for write-ready on the socket used for transmission. For a // model of waiting for read-ready see test-c-server-bonding.c file. int WaitForWriteReady(int eid, SRTSOCKET ss) { int ready_err[2]; int ready_err_len = 2; int ready_out[2]; int ready_out_len = 2; int st = srt_epoll_wait(eid, ready_err, &ready_err_len, ready_out, &ready_out_len, -1, 0, 0, 0, 0); // Note: with indefinite wait time we can either have a connection reported // or possibly error. Also srt_epoll_wait never returns 0 - at least the number // of ready connections is reported or -1 is returned for error, including timeout. if (st < 1) { fprintf(stderr, "srt_epoll_wait: %s\n", srt_getlasterror_str()); return 0; } // Check if this was reported as error-ready, in which case it doesn't // matter if read-ready. if (ready_err[0] == ss) { int reason = srt_getrejectreason(ss); fprintf(stderr, "srt_epoll_wait: socket @%d reported error reason=%d: %s\n", ss, reason, srt_rejectreason_str(reason)); return 0; } return 1; } int main(int argc, char** argv) { int ss, st; struct sockaddr_in sa; //int yes = 1; // for options, none needed so far const char message [] = "This message should be sent to the other side"; if (argc < 3) { fprintf(stderr, "Usage: %s { }... \n", argv[0]); return 1; } int gtype = SRT_GTYPE_BROADCAST; size_t i; for (i = 0; i < SIZE(group_types); ++i) if (0 == strcmp(group_types[i].name, argv[1])) { gtype = group_types[i].gtype; break; } int is_nonblocking = 0; size_t nmemb = argc - 2; if (nmemb < 2) { fprintf(stderr, "Usage error: no members specified\n"); return 1; } if (nmemb % 2) { // Last argument is then optionset --nmemb; const char* opt = argv[argc-1]; if (strchr(opt, 'n')) is_nonblocking = 1; } nmemb /= 2; printf("srt startup\n"); srt_startup(); // Declare all variables before any destructive goto. // In C++ such a code that jumps over initialization would be illegal, // in C it causes an uninitialized value to be used. int eid = -1; int write_modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; SRT_SOCKGROUPDATA* grpdata = NULL; SRT_SOCKGROUPCONFIG* grpconfig = calloc(nmemb, sizeof (SRT_SOCKGROUPCONFIG)); printf("srt group\n"); ss = srt_create_group(gtype); if (ss == SRT_ERROR) { fprintf(stderr, "srt_create_group: %s\n", srt_getlasterror_str()); goto end; } const int B = 2; for (i = 0; i < nmemb; ++i) { printf("srt remote address #%zi\n", i); sa.sin_family = AF_INET; sa.sin_port = htons(atoi(argv[B + 2*i + 1])); if (inet_pton(AF_INET, argv[B + 2*i], &sa.sin_addr) != 1) { fprintf(stderr, "inet_pton: can't resolve address: %s\n", argv[B + 2*i]); goto end; } grpconfig[i] = srt_prepare_endpoint(NULL, (struct sockaddr*)&sa, sizeof sa); } if (is_nonblocking) { int blockingmode = 0; srt_setsockflag(ss, SRTO_RCVSYN, &blockingmode, sizeof (blockingmode)); eid = srt_epoll_create(); srt_epoll_add_usock(eid, ss, &write_modes); } printf("srt connect (group)\n"); // Note: this function unblocks at the moment when at least one connection // from the array is established (no matter which one); the others will // continue in background. st = srt_connect_group(ss, grpconfig, nmemb); if (st == SRT_ERROR) { fprintf(stderr, "srt_connect: %s\n", srt_getlasterror_str()); goto end; } // In non-blocking mode the srt_connect function returns immediately // and displays only errors of the initial usage, not runtime errors. // These could be reported by epoll. if (is_nonblocking) { // WRITE-ready means connected printf("srt wait for socket reporting connection success\n"); if (!WaitForWriteReady(eid, ss)) goto end; } // In non-blocking mode now is the time to possibly change the epoll. // As this socket will be used for writing, it is in the right mode already. // Just set the right flag, as for non-blocking connect it needs RCVSYN. if (is_nonblocking) { int blockingmode = 0; srt_setsockflag(ss, SRTO_SNDSYN, &blockingmode, sizeof (blockingmode)); } // Important: Normally you need that at least one link is ready for // the group link to be ready. All but first are done actually in // background, so this sleep only makes it more probable. If you'd like // to make sure that ALL links are established - by some reason - then // you'd have to subscribe for epoll event SRT_EPOLL_UPDATE and after the // connect function exits do checks by srt_group_data to see if all links // are established, and if not, repeat it after srt_epoll_wait for the // SRT_EPOLL_UPDATE signal. printf("sleeping 1s to make it probable all links are established\n"); sleep(1); grpdata = calloc(nmemb, sizeof (SRT_SOCKGROUPDATA)); for (i = 0; i < 100; i++) { printf("srt sendmsg2 #%zd >> %s\n",i,message); SRT_MSGCTRL mc = srt_msgctrl_default; mc.grpdata = grpdata; mc.grpdata_size = nmemb; // Set maximum known if (is_nonblocking) { // Block in epoll as srt_recvmsg2 will not block. if (!WaitForWriteReady(eid, ss)) goto end; } st = srt_sendmsg2(ss, message, sizeof message, &mc); if (st == SRT_ERROR) { fprintf(stderr, "srt_sendmsg: %s\n", srt_getlasterror_str()); goto end; } // Perform the group check. This can be used to recognize broken connections // and probably reestablish them by calling `srt_connect` for them. Here they // are only shown. printf(" ++ Group status [%zi]:", mc.grpdata_size); if (!mc.grpdata) { printf(" (ERROR: array too small!)\n"); } else { for (i = 0; i < mc.grpdata_size; ++i) { printf( "[%zd] result=%d state=%s ", i, mc.grpdata[i].result, mc.grpdata[i].sockstate <= SRTS_CONNECTING ? "pending" : mc.grpdata[i].sockstate == SRTS_CONNECTED ? "connected" : "broken"); } printf("\n"); } usleep(1000); // 1 ms } end: if (eid != -1) { srt_epoll_release(eid); } printf("srt close\n"); st = srt_close(ss); if (st == SRT_ERROR) { fprintf(stderr, "srt_close: %s\n", srt_getlasterror_str()); return 1; } free(grpdata); free(grpconfig); //cleanup: printf("srt cleanup\n"); srt_cleanup(); return 0; } srt-1.4.4/examples/test-c-client.c000066400000000000000000000050261412557703600170020ustar00rootroot00000000000000/* * 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 #ifdef _WIN32 #define usleep(x) Sleep(x / 1000) #else #include #endif #include "srt.h" 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"; if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } printf("srt startup\n"); srt_startup(); printf("srt socket\n"); ss = srt_create_socket(); if (ss == SRT_ERROR) { fprintf(stderr, "srt_socket: %s\n", srt_getlasterror_str()); return 1; } printf("srt remote address\n"); sa.sin_family = AF_INET; sa.sin_port = htons(atoi(argv[2])); if (inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1) { return 1; } printf("srt setsockflag\n"); srt_setsockflag(ss, SRTO_SENDER, &yes, sizeof yes); // Test deprecated //srt_setsockflag(ss, SRTO_STRICTENC, &yes, sizeof yes); printf("srt connect\n"); st = srt_connect(ss, (struct sockaddr*)&sa, sizeof sa); if (st == SRT_ERROR) { fprintf(stderr, "srt_connect: %s\n", srt_getlasterror_str()); return 1; } int i; for (i = 0; i < 100; i++) { printf("srt sendmsg2 #%d >> %s\n",i,message); st = srt_sendmsg2(ss, message, sizeof message, NULL); if (st == SRT_ERROR) { fprintf(stderr, "srt_sendmsg: %s\n", srt_getlasterror_str()); return 1; } usleep(1000); // 1 ms } printf("srt close\n"); st = srt_close(ss); if (st == SRT_ERROR) { fprintf(stderr, "srt_close: %s\n", srt_getlasterror_str()); return 1; } printf("srt cleanup\n"); srt_cleanup(); return 0; } srt-1.4.4/examples/test-c-server-bonding.c000066400000000000000000000207361412557703600204550ustar00rootroot00000000000000/* * 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 "srt.h" // Note that in this example application there's a listening // socket, off which then a transmission socket is accepted, // then this socket will be used for reading. Therefore the same // function will be used for waiting for the listener to get the // accepted socket ready and then to wait for read-readiness on // the transmission socket. For a model of waiting for write-ready // see test-c-client-bonding.c file. int WaitForReadReady(int eid, SRTSOCKET ss) { int ready_in[2]; int ready_in_len = 2; int ready_err[2]; int ready_err_len = 2; int st = srt_epoll_wait(eid, ready_in, &ready_in_len, ready_err, &ready_err_len, -1, 0, 0, 0, 0); // Note: with indefinite wait time we can either have a connection reported // or possibly error. Also srt_epoll_wait never returns 0 - at least the number // of ready connections is reported or -1 is returned for error, including timeout. if (st < 1) { fprintf(stderr, "srt_epoll_wait: %s\n", srt_getlasterror_str()); return 0; } // Check if this was reported as error-ready, in which case it doesn't // matter if read-ready. if (ready_err[0] == ss) { fprintf(stderr, "srt_epoll_wait: socket @%d reported error\n", ss); return 0; } if (ready_in[0] != ss) { fprintf(stderr, "srt_epoll_wait: socket @%d not reported ready\n", ss); return 0; } return 1; } int main(int argc, char** argv) { int globstatus = 0; int ss, st; struct sockaddr_in sa; int yes = 1; struct sockaddr_storage their_addr; SRT_SOCKGROUPDATA* grpdata = NULL; if (argc < 3 || argc > 4) { fprintf(stderr, "Usage: %s [options]\n", argv[0]); return 1; } printf("srt startup\n"); srt_startup(); // Since now, srt_cleanup() must be done before exiting. printf("srt socket\n"); ss = srt_create_socket(); if (ss == SRT_ERROR) { fprintf(stderr, "srt_socket: %s\n", srt_getlasterror_str()); globstatus = 1; goto cleanup; } // Now that the socket is created, jump to 'end' on error. // Check options int is_nonblocking = 0; SRTSOCKET their_fd = SRT_INVALID_SOCK; // declared early because of gotos int eid = -1; int lsn_modes = SRT_EPOLL_IN | SRT_EPOLL_ERR; int read_modes = lsn_modes; if (argc > 3) { const char* opt = argv[3]; if (strchr(opt, 'n')) is_nonblocking = 1; } printf("srt bind address\n"); if (0 == strcmp(argv[1], "0")) { memset(&sa, 0, sizeof sa); } else if (inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1) { fprintf(stderr, "srt_bind: Can't resolve address: %s\n", argv[1]); globstatus = 1; goto end; } sa.sin_family = AF_INET; sa.sin_port = htons(atoi(argv[2])); printf("srt setsockflag: groupconnect\n"); srt_setsockflag(ss, SRTO_GROUPCONNECT, &yes, sizeof yes); printf("srt bind\n"); st = srt_bind(ss, (struct sockaddr*)&sa, sizeof sa); if (st == SRT_ERROR) { fprintf(stderr, "srt_bind: %s\n", srt_getlasterror_str()); globstatus = 1; goto end; } if (is_nonblocking) { int blockingmode = 0; srt_setsockflag(ss, SRTO_RCVSYN, &blockingmode, sizeof (blockingmode)); eid = srt_epoll_create(); srt_epoll_add_usock(eid, ss, &lsn_modes); } printf("srt listen\n"); // We set here 10, just for a case. Every unit in this number // defines a maximum number of connections that can be pending // simultaneously - it doesn't matter here if particular connection // will belong to a bonding group or will be a single-socket connection. st = srt_listen(ss, 10); if (st == SRT_ERROR) { fprintf(stderr, "srt_listen: %s\n", srt_getlasterror_str()); globstatus = 1; goto end; } // In this example, there will be prepared an array of 10 items. // The listener, however, doesn't know how many member connections // one bonded connection will contain, so a real application should be // prepared for dynamically adjusting the array size. const size_t N = 10; grpdata = calloc(N, sizeof (SRT_SOCKGROUPDATA)); // In non-blocking mode you can't call srt_accept immediately. // You must first wait for readiness on the listener socket. if (is_nonblocking) { printf("srt wait for listener socket reporting in a new connection\n"); if (!WaitForReadReady(eid, ss)) goto end; } printf("srt accept\n"); int addr_size = sizeof their_addr; their_fd = srt_accept(ss, (struct sockaddr *)&their_addr, &addr_size); if (their_fd == -1) { fprintf(stderr, "srt_accept: %s\n", srt_getlasterror_str()); goto end; } printf("accepted socket: @%d\n", their_fd); // You never know if `srt_accept` is going to give you a socket or a group. // You have to check it on your own. The SRTO_GROUPCONNECT flag doesn't disallow // single socket connections. int isgroup = their_fd & SRTGROUP_MASK; if (!isgroup) { fprintf(stderr, "srt_accept: Accepted @%d is not a group???\n", their_fd); goto end; } if (is_nonblocking) { // NOTE: The SRTO_RCVSYN = false flag will be derived from // the listener socket and we are going to read, so it matches // the need. In case when you'd like to write to the accepted // socket, you'd have to set also SRTO_SNDSYN = false. srt_epoll_add_usock(eid, their_fd, &read_modes); // The listener socket is no longer important. srt_epoll_remove_usock(eid, ss); } // Still, use the same procedure for receiving, no matter if // this is a bonded or single connection. int i; for (i = 0; i < 100; i++) { printf("srt recvmsg #%d... ",i); char msg[2048]; SRT_MSGCTRL mc = srt_msgctrl_default; mc.grpdata = grpdata; mc.grpdata_size = N; if (is_nonblocking) { // Block in epoll as srt_recvmsg2 will not block. if (!WaitForReadReady(eid, their_fd)) goto end; } st = srt_recvmsg2(their_fd, msg, sizeof msg, &mc); if (st == SRT_ERROR) { fprintf(stderr, "srt_recvmsg: %s\n", srt_getlasterror_str()); goto end; } printf("Got msg of len %d << %s (%s)\n", st, msg, isgroup ? "group" : "single"); if (!isgroup) continue; if (!mc.grpdata) printf("Group status: [%zi] members > %zi, can't handle.\n", mc.grpdata_size, N); else { printf(" ++ Group status [%zi]: ", mc.grpdata_size); size_t z; for (z = 0; z < mc.grpdata_size; ++z) { printf( "[%zd] result=%d state=%s ", z, mc.grpdata[z].result, mc.grpdata[z].sockstate <= SRTS_CONNECTING ? "pending" : mc.grpdata[z].sockstate == SRTS_CONNECTED ? "connected" : "broken"); } printf("\n"); } } end: if (eid != -1) { srt_epoll_release(eid); } free(grpdata); printf("srt close\n"); st = srt_close(their_fd); // just for a case; broken socket should be wiped out anyway if (st == SRT_ERROR) { fprintf(stderr, "srt_close: %s\n", srt_getlasterror_str()); // But not matter, we're finishing here. } st = srt_close(ss); if (st == SRT_ERROR) { fprintf(stderr, "srt_close: %s\n", srt_getlasterror_str()); // But not matter, we're finishing here. } cleanup: printf("srt cleanup\n"); srt_cleanup(); return globstatus; } srt-1.4.4/examples/test-c-server.c000066400000000000000000000052441412557703600170340ustar00rootroot00000000000000/* * 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 "srt.h" int main(int argc, char** argv) { int ss, st; struct sockaddr_in sa; int yes = 1; struct sockaddr_storage their_addr; if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } printf("srt startup\n"); srt_startup(); printf("srt socket\n"); ss = srt_create_socket(); if (ss == SRT_ERROR) { fprintf(stderr, "srt_socket: %s\n", srt_getlasterror_str()); return 1; } printf("srt bind address\n"); sa.sin_family = AF_INET; sa.sin_port = htons(atoi(argv[2])); if (inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1) { return 1; } printf("srt setsockflag\n"); srt_setsockflag(ss, SRTO_RCVSYN, &yes, sizeof yes); printf("srt bind\n"); st = srt_bind(ss, (struct sockaddr*)&sa, sizeof sa); if (st == SRT_ERROR) { fprintf(stderr, "srt_bind: %s\n", srt_getlasterror_str()); return 1; } printf("srt listen\n"); st = srt_listen(ss, 2); if (st == SRT_ERROR) { fprintf(stderr, "srt_listen: %s\n", srt_getlasterror_str()); return 1; } printf("srt accept\n"); int addr_size = sizeof their_addr; int their_fd = srt_accept(ss, (struct sockaddr *)&their_addr, &addr_size); int i; for (i = 0; i < 100; i++) { printf("srt recvmsg #%d... ",i); char msg[2048]; st = srt_recvmsg(their_fd, msg, sizeof msg); if (st == SRT_ERROR) { fprintf(stderr, "srt_recvmsg: %s\n", srt_getlasterror_str()); goto end; } printf("Got msg of len %d << %s\n", st, msg); } end: printf("srt close\n"); st = srt_close(ss); if (st == SRT_ERROR) { fprintf(stderr, "srt_close: %s\n", srt_getlasterror_str()); return 1; } printf("srt cleanup\n"); srt_cleanup(); return 0; } srt-1.4.4/examples/testcapi-connect.c000066400000000000000000000044141412557703600175720ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include #include #include "srt.h" int main( int argc, char** argv ) { if (argc < 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } 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_create_socket(); 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; } // This is obligatory only in live mode, if you predict to connect // to a peer with SRT version 1.2.0 or older. Not required since // 1.3.0, and all older versions support only live mode. //srt_setsockflag(ss, SRTO_SENDER, &yes, sizeof yes); // // In order to make sure that the client supports non-live message // mode, let's require this. int minversion = SRT_VERSION_FEAT_HSv5; srt_setsockflag(ss, SRTO_MINVERSION, &minversion, sizeof minversion); // Require also non-live message mode. int file_mode = SRTT_FILE; srt_setsockflag(ss, SRTO_TRANSTYPE, &file_mode, sizeof file_mode); srt_setsockflag(ss, SRTO_MESSAGEAPI, &yes, sizeof yes); // Note that the other side will reject the connection if the // listener didn't set the same mode. 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_send(ss, message, sizeof message); 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; } srt-1.4.4/haicrypt/000077500000000000000000000000001412557703600141655ustar00rootroot00000000000000srt-1.4.4/haicrypt/cryspr-config.h000066400000000000000000000011471412557703600171260ustar00rootroot00000000000000#ifndef INC_SRT_CRYSPR_CONFIG_H #define INC_SRT_CRYSPR_CONFIG_H // Size of the single block for encryption. // This might need tweaking for particular implementation library. #define CRYSPR_AESBLKSZ 16 /* 128-bit */ #if defined(USE_OPENSSL) #include "cryspr-openssl.h" #define cryspr4SRT() crysprOpenSSL() #elif defined(USE_GNUTLS) #include "cryspr-gnutls.h" #define cryspr4SRT() crysprGnuTLS() #elif defined(USE_MBEDTLS) #include "cryspr-mbedtls.h" #define cryspr4SRT() crysprMbedtls() #else #error Cryspr implementation not selected. Please define USE_* + OPENSSL/GNUTLS/MBEDTLS. #endif #endif srt-1.4.4/haicrypt/cryspr-gnutls.c000066400000000000000000000141651412557703600171740ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. 2019-06-27 (jdube) GnuTLS/Nettle CRYSPR/4SRT (CRYypto Service PRovider for SRT) *****************************************************************************/ #include "hcrypt.h" #include typedef struct tag_crysprGnuTLS_AES_cb { CRYSPR_cb ccb; /* CRYSPR control block */ /* Add other cryptolib specific data here */ } crysprGnuTLS_cb; int crysprGnuTLS_Prng(unsigned char *rn, int len) { return(gnutls_rnd(GNUTLS_RND_KEY,(rn),(len)) < 0 ? -1 : 0); } int crysprGnuTLS_AES_SetKey( bool bEncrypt, /* true:encrypt key, false:decrypt key*/ const unsigned char *kstr, /* key string */ size_t kstr_len, /* kstr length in bytes (16, 24, or 32 bytes (for AES128,AES192, or AES256) */ CRYSPR_AESCTX *aes_key) /* Cryptolib Specific AES key context */ { if (bEncrypt) { /* Encrypt key */ if (!(kstr_len == 16 || kstr_len == 24 || kstr_len == 32)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_encrypt_key(kek) bad length\n"); return -1; } aes_set_encrypt_key (aes_key, kstr_len, kstr); } else { /* Decrypt key */ if (!(kstr_len == 16 || kstr_len == 24 || kstr_len == 32)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_decrypt_key(kek) bad length\n"); return -1; } aes_set_decrypt_key (aes_key, kstr_len, kstr); } return(0); } int crysprGnuTLS_AES_EcbCipher( /* AES Electronic Codebook cipher*/ bool bEncrypt, /* true:encrypt, false:decrypt */ CRYSPR_AESCTX *aes_key, /* CryptoLib AES context */ const unsigned char *indata,/* src (clear text)*/ size_t inlen, /* length */ unsigned char *out_txt, /* dst (cipher text) */ size_t *outlen) /* dst len */ { int nblk = inlen/CRYSPR_AESBLKSZ; int nmore = inlen%CRYSPR_AESBLKSZ; int i; if (bEncrypt) { /* Encrypt packet payload, block by block, in output buffer */ for (i=0; i #include //gnutls_rnd() #include //has AES cipher #include //has CTR cipher mode #include //has Password-based Key Derivation Function 2 //#include //No need for sha1 since we have pbkdf2 /* Define CRYSPR_HAS_AESCTR to 1 if this CRYSPR has AESCTR cipher mode if not set it 0 to use enable CTR cipher mode implementation using ECB cipher mode and provide the aes_ecb_cipher method. */ #define CRYSPR_HAS_AESCTR 1 /* Define CRYSPR_HAS_AESKWRAP to 1 if this CRYSPR has AES Key Wrap if not set to 0 to enable default/fallback crysprFallback_AES_WrapKey/crysprFallback_AES_UnwrapKey methods and provide the aes_ecb_cipher method . */ #define CRYSPR_HAS_AESKWRAP 0 /* Define CRYSPR_HAS_PBKDF2 to 1 if this CRYSPR has SHA1-HMAC Password-based Key Derivaion Function 2 if not set to 0 to enable not-yet-implemented/fallback crysprFallback.km_pbkdf2 method and provide the sha1_msg_digest method. */ #define CRYSPR_HAS_PBKDF2 1 /* #define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK It is set from hte keystring through CRYSPR_methods.aes_set_key and passed to CRYSPR_methods.aes_XXX. */ typedef struct aes_ctx CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */ struct tag_CRYSPR_methods *crysprGnuTLS(void); #endif /* CRYSPR_GNUTLS_H */ srt-1.4.4/haicrypt/cryspr-mbedtls.c000066400000000000000000000167171412557703600173170ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. 2019-06-27 (jdube) GnuTLS/Nettle CRYSPR/4SRT (CRYypto Service PRovider for SRT) *****************************************************************************/ #include "hcrypt.h" #include #include #include #include #include // Static members of cryspr::mbedtls class. static mbedtls_ctr_drbg_context crysprMbedtls_ctr_drbg; static mbedtls_entropy_context crysprMbedtls_entropy; static mbedtls_md_context_t crysprMbedtls_mdctx; typedef struct tag_crysprGnuTLS_AES_cb { CRYSPR_cb ccb; /* CRYSPR control block */ /* Add other cryptolib specific data here */ } crysprMbedtls_cb; int crysprMbedtls_Prng(unsigned char *rn, int len) { int ret = mbedtls_ctr_drbg_random( &crysprMbedtls_ctr_drbg, rn, len ); if (ret != 0) { return -1; } return 0; } int crysprMbedtls_AES_SetKey( bool bEncrypt, /* true:encrypt key, false:decrypt key*/ const unsigned char *kstr, /* key string */ size_t kstr_len, /* kstr length in bytes (16, 24, or 32 bytes, for AES128,AES192, or AES256) */ CRYSPR_AESCTX *aes_key) /* Cryptolib Specific AES key context */ { if (!(kstr_len == 16 || kstr_len == 24 || kstr_len == 32)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_encrypt_key(kek) bad length\n"); return -1; } int ret; // mbedtls uses the "bits" convention (128, 192, 254), just like openssl. // kstr_len is in "bytes" convention (16, 24, 32). if (bEncrypt) { /* Encrypt key */ ret = mbedtls_aes_setkey_enc(aes_key, kstr, kstr_len*8); } else { /* Decrypt key */ ret = mbedtls_aes_setkey_dec(aes_key, kstr, kstr_len*8); } return ret == 0 ? 0 : -1; } int crysprMbedtls_AES_EcbCipher( /* AES Electronic Codebook cipher*/ bool bEncrypt, /* true:encrypt, false:decrypt */ CRYSPR_AESCTX *aes_key, /* CryptoLib AES context */ const unsigned char *indata,/* src (clear text)*/ size_t inlen, /* length */ unsigned char *out_txt, /* dst (cipher text) */ size_t *outlen) /* dst len */ { int nblk = inlen/CRYSPR_AESBLKSZ; int nmore = inlen%CRYSPR_AESBLKSZ; int i; if (bEncrypt) { /* Encrypt packet payload, block by block, in output buffer */ for (i = 0; i < nblk; i++) { // NOTE: CRYSPR_AESBLKSZ is implicitly the ONLY POSSIBLE // size of the block. mbedtls_aes_crypt_ecb(aes_key, MBEDTLS_AES_ENCRYPT, &indata[(i*CRYSPR_AESBLKSZ)], &out_txt[(i*CRYSPR_AESBLKSZ)]); } /* Encrypt last incomplete block */ if (0 < nmore) { unsigned char intxt[CRYSPR_AESBLKSZ]; memcpy(intxt, &indata[(nblk*CRYSPR_AESBLKSZ)], nmore); memset(intxt+nmore, 0, CRYSPR_AESBLKSZ-nmore); mbedtls_aes_crypt_ecb(aes_key, MBEDTLS_AES_ENCRYPT, intxt, &out_txt[(nblk*CRYSPR_AESBLKSZ)]); nblk++; } if (outlen != NULL) *outlen = nblk*CRYSPR_AESBLKSZ; } else { /* Decrypt */ for (i=0; i #include /* Define CRYSPR_HAS_AESCTR to 1 if this CRYSPR has AESCTR cipher mode if not set it 0 to use enable CTR cipher mode implementation using ECB cipher mode and provide the aes_ecb_cipher method. */ #define CRYSPR_HAS_AESCTR 1 /* Define CRYSPR_HAS_AESKWRAP to 1 if this CRYSPR has AES Key Wrap if not set to 0 to enable default/fallback crysprFallback_AES_WrapKey/crysprFallback_AES_UnwrapKey methods and provide the aes_ecb_cipher method . */ #define CRYSPR_HAS_AESKWRAP 0 /* Define CRYSPR_HAS_PBKDF2 to 1 if this CRYSPR has SHA1-HMAC Password-based Key Derivaion Function 2 if not set to 0 to enable not-yet-implemented/fallback crysprFallback.km_pbkdf2 method and provide the sha1_msg_digest method. */ #define CRYSPR_HAS_PBKDF2 1 // mbedtls uses in the enc/dec functions 16-byte blocks // for xcryption. This is not marked by any constant. See // e.g. , mbedtls_aes_crypt_ecb signature. #if CRYSPR_AESBLKSZ != 16 #error mbedtls requires AES single block size 16 bytes, implicitly. #endif /* #define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK It is set from hte keystring through CRYSPR_methods.aes_set_key and passed to CRYSPR_methods.aes_XXX. */ typedef mbedtls_aes_context CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */ struct tag_CRYSPR_methods *crysprMbedtls(void); #endif /* CRYSPR_GNUTLS_H */ srt-1.4.4/haicrypt/cryspr-openssl.c000066400000000000000000000173171412557703600173450ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. 2019-06-26 (jdube) OpenSSL CRYSPR/4SRT (CRYypto Service PRovider for SRT). *****************************************************************************/ #include "hcrypt.h" #include typedef struct tag_crysprOpenSSL_AES_cb { CRYSPR_cb ccb; /* Add cryptolib specific data here */ } crysprOpenSSL_cb; int crysprOpenSSL_Prng(unsigned char *rn, int len) { return(RAND_bytes(rn, len) <= 0 ? -1 : 0); } int crysprOpenSSL_AES_SetKey( bool bEncrypt, /* true Enxcrypt key, false: decrypt */ const unsigned char *kstr, /* key sttring*/ size_t kstr_len, /* kstr len in bytes (16, 24, or 32 bytes (for AES128,AES192, or AES256) */ CRYSPR_AESCTX *aes_key) /* CRYpto Service PRovider AES Key context */ { if (bEncrypt) { /* Encrypt key */ if (AES_set_encrypt_key(kstr, kstr_len * 8, aes_key)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_encrypt_key(kek) failed\n"); return(-1); } } else { /* Decrypt key */ if (AES_set_decrypt_key(kstr, kstr_len * 8, aes_key)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_decrypt_key(kek) failed\n"); return(-1); } } return(0); } #if !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP) int crysprOpenSSL_AES_EcbCipher( bool bEncrypt, /* true:encrypt, false:decrypt */ CRYSPR_AESCTX *aes_key, /* CRYpto Service PRovider AES Key context */ const unsigned char *indata,/* src (clear text if encrypt, cipher text otherwise)*/ size_t inlen, /* indata length */ unsigned char *out_txt, /* dst (cipher text if encrypt, clear text otherwise) */ size_t *outlen) /* in/out dst len */ { int nblk = inlen/CRYSPR_AESBLKSZ; int nmore = inlen%CRYSPR_AESBLKSZ; size_t outsiz = (outlen ? *outlen : 0); int i; if (outsiz % CRYSPR_AESBLKSZ) return(-1); /* output buf size must be a multiple of AES block size (16) */ if (bEncrypt) { if (outsiz > 16 && outsiz < (nblk+nmore)*CRYSPR_AESBLKSZ) return(-1); /* output buf size must have room for PKCS7 padding */ /* Encrypt packet payload, block by block, in output buffer */ for (i=0; i= 0x10100000L && !defined(OPENSSL_IS_BORINGSSL)) CRYPTO_ctr128_encrypt(indata, out_txt, inlen, aes_key, iv, ctr, &blk_ofs, (block128_f) AES_encrypt); #else AES_ctr128_encrypt(indata, out_txt, inlen, aes_key, iv, ctr, &blk_ofs); #endif return 0; } /* * Password-based Key Derivation Function */ int crysprOpenSSL_KmPbkdf2( CRYSPR_cb *cryspr_cb, char *passwd, /* passphrase */ size_t passwd_len, /* passphrase len */ unsigned char *salt, /* salt */ size_t salt_len, /* salt_len */ int itr, /* iterations */ size_t key_len, /* key_len */ unsigned char *out) /* derived key */ { (void)cryspr_cb; int rc = PKCS5_PBKDF2_HMAC_SHA1(passwd,passwd_len,salt,salt_len,itr,key_len,out); return(rc == 1? 0 : -1); } #if CRYSPR_HAS_AESKWRAP int crysprOpenSSL_KmWrap(CRYSPR_cb *cryspr_cb, unsigned char *wrap, const unsigned char *sek, unsigned int seklen) { crysprOpenSSL_cb *aes_data = (crysprOpenSSL_cb *)cryspr_cb; AES_KEY *kek = &aes_data->ccb.aes_kek; //key encrypting key return(((seklen + HAICRYPT_WRAPKEY_SIGN_SZ) == (unsigned int)AES_wrap_key(kek, NULL, wrap, sek, seklen)) ? 0 : -1); } int crysprOpenSSL_KmUnwrap( CRYSPR_cb *cryspr_cb, unsigned char *sek, //Stream encrypting key const unsigned char *wrap, unsigned int wraplen) { crysprOpenSSL_cb *aes_data = (crysprOpenSSL_cb *)cryspr_cb; AES_KEY *kek = &aes_data->ccb.aes_kek; //key encrypting key return(((wraplen - HAICRYPT_WRAPKEY_SIGN_SZ) == (unsigned int)AES_unwrap_key(kek, NULL, sek, wrap, wraplen)) ? 0 : -1); } #endif /*CRYSPR_HAS_AESKWRAP*/ static CRYSPR_methods crysprOpenSSL_methods; CRYSPR_methods *crysprOpenSSL(void) { if(NULL == crysprOpenSSL_methods.open) { crysprInit(&crysprOpenSSL_methods); //Default/fallback methods crysprOpenSSL_methods.prng = crysprOpenSSL_Prng; //--CryptoLib Primitive API----------------------------------------------- crysprOpenSSL_methods.aes_set_key = crysprOpenSSL_AES_SetKey; #if CRYSPR_HAS_AESCTR crysprOpenSSL_methods.aes_ctr_cipher = crysprOpenSSL_AES_CtrCipher; #endif #if !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP) /* AES-ECB only required if cryspr has no AES-CTR and no AES KeyWrap */ /* OpenSSL has both AESCTR and AESKWRP and the AESECB wrapper is only used to test the falback methods */ crysprOpenSSL_methods.aes_ecb_cipher = crysprOpenSSL_AES_EcbCipher; #endif #if !CRYSPR_HAS_PBKDF2 crysprOpenSSL_methods.sha1_msg_digest= NULL; //Required to use eventual default/fallback KmPbkdf2 #endif //--Crypto Session API----------------------------------------- // crysprOpenSSL_methods.open = // crysprOpenSSL_methods.close = //--Keying material (km) encryption #if CRYSPR_HAS_PBKDF2 crysprOpenSSL_methods.km_pbkdf2 = crysprOpenSSL_KmPbkdf2; #else #error There is no default/fallback method for PBKDF2 #endif // crysprOpenSSL_methods.km_setkey = #if CRYSPR_HAS_AESKWRAP crysprOpenSSL_methods.km_wrap = crysprOpenSSL_KmWrap; crysprOpenSSL_methods.km_unwrap = crysprOpenSSL_KmUnwrap; #endif //--Media stream (ms) encryption // crysprOpenSSL_methods.ms_setkey = // crysprOpenSSL_methods.ms_encrypt = // crysprOpenSSL_methods.ms_decrypt = } return(&crysprOpenSSL_methods); } srt-1.4.4/haicrypt/cryspr-openssl.h000066400000000000000000000044231412557703600173440ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. 2019-06-26 (jdube) OpenSSL Direct AES CRYSPR/4SRT (CRYypto Service PRovider for SRT). *****************************************************************************/ #ifndef CRYSPR_OPENSSL_H #define CRYSPR_OPENSSL_H #include /* PKCS5_xxx() */ #include /* AES_xxx() */ #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_IS_BORINGSSL)) # include /* CRYPTO_xxx() */ #endif #include #include #include /* OPENSSL_VERSION_NUMBER */ /* Define CRYSPR_HAS_AESCTR to 1 if this CRYSPR has AESCTR cipher mode if not set it 0 to use enable CTR cipher mode implementation using ECB cipher mode and provide the aes_ecb_cipher method. */ #define CRYSPR_HAS_AESCTR 1 /* Define CRYSPR_HAS_AESKWRAP to 1 if this CRYSPR has AES Key Wrap if not set to 0 to enable default/fallback crysprFallback_AES_WrapKey/crysprFallback_AES_UnwrapKey methods and provide the aes_ecb_cipher method . */ #if (OPENSSL_VERSION_NUMBER < 0x0090808fL) //0.9.8h #define CRYSPR_HAS_AESKWRAP 0 #else #define CRYSPR_HAS_AESKWRAP 1 #endif /* Define CRYSPR_HAS_PBKDF2 to 1 if this CRYSPR has SHA1-HMAC Password-based Key Derivaion Function 2 if not set to 0 to enable not-yet-implemented/fallback crysprFallback.km_pbkdf2 method and provide the sha1_msg_digest method. */ #define CRYSPR_HAS_PBKDF2 1 /* Define to 1 if CRYSPR has Password-based Key Derivaion Function 2 */ /* #define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK It is set from hte keystring through CRYSPR_methods.aes_set_key and passed to CRYSPR_methods.aes_*. */ typedef AES_KEY CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */ struct tag_CRYSPR_methods *crysprOpenSSL(void); #endif /* CRYSPR_OPENSSL_H */ srt-1.4.4/haicrypt/cryspr.c000066400000000000000000000517671412557703600156730ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. 2019-06-28 (jdube) CRYSPR/4SRT Initial implementation. *****************************************************************************/ #include "hcrypt.h" #include "cryspr.h" #include #include int crysprStub_Prng(unsigned char *rn, int len) { (void)rn; (void)len; return(0); } int crysprStub_AES_SetKey( bool bEncrypt, /* true Enxcrypt key, false: decrypt */ const unsigned char *kstr, /* key sttring*/ size_t kstr_len, /* kstr len in bytes (16, 24, or 32 bytes (for AES128,AES192, or AES256) */ CRYSPR_AESCTX *aes_key) /* Cryptolib Specific AES key context */ { (void)bEncrypt; (void)kstr; (void)kstr_len; (void)aes_key; return(0); } int crysprStub_AES_EcbCipher( bool bEncrypt, /* true:encrypt, false:decrypt */ CRYSPR_AESCTX *aes_key, /* AES context */ const unsigned char *indata,/* src (clear text)*/ size_t inlen, /* length */ unsigned char *out_txt, /* dst (cipher text) */ size_t *outlen) /* dst len */ { (void)bEncrypt; (void)aes_key; (void)indata; (void)inlen; (void)out_txt; (void)outlen; return -1; } int crysprStub_AES_CtrCipher( bool bEncrypt, /* true:encrypt, false:decrypt */ CRYSPR_AESCTX *aes_key, /* AES context */ unsigned char *iv, /* iv */ const unsigned char *indata,/* src */ size_t inlen, /* length */ unsigned char *out_txt) /* dest */ { (void)bEncrypt; (void)aes_key; (void)iv; (void)indata; (void)inlen; (void)out_txt; return(-1); } unsigned char *crysprStub_SHA1_MsgDigest( const unsigned char *m, /* in: message */ size_t m_len, /* message length */ unsigned char *md) /* out: message digest buffer *160 bytes */ { (void)m; (void)m_len; (void)md; return(NULL);//return md; } /* * Password-based Key Derivation Function */ int crysprStub_KmPbkdf2( CRYSPR_cb *cryspr_cb, char *passwd, /* passphrase */ size_t passwd_len, /* passphrase len */ unsigned char *salt, /* salt */ size_t salt_len, /* salt_len */ int itr, /* iterations */ size_t key_len, /* key_len */ unsigned char *out) /* derived key */ { (void)cryspr_cb; (void)passwd; (void)passwd_len; (void)salt; (void)salt_len; (void)itr; (void)key_len; (void)out; /* >>Todo: * develop PBKDF2 using SHA1 primitive cryspr_cb->cryspr->sha1_msg_digest() for cryptolibs not providing it */ return(-1); } static int crysprFallback_KmSetKey(CRYSPR_cb *cryspr_cb, bool bWrap, const unsigned char *kek, size_t kek_len) { CRYSPR_AESCTX *aes_kek = &cryspr_cb->aes_kek; if (cryspr_cb->cryspr->aes_set_key(bWrap, kek, kek_len, aes_kek)) { HCRYPT_LOG(LOG_ERR, "AES_set_%s_key(kek) failed\n", bWrap? "encrypt": "decrypt"); return(-1); } return(0); } /* * AES_wrap_key()/AES_unwrap_key() introduced in openssl 0.9.8h * Here is an implementation using AES native API for cryspr not providing it. */ static const unsigned char default_iv[] = { 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, }; int crysprFallback_AES_WrapKey(CRYSPR_cb *cryspr_cb, unsigned char *out, const unsigned char *in, unsigned int inlen) { unsigned char *A, B[16], *R; const unsigned char *iv = default_iv; unsigned int i, j, t; if ((inlen & 0x7) || (inlen < 8)) return -1; A = B; t = 1; memcpy(out + 8, in, inlen); 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); { size_t outlen = 16; cryspr_cb->cryspr->aes_ecb_cipher(true, &cryspr_cb->aes_kek, B, 16, B, &outlen); } 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 0; } int crysprFallback_AES_UnwrapKey(CRYSPR_cb *cryspr_cb, unsigned char *out, const unsigned char *in, unsigned int inlen) { unsigned char *A, B[16], *R; const unsigned char *iv = default_iv; 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); { size_t outlen = 16; cryspr_cb->cryspr->aes_ecb_cipher(false, &cryspr_cb->aes_kek, B, 16, B, &outlen); } memcpy(R, B + 8, 8); } } if (memcmp(A, iv, 8)) { memset(out, 0, inlen); return -1; } return 0; } static unsigned char *_crysprFallback_GetOutbuf(CRYSPR_cb *cryspr_cb, size_t pfx_len, size_t out_len) { unsigned char *out_buf; if ((pfx_len + out_len) > (cryspr_cb->outbuf_siz - cryspr_cb->outbuf_ofs)) { /* Not enough room left, circle buffers */ cryspr_cb->outbuf_ofs = 0; } out_buf = &cryspr_cb->outbuf[cryspr_cb->outbuf_ofs]; cryspr_cb->outbuf_ofs += (pfx_len + out_len); return(out_buf); } static CRYSPR_cb *crysprFallback_Open(CRYSPR_methods *cryspr, size_t max_len) { CRYSPR_cb *cryspr_cb; 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(*cryspr_cb) + (CRYSPR_OUTMSGMAX * padded_len); #if !CRYSPR_HAS_AESCTR memsiz += HCRYPT_CTR_STREAM_SZ; #endif /* !CRYSPR_HAS_AESCTR */ cryspr_cb = malloc(memsiz); if (NULL == cryspr_cb) { HCRYPT_LOG(LOG_ERR, "malloc(%zd) failed\n", memsiz); return(NULL); } membuf = (unsigned char *)cryspr_cb; membuf += sizeof(*cryspr_cb); #if !CRYSPR_HAS_AESCTR cryspr_cb->ctr_stream = membuf; membuf += HCRYPT_CTR_STREAM_SZ; cryspr_cb->ctr_stream_siz = HCRYPT_CTR_STREAM_SZ; cryspr_cb->ctr_stream_len = 0; #endif /* !CRYSPR_HAS_AESCTR */ cryspr_cb->outbuf = membuf; cryspr_cb->outbuf_siz = CRYSPR_OUTMSGMAX * padded_len; cryspr_cb->outbuf_ofs = 0; // membuf += cryspr_cb->outbuf_siz; cryspr_cb->cryspr=(CRYSPR_methods *)cryspr; return(cryspr_cb); } static int crysprFallback_Close(CRYSPR_cb *cryspr_cb) { free(cryspr_cb); return(0); } static int crysprFallback_MsSetKey(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, const unsigned char *key, size_t key_len) { CRYSPR_AESCTX *aes_sek = &cryspr_cb->aes_sek[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 (cryspr_cb->cryspr->aes_set_key(true, key, key_len, aes_sek)) { HCRYPT_LOG(LOG_ERR, "%s", "CRYSPR->set_encrypt_key(sek) failed\n"); return(-1); } } else { /* Decrypt key */ if (cryspr_cb->cryspr->aes_set_key(false, key, key_len, aes_sek)) { HCRYPT_LOG(LOG_ERR, "%s", "CRYSPR->set_decrypt_key(sek) failed\n"); return(-1); } } return(0); } #if !CRYSPR_HAS_AESCTR static int _crysprFallback_AES_SetCtrStream(CRYSPR_cb *cryspr_cb, 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_CTR_BLK_SZ]; unsigned nblk; ASSERT(NULL != cryspr_cb); ASSERT(NULL != ctx); memcpy(ctr, iv, HCRYPT_CTR_BLK_SZ); nblk = (len + (HCRYPT_CTR_BLK_SZ-1))/HCRYPT_CTR_BLK_SZ; if ((nblk * HCRYPT_CTR_BLK_SZ) <= cryspr_cb->ctr_stream_siz) { unsigned blk; unsigned char *csp = &cryspr_cb->ctr_stream[0]; for(blk = 0; blk < nblk; blk++) { memcpy(csp, ctr, HCRYPT_CTR_BLK_SZ); csp += HCRYPT_CTR_BLK_SZ; if (0 == ++(ctr[HCRYPT_CTR_BLK_SZ-1])) ++(ctr[HCRYPT_CTR_BLK_SZ-2]); } cryspr_cb->ctr_stream_len = nblk * HCRYPT_CTR_BLK_SZ; } else { HCRYPT_LOG(LOG_ERR, "packet too long(%zd)\n", len); return(-1); } return(0); } #endif static int crysprFallback_MsEncrypt( CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, hcrypt_DataDesc *in_data, int nbin ATR_UNUSED, void *out_p[], size_t out_len_p[], int *nbout_p) { unsigned char *out_msg; size_t out_len = 0; //payload size int pfx_len; ASSERT(NULL != ctx); ASSERT(NULL != cryspr_cb); 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 = _crysprFallback_GetOutbuf(cryspr_cb, pfx_len, in_data[0].len); if (NULL != out_msg) { switch(ctx->mode) { case HCRYPT_CTX_MODE_AESCTR: /* Counter mode */ { #if CRYSPR_HAS_AESCTR /* Get current key (odd|even) from context */ CRYSPR_AESCTX *aes_key = &cryspr_cb->aes_sek[hcryptCtx_GetKeyIndex(ctx)]; unsigned char iv[CRYSPR_AESBLKSZ]; /* 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) */ hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); cryspr_cb->cryspr->aes_ctr_cipher(true, aes_key, iv, in_data[0].payload, in_data[0].len, &out_msg[pfx_len]); #else /*CRYSPR_HAS_AESCTR*/ /* Get current key (odd|even) from context */ CRYSPR_AESCTX *aes_key = &cryspr_cb->aes_sek[hcryptCtx_GetKeyIndex(ctx)]; unsigned char iv[CRYSPR_AESBLKSZ]; int iret = 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) */ hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); /* Create CtrStream. May be longer than in_len (next cryspr block size boundary) */ iret = _crysprFallback_AES_SetCtrStream(cryspr_cb, ctx, in_data[0].len, iv); if (iret) { return(iret); } /* Reserve output buffer for cryspr */ out_msg = _crysprFallback_GetOutbuf(cryspr_cb, pfx_len, cryspr_cb->ctr_stream_len); /* Create KeyStream (encrypt CtrStream) */ iret = cryspr_cb->cryspr->aes_ecb_cipher(true, aes_key, cryspr_cb->ctr_stream, cryspr_cb->ctr_stream_len, &out_msg[pfx_len], &out_len); if (iret) { HCRYPT_LOG(LOG_ERR, "%s", "hcOpenSSL_AES_ecb_cipher(encrypt, failed\n"); return(iret); } #endif/*CRYSPR_HAS_AESCTR*/ /* 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_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); #if !CRYSPR_HAS_AESCTR if (ctx->mode == HCRYPT_CTX_MODE_AESCTR) { /* XOR KeyStream with input text directly in input buffer */ hcrypt_XorStream(in_data[0].payload, &out_msg[pfx_len], out_len); }else{ /* Copy output data back in input buffer */ memcpy(in_data[0].payload, &out_msg[pfx_len], out_len); } #else /* CRYSPR_HAS_AESCTR */ /* Copy output data back in input buffer */ memcpy(in_data[0].payload, &out_msg[pfx_len], out_len); #endif /* CRYSPR_HAS_AESCTR */ } else { /* Copy header in output buffer if needed */ if (pfx_len > 0) memcpy(out_msg, in_data[0].pfx, pfx_len); #if !CRYSPR_HAS_AESCTR if (ctx->mode == HCRYPT_CTX_MODE_AESCTR) { hcrypt_XorStream(&out_msg[pfx_len], in_data[0].payload, out_len); } #endif /* CRYSPR_HAS_AESCTR */ 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 crysprFallback_MsDecrypt(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, hcrypt_DataDesc *in_data, int nbin ATR_UNUSED, void *out_p[], size_t out_len_p[], int *nbout_p) { unsigned char *out_txt; size_t out_len; int iret = 0; ASSERT(NULL != cryspr_cb); ASSERT(NULL != ctx); ASSERT((NULL != in_data) || (1 == nbin)); //Only one in_data[] supported /* Reserve output buffer (w/no header) */ out_txt = _crysprFallback_GetOutbuf(cryspr_cb, 0, in_data[0].len); if (NULL != out_txt) { switch(ctx->mode) { case HCRYPT_CTX_MODE_AESCTR: { #if CRYSPR_HAS_AESCTR /* Get current key (odd|even) from context */ CRYSPR_AESCTX *aes_key = &cryspr_cb->aes_sek[hcryptCtx_GetKeyIndex(ctx)]; unsigned char iv[CRYSPR_AESBLKSZ]; /* 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) */ hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); cryspr_cb->cryspr->aes_ctr_cipher(false, aes_key, iv, in_data[0].payload, in_data[0].len, out_txt); out_len = in_data[0].len; #else /*CRYSPR_HAS_AESCTR*/ /* Get current key (odd|even) from context */ CRYSPR_AESCTX *aes_key = &cryspr_cb->aes_sek[hcryptCtx_GetKeyIndex(ctx)]; unsigned char iv[CRYSPR_AESBLKSZ]; int iret = 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) */ hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); /* Create CtrStream. May be longer than in_len (next cipher block size boundary) */ iret = _crysprFallback_AES_SetCtrStream(cryspr_cb, ctx, in_data[0].len, iv); if (iret) { return(iret); } /* Reserve output buffer for cryspr */ out_txt = _crysprFallback_GetOutbuf(cryspr_cb, 0, cryspr_cb->ctr_stream_len); /* Create KeyStream (encrypt CtrStream) */ iret = cryspr_cb->cryspr->aes_ecb_cipher(true, aes_key, cryspr_cb->ctr_stream, cryspr_cb->ctr_stream_len, out_txt, &out_len); if (iret) { HCRYPT_LOG(LOG_ERR, "%s", "crysprNatural_AES_ecb_cipher(encrypt failed\n"); return(iret); } #endif /*CRYSPR_HAS_AESCTR*/ break; } case HCRYPT_CTX_MODE_CLRTXT: memcpy(out_txt, in_data[0].payload, in_data[0].len); out_len = in_data[0].len; break; default: return(-1); } } else { return(-1); } if (out_len > 0) { if (NULL == out_p) { /* * Application did not provided output buffer, * so copy encrypted message back in input buffer */ #if !CRYSPR_HAS_AESCTR if (ctx->mode == HCRYPT_CTX_MODE_AESCTR) { /* XOR KeyStream with input text directly in input buffer */ hcrypt_XorStream(in_data[0].payload, out_txt, out_len); }else{ /* Copy output data back in input buffer */ memcpy(in_data[0].payload, out_txt, out_len); } #else /* CRYSPR_HAS_AESCTR */ /* Copy output data back in input buffer */ memcpy(in_data[0].payload, out_txt, out_len); #endif /* CRYSPR_HAS_AESCTR */ } else { /* Copy header in output buffer if needed */ #if !CRYSPR_HAS_AESCTR if (ctx->mode == HCRYPT_CTX_MODE_AESCTR) { hcrypt_XorStream(out_txt, in_data[0].payload, out_len); } #endif /* CRYSPR_HAS_AESCTR */ 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); } CRYSPR_methods *crysprInit(CRYSPR_methods *cryspr) { /* CryptoLib Primitive API */ cryspr->prng = crysprStub_Prng; cryspr->aes_set_key = crysprStub_AES_SetKey; cryspr->aes_ecb_cipher = crysprStub_AES_EcbCipher; cryspr->aes_ctr_cipher = crysprStub_AES_CtrCipher; cryspr->sha1_msg_digest = crysprStub_SHA1_MsgDigest; /* Crypto Session API */ cryspr->open = crysprFallback_Open; cryspr->close = crysprFallback_Close; //Keying material (km) encryption cryspr->km_pbkdf2 = crysprStub_KmPbkdf2; cryspr->km_setkey = crysprFallback_KmSetKey; cryspr->km_wrap = crysprFallback_AES_WrapKey; cryspr->km_unwrap = crysprFallback_AES_UnwrapKey; //Media stream (ms) encryption cryspr->ms_setkey = crysprFallback_MsSetKey; cryspr->ms_encrypt = crysprFallback_MsEncrypt; cryspr->ms_decrypt = crysprFallback_MsDecrypt; return(cryspr); } HaiCrypt_Cryspr HaiCryptCryspr_Get_Instance(void) { return((HaiCrypt_Cryspr)cryspr4SRT()); } srt-1.4.4/haicrypt/cryspr.h000066400000000000000000000172131412557703600156640ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. 2019-06-28 (jdube) CRYSPR/4SRT Initial implementation. *****************************************************************************/ #ifndef CRYSPR_H #define CRYSPR_H #include #include #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 #ifdef __cplusplus extern "C" { #endif #include "cryspr-config.h" typedef struct tag_CRYSPR_cb { CRYSPR_AESCTX aes_kek; /* Key Encrypting Key (KEK) */ CRYSPR_AESCTX aes_sek[2]; /* even/odd Stream Encrypting Key (SEK) */ struct tag_CRYSPR_methods *cryspr; #if !CRYSPR_HAS_AESCTR /* Reserve room to build the counter stream ourself */ #define HCRYPT_CTR_BLK_SZ CRYSPR_AESBLKSZ #define HCRYPT_CTR_STREAM_SZ 2048 unsigned char * ctr_stream; size_t ctr_stream_len; /* Content size */ size_t ctr_stream_siz; /* Allocated length */ #endif /* !CRYSPR_HAS_AESCTR */ #define CRYSPR_OUTMSGMAX 6 uint8_t * outbuf; /* output circle buffer */ size_t outbuf_ofs; /* write offset in circle buffer */ size_t outbuf_siz; /* circle buffer size */ } CRYSPR_cb; typedef struct tag_CRYSPR_methods { /* * prng: * Pseudo-Random Number Generator */ int (*prng)( unsigned char *rn, /* out: pseudo random number */ int rn_len); int (*aes_set_key)( bool bEncrypt, /* true Enxcrypt key, false: decrypt */ const unsigned char *kstr,/* key string*/ size_t kstr_len, /* kstr len in bytes (16, 24, or 32 bytes (for AES128,AES192, or AES256) */ CRYSPR_AESCTX *aeskey); /* Cryptolib Specific AES key context */ int (*aes_ecb_cipher)( bool bEncrypt, /* true:encrypt false:decrypt */ CRYSPR_AESCTX *aes_key, /* ctx */ const unsigned char *indata, /* src (clear text)*/ size_t inlen, /* src length */ unsigned char *out_txt, /* dst (cipher text) */ size_t *outlen); /* dst length */ int (*aes_ctr_cipher)( bool bEncrypt, /* true:encrypt false:decrypt (don't care with CTR) */ CRYSPR_AESCTX *aes_key, /* ctx */ unsigned char *iv, /* iv */ const unsigned char *indata, /* src (clear text) */ size_t inlen, /* src length */ unsigned char *out_txt);/* dest */ unsigned char *(*sha1_msg_digest)( const unsigned char *m, /* in: message */ size_t m_len, /* message length */ unsigned char *md); /* out: message digest buffer *160 bytes */ /* * open: * Create a cipher instance * Allocate output buffers */ CRYSPR_cb *(*open)( struct tag_CRYSPR_methods *cryspr, size_t max_len); /* Maximum packet length that will be encrypted/decrypted */ /* * close: * Release any cipher resources */ int (*close)( CRYSPR_cb *cryspr_data); /* Cipher handle, internal data */ /* * pbkdf2_hmac_sha1 * Password-based Key Derivation Function 2 */ int (*km_pbkdf2)( CRYSPR_cb *cryspr_cb, /* Cryspr Control Block */ char *passwd, /* passphrase */ size_t passwd_len, /* passphrase len */ unsigned char *salt, /* salt */ size_t salt_len, /* salt_len */ int itr, /* iterations */ size_t out_len, /* key_len */ unsigned char *out); /* derived key */ /* * km_setkey: * Set the Key Encypting Key for Wrap (Encryption) or UnWrap (Decryption). * Context (ctx) tells if it's for Wrap or Unwrap * A Context flags (ctx->flags) also tells if this is for wrap(encryption) or unwrap(decryption) context (HCRYPT_CTX_F_ENCRYPT) */ int (*km_setkey)( CRYSPR_cb *cryspr_cb, /* Cryspr Control Block */ bool bWrap, /* True: Wrap KEK, False: Unwrap KEK */ const unsigned char *kek, size_t kek_len); /* KEK: Key Encrypting Key */ /* * km_wrap: * wrap media stream key */ int (*km_wrap)(CRYSPR_cb *cryspr_cb, unsigned char *wrap, const unsigned char *sek, unsigned int seklen); /* * km_unwrap: * wrap media stream key */ int (*km_unwrap)(CRYSPR_cb *cryspr_cb, unsigned char *sek, const unsigned char *wrap, unsigned int wraplen); /* * 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 (*ms_setkey)( CRYSPR_cb *cryspr_cb, /* Cryspr Control Block */ hcrypt_Ctx *ctx, /* HaiCrypt Context (cipher, keys, Odd/Even, etc..) */ const unsigned char *key, size_t kwelen); /* 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 (*ms_encrypt)( CRYSPR_cb *cryspr_cb, /* Cryspr Control Block */ 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 (*ms_decrypt)( CRYSPR_cb *cryspr_cb, /* Cryspr Control Block */ 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 */ } CRYSPR_methods; CRYSPR_methods *crysprInit(CRYSPR_methods *cryspr); #ifdef __cplusplus } #endif #endif /* CRYSPR_H */ srt-1.4.4/haicrypt/filelist-gnutls.maf000066400000000000000000000010411412557703600177730ustar00rootroot00000000000000# 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 PRIVATE HEADERS hcrypt.h cryspr.h cryspr-gnutls.h haicrypt_log.h SOURCES cryspr.c cryspr-gnutls.c hcrypt.c hcrypt_ctx_rx.c hcrypt_ctx_tx.c hcrypt_rx.c hcrypt_sa.c hcrypt_tx.c hcrypt_xpt_srt.c haicrypt_log.cpp srt-1.4.4/haicrypt/filelist-mbedtls.maf000066400000000000000000000004461412557703600201210ustar00rootroot00000000000000# HaiCrypt library contents PUBLIC HEADERS haicrypt.h hcrypt_ctx.h hcrypt_msg.h PRIVATE HEADERS hcrypt.h cryspr.h cryspr-mbedtls.h haicrypt_log.h SOURCES cryspr.c cryspr-mbedtls.c hcrypt.c hcrypt_ctx_rx.c hcrypt_ctx_tx.c hcrypt_rx.c hcrypt_sa.c hcrypt_tx.c hcrypt_xpt_srt.c haicrypt_log.cpp srt-1.4.4/haicrypt/filelist-openssl.maf000066400000000000000000000004461412557703600201520ustar00rootroot00000000000000# HaiCrypt library contents PUBLIC HEADERS haicrypt.h hcrypt_ctx.h hcrypt_msg.h PRIVATE HEADERS hcrypt.h cryspr.h cryspr-openssl.h haicrypt_log.h SOURCES cryspr.c cryspr-openssl.c hcrypt.c hcrypt_ctx_rx.c hcrypt_ctx_tx.c hcrypt_rx.c hcrypt_sa.c hcrypt_tx.c hcrypt_xpt_srt.c haicrypt_log.cpp srt-1.4.4/haicrypt/haicrypt.h000066400000000000000000000114271412557703600161660ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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 typedef void *HaiCrypt_Cryspr; HaiCrypt_Cryspr HaiCryptCryspr_Get_Instance (void); /* Return a default cryspr instance */ #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_Cryspr cryspr; /* CRYSPR 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 enum HaiCrypt_CryptoDir { HAICRYPT_CRYPTO_DIR_RX, HAICRYPT_CRYPTO_DIR_TX } HaiCrypt_CryptoDir; //typedef void *HaiCrypt_Handle; // internally it will be correctly interpreted, // for the outsider it's just some kinda incomplete type // but still if you use any kinda pointer instead, you'll get complaints typedef struct hcrypt_Session_str* HaiCrypt_Handle; int HaiCrypt_SetLogLevel(int level, int logfa); int HaiCrypt_Create(const HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc); int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, 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); /* Status values */ #define HAICRYPT_ERROR -1 #define HAICRYPT_ERROR_WRONG_SECRET -2 #define HAICRYPT_OK 0 #ifdef __cplusplus } #endif #endif /* HAICRYPT_H */ srt-1.4.4/haicrypt/haicrypt_log.cpp000066400000000000000000000076711412557703600173700ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #if ENABLE_HAICRYPT_LOGGING #include "hcrypt.h" #include "haicrypt.h" #include "../srtcore/srt.h" #include "../srtcore/logging.h" extern srt_logging::LogConfig srt_logger_config; // LOGFA symbol defined in srt.h srt_logging::Logger hclog(SRT_LOGFA_HAICRYPT, srt_logger_config, "SRT.hc"); extern "C" { int HaiCrypt_SetLogLevel(int level, int logfa) { srt_setloglevel(level); if (logfa != SRT_LOGFA_GENERAL) // General can't be turned on or off { srt_addlogfa(logfa); } return 0; } // HaiCrypt will be using its own FA, which will be turned off by default. // Templates made C way. // It's tempting to use the HAICRYPT_DEFINE_LOG_DISPATCHER macro here because it would provide the // exact signature that is needed here, the problem is though that this would expand the LOGLEVEL // parameter, which is also a macro, into the value that the macro designates, which would generate // the HaiCrypt_LogF_0 instead of HaiCrypt_LogF_LOG_DEBUG, for example. #define HAICRYPT_DEFINE_LOG_DISPATCHER(LOGLEVEL, dispatcher) \ int HaiCrypt_LogF_##LOGLEVEL ( const char* file, int line, const char* function, const char* format, ...) \ { \ va_list ap; \ va_start(ap, format); \ srt_logging::LogDispatcher& lg = hclog.dispatcher; \ if (!lg.CheckEnabled()) return -1; \ lg().setloc(file, line, function).vform(format, ap); \ va_end(ap); \ return 0; \ } HAICRYPT_DEFINE_LOG_DISPATCHER(LOG_DEBUG, Debug); HAICRYPT_DEFINE_LOG_DISPATCHER(LOG_NOTICE, Note); HAICRYPT_DEFINE_LOG_DISPATCHER(LOG_INFO, Note); HAICRYPT_DEFINE_LOG_DISPATCHER(LOG_WARNING, Warn); HAICRYPT_DEFINE_LOG_DISPATCHER(LOG_ERR, Error); HAICRYPT_DEFINE_LOG_DISPATCHER(LOG_CRIT, Fatal); HAICRYPT_DEFINE_LOG_DISPATCHER(LOG_ALERT, Fatal); HAICRYPT_DEFINE_LOG_DISPATCHER(LOG_EMERG, Fatal); static void DumpCfgFlags(int flags, std::ostream& out) { static struct { int flg; const char* desc; } flgtable [] = { #define HCRYPTF(name) { HAICRYPT_CFG_F_##name, #name } HCRYPTF(TX), HCRYPTF(CRYPTO), HCRYPTF(FEC) #undef HCRYPTF }; size_t flgtable_size = sizeof(flgtable)/sizeof(flgtable[0]); size_t i; out << "{"; const char* sep = ""; const char* sep_bar = " | "; for (i = 0; i < flgtable_size; ++i) { if ( (flgtable[i].flg & flags) != 0 ) { out << sep << flgtable[i].desc; sep = sep_bar; } } out << "}"; } void HaiCrypt_DumpConfig(const HaiCrypt_Cfg* cfg) { std::ostringstream cfg_flags; DumpCfgFlags(cfg->flags, cfg_flags); LOGC(hclog.Debug, log << "CFG DUMP: flags=" << cfg_flags.str() << " xport=" << (cfg->xport == HAICRYPT_XPT_SRT ? "SRT" : "INVALID") << " cipher=" << (cfg->cipher == HaiCryptCipher_OpenSSL_EVP_CTR() ? "OSSL-EVP-CTR": cfg->cipher == HaiCryptCipher_OpenSSL_AES() ? "OSSL-AES": // This below is used as the only one when Nettle is used. When OpenSSL // is used, one of the above will trigger, and the one below will then never trigger. cfg->cipher == HaiCryptCipher_Get_Instance() ? "Nettle-AES": "UNKNOWN") << " key_len=" << cfg->key_len << " data_max_len=" << cfg->data_max_len); LOGC(hclog.Debug, log << "CFG DUMP: txperiod=" << cfg->km_tx_period_ms << "ms kmrefresh=" << cfg->km_refresh_rate_pkt << " kmpreannounce=" << cfg->km_pre_announce_pkt << " secret " << "{tp=" << (cfg->secret.typ == 1 ? "PSK" : cfg->secret.typ == 2 ? "PWD" : "???") << " len=" << cfg->secret.len << " pwd=" << cfg->secret.str << "}"); } } // extern "C" #endif // Block for the whole file srt-1.4.4/haicrypt/haicrypt_log.h000066400000000000000000000016431412557703600170260ustar00rootroot00000000000000#ifndef INC_SRT_HAICRYPT_LOG_H #define INC_SRT_HAICRYPT_LOG_H #ifdef __cplusplus extern "C" { #endif #define HAICRYPT_DECLARE_LOG_DISPATCHER(LOGLEVEL) \ int HaiCrypt_LogF_##LOGLEVEL ( const char* file, int line, const char* function, const char* format, ...) // Now declare all dispatcher functions HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_DEBUG); HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_NOTICE); HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_INFO); HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_WARNING); HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_ERR); HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_CRIT); HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_ALERT); HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_EMERG); #define HCRYPT_LOG_INIT() #define HCRYPT_LOG_EXIT() #define HCRYPT_LOG(lvl, fmt, ...) HaiCrypt_LogF_##lvl (__FILE__, __LINE__, __FUNCTION__, fmt, __VA_ARGS__) #if ENABLE_HAICRYPT_LOGGING == 2 #define HCRYPT_DEV 1 #endif #ifdef __cplusplus } #endif #endif // macroguard srt-1.4.4/haicrypt/hcrypt.c000066400000000000000000000256331412557703600156530ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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" #if ENABLE_HAICRYPT_LOGGING void HaiCrypt_DumpConfig(const HaiCrypt_Cfg* cfg); #else #define HaiCrypt_DumpConfig(x) (void)0 #endif static hcrypt_Session* sHaiCrypt_PrepareHandle(const HaiCrypt_Cfg* cfg, HaiCrypt_CryptoDir tx) { hcrypt_Session *crypto; unsigned char *mem_buf; size_t mem_siz, inbuf_siz; HaiCrypt_DumpConfig(cfg); HCRYPT_PRINTKEY(cfg->secret.str, cfg->secret.len, "cfgkey"); inbuf_siz = 0; 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 NULL; } 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; } crypto->cryspr = cfg->cryspr; crypto->cfg.data_max_len = cfg->data_max_len; /* Setup transport packet info */ switch (cfg->xport) { 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 NULL; } 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->cryspr_cb = crypto->cryspr->open(crypto->cryspr, cfg->data_max_len); if (NULL == crypto->cryspr_cb) { free(crypto); return NULL; } return crypto; } int HaiCrypt_Create(const HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc) { ASSERT(cfg != NULL); ASSERT(phhc != NULL); hcrypt_Session *crypto; HaiCrypt_CryptoDir tx = (HaiCrypt_CryptoDir)(HAICRYPT_CFG_F_TX & cfg->flags); *phhc = NULL; 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 (%d). Expected: 16, 24, 32\n", (int)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 (%d)\n", (int)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 (%d) smaller than key length (%d)\n", (int)cfg->secret.len, (int)cfg->key_len); return(-1); } else if (NULL == cfg->cryspr) { HCRYPT_LOG(LOG_ERR, "%s\n", "no cryspr specified"); return(-1); } else if (0 == cfg->data_max_len) { HCRYPT_LOG(LOG_ERR, "%s\n", "no data_max_len specified"); return(-1); } crypto = sHaiCrypt_PrepareHandle(cfg, tx); if (!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_ExtractConfig(HaiCrypt_Handle hhcSrc, HaiCrypt_Cfg* pcfg) { hcrypt_Session *crypto = (hcrypt_Session *)hhcSrc; hcrypt_Ctx* ctx = crypto->ctx; if (!ctx) { // Fall back to the first of the pair; // Should this be not initialized, ignore it. ctx = &crypto->ctx_pair[0]; // We assume that when ctx != NULL, it is active or keyed anyway. if (ctx->status != HCRYPT_CTX_S_KEYED && ctx->status != HCRYPT_CTX_S_ACTIVE) return -1; } pcfg->flags = HAICRYPT_CFG_F_CRYPTO; if ((ctx->flags & HCRYPT_CTX_F_ENCRYPT) == HCRYPT_CTX_F_ENCRYPT) pcfg->flags |= HAICRYPT_CFG_F_TX; /* Set this explicitly - this use of this library is SRT only. */ pcfg->xport = HAICRYPT_XPT_SRT; pcfg->cryspr = crypto->cryspr; pcfg->key_len = ctx->cfg.key_len; if (pcfg->key_len == 0) // not initialized - usual in RX { pcfg->key_len = ctx->sek_len; } pcfg->data_max_len = crypto->cfg.data_max_len; pcfg->km_tx_period_ms = 0;//No HaiCrypt KM inject period, handled in SRT; pcfg->km_refresh_rate_pkt = crypto->km.refresh_rate; pcfg->km_pre_announce_pkt = crypto->km.pre_announce; /* As SRT is using only the PASSPHRASE type, never PRESHARED, * this is so assumed here, although there are completely no * premises as to which is currently used by the hhcSrc. */ pcfg->secret.typ = HAICRYPT_SECTYP_PASSPHRASE; pcfg->secret.len = ctx->cfg.pwd_len; memcpy(pcfg->secret.str, ctx->cfg.pwd, pcfg->secret.len); return 0; } int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handle *phhc) { hcrypt_Session *cryptoSrc = (hcrypt_Session *)hhcSrc; hcrypt_Session *cryptoClone; unsigned char *mem_buf; size_t mem_siz, inbuf_siz; *phhc = NULL; ASSERT(NULL != hhcSrc); HCRYPT_LOG(LOG_INFO, "%s\n", "creating CLONED crypto context"); if (tx) { HaiCrypt_Cfg crypto_config; HaiCrypt_ExtractConfig(hhcSrc, &crypto_config); /* * Just invert the direction written in flags and use the * standard way of creating the context, as you already have a config. */ crypto_config.flags |= HAICRYPT_CFG_F_TX; cryptoClone = sHaiCrypt_PrepareHandle(&crypto_config, tx); if (!cryptoClone) return -1; /* Configure initial context */ if (hcryptCtx_Tx_Init(cryptoClone, &cryptoClone->ctx_pair[0], &crypto_config) || hcryptCtx_Tx_Init(cryptoClone, &cryptoClone->ctx_pair[1], &crypto_config)) { free(cryptoClone); return(-1); } /* Clone keys for first (default) context from the source RX crypto */ if (hcryptCtx_Tx_CloneKey(cryptoClone, &cryptoClone->ctx_pair[0], cryptoSrc)) { free(cryptoClone); return(-1); } cryptoClone->ctx = &cryptoClone->ctx_pair[0]; cryptoClone->ctx->flags |= (HCRYPT_CTX_F_ANNOUNCE | HCRYPT_CTX_F_TTSEND); cryptoClone->ctx->status = HCRYPT_CTX_S_ACTIVE; } else { /* Receiver */ /* * If cryspr has no special input buffer alignment requirement, * handle it in the crypto session. */ inbuf_siz = cryptoSrc->inbuf_siz ; /* Allocate crypto session control struct */ mem_siz = sizeof(hcrypt_Session) // structure + inbuf_siz; cryptoClone = malloc(mem_siz); if (NULL == cryptoClone) { HCRYPT_LOG(LOG_ERR, "%s\n", "malloc failed"); return(-1); } mem_buf = (unsigned char *)cryptoClone; mem_buf += sizeof(*cryptoClone); memcpy(cryptoClone, cryptoSrc, sizeof(*cryptoClone)); if (inbuf_siz) { cryptoClone->inbuf = mem_buf; mem_buf += inbuf_siz; } timerclear(&cryptoClone->km.tx_last); /* Adjust pointers pointing into cryproSrc after copy msg_info and crysprs are extern statics so this is ok*/ cryptoClone->ctx_pair[0].alt = &cryptoClone->ctx_pair[1]; cryptoClone->ctx_pair[1].alt = &cryptoClone->ctx_pair[0]; /* create a new cryspr (OpenSSL) context */ cryptoClone->cryspr_cb = cryptoClone->cryspr->open(cryptoClone->cryspr, cryptoClone->cfg.data_max_len); if (NULL == cryptoClone->cryspr_cb) { //shred free(cryptoClone); return(-1); } /* Configure contexts */ if (hcryptCtx_Rx_Init(cryptoClone, &cryptoClone->ctx_pair[0], NULL) || hcryptCtx_Rx_Init(cryptoClone, &cryptoClone->ctx_pair[1], NULL)) { free(cryptoClone); return(-1); } /* Clear salt to force later regeneration of KEK as AES decrypting key, copyed one is encrypting key */ cryptoClone->ctx_pair[0].flags &= ~HCRYPT_CTX_F_ENCRYPT; cryptoClone->ctx_pair[1].flags &= ~HCRYPT_CTX_F_ENCRYPT; memset(cryptoClone->ctx_pair[0].salt, 0, sizeof(cryptoClone->ctx_pair[0].salt)); cryptoClone->ctx_pair[0].salt_len = 0; } *phhc = (void *)cryptoClone; return(0); } int HaiCrypt_Close(HaiCrypt_Handle hhc) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; int rc = -1; if (crypto) { if (crypto->cryspr && crypto->cryspr->close) crypto->cryspr->close(crypto->cryspr_cb); free(crypto); rc = 0; } HCRYPT_LOG_EXIT(); return rc; } srt-1.4.4/haicrypt/hcrypt.h000066400000000000000000000131551412557703600156540ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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 INC_SRT_HCRYPT_H #define INC_SRT_HCRYPT_H #include #ifdef _WIN32 #include #include #if defined(_MSC_VER) #pragma warning(disable:4267) #pragma warning(disable:4018) #endif #else #include #endif #ifdef __GNUC__ #define ATR_UNUSED __attribute__((unused)) #else #define ATR_UNUSED #endif #include "haicrypt.h" #include "hcrypt_msg.h" #include "hcrypt_ctx.h" #include "cryspr.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 hcrypt_Session_str { #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 */ CRYSPR_methods * cryspr; CRYSPR_cb * cryspr_cb; 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 { size_t data_max_len; }cfg; struct { struct timeval tx_period; /* Keying Material tx period (milliseconds) */ struct timeval tx_last; /* Keying Material last tx time */ unsigned int refresh_rate; /* SEK use period */ unsigned int pre_announce; /* Pre/Post next/old SEK announce */ }km; } hcrypt_Session; #if ENABLE_HAICRYPT_LOGGING #include "haicrypt_log.h" #else #define HCRYPT_LOG_INIT() #define HCRYPT_LOG_EXIT() #define HCRYPT_LOG(lvl, fmt, ...) #endif #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 /* 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, const HaiCrypt_Secret *secret); int hcryptCtx_GenSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx); int hcryptCtx_Tx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cfg *cfg); int hcryptCtx_Tx_Rekey(hcrypt_Session *crypto, hcrypt_Ctx *ctx); int hcryptCtx_Tx_CloneKey(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const hcrypt_Session* cryptoSrc); 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, const HaiCrypt_Cfg *cfg); int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *msg, size_t msg_len); #endif /* HCRYPT_H */ srt-1.4.4/haicrypt/hcrypt_ctx.h000066400000000000000000000062141412557703600165300ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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 #include "hcrypt.h" #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 hcryptCtx_GetKeyFlags(ctx) ((ctx)->flags & HCRYPT_CTX_F_xSEK) #define hcryptCtx_GetKeyIndex(ctx) (((ctx)->flags & HCRYPT_CTX_F_xSEK)>>1) #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]; 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; #endif /* HCRYPT_CTX_H */ srt-1.4.4/haicrypt/hcrypt_ctx_rx.c000066400000000000000000000131301412557703600172270ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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, const HaiCrypt_Cfg *cfg) { ctx->mode = HCRYPT_CTX_MODE_AESCTR; ctx->status = HCRYPT_CTX_S_INIT; ctx->msg_info = crypto->msg_info; if (cfg && hcryptCtx_SetSecret(crypto, ctx, &cfg->secret)) { return(-1); } ctx->status = HCRYPT_CTX_S_SARDY; return(0); } int hcryptCtx_Rx_Rekey(hcrypt_Session *crypto, hcrypt_Ctx *ctx, unsigned char *sek, size_t sek_len) { if (crypto->cryspr->ms_setkey(crypto->cryspr_cb, ctx, sek, sek_len)) { HCRYPT_LOG(LOG_ERR, "cryspr 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_ERR, "Rx_ParseKM: invalid params: crypto=%p\n", crypto); 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 > crypto->cryspr->km_unwrap(crypto->cryspr_cb, 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); } srt-1.4.4/haicrypt/hcrypt_ctx_tx.c000066400000000000000000000273471412557703600172500ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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, const 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 = crypto->cryspr->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 = crypto->cryspr->prng(ctx->sek, ctx->sek_len))) { HCRYPT_LOG(LOG_ERR, "PRNG(sek[%zd] failed\n", ctx->sek_len); return(iret); } /* Set SEK in cryspr */ if (crypto->cryspr->ms_setkey(crypto->cryspr_cb, ctx, ctx->sek, ctx->sek_len)) { HCRYPT_LOG(LOG_ERR, "cryspr 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) { iret = hcryptCtx_GenSecret(crypto, ctx); if (iret < 0) 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); } int hcryptCtx_Tx_CloneKey(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const hcrypt_Session* cryptoSrc) { int iret; ASSERT(HCRYPT_CTX_S_SARDY <= ctx->status); const hcrypt_Ctx* ctxSrc = cryptoSrc->ctx; if (!ctxSrc) { /* Probbly the context is not yet completely initialized, so * use blindly the first context from the pair */ ctxSrc = &cryptoSrc->ctx_pair[0]; } /* Copy SALT (instead of generating) */ ctx->salt_len = ctxSrc->salt_len; memcpy(ctx->salt, ctxSrc->salt, ctx->salt_len); /* Copy SEK */ ctx->sek_len = ctxSrc->sek_len; memcpy(ctx->sek, ctxSrc->sek, ctx->sek_len); /* Set SEK in cryspr */ if (crypto->cryspr->ms_setkey(crypto->cryspr_cb, ctx, ctx->sek, ctx->sek_len)) { HCRYPT_LOG(LOG_ERR, "cryspr setkey(sek[%zd]) failed\n", ctx->sek_len); return(-1); } HCRYPT_LOG(LOG_NOTICE, "clone-keyed 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) */ /* (note for CloneKey imp: it's expected that the same passphrase-salt pair shall generate the same KEK. GenSecret also prints the KEK */ if (0 < ctx->cfg.pwd_len) { iret = hcryptCtx_GenSecret(crypto, ctx); if (iret < 0) 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; HCRYPT_LOG(LOG_DEBUG, "refresh/generate SEK. salt_len=%d sek_len=%d\n", (int)new_ctx->salt_len, (int)new_ctx->sek_len); if (0 > crypto->cryspr->prng(new_ctx->sek, new_ctx->sek_len)) { HCRYPT_LOG(LOG_ERR, "PRNG(sek[%zd] failed\n", new_ctx->sek_len); return(-1); } /* Cryspr's dependent key */ if (crypto->cryspr->ms_setkey(crypto->cryspr_cb, new_ctx, new_ctx->sek, new_ctx->sek_len)) { HCRYPT_LOG(LOG_ERR, "refresh cryspr setkey(sek[%d]) failed\n", new_ctx->sek_len); 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 > crypto->cryspr->km_wrap(crypto->cryspr_cb, &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); HCRYPT_LOG(LOG_DEBUG, "KM[%d] KEY STATUS: pkt_cnt=%u against ref.rate=%u and pre.announce=%u\n", (ctx->alt->flags & HCRYPT_CTX_F_xSEK)/2, ctx->pkt_cnt, crypto->km.refresh_rate, crypto->km.pre_announce); 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 ATR_UNUSED) { 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); } srt-1.4.4/haicrypt/hcrypt_msg.h000066400000000000000000000144641412557703600165260ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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 */ srt-1.4.4/haicrypt/hcrypt_rx.c000066400000000000000000000100161412557703600163510ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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->cryspr); /* Header check should prevent this error */ crypto->ctx = ctx; /* Context of last received msg */ if (NULL == crypto->cryspr->ms_decrypt) { HCRYPT_LOG(LOG_ERR, "%s", "cryspr 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->cryspr->ms_decrypt(crypto->cryspr_cb, ctx, &indata, 1, NULL, NULL, NULL))) { HCRYPT_LOG(LOG_ERR, "%s", "ms_decrypt 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->cryspr); /* Header check should prevent this error */ crypto->ctx = ctx; /* Context of last received msg */ if (NULL == crypto->cryspr->ms_decrypt) { HCRYPT_LOG(LOG_ERR, "%s", "cryspr 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->cryspr->ms_decrypt(crypto->cryspr_cb, ctx, &indata, 1, out_p, out_len_p, &nbout)) { HCRYPT_LOG(LOG_ERR, "%s", "ms_decrypt 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); } srt-1.4.4/haicrypt/hcrypt_sa.c000066400000000000000000000053241412557703600163310ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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, const 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 (0 > (iret = crypto->cryspr->km_setkey(crypto->cryspr_cb, (HCRYPT_CTX_F_ENCRYPT & ctx->flags ? true : false), secret->str, secret->len))) { HCRYPT_LOG(LOG_ERR, "km_setkey(pdkek[%zd]) failed (rc=%d)\n", 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 = 0; (void)crypto; iret = crypto->cryspr->km_pbkdf2(crypto->cryspr_cb, 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); if(iret) { HCRYPT_LOG(LOG_ERR, "km_pbkdf2() failed (rc=%d)\n", iret); return(-1); } HCRYPT_PRINTKEY(ctx->cfg.pwd, ctx->cfg.pwd_len, "pwd"); HCRYPT_PRINTKEY(kek, kek_len, "kek"); /* KEK: Key Encrypting Key */ if (0 > (iret = crypto->cryspr->km_setkey(crypto->cryspr_cb, (HCRYPT_CTX_F_ENCRYPT & ctx->flags ? true : false), kek, kek_len))) { HCRYPT_LOG(LOG_ERR, "km_setkey(pdkek[%zd]) failed (rc=%d)\n", kek_len, iret); return(-1); } return(0); } srt-1.4.4/haicrypt/hcrypt_tx.c000066400000000000000000000107201412557703600163550ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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; ASSERT(NULL != crypto); ASSERT(NULL != crypto->cryspr); int pad_factor = (HCRYPT_CTX_MODE_AESECB == crypto->ctx->mode ? 128/8 : 1); #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 = crypto->ctx; int nbout = 0; if ((NULL == crypto) || (NULL == ctx) || (NULL == out_p) || (NULL == out_len_p)) { HCRYPT_LOG(LOG_ERR, "ManageKeys: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } /* Manage Key Material (refresh, announce, decommission) */ hcryptCtx_Tx_ManageKM(crypto); ctx = crypto->ctx; if (NULL == 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 = crypto->ctx; if ((NULL == crypto) || (NULL == ctx)){ HCRYPT_LOG(LOG_ERR, "GetKeyFlags: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); 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 = crypto->ctx; int nbout = 0; if ((NULL == crypto) || (NULL == ctx)){ HCRYPT_LOG(LOG_ERR, "Tx_Data: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); 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->cryspr->ms_encrypt(crypto->cryspr_cb, ctx, &indata, 1, NULL, NULL, NULL))) { HCRYPT_LOG(LOG_ERR, "%s", "ms_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 = crypto->ctx; int nb, nbout = 0; if ((NULL == crypto) || (NULL == ctx) || (NULL == out_p) || (NULL == out_len_p)) { HCRYPT_LOG(LOG_ERR, "Tx_Process: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } /* Manage Key Material (refresh, announce, decommission) */ hcryptCtx_Tx_ManageKM(crypto); ctx = crypto->ctx; if (NULL == 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->cryspr->ms_encrypt(crypto->cryspr_cb, ctx, &indata, 1, &out_p[nbout], &out_len_p[nbout], &nb)) { HCRYPT_LOG(LOG_ERR, "%s", "ms_encrypt failed\n"); return(nbout); } } nbout += nb; ctx->pkt_cnt++; return(nbout); } srt-1.4.4/haicrypt/hcrypt_ut.c000066400000000000000000000131671412557703600163620ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. 2011-07-11 (jdube) HaiCrypt initial implementation. *****************************************************************************/ #include /* memcpy */ #include #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 srt-1.4.4/haicrypt/hcrypt_xpt_srt.c000066400000000000000000000135501412557703600174310ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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); } srt-1.4.4/nuget.config000066400000000000000000000006741412557703600146620ustar00rootroot00000000000000 srt-1.4.4/scripts/000077500000000000000000000000001412557703600140315ustar00rootroot00000000000000srt-1.4.4/scripts/CheckCXXAtomic.cmake000066400000000000000000000027611412557703600175760ustar00rootroot00000000000000# # SRT - Secure, Reliable, Transport # Copyright (c) 2021 Haivision Systems Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # Check for c++11 std::atomic. # # Sets: # HAVE_CXX_ATOMIC # HAVE_CXX_ATOMIC_STATIC include(CheckCXXSourceCompiles) include(CheckLibraryExists) function(CheckCXXAtomic) unset(HAVE_CXX_ATOMIC CACHE) unset(HAVE_CXX_ATOMIC_STATIC CACHE) unset(CMAKE_REQUIRED_FLAGS) unset(CMAKE_REQUIRED_LIBRARIES) unset(CMAKE_REQUIRED_LINK_OPTIONS) set(CheckCXXAtomic_CODE " #include #include int main(void) { std::atomic x(0); std::atomic y(0); return x + y; } ") set(CMAKE_REQUIRED_FLAGS "-std=c++11") check_cxx_source_compiles( "${CheckCXXAtomic_CODE}" HAVE_CXX_ATOMIC) if(HAVE_CXX_ATOMIC) # CMAKE_REQUIRED_LINK_OPTIONS was introduced in CMake 3.14. if(CMAKE_VERSION VERSION_LESS "3.14") set(CMAKE_REQUIRED_LINK_OPTIONS "-static") else() set(CMAKE_REQUIRED_FLAGS "-std=c++11 -static") endif() check_cxx_source_compiles( "${CheckCXXAtomic_CODE}" HAVE_CXX_ATOMIC_STATIC) endif() unset(CMAKE_REQUIRED_FLAGS) unset(CMAKE_REQUIRED_LIBRARIES) unset(CMAKE_REQUIRED_LINK_OPTIONS) endfunction(CheckCXXAtomic) srt-1.4.4/scripts/CheckGCCAtomicIntrinsics.cmake000066400000000000000000000061761412557703600216020ustar00rootroot00000000000000# # SRT - Secure, Reliable, Transport Copyright (c) 2021 Haivision Systems Inc. # # This Source Code Form is subject to the terms of the Mozilla Public License, # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at http://mozilla.org/MPL/2.0/. # # Check for GCC Atomic Intrinsics and whether libatomic is required. # # Sets: # HAVE_LIBATOMIC # HAVE_LIBATOMIC_COMPILES # HAVE_LIBATOMIC_COMPILES_STATIC # HAVE_GCCATOMIC_INTRINSICS # HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC # # See # https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html # https://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync include(CheckCSourceCompiles) include(CheckLibraryExists) function(CheckGCCAtomicIntrinsics) unset(HAVE_LIBATOMIC CACHE) unset(HAVE_LIBATOMIC_COMPILES CACHE) unset(HAVE_LIBATOMIC_COMPILES_STATIC CACHE) unset(HAVE_GCCATOMIC_INTRINSICS CACHE) unset(HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC CACHE) set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) # CMake 3.6 unset(CMAKE_REQUIRED_FLAGS) unset(CMAKE_REQUIRED_LIBRARIES) unset(CMAKE_REQUIRED_LINK_OPTIONS) # Check for existance of libatomic and whether this symbol is present. check_library_exists(atomic __atomic_fetch_add_8 "" HAVE_LIBATOMIC) set(CheckLibAtomicCompiles_CODE " int main(void) { const int result = 0; return result; } ") set(CMAKE_REQUIRED_LIBRARIES "atomic") # Check that the compiler can build a simple application and link with # libatomic. check_c_source_compiles("${CheckLibAtomicCompiles_CODE}" HAVE_LIBATOMIC_COMPILES) if(NOT HAVE_LIBATOMIC_COMPILES) set(HAVE_LIBATOMIC 0 CACHE INTERNAL "" FORCE) endif() if(HAVE_LIBATOMIC AND HAVE_LIBATOMIC_COMPILES) # CMAKE_REQUIRED_LINK_OPTIONS was introduced in CMake 3.14. if(CMAKE_VERSION VERSION_LESS "3.14") set(CMAKE_REQUIRED_LINK_OPTIONS "-static") else() set(CMAKE_REQUIRED_FLAGS "-static") endif() # Check that the compiler can build a simple application and statically link # with libatomic. check_c_source_compiles("${CheckLibAtomicCompiles_CODE}" HAVE_LIBATOMIC_COMPILES_STATIC) else() set(HAVE_LIBATOMIC_COMPILES_STATIC 0 CACHE INTERNAL "" FORCE) endif() unset(CMAKE_REQUIRED_FLAGS) unset(CMAKE_REQUIRED_LIBRARIES) unset(CMAKE_REQUIRED_LINK_OPTIONS) set(CheckGCCAtomicIntrinsics_CODE " #include #include int main(void) { ptrdiff_t x = 0; intmax_t y = 0; __atomic_add_fetch(&x, 1, __ATOMIC_SEQ_CST); __atomic_add_fetch(&y, 1, __ATOMIC_SEQ_CST); return __atomic_sub_fetch(&x, 1, __ATOMIC_SEQ_CST) + __atomic_sub_fetch(&y, 1, __ATOMIC_SEQ_CST); } ") set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) # CMake 3.6 check_c_source_compiles("${CheckGCCAtomicIntrinsics_CODE}" HAVE_GCCATOMIC_INTRINSICS) if(NOT HAVE_GCCATOMIC_INTRINSICS AND HAVE_LIBATOMIC) set(CMAKE_REQUIRED_LIBRARIES "atomic") check_c_source_compiles("${CheckGCCAtomicIntrinsics_CODE}" HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC) if(HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC) set(HAVE_GCCATOMIC_INTRINSICS 1 CACHE INTERNAL "" FORCE) endif() endif() endfunction(CheckGCCAtomicIntrinsics) srt-1.4.4/scripts/FindMbedTLS.cmake000066400000000000000000000066561412557703600171030ustar00rootroot00000000000000# original file from obs-studio: # https://github.com/obsproject/obs-studio # /cmake/Modules/FindMbedTLS.cmake # # Once done these will be defined: # # LIBMBEDTLS_FOUND # LIBMBEDTLS_INCLUDE_DIRS # LIBMBEDTLS_LIBRARIES # # For use in OBS: # # MBEDTLS_INCLUDE_DIR find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(_MBEDTLS QUIET mbedtls) endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_lib_suffix 64) else() set(_lib_suffix 32) endif() # If we're on MacOS or Linux, please try to statically-link mbedtls. if(STATIC_MBEDTLS AND (APPLE OR UNIX)) set(_MBEDTLS_LIBRARIES libmbedtls.a) set(_MBEDCRYPTO_LIBRARIES libmbedcrypto.a) set(_MBEDX509_LIBRARIES libmbedx509.a) endif() find_path(MBEDTLS_INCLUDE_DIR NAMES mbedtls/ssl.h HINTS ${MBEDTLS_PREFIX} PATHS /usr/include /usr/local/include /opt/local/include /sw/include PATH_SUFFIXES include) find_library(MBEDTLS_LIB NAMES ${_MBEDTLS_LIBRARIES} mbedtls libmbedtls HINTS ${MBEDTLS_PREFIX} PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib PATH_SUFFIXES lib${_lib_suffix} lib libs${_lib_suffix} libs bin${_lib_suffix} bin ../lib${_lib_suffix} ../lib ../libs${_lib_suffix} ../libs ../bin${_lib_suffix} ../bin) find_library(MBEDCRYPTO_LIB NAMES ${_MBEDCRYPTO_LIBRARIES} mbedcrypto libmbedcrypto HINTS ${MBEDTLS_PREFIX} PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib PATH_SUFFIXES lib${_lib_suffix} lib libs${_lib_suffix} libs bin${_lib_suffix} bin ../lib${_lib_suffix} ../lib ../libs${_lib_suffix} ../libs ../bin${_lib_suffix} ../bin) find_library(MBEDX509_LIB NAMES ${_MBEDX509_LIBRARIES} mbedx509 libmbedx509 HINTS ${MBEDTLS_PREFIX} PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib PATH_SUFFIXES lib${_lib_suffix} lib libs${_lib_suffix} libs bin${_lib_suffix} bin ../lib${_lib_suffix} ../lib ../libs${_lib_suffix} ../libs ../bin${_lib_suffix} ../bin) # Sometimes mbedtls is split between three libs, and sometimes it isn't. # If it isn't, let's check if the symbols we need are all in MBEDTLS_LIB. if(MBEDTLS_LIB AND NOT MBEDCRYPTO_LIB AND NOT MBEDX509_LIB) set(CMAKE_REQUIRED_LIBRARIES ${MBEDTLS_LIB}) set(CMAKE_REQUIRED_INCLUDES ${MBEDTLS_INCLUDE_DIR}) check_symbol_exists(mbedtls_x509_crt_init "mbedtls/x509_crt.h" MBEDTLS_INCLUDES_X509) check_symbol_exists(mbedtls_sha256_init "mbedtls/sha256.h" MBEDTLS_INCLUDES_CRYPTO) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) endif() # If we find all three libraries, then go ahead. if(MBEDTLS_LIB AND MBEDCRYPTO_LIB AND MBEDX509_LIB) set(LIBMBEDTLS_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR}) set(LIBMBEDTLS_LIBRARIES ${MBEDTLS_LIB} ${MBEDCRYPTO_LIB} ${MBEDX509_LIB}) set(MBEDTLS_INCLUDE_DIRS ${LIBMBEDTLS_INCLUDE_DIRS}) set(MBEDTLS_LIBRARIES ${LIBMBEDTLS_LIBRARIES}) # Otherwise, if we find MBEDTLS_LIB, and it has both CRYPTO and x509 # within the single lib (i.e. a windows build environment), then also # feel free to go ahead. elseif(MBEDTLS_LIB AND MBEDTLS_INCLUDES_CRYPTO AND MBEDTLS_INCLUDES_X509) set(LIBMBEDTLS_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR}) set(LIBMBEDTLS_LIBRARIES ${MBEDTLS_LIB}) set(MBEDTLS_INCLUDE_DIRS ${LIBMBEDTLS_INCLUDE_DIRS}) set(MBEDTLS_LIBRARIES ${LIBMBEDTLS_LIBRARIES}) endif() # Now we've accounted for the 3-vs-1 library case: include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MbedTLS DEFAULT_MSG MBEDTLS_LIBRARIES MBEDTLS_INCLUDE_DIRS) mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARIES MBEDTLS_INCLUDE_DIRS) srt-1.4.4/scripts/FindPThreadGetSetName.cmake000066400000000000000000000052331412557703600211030ustar00rootroot00000000000000# # SRT - Secure, Reliable, Transport # Copyright (c) 2021 Haivision Systems Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # Check for pthread_getname_np(3) and pthread_setname_np(3) # used in srtcore/threadname.h. # # Some BSD distros need to include for pthread_getname_np(). # # TODO: Some BSD distros have pthread_get_name_np() and pthread_set_name_np() # instead of pthread_getname_np() and pthread_setname_np(). # # Sets: # HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H # HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H # HAVE_PTHREAD_GETNAME_NP # HAVE_PTHREAD_SETNAME_NP # Sets as appropriate: # add_definitions(-DHAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H=1) # add_definitions(-DHAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H=1) # add_definitions(-DHAVE_PTHREAD_GETNAME_NP=1) # add_definitions(-DHAVE_PTHREAD_SETNAME_NP=1) include(CheckSymbolExists) function(FindPThreadGetSetName) unset(HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H CACHE) unset(HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H CACHE) unset(HAVE_PTHREAD_GETNAME_NP CACHE) unset(HAVE_PTHREAD_SETNAME_NP CACHE) set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE -D_DARWIN_C_SOURCE -D_POSIX_SOURCE=1) set(CMAKE_REQUIRED_FLAGS "-pthread") message(STATUS "Checking for pthread_(g/s)etname_np in 'pthread_np.h':") check_symbol_exists( pthread_getname_np "pthread_np.h" HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) if (HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) add_definitions(-DHAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H=1) endif() check_symbol_exists( pthread_setname_np "pthread_np.h" HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) if (HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) add_definitions(-DHAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H=1) endif() message(STATUS "Checking for pthread_(g/s)etname_np in 'pthread.h':") check_symbol_exists(pthread_getname_np "pthread.h" HAVE_PTHREAD_GETNAME_NP) if (HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) set(HAVE_PTHREAD_GETNAME_NP 1 CACHE INTERNAL "" FORCE) endif() check_symbol_exists(pthread_setname_np "pthread.h" HAVE_PTHREAD_SETNAME_NP) if (HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) set(HAVE_PTHREAD_SETNAME_NP 1 CACHE INTERNAL "" FORCE) endif() if (HAVE_PTHREAD_GETNAME_NP) add_definitions(-DHAVE_PTHREAD_GETNAME_NP=1) endif() if (HAVE_PTHREAD_SETNAME_NP) add_definitions(-DHAVE_PTHREAD_SETNAME_NP=1) endif() unset(CMAKE_REQUIRED_DEFINITIONS) unset(CMAKE_REQUIRED_FLAGS) endfunction(FindPThreadGetSetName) srt-1.4.4/scripts/build-android/000077500000000000000000000000001412557703600165465ustar00rootroot00000000000000srt-1.4.4/scripts/build-android/README.md000066400000000000000000000002011412557703600200160ustar00rootroot00000000000000## Scripts for building SRT for Android See [Building SRT for Android](../../docs/build/build-android.md) for the instructions. srt-1.4.4/scripts/build-android/build-android000077500000000000000000000064221412557703600212150ustar00rootroot00000000000000#!/bin/sh echo_help() { echo "Usage: $0 [options...]" echo " -n Specify NDK root path for the build." echo " -a Select target API level." echo " -t Select target architectures." echo " Android supports the following architectures: armeabi-v7a arm64-v8a x86 x86_64." echo " -e Encryption library to be used. Possible options: openssl (default) mbedtls" echo " -o Select OpenSSL (1.1.1 series) version. E.g. 1.1.1h" echo " -m Select Mbed TLS version. E.g. v2.26.0" echo " -s Select SRT version. E.g. v1.4.3" echo echo "Example: ./build-android -n /home/username/Android/Sdk/ndk/21.4.7075529 -a 28 -t \"armeabi-v7a arm64-v8a x86 x86_64\" -o 1.1.1h -s v1.4.3" echo } # Init optional command line vars NDK_ROOT="" API_LEVEL=28 BUILD_TARGETS="armeabi-v7a arm64-v8a x86 x86_64" OPENSSL_VERSION=1.1.1h SRT_VERSION="" ENC_LIB=openssl MBEDTLS_VERSION=v2.26.0 while getopts n:a:t:o:s:e:m: option do case "${option}" in n) NDK_ROOT=${OPTARG};; a) API_LEVEL=${OPTARG};; t) BUILD_TARGETS=${OPTARG};; o) OPENSSL_VERSION=${OPTARG};; s) SRT_VERSION=${OPTARG};; e) ENC_LIB=${OPTARG};; m) MBEDTLS_VERSION=${OPTARG};; *) twentytwo=${OPTARG};; esac done echo_help if [ -z "$NDK_ROOT" ] ; then echo "NDK directory not set." exit 128 else if [ ! -d "$NDK_ROOT" ]; then echo "NDK directory does not exist: $NDK_ROOT" exit 128 fi fi SCRIPT_DIR=$(pwd) HOST_TAG='unknown' unamestr=$(uname -s) if [ "$unamestr" = 'Linux' ]; then SCRIPT_DIR=$(readlink -f $0 | xargs dirname) HOST_TAG='linux-x86_64' elif [ "$unamestr" = 'Darwin' ]; then SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) if [ $(uname -p) = 'arm' ]; then echo "NDK does not currently support ARM64" exit 128 else HOST_TAG='darwin-x86_64' fi fi # Write files relative to current location BASE_DIR=$(pwd) case "${BASE_DIR}" in *\ * ) echo "Your path contains whitespaces, which is not supported by 'make install'." exit 128 ;; esac cd "${BASE_DIR}" if [ $ENC_LIB = 'openssl' ]; then echo "Building OpenSSL $OPENSSL_VERSION" $SCRIPT_DIR/mkssl -n $NDK_ROOT -a $API_LEVEL -t "$BUILD_TARGETS" -o $OPENSSL_VERSION -d $BASE_DIR -h $HOST_TAG elif [ $ENC_LIB = 'mbedtls' ]; then if [ ! -d $BASE_DIR/mbedtls ]; then git clone https://github.com/ARMmbed/mbedtls mbedtls if [ ! -z "$MBEDTLS_VERSION" ]; then git -C $BASE_DIR/mbedtls checkout $MBEDTLS_VERSION fi fi else echo "Unknown encryption library. Possible options: openssl mbedtls" exit 128 fi if [ ! -d $BASE_DIR/srt ]; then git clone https://github.com/Haivision/srt srt if [ ! -z "$SRT_VERSION" ]; then git -C $BASE_DIR/srt checkout $SRT_VERSION fi fi for build_target in $BUILD_TARGETS; do LIB_DIR=$BASE_DIR/$build_target/lib JNI_DIR=$BASE_DIR/prebuilt/$build_target mkdir -p $JNI_DIR if [ $ENC_LIB = 'mbedtls' ]; then $SCRIPT_DIR/mkmbedtls -n $NDK_ROOT -a $API_LEVEL -t $build_target -s $BASE_DIR/mbedtls -i $BASE_DIR/$build_target cp $LIB_DIR/libmbedcrypto.so $JNI_DIR/libmbedcrypto.so cp $LIB_DIR/libmbedtls.so $JNI_DIR/libmbedtls.so cp $LIB_DIR/libmbedx509.so $JNI_DIR/libmbedx509.so fi git -C $BASE_DIR/srt clean -fd $SCRIPT_DIR/mksrt -n $NDK_ROOT -a $API_LEVEL -t $build_target -e $ENC_LIB -s $BASE_DIR/srt -i $BASE_DIR/$build_target cp $LIB_DIR/libsrt.so $JNI_DIR/libsrt.so done srt-1.4.4/scripts/build-android/mkmbedtls000077500000000000000000000013321412557703600204550ustar00rootroot00000000000000#!/bin/sh while getopts s:i:t:n:a: option do case "${option}" in s) SRC_DIR=${OPTARG};; i) INSTALL_DIR=${OPTARG};; t) ARCH_ABI=${OPTARG};; n) NDK_ROOT=${OPTARG};; a) API_LEVEL=${OPTARG};; *) twentytwo=${OPTARG};; esac done BUILD_DIR=/tmp/mbedtls_android_build rm -rf $BUILD_DIR mkdir $BUILD_DIR cd $BUILD_DIR cmake -DENABLE_TESTING=Off -DUSE_SHARED_MBEDTLS_LIBRARY=On \ -DCMAKE_PREFIX_PATH=$INSTALL_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR -DCMAKE_ANDROID_NDK=$NDK_ROOT \ -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=$API_LEVEL -DCMAKE_ANDROID_ARCH_ABI=$ARCH_ABI \ -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_SHARED_LINKER_FLAGS="-Wl,--build-id" \ -DCMAKE_BUILD_TYPE=RelWithDebInfo $SRC_DIR cmake --build . cmake --install . srt-1.4.4/scripts/build-android/mksrt000077500000000000000000000020731412557703600176360ustar00rootroot00000000000000#!/bin/sh while getopts s:i:t:n:a:e: option do case "${option}" in s) SRC_DIR=${OPTARG};; i) INSTALL_DIR=${OPTARG};; t) ARCH_ABI=${OPTARG};; n) NDK_ROOT=${OPTARG};; a) API_LEVEL=${OPTARG};; e) ENC_LIB=${OPTARG};; *) twentytwo=${OPTARG};; esac done cd $SRC_DIR ./configure --use-enclib=$ENC_LIB \ --use-openssl-pc=OFF --OPENSSL_USE_STATIC_LIBS=TRUE \ --OPENSSL_INCLUDE_DIR=$INSTALL_DIR/include \ --OPENSSL_CRYPTO_LIBRARY=$INSTALL_DIR/lib/libcrypto.a --OPENSSL_SSL_LIBRARY=$INSTALL_DIR/lib/libssl.a \ --STATIC_MBEDTLS=FALSE \ --MBEDTLS_INCLUDE_DIR=$INSTALL_DIR/include --MBEDTLS_INCLUDE_DIRS=$INSTALL_DIR/include \ --MBEDTLS_LIBRARIES=$INSTALL_DIR/lib/libmbedtls.so \ --CMAKE_PREFIX_PATH=$INSTALL_DIR --CMAKE_INSTALL_PREFIX=$INSTALL_DIR --CMAKE_ANDROID_NDK=$NDK_ROOT \ --CMAKE_SYSTEM_NAME=Android --CMAKE_SYSTEM_VERSION=$API_LEVEL --CMAKE_ANDROID_ARCH_ABI=$ARCH_ABI \ --CMAKE_C_FLAGS="-fPIC" --CMAKE_SHARED_LINKER_FLAGS="-Wl,--build-id" \ --enable-c++11 --enable-stdcxx-sync \ --enable-debug=2 --enable-logging=0 --enable-heavy-logging=0 --enable-apps=0 make make install srt-1.4.4/scripts/build-android/mkssl000077500000000000000000000077721412557703600176420ustar00rootroot00000000000000#!/bin/sh while getopts n:o:a:t:d:h: option do case "${option}" in n) ANDROID_NDK=${OPTARG};; o) OPENSSL_VERSION=${OPTARG};; a) API_LEVEL=${OPTARG};; t) BUILD_TARGETS=${OPTARG};; d) OUT_DIR=${OPTARG};; h) HOST_TAG=${OPTARG};; *) twentytwo=${OPTARG};; esac done BUILD_DIR=/tmp/openssl_android_build if [ ! -d openssl-${OPENSSL_VERSION} ] then if [ ! -f openssl-${OPENSSL_VERSION}.tar.gz ] then wget https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz || exit 128 fi tar xzf openssl-${OPENSSL_VERSION}.tar.gz || exit 128 fi cd openssl-${OPENSSL_VERSION} || exit 128 ##### Prepare Files ##### sed -i.bak 's/.*-mandroid.*//' Configurations/15-android.conf patch -p1 -N <{\$lib}. \$shlibvariant. '\$(SHLIB_EXT)'; + + if (windowsdll()) { + return \$lib . '\$(SHLIB_EXT_IMPORT)'; + } + return \$lib . '\$(SHLIB_EXT_SIMPLE)'; } - sub shlib_simple { + + sub shlib { my \$lib = shift; return () if \$disabled{shared} || \$lib =~ /\\.a$/; EOP ##### remove output-directory ##### #rm -rf $OUT_DIR ##### export ndk directory. Required by openssl-build-scripts ##### export ANDROID_NDK ##### build-function ##### build_the_thing() { TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG export PATH=$TOOLCHAIN/$TRIBLE/bin:$TOOLCHAIN/bin:"$PATH" echo $PATH make clean #./Configure $SSL_TARGET $OPTIONS -fuse-ld="$TOOLCHAIN/$TRIBLE/bin/ld" "-gcc-toolchain $TOOLCHAIN" && \ ./Configure $SSL_TARGET $OPTIONS -fuse-ld="$TOOLCHAIN/$TRIBLE/bin/ld" && \ make && \ make install DESTDIR=$DESTDIR || exit 128 } ##### set variables according to build-tagret ##### for build_target in $BUILD_TARGETS do case $build_target in armeabi) TRIBLE="arm-linux-androideabi" TC_NAME="arm-linux-androideabi-4.9" #OPTIONS="--target=armv5te-linux-androideabi -mthumb -fPIC -latomic -D__ANDROID_API__=$API_LEVEL" OPTIONS="--target=armv5te-linux-androideabi -mthumb -fPIC -latomic -D__ANDROID_API__=$API_LEVEL" DESTDIR="$BUILD_DIR/armeabi" SSL_TARGET="android-arm" ;; armeabi-v7a) TRIBLE="arm-linux-androideabi" TC_NAME="arm-linux-androideabi-4.9" OPTIONS="--target=armv7a-linux-androideabi -Wl,--fix-cortex-a8 -fPIC -D__ANDROID_API__=$API_LEVEL" DESTDIR="$BUILD_DIR/armeabi-v7a" SSL_TARGET="android-arm" ;; x86) TRIBLE="i686-linux-android" TC_NAME="x86-4.9" OPTIONS="-fPIC -D__ANDROID_API__=${API_LEVEL}" DESTDIR="$BUILD_DIR/x86" SSL_TARGET="android-x86" ;; x86_64) TRIBLE="x86_64-linux-android" TC_NAME="x86_64-4.9" OPTIONS="-fPIC -D__ANDROID_API__=${API_LEVEL}" DESTDIR="$BUILD_DIR/x86_64" SSL_TARGET="android-x86_64" ;; arm64-v8a) TRIBLE="aarch64-linux-android" TC_NAME="aarch64-linux-android-4.9" OPTIONS="-fPIC -D__ANDROID_API__=${API_LEVEL}" DESTDIR="$BUILD_DIR/arm64-v8a" SSL_TARGET="android-arm64" ;; esac rm -rf $DESTDIR build_the_thing #### copy libraries and includes to output-directory ##### mkdir -p $OUT_DIR/$build_target/include cp -R $DESTDIR/usr/local/include/* $OUT_DIR/$build_target/include cp -R $DESTDIR/usr/local/ssl/* $OUT_DIR/$build_target/ mkdir -p $OUT_DIR/$build_target/lib cp -R $DESTDIR/usr/local/lib/*.so $OUT_DIR/$build_target/lib cp -R $DESTDIR/usr/local/lib/*.a $OUT_DIR/$build_target/lib done echo Success srt-1.4.4/scripts/build-windows.bat000066400000000000000000000001461412557703600173110ustar00rootroot00000000000000@ECHO OFF %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\PowerShell.exe -Command "& '%~dpn0.ps1'" pause srt-1.4.4/scripts/build-windows.ps1000066400000000000000000000245111412557703600172500ustar00rootroot00000000000000################################################################################ # Windows SRT Build Script #============================ # Usable on a Windows PC with Powershell and Visual studio, # or called by CI systems like AppVeyor # # By default produces a VS2019 64-bit Release binary using C++11 threads, without # encryption or unit tests enabled, but including test apps. # Before enabling any encryption options, install OpenSSL or set VCKPG flag to build ################################################################################ param ( [Parameter()][String]$VS_VERSION = "2019", [Parameter()][String]$CONFIGURATION = "Release", [Parameter()][String]$DEVENV_PLATFORM = "x64", [Parameter()][String]$ENABLE_ENCRYPTION = "OFF", [Parameter()][String]$STATIC_LINK_SSL = "OFF", [Parameter()][String]$CXX11 = "ON", [Parameter()][String]$BUILD_APPS = "ON", [Parameter()][String]$UNIT_TESTS = "OFF", [Parameter()][String]$BUILD_DIR = "_build", [Parameter()][String]$VCPKG_OPENSSL = "OFF" ) # cmake can be optionally installed (useful when running interactively on a developer station). # The URL for automatic download is defined later in the script, but it should be possible to just vary the # specific version set below and the URL should be stable enough to still work - you have been warned. $cmakeVersion = "3.17.3" # make all errors trigger a script stop, rather than just carry on $ErrorActionPreference = "Stop" $projectRoot = Join-Path $PSScriptRoot "/.." -Resolve # if running within AppVeyor, use environment variables to set params instead of passed-in values if ( $Env:APPVEYOR ) { if ( $Env:PLATFORM -eq 'x86' ) { $DEVENV_PLATFORM = 'Win32' } else { $DEVENV_PLATFORM = 'x64' } if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2019' ) { $VS_VERSION='2019' } if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2015' ) { $VS_VERSION='2015' } if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2013' ) { $VS_VERSION='2013' } #if not statically linking OpenSSL, set flag to gather the specific openssl package from the build server into package if ( $STATIC_LINK_SSL -eq 'OFF' ) { $Env:GATHER_SSL_INTO_PACKAGE = $true } #if unit tests are on, set flag to actually execute ctest step if ( $UNIT_TESTS -eq 'ON' ) { $Env:RUN_UNIT_TESTS = $true } $CONFIGURATION = $Env:CONFIGURATION #appveyor has many openssl installations - place the latest one in the default location unless VS2013 if( $VS_VERSION -ne '2013' ) { Remove-Item -Path "C:\OpenSSL-Win32" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null Remove-Item -Path "C:\OpenSSL-Win64" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null Copy-Item -Path "C:\OpenSSL-v111-Win32" "C:\OpenSSL-Win32" -Recurse | Out-Null Copy-Item -Path "C:\OpenSSL-v111-Win64" "C:\OpenSSL-Win64" -Recurse | Out-Null } } # persist VS_VERSION so it can be used in an artifact name later $Env:VS_VERSION = $VS_VERSION # select the appropriate cmake generator string given the environment if ( $VS_VERSION -eq '2019' ) { $CMAKE_GENERATOR = 'Visual Studio 16 2019'; $MSBUILDVER = "16.0"; } if ( $VS_VERSION -eq '2015' -and $DEVENV_PLATFORM -eq 'Win32' ) { $CMAKE_GENERATOR = 'Visual Studio 14 2015'; $MSBUILDVER = "14.0"; } if ( $VS_VERSION -eq '2015' -and $DEVENV_PLATFORM -eq 'x64' ) { $CMAKE_GENERATOR = 'Visual Studio 14 2015 Win64'; $MSBUILDVER = "14.0"; } if ( $VS_VERSION -eq '2013' -and $DEVENV_PLATFORM -eq 'Win32' ) { $CMAKE_GENERATOR = 'Visual Studio 12 2013'; $MSBUILDVER = "12.0"; } if ( $VS_VERSION -eq '2013' -and $DEVENV_PLATFORM -eq 'x64' ) { $CMAKE_GENERATOR = 'Visual Studio 12 2013 Win64'; $MSBUILDVER = "12.0"; } # clear any previous build and create & enter the build directory $buildDir = Join-Path "$projectRoot" "$BUILD_DIR" Write-Output "Creating (or cleaning if already existing) the folder $buildDir for project files and outputs" Remove-Item -Path $buildDir -Recurse -Force -ErrorAction SilentlyContinue | Out-Null New-Item -ItemType Directory -Path $buildDir -ErrorAction SilentlyContinue | Out-Null Push-Location $buildDir # check cmake is installed if ( $null -eq (Get-Command "cmake.exe" -ErrorAction SilentlyContinue) ) { $installCmake = Read-Host "Unable to find cmake in your PATH - would you like to download and install automatically? [yes/no]" if ( $installCmake -eq "y" -or $installCmake -eq "yes" ) { # download cmake and run MSI for user $client = New-Object System.Net.WebClient $tempDownloadFile = New-TemporaryFile $cmakeUrl = "https://github.com/Kitware/CMake/releases/download/v$cmakeVersion/cmake-$cmakeVersion-win64-x64.msi" $cmakeMsiFile = "$tempDownloadFile.cmake-$cmakeVersion-win64-x64.msi" Write-Output "Downloading cmake from $cmakeUrl (temporary file location $cmakeMsiFile)" Write-Output "Note: select the option to add cmake to path for this script to operate" $client.DownloadFile("$cmakeUrl", "$cmakeMsiFile") Start-Process $cmakeMsiFile -Wait Remove-Item $cmakeMsiFile Write-Output "Cmake should have installed, this script will now exit because of path updates - please now re-run this script" throw } else{ Write-Output "Quitting because cmake is required" throw } } # get pthreads from nuget if CXX11 is not enabled if ( $CXX11 -eq "OFF" ) { # get pthreads (this is legacy, and is only availble in nuget for VS2015 and VS2013) if ( $VS_VERSION -gt 2015 ) { Write-Output "Pthreads is not recommended for use beyond VS2015 and is not supported by this build script - aborting build" throw } if ( $DEVENV_PLATFORM -eq 'Win32' ) { nuget install cinegy.pthreads-win32-$VS_VERSION -version 2.9.1.24 -OutputDirectory ../_packages } else { nuget install cinegy.pthreads-win64-$VS_VERSION -version 2.9.1.24 -OutputDirectory ../_packages } } # check to see if static SSL linking was requested, and enable encryption if not already ON if ( $STATIC_LINK_SSL -eq "ON" ) { if ( $ENABLE_ENCRYPTION -eq "OFF" ) { # requesting a static link implicitly requires encryption support Write-Output "Static linking to OpenSSL requested, will force encryption feature ON" $ENABLE_ENCRYPTION = "ON" } } # check to see if VCPKG is marked to provide OpenSSL, and enable encryption if not already ON if ( $VCPKG_OPENSSL -eq "ON" ) { if ( $ENABLE_ENCRYPTION -eq "OFF" ) { # requesting VCPKG to provide OpenSSL requires encryption support Write-Output "VCPKG compilation of OpenSSL requested, will force encryption feature ON" $ENABLE_ENCRYPTION = "ON" } } # build the cmake command flags from arguments $cmakeFlags = "-DCMAKE_BUILD_TYPE=$CONFIGURATION " + "-DENABLE_STDCXX_SYNC=$CXX11 " + "-DENABLE_APPS=$BUILD_APPS " + "-DENABLE_ENCRYPTION=$ENABLE_ENCRYPTION " + "-DENABLE_UNITTESTS=$UNIT_TESTS" # if VCPKG is flagged to provide OpenSSL, checkout VCPKG and install package if ( $VCPKG_OPENSSL -eq 'ON' ) { Push-Location $projectRoot Write-Output "Cloning VCPKG into: $(Get-Location)" if (Test-Path -Path ".\vcpkg") { Set-Location .\vcpkg git pull } else { git clone https://github.com/microsoft/vcpkg Set-Location .\vcpkg } .\bootstrap-vcpkg.bat if($DEVENV_PLATFORM -EQ "x64"){ if($STATIC_LINK_SSL -EQ "ON"){ .\vcpkg install openssl:x64-windows-static $cmakeFlags += " -DVCPKG_TARGET_TRIPLET=x64-windows-static" } else{ .\vcpkg install openssl:x64-windows } } else{ if($STATIC_LINK_SSL -EQ "ON"){ .\vcpkg install openssl:x86-windows-static $cmakeFlags += " -DVCPKG_TARGET_TRIPLET=x86-windows-static" } else{ .\vcpkg install openssl:x86-windows } } .\vcpkg integrate install Pop-Location $cmakeFlags += " -DCMAKE_TOOLCHAIN_FILE=$projectRoot\vcpkg\scripts\buildsystems\vcpkg.cmake" } else { $cmakeFlags += " -DOPENSSL_USE_STATIC_LIBS=$STATIC_LINK_SSL " } # cmake uses a flag for architecture from vs2019, so add that as a suffix if ( $VS_VERSION -eq '2019' ) { $cmakeFlags += " -A `"$DEVENV_PLATFORM`"" } # fire cmake to build project files $execVar = "cmake ../ -G`"$CMAKE_GENERATOR`" $cmakeFlags" Write-Output $execVar # Reset reaction to Continue for cmake as it sometimes tends to print # things on stderr, which is understood by PowerShell as error. The # exit code from cmake will be checked anyway. $ErrorActionPreference = "Continue" Invoke-Expression "& $execVar" # check build ran OK, exit if cmake failed if( $LASTEXITCODE -ne 0 ) { Write-Output "Non-zero exit code from cmake: $LASTEXITCODE" throw } $ErrorActionPreference = "Stop" # run the set-version-metadata script to inject build numbers into appveyors console and the resulting DLL . $PSScriptRoot/set-version-metadata.ps1 # look for msbuild $msBuildPath = Get-Command "msbuild.exe" -ErrorAction SilentlyContinue if ( $null -eq $msBuildPath ) { # no mbsuild in the path, so try to locate with 'vswhere' $vsWherePath = Get-Command "vswhere.exe" -ErrorAction SilentlyContinue if ( $null -eq $vsWherePath ) { # no vswhere in the path, so check the Microsoft published location (true since VS2017 Update 2) $vsWherePath = Get-Command "${Env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -ErrorAction SilentlyContinue if ( $null -eq $vsWherePath ) { Write-Output "Cannot find vswhere (used to locate msbuild). Please install VS2017 update 2 (or later) or add vswhere to your path and try again" throw } } $msBuildPath = & $vsWherePath -products * -version $MSBUILDVER -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | select-object -first 1 if ( $null -eq $msBuildPath ) { Write-Output "vswhere.exe cannot find msbuild for the specified Visual Studio version - please check the installation" throw } } & $msBuildPath SRT.sln -m /p:Configuration=$CONFIGURATION /p:Platform=$DEVENV_PLATFORM # return to the directory previously occupied before running the script Pop-Location # if msbuild returned non-zero, throw to cause failure in CI if( $LASTEXITCODE -ne 0 ) { throw } srt-1.4.4/scripts/changelog/000077500000000000000000000000001412557703600157605ustar00rootroot00000000000000srt-1.4.4/scripts/changelog/README.md000066400000000000000000000006501412557703600172400ustar00rootroot00000000000000# Changelog Script designed to create changelog out of `.csv` SRT git log. The output `changelog.md` is generated in the root folder. In order to generate git log file since the previous release (e.g., v1.4.0), use the following command: ``` git log --pretty=format:"%h|%s|%an|%ae" v1.4.0...HEAD^ > commits.csv ``` ## Requirements * python 3.6+ To install python libraries use: ``` pip install -r requirements.txt ``` srt-1.4.4/scripts/changelog/changelog.py000066400000000000000000000044361412557703600202700ustar00rootroot00000000000000import enum import click import numpy as np import pandas as pd @enum.unique class Area(enum.Enum): core = 'core' tests = 'tests' build = 'build' apps = 'apps' docs = 'docs' def define_area(msg): areas = [e.value for e in Area] for area in areas: if msg.startswith(f'[{area}] '): return area return np.NaN def delete_prefix(msg): prefixes = [f'[{e.value}] ' for e in Area] for prefix in prefixes: if msg.startswith(prefix): return msg[len(prefix):] return msg[:] def write_into_changelog(df, f): f.write('\n') for _, row in df.iterrows(): f.write(f"\n{row['commit']} {row['message']}") f.write('\n') @click.command() @click.argument( 'git_log', type=click.Path(exists=True) ) def main(git_log): """ Script designed to create changelog out of .csv SRT git log """ df = pd.read_csv(git_log, sep = '|', names = ['commit', 'message', 'author', 'email']) df['area'] = df['message'].apply(define_area) df['message'] = df['message'].apply(delete_prefix) core = df[df['area']=='core'] tests = df[df['area']=='tests'] build = df[df['area']=='build'] apps = df[df['area']=='apps'] docs = df[df['area']=='docs'] other = df[df['area'].isna()] with open('changelog.md', 'w') as f: f.write('# Release Notes\n') f.write('\n## Changelog\n') f.write('\n
Click to expand/collapse') f.write('\n

') f.write('\n') if not core.empty: f.write('\n### Core Functionality') write_into_changelog(core, f) if not tests.empty: f.write('\n### Unit Tests') write_into_changelog(tests, f) if not build.empty: f.write('\n### Build Scripts (CMake, etc.)') write_into_changelog(build, f) if not apps.empty: f.write('\n### Sample Applications') write_into_changelog(apps, f) if not docs.empty: f.write('\n### Documentation') write_into_changelog(docs, f) if not other.empty: f.write('\n### Other') write_into_changelog(other, f) f.write('\n

') f.write('\n
') if __name__ == '__main__': main() srt-1.4.4/scripts/changelog/requirements.txt000066400000000000000000000000511412557703600212400ustar00rootroot00000000000000click>=7.1.2 numpy>=1.19.1 pandas>=0.25.3srt-1.4.4/scripts/check-deps000077500000000000000000000027231412557703600157710ustar00rootroot00000000000000#!/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) 2018 Haivision Systems Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # if { [catch {package require Tcl 8.5}] } { puts stderr "Tcl version at least 8.5 required, please upgrade" exit 1 } set ok 1 if { [catch {exec pkg-config --exists openssl}] } { set ok 0 puts "Openssl: NOT INSTALLED, please install (libssl-dev\[el\], openssl-dev\[el\] etc.)" } else { puts "Openssl: found version [exec pkg-config --modversion openssl] -- ok" } set nothave [catch {set cmake [exec cmake --version]}] if { $nothave } { puts "CMake version >= 2.8 required - please install cmake" set ok 0 } else { set cmakel1 [lindex [split $cmake \n] 0] set cv [lindex $cmakel1 end] if { [package vcompare $cv 2.8] == -1 } { puts "CMake version >= 2.8 required - please upgrade cmake" set ok 0 } else { puts "Cmake version $cv -- ok." } } # 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 srt-1.4.4/scripts/collect-gcov.sh000066400000000000000000000001651412557703600167500ustar00rootroot00000000000000#!/bin/bash shopt -s globstar gcov_data_dir="." for x in ./**/*.o; do echo "x: $x" gcov "$gcov_data_dir/$x" done srt-1.4.4/scripts/gather-package.bat000066400000000000000000000031711412557703600173660ustar00rootroot00000000000000rem Create empty directories for package bundle @echo off IF "%PLATFORM%"=="x86" ( SET FOLDER_PLATFORM="32" ) ELSE IF "%PLATFORM%"=="x64" ( SET FOLDER_PLATFORM="64" ) ELSE ( echo "Platform %PLATFORM% is not supported" exit 1 ) md %APPVEYOR_BUILD_FOLDER%\package md %APPVEYOR_BUILD_FOLDER%\package\include md %APPVEYOR_BUILD_FOLDER%\package\include\win md %APPVEYOR_BUILD_FOLDER%\package\bin md %APPVEYOR_BUILD_FOLDER%\package\lib IF "%GATHER_SSL_INTO_PACKAGE%"=="True" ( md %APPVEYOR_BUILD_FOLDER%\package\openssl-win%FOLDER_PLATFORM% ) rem Gather SRT includes, binaries and libs copy %APPVEYOR_BUILD_FOLDER%\version.h %APPVEYOR_BUILD_FOLDER%\package\include\ copy %APPVEYOR_BUILD_FOLDER%\srtcore\*.h %APPVEYOR_BUILD_FOLDER%\package\include\ copy %APPVEYOR_BUILD_FOLDER%\haicrypt\*.h %APPVEYOR_BUILD_FOLDER%\package\include\ copy %APPVEYOR_BUILD_FOLDER%\common\*.h %APPVEYOR_BUILD_FOLDER%\package\include\ copy %APPVEYOR_BUILD_FOLDER%\common\win\*.h %APPVEYOR_BUILD_FOLDER%\package\include\win\ copy %APPVEYOR_BUILD_FOLDER%\_build\%CONFIGURATION%\*.exe %APPVEYOR_BUILD_FOLDER%\package\bin\ copy %APPVEYOR_BUILD_FOLDER%\_build\%CONFIGURATION%\*.dll %APPVEYOR_BUILD_FOLDER%\package\bin\ copy %APPVEYOR_BUILD_FOLDER%\_build\%CONFIGURATION%\*.lib %APPVEYOR_BUILD_FOLDER%\package\lib\ copy %APPVEYOR_BUILD_FOLDER%\_build\%CONFIGURATION%\*.pdb %APPVEYOR_BUILD_FOLDER%\package\bin\ rem Gather 3rd party openssl elements IF "%GATHER_SSL_INTO_PACKAGE%"=="True" ( (robocopy c:\openssl-win%FOLDER_PLATFORM%\ %APPVEYOR_BUILD_FOLDER%\package\openssl-win%FOLDER_PLATFORM% /s /e /np) ^& IF %ERRORLEVEL% GTR 1 exit %ERRORLEVEL% ) exit 0 srt-1.4.4/scripts/generate-configure-options.tcl000077500000000000000000000043541412557703600220100ustar00rootroot00000000000000#!/usr/bin/tclsh set cachefile [lindex $argv 0] if { $cachefile == "" } { puts stderr "Usage: [file tail $argv0] " exit 1 } set struct { name type value description } set fd [open $cachefile r] set cached "" set dbase "" while {[gets $fd line] != -1 } { set line [string trim $line] # Hash comment if { [string index $line 0] == "#" } { continue } # empty line if { $line == "" } { set cached "" continue } if { [string range $line 0 1] == "//" } { set linepart [string range $line 2 end] # Variable description. Add to cache. if { $cached != "" && [string index $cached end] != " " && [string index $linepart 0] != " " } { append cached " " } append cached $linepart } # Possibly a variable if [string is alpha [string index $line 0]] { # Note: this skips variables starting grom underscore. if { [string range $line 0 5] == "CMAKE_" } { # Skip variables with CMAKE_ prefix, they are internal. continue } lassign [split $line =] vartype value lassign [split $vartype :] var type # Store the variable now set storage [list $var $type $value $cached] set cached "" lappend dbase $storage continue } #puts stderr "Ignored line: $line" # Ignored. } # Now look over the stored variables set lenlimit 80 foreach stor $dbase { lassign $stor {*}$struct if { [string length $description] > $lenlimit } { set description [string range $description 0 $lenlimit-2]... } if { $type in {STATIC INTERNAL} } { continue } # Check special case of CXX to turn back to c++. set pos [string first CXX $name] if { $pos != -1 } { # Check around, actually after XX should be no letter. if { $pos+3 >= [string length $name] || ![string is alpha [string index $name $pos+3]] } { set name [string replace $name $pos $pos+2 C++] } } set optname [string tolower [string map {_ -} $name]] # Variables of type bool are just empty. # Variables of other types must have = added. # Lowercase cmake type will be used here. set optassign "" set def "" if { $type != "BOOL" } { set optassign "=<[string tolower $type]>" } else { # Supply default for boolean option set def " (default: $value)" } puts " $optname$optassign \"$description$def\"" } srt-1.4.4/scripts/generate-error-types.tcl000077500000000000000000000144651412557703600206350ustar00rootroot00000000000000#!/usr/bin/tclsh #* #* SRT - Secure, Reliable, Transport #* Copyright (c) 2020 Haivision Systems Inc. #* #* This Source Code Form is subject to the terms of the Mozilla Public #* License, v. 2.0. If a copy of the MPL was not distributed with this #* file, You can obtain one at http://mozilla.org/MPL/2.0/. #* #*/ # #***************************************************************************** #written by # Haivision Systems Inc. #***************************************************************************** set code_major { UNKNOWN -1 SUCCESS 0 SETUP 1 CONNECTION 2 SYSTEMRES 3 FILESYSTEM 4 NOTSUP 5 AGAIN 6 PEERERROR 7 } set code_minor { NONE 0 TIMEOUT 1 REJECTED 2 NORES 3 SECURITY 4 CLOSED 5 CONNLOST 1 NOCONN 2 THREAD 1 MEMORY 2 OBJECT 3 SEEKGFAIL 1 READFAIL 2 SEEKPFAIL 3 WRITEFAIL 4 ISBOUND 1 ISCONNECTED 2 INVAL 3 SIDINVAL 4 ISUNBOUND 5 NOLISTEN 6 ISRENDEZVOUS 7 ISRENDUNBOUND 8 INVALMSGAPI 9 INVALBUFFERAPI 10 BUSY 11 XSIZE 12 EIDINVAL 13 EEMPTY 14 BUSYPORT 15 WRAVAIL 1 RDAVAIL 2 XMTIMEOUT 3 CONGESTION 4 } set errortypes { SUCCESS "Success" { NONE "" } SETUP "Connection setup failure" { NONE "" TIMEOUT "connection timed out" REJECTED "connection rejected" NORES "unable to create/configure SRT socket" SECURITY "aborted for security reasons" CLOSED "socket closed during operation" } CONNECTION "" { NONE "" CONNLOST "Connection was broken" NOCONN "Connection does not exist" } SYSTEMRES "System resource failure" { NONE "" THREAD "unable to create new threads" MEMORY "unable to allocate buffers" OBJECT "unable to allocate a system object" } FILESYSTEM "File system failure" { NONE "" SEEKGFAIL "cannot seek read position" READFAIL "failure in read" SEEKPFAIL "cannot seek write position" WRITEFAIL "failure in write" } NOTSUP "Operation not supported" { NONE "" ISBOUND "Cannot do this operation on a BOUND socket" ISCONNECTED "Cannot do this operation on a CONNECTED socket" INVAL "Bad parameters" SIDINVAL "Invalid socket ID" ISUNBOUND "Cannot do this operation on an UNBOUND socket" NOLISTEN "Socket is not in listening state" ISRENDEZVOUS "Listen/accept is not supported in rendezous connection setup" ISRENDUNBOUND "Cannot call connect on UNBOUND socket in rendezvous connection setup" INVALMSGAPI "Incorrect use of Message API (sendmsg/recvmsg)." INVALBUFFERAPI "Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile)." BUSY "Another socket is already listening on the same port" XSIZE "Message is too large to send (it must be less than the SRT send buffer size)" EIDINVAL "Invalid epoll ID" EEMPTY "All sockets removed from epoll, waiting would deadlock" BUSYPORT "Another socket is bound to that port and is not reusable for requested settings" } AGAIN "Non-blocking call failure" { NONE "" WRAVAIL "no buffer available for sending" RDAVAIL "no data available for reading" XMTIMEOUT "transmission timed out" CONGESTION "early congestion notification" } PEERERROR "The peer side has signaled an error" { NONE "" } } set main_array_item { const char** strerror_array_major [] = { $minor_array_list }; } set major_size_item { const size_t strerror_array_sizes [] = { $minor_array_sizes }; } set minor_array_item { const char* strerror_msgs_$majorlc [] = { $minor_message_items }; } set strerror_function { const char* strerror_get_message(size_t major, size_t minor) { static const char* const undefined = "UNDEFINED ERROR"; // Extract the major array if (major >= sizeof(strerror_array_major)/sizeof(const char**)) { return undefined; } const char** array = strerror_array_major[major]; size_t size = strerror_array_sizes[major]; if (minor >= size) { return undefined; } return array[minor]; } } set globalheader { /* WARNING: Generated from ../scripts/generate-error-types.tcl DO NOT MODIFY. Copyright applies as per the generator script. */ #include } proc Generate:imp {} { puts $::globalheader puts "namespace srt\n\{" # Generate major array set majitem 0 set minor_array_sizes "" foreach {mt mm cont} $::errortypes { puts "// MJ_$mt '$mm'" # Generate minor array set majorlc [string tolower $mt] set minor_message_items "" set minitem 0 foreach {mnt mnm} $cont { if {$mm == ""} { set msg $mnm } elseif {$mnm == ""} { set msg $mm } else { set msg "$mm: $mnm" } append minor_message_items " \"$msg\", // MN_$mnt = $minitem\n" incr minitem } append minor_message_items " \"\"" puts [subst -nobackslashes -nocommands $::minor_array_item] append minor_array_list " strerror_msgs_$majorlc, // MJ_$mt = $majitem\n" #append minor_array_sizes " [expr {$minitem}],\n" append minor_array_sizes " SRT_ARRAY_SIZE(strerror_msgs_$majorlc) - 1,\n" incr majitem } append minor_array_list " NULL" append minor_array_sizes " 0" puts [subst -nobackslashes -nocommands $::main_array_item] puts {#define SRT_ARRAY_SIZE(ARR) sizeof(ARR) / sizeof(ARR[0])} puts [subst -nobackslashes -nocommands $::major_size_item] puts $::strerror_function puts "\} // namespace srt" } set defmode imp if {[lindex $argv 0] != ""} { set defmode [lindex $argv 0] } Generate:$defmode srt-1.4.4/scripts/generate-logging-defs.tcl000077500000000000000000000243721412557703600207050ustar00rootroot00000000000000#!/usr/bin/tclsh #* #* SRT - Secure, Reliable, Transport #* Copyright (c) 2020 Haivision Systems Inc. #* #* This Source Code Form is subject to the terms of the Mozilla Public #* License, v. 2.0. If a copy of the MPL was not distributed with this #* file, You can obtain one at http://mozilla.org/MPL/2.0/. #* #*/ # #***************************************************************************** #written by # Haivision Systems Inc. #***************************************************************************** # What fields are there in every entry set model { longname shortname id description } # Logger definitions. # Comments here allowed, just only for the whole line. # Use values greater than 0. Value 0 is reserved for LOGFA_GENERAL, # which is considered always enabled. set loggers { GENERAL gg 0 "General uncategorized log, for serious issues only" SOCKMGMT sm 1 "Socket create/open/close/configure activities" CONN cn 2 "Connection establishment and handshake" XTIMER xt 3 "The checkTimer and around activities" TSBPD ts 4 "The TsBPD thread" RSRC rs 5 "System resource allocation and management" CONGEST cc 7 "Congestion control module" PFILTER pf 8 "Packet filter module" API_CTRL ac 11 "API part for socket and library managmenet" QUE_CTRL qc 13 "Queue control activities" EPOLL_UPD ei 16 "EPoll, internal update activities" API_RECV ar 21 "API part for receiving" BUF_RECV br 22 "Buffer, receiving side" QUE_RECV qr 23 "Queue, receiving side" CHN_RECV kr 24 "CChannel, receiving side" GRP_RECV gr 25 "Group, receiving side" API_SEND as 31 "API part for sending" BUF_SEND bs 32 "Buffer, sending side" QUE_SEND qs 33 "Queue, sending side" CHN_SEND ks 34 "CChannel, sending side" GRP_SEND gs 35 "Group, sending side" INTERNAL in 41 "Internal activities not connected directly to a socket" QUE_MGMT qm 43 "Queue, management part" CHN_MGMT km 44 "CChannel, management part" GRP_MGMT gm 45 "Group, management part" EPOLL_API ea 46 "EPoll, API part" } set hidden_loggers { # Haicrypt logging - usually off. HAICRYPT hc 6 "Haicrypt module area" # defined in apps, this is only a stub to lock the value APPLOG ap 10 "Applications" } set globalheader { /* WARNING: Generated from ../scripts/generate-logging-defs.tcl DO NOT MODIFY. Copyright applies as per the generator script. */ } # This defines, what kind of definition will be generated # for a given file out of the log FA entry list. # Fields: # - prefix/postfix model # - logger_format # - hidden_logger_format # COMMENTS NOT ALLOWED HERE! Only as C++ comments inside C++ model code. set special { srtcore/logger_default.cpp { if {"$longname" == "HAICRYPT"} { puts $od " #if ENABLE_HAICRYPT_LOGGING allfa.set(SRT_LOGFA_HAICRYPT, true); #endif" } } } proc GenerateModelForSrtH {} { # `path` will be set to the git top path global path set fd [open [file join $path srtcore/srt.h] r] set contents "" set state read set pass looking while { [gets $fd line] != -1 } { if { $state == "read" } { if { $pass != "passed" } { set re [regexp {SRT_LOGFA BEGIN GENERATED SECTION} $line] if {$re} { set state skip set pass found } } append contents "$line\n" continue } if {$state == "skip"} { if { [string trim $line] == "" } { # Empty line, continue skipping continue } set re [regexp {SRT_LOGFA END GENERATED SECTION} $line] if {!$re} { # Still SRT_LOGFA definitions continue } # End of generated section. Switch back to pass-thru. # First fill the gap append contents "\n\$entries\n\n" append contents "$line\n" set state read set pass passed } } close $fd # Sanity check if {$pass != "passed"} { error "Invalid contents of `srt.h` file, can't find '#define SRT_LOGFA_' phrase" } return $contents } # COMMENTS NOT ALLOWED HERE! Only as C++ comments inside C++ model code. # (NOTE: Tcl syntax highlighter will likely falsely highlight # as comment here) # # Model: TARGET-NAME { format-model logger-pattern hidden-logger-pattern } # # Special syntax: # # % : a high-level command execution. This declares a command that # must be executed to GENERATE the model. Then, [subst] is executed # on the results. # # = : when placed as the hidden-logger-pattern, it's equal to logger-pattern. # set generation { srtcore/srt.h { {%GenerateModelForSrtH} {#define [format "%-20s %-3d" SRT_LOGFA_${longname} $id] // ${shortname}log: $description} = } srtcore/logger_default.cpp { { $globalheader #include "srt.h" #include "logging.h" #include "logger_defs.h" namespace srt_logging { AllFaOn::AllFaOn() { $entries } } // namespace srt_logging } { allfa.set(SRT_LOGFA_${longname}, true); } } srtcore/logger_defs.cpp { { $globalheader #include "srt.h" #include "logging.h" #include "logger_defs.h" namespace srt_logging { AllFaOn logger_fa_all; } // We need it outside the namespace to preserve the global name. // It's a part of "hidden API" (used by applications) SRT_API srt_logging::LogConfig srt_logger_config(srt_logging::logger_fa_all.allfa); namespace srt_logging { $entries } // namespace srt_logging } { Logger ${shortname}log(SRT_LOGFA_${longname}, srt_logger_config, "SRT.${shortname}"); } } srtcore/logger_defs.h { { $globalheader #ifndef INC_SRT_LOGGER_DEFS_H #define INC_SRT_LOGGER_DEFS_H #include "srt.h" #include "logging.h" namespace srt_logging { struct AllFaOn { LogConfig::fa_bitset_t allfa; AllFaOn(); }; $entries } // namespace srt_logging #endif } { extern Logger ${shortname}log; } } apps/logsupport_appdefs.cpp { { $globalheader #include "logsupport.hpp" LogFANames::LogFANames() { $entries } } { Install("$longname", SRT_LOGFA_${longname}); } { Install("$longname", SRT_LOGFA_${longname}); } } } # EXECUTION set here [file dirname [file normalize $argv0]] if {[lindex [file split $here] end] != "scripts"} { puts stderr "The script is in weird location." exit 1 } set path [file join {*}[lrange [file split $here] 0 end-1]] # Utility. Allows to put line-oriented comments and have empty lines proc no_comments {input} { set output "" foreach line [split $input \n] { set nn [string trim $line] if { $nn == "" || [string index $nn 0] == "#" } { continue } append output $line\n } return $output } proc generate_file {od target} { global globalheader lassign [dict get $::generation $target] format_model pattern hpattern set ptabprefix "" if {[string index $format_model 0] == "%"} { set command [string range $format_model 1 end] set format_model [eval $command] } if {$format_model != ""} { set beginindex 0 while { [string index $format_model $beginindex] == "\n" } { incr beginindex } set endindex $beginindex while { [string is space [string index $format_model $endindex]] } { incr endindex } set tabprefix [string range $pattern $beginindex $endindex-1] set newformat "" foreach line [split $format_model \n] { if {[string trim $line] == ""} { append newformat "\n" continue } if {[string first $tabprefix $line] == 0} { set line [string range $line [string length $tabprefix] end] } append newformat $line\n set ie [string first {$} $line] if {$ie != -1} { if {[string range $line $ie end] == {$entries}} { set ptabprefix "[string range $line 0 $ie-1]" } } } set format_model $newformat unset newformat } set entries "" if {[string trim $pattern] != "" } { set prevval 0 set pattern [string trim $pattern] # The first "$::model" will expand into variable names # as defined there. foreach [list {*}$::model] [no_comments $::loggers] { if {$prevval + 1 != $id} { append entries "\n" } append entries "${ptabprefix}[subst -nobackslashes $pattern]\n" set prevval $id } } if {$hpattern != ""} { if {$hpattern == "="} { set hpattern $pattern } else { set hpattern [string trim $hpattern] } # Extra line to separate from the normal entries append entries "\n" foreach [list {*}$::model] [no_comments $::hidden_loggers] { append entries "${ptabprefix}[subst -nobackslashes $hpattern]\n" } } if { [dict exists $::special $target] } { set code [subst [dict get $::special $target]] # The code should contain "append entries" ! eval $code } set entries [string trim $entries] if {$format_model == ""} { set format_model $entries } # For any case, cut external spaces puts $od [string trim [subst -nocommands -nobackslashes $format_model]] } proc debug_vars {list} { set output "" foreach name $list { upvar $name _${name} lappend output "${name}=[set _${name}]" } return $output } # MAIN set entryfiles $argv if {$entryfiles == ""} { set entryfiles [dict keys $generation] } else { foreach ef $entryfiles { if { $ef ni [dict keys $generation] } { error "Unknown generation target: $entryfiles" } } } foreach f $entryfiles { # Set simple relative path, if the file isn't defined as path. if { [llength [file split $f]] == 1 } { set filepath $f } else { set filepath [file join $path $f] } puts stderr "Generating '$filepath'" set od [open $filepath.tmp w] generate_file $od $f close $od if { [file exists $filepath] } { puts "WARNING: will overwrite exiting '$f'. Hit ENTER to confirm, or Control-C to stop" gets stdin } file rename -force $filepath.tmp $filepath } puts stderr Done. srt-1.4.4/scripts/googletest-download.cmake000066400000000000000000000007431412557703600210200ustar00rootroot00000000000000# code copied from https://crascit.com/2015/07/25/cmake-gtest/ cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) project(googletest-download NONE) include(ExternalProject) ExternalProject_Add( googletest SOURCE_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-src" BINARY_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-build" GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" TEST_COMMAND "" ) srt-1.4.4/scripts/googletest.cmake000066400000000000000000000014701412557703600172110ustar00rootroot00000000000000# the following code to fetch googletest # is inspired by and adapted after https://crascit.com/2015/07/25/cmake-gtest/ # download and unpack googletest at configure time macro(fetch_googletest _download_module_path _download_root) set(GOOGLETEST_DOWNLOAD_ROOT ${_download_root}) configure_file( ${_download_module_path}/googletest-download.cmake ${_download_root}/CMakeLists.txt @ONLY ) unset(GOOGLETEST_DOWNLOAD_ROOT) execute_process( COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${_download_root} ) execute_process( COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY ${_download_root} ) # adds the targers: gtest, gtest_main, gmock, gmock_main add_subdirectory( ${_download_root}/googletest-src ${_download_root}/googletest-build ) endmacro() srt-1.4.4/scripts/haiUtil.cmake000066400000000000000000000227411412557703600164400ustar00rootroot00000000000000# # SRT - Secure, Reliable, Transport # Copyright (c) 2018 Haivision Systems Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # include(CheckCXXSourceCompiles) # Useful for combinging paths function(adddirname prefix lst out_lst) set(output) foreach(item ${lst}) list(APPEND output "${prefix}/${item}") endforeach() set(${out_lst} ${${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) FUNCTION(join_arguments outvar) set (output) foreach (i ${ARGN}) set(output "${output} ${i}") endforeach() set (${outvar} ${output} PARENT_SCOPE) ENDFUNCTION() # The directory specifies the location of maffile and # all files specified in the list. MACRO(MafReadDir directory maffile) # ARGN contains the extra "section-variable" pairs # If empty, return nothing set (MAFREAD_TAGS SOURCES # source files PUBLIC_HEADERS # installable headers for include PROTECTED_HEADERS # installable headers used by other headers PRIVATE_HEADERS # non-installable headers SOURCES_WIN32_SHARED # windows specific SOURCES PRIVATE_HEADERS_WIN32_SHARED # windows specific PRIVATE_HEADERS OPTIONS ) cmake_parse_arguments(MAFREAD_VAR "" "${MAFREAD_TAGS}" "" ${ARGN}) # Arguments for these tags are variables to be filled # with the contents of particular section. # While reading the file, extract the section. # Section is recognized by either first uppercase character or space. # @c http://cmake.org/pipermail/cmake/2007-May/014222.html FILE(READ ${directory}/${maffile} MAFREAD_CONTENTS) STRING(REGEX REPLACE ";" "\\\\;" MAFREAD_CONTENTS "${MAFREAD_CONTENTS}") STRING(REGEX REPLACE "\n" ";" MAFREAD_CONTENTS "${MAFREAD_CONTENTS}") # Once correctly read, declare this file as dependency of the build file. # Normally you should use cmake_configure_depends(), but this is # available only since 3.0 version. configure_file(${directory}/${maffile} dummy_${maffile}.cmake.out) file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/dummy_${maffile}.cmake.out) #message("DEBUG: MAF FILE CONTENTS: ${MAFREAD_CONTENTS}") #message("DEBUG: PASSED VARIABLES:") #foreach(DEBUG_VAR ${MAFREAD_TAGS}) # message("DEBUG: ${DEBUG_VAR}=${MAFREAD_VAR_${DEBUG_VAR}}") #endforeach() # The unnamed section becomes SOURCES set (MAFREAD_VARIABLE ${MAFREAD_VAR_SOURCES}) set (MAFREAD_UNASSIGNED "") # Default section type. Another is 'flags'. set (MAFREAD_SECTION_TYPE file) FOREACH(MAFREAD_LINE ${MAFREAD_CONTENTS}) # Test what this line is string(STRIP ${MAFREAD_LINE} MAFREAD_OLINE) string(SUBSTRING ${MAFREAD_OLINE} 0 1 MAFREAD_FIRST) #message("DEBUG: LINE='${MAFREAD_LINE}' FIRST='${MAFREAD_FIRST}'") # The 'continue' command is cmake 3.2 - very late discovery if (MAFREAD_FIRST STREQUAL "") #message("DEBUG: ... skipped: empty") elseif (MAFREAD_FIRST STREQUAL "#") #message("DEBUG: ... skipped: comment") else() # Will be skipped if the line was a comment/empty string(REGEX MATCH "[ A-Z-]" MAFREAD_SECMARK ${MAFREAD_FIRST}) if (MAFREAD_SECMARK STREQUAL "") # This isn't a section, it's a list element. #message("DEBUG: ITEM: ${MAFREAD_OLINE} --> ${MAFREAD_VARIABLE}") if (${MAFREAD_SECTION_TYPE} STREQUAL file) get_filename_component(MAFREAD_OLINE ${directory}/${MAFREAD_OLINE} ABSOLUTE) endif() set (MAFREAD_CONDITION_OK 1) if (DEFINED MAFREAD_CONDITION_LIST) FOREACH(MFITEM IN ITEMS ${MAFREAD_CONDITION_LIST}) separate_arguments(MFITEM) FOREACH(MFVAR IN ITEMS ${MFITEM}) STRING(SUBSTRING ${MFVAR} 0 1 MFPREFIX) if (MFPREFIX STREQUAL "!") STRING(SUBSTRING ${MFVAR} 1 -1 MFVAR) if (${MFVAR}) set (MFCONDITION_RESULT 0) else() set (MFCONDITION_RESULT 1) endif() else() if (${MFVAR}) set (MFCONDITION_RESULT 1) else() set (MFCONDITION_RESULT 0) endif() endif() #message("CONDITION: ${MFPREFIX} ${MFVAR} -> ${MFCONDITION_RESULT}") MATH(EXPR MAFREAD_CONDITION_OK "${MAFREAD_CONDITION_OK} & (${MFCONDITION_RESULT})") ENDFOREACH() ENDFOREACH() endif() if (MAFREAD_CONDITION_OK) LIST(APPEND ${MAFREAD_VARIABLE} ${MAFREAD_OLINE}) else() #message("... NOT ADDED ITEM: ${MAFREAD_OLINE}") endif() else() # It's a section # Check for conditionals (clear current conditions first) unset(MAFREAD_CONDITION_LIST) STRING(FIND ${MAFREAD_OLINE} " -" MAFREAD_HAVE_CONDITION) if (NOT MAFREAD_HAVE_CONDITION EQUAL -1) # Cut off conditional specification, and # grab the section name and condition list STRING(REPLACE " -" ";" MAFREAD_CONDITION_LIST ${MAFREAD_OLINE}) #message("CONDITION READ: ${MAFREAD_CONDITION_LIST}") LIST(GET MAFREAD_CONDITION_LIST 0 MAFREAD_OLINE) LIST(REMOVE_AT MAFREAD_CONDITION_LIST 0) #message("EXTRACTING SECTION=${MAFREAD_OLINE} CONDITIONS=${MAFREAD_CONDITION_LIST}") endif() # change the running variable # Make it section name STRING(REPLACE " " "_" MAFREAD_SECNAME ${MAFREAD_OLINE}) #message("MAF SECTION: ${MAFREAD_SECNAME}") # The cmake's version of 'if (MAFREAD_SECNAME[0] == '-')' - sigh... string(SUBSTRING ${MAFREAD_SECNAME} 0 1 MAFREAD_SECNAME0) if (${MAFREAD_SECNAME0} STREQUAL "-") set (MAFREAD_SECTION_TYPE option) string(SUBSTRING ${MAFREAD_SECNAME} 1 -1 MAFREAD_SECNAME) else() set (MAFREAD_SECTION_TYPE file) endif() set(MAFREAD_VARIABLE ${MAFREAD_VAR_${MAFREAD_SECNAME}}) if (MAFREAD_VARIABLE STREQUAL "") set(MAFREAD_VARIABLE MAFREAD_UNASSIGNED) endif() #message("DEBUG: NEW SECTION: '${MAFREAD_SECNAME}' --> VARIABLE: '${MAFREAD_VARIABLE}'") endif() endif() ENDFOREACH() # Final debug report #set (ALL_VARS "") #message("DEBUG: extracted variables:") #foreach(DEBUG_VAR ${MAFREAD_TAGS}) # list(APPEND ALL_VARS ${MAFREAD_VAR_${DEBUG_VAR}}) #endforeach() #list(REMOVE_DUPLICATES ALL_VARS) #foreach(DEBUG_VAR ${ALL_VARS}) # message("DEBUG: --> ${DEBUG_VAR} = ${${DEBUG_VAR}}") #endforeach() ENDMACRO(MafReadDir) # NOTE: This is historical only. Not in use. # It should be a similar interface to mafread.tcl like # the above MafRead macro. MACRO(GetMafHeaders directory outvar) EXECUTE_PROCESS( COMMAND ${CMAKE_MODULE_PATH}/mafread.tcl ${CMAKE_SOURCE_DIR}/${directory}/HEADERS.maf "PUBLIC HEADERS" "PROTECTED HEADERS" OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE ${outvar} ) SEPARATE_ARGUMENTS(${outvar}) adddirname(${CMAKE_SOURCE_DIR}/${directory} "${${outvar}}" ${outvar}) ENDMACRO(GetMafHeaders) function (getVarsWith _prefix _varResult) get_cmake_property(_vars VARIABLES) string (REGEX MATCHALL "(^|;)${_prefix}[A-Za-z0-9_]*" _matchedVars "${_vars}") set (${_varResult} ${_matchedVars} PARENT_SCOPE) endfunction() function (check_testcode_compiles testcode libraries _successful) set (save_required_libraries ${CMAKE_REQUIRED_LIBRARIES}) set (CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES} ${libraries}") check_cxx_source_compiles("${testcode}" ${_successful}) set (${_successful} ${${_successful}} PARENT_SCOPE) set (CMAKE_REQUIRED_LIBRARIES ${save_required_libraries}) endfunction() function (test_requires_clock_gettime _enable _linklib) # This function tests if clock_gettime can be used # - at all # - with or without librt # Result will be: # - CLOCK_MONOTONIC is available, link with librt: # _enable = ON; _linklib = "-lrt". # - CLOCK_MONOTONIC is available, link without librt: # _enable = ON; _linklib = "". # - CLOCK_MONOTONIC is not available: # _enable = OFF; _linklib = "-". set (code " #include int main() { timespec res\; int result = clock_gettime(CLOCK_MONOTONIC, &res)\; return result == 0\; } ") check_testcode_compiles(${code} "" HAVE_CLOCK_GETTIME_IN) if (HAVE_CLOCK_GETTIME_IN) message(STATUS "CLOCK_MONOTONIC: available, no extra libs needed") set (${_enable} ON PARENT_SCOPE) set (${_linklib} "" PARENT_SCOPE) return() endif() check_testcode_compiles(${code} "rt" HAVE_CLOCK_GETTIME_LIBRT) if (HAVE_CLOCK_GETTIME_LIBRT) message(STATUS "CLOCK_MONOTONIC: available, requires -lrt") set (${_enable} ON PARENT_SCOPE) set (${_linklib} "-lrt" PARENT_SCOPE) return() endif() set (${_enable} OFF PARENT_SCOPE) set (${_linklib} "-" PARENT_SCOPE) message(STATUS "CLOCK_MONOTONIC: not available on this system") endfunction() function (parse_compiler_type wct _type _suffix) if (wct STREQUAL "") set(${_type} "" PARENT_SCOPE) set(${_suffix} "" PARENT_SCOPE) else() string(REPLACE "-" ";" OUTLIST ${wct}) list(LENGTH OUTLIST OUTLEN) list(GET OUTLIST 0 ITEM) set(${_type} ${ITEM} PARENT_SCOPE) if (OUTLEN LESS 2) set(_suffix "" PARENT_SCOPE) else() list(GET OUTLIST 1 ITEM) set(${_suffix} "-${ITEM}" PARENT_SCOPE) endif() endif() endfunction() srt-1.4.4/scripts/iOS.cmake000066400000000000000000000170571412557703600155370ustar00rootroot00000000000000# This file is based off of the Platform/Darwin.cmake and Platform/UnixPaths.cmake # files which are included with CMake 2.8.4 # It has been altered for iOS development # Options: # # IOS_PLATFORM = OS (default) or SIMULATOR or SIMULATOR64 # This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders # OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch. # SIMULATOR - used to build for the Simulator platforms, which have an x86 arch. # # IOS_ARCH = arm64 (default for OS), armv7, armv7s, i386 (default for SIMULATOR), x86_64 (default for SIMULATOR64) # # CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder # By default this location is automatcially chosen based on the IOS_PLATFORM value above. # If set manually, it will override the default location and force the user of a particular Developer Platform # # CMAKE_IOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder # By default this location is automatcially chosen based on the CMAKE_IOS_DEVELOPER_ROOT value. # In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path. # If set manually, this will force the use of a specific SDK version # # IOS_DISABLE_BITCODE - set to 1 if you want to disable bitcode generation # Standard settings set (CMAKE_SYSTEM_NAME Darwin) set (CMAKE_SYSTEM_VERSION 1) set (UNIX True) set (APPLE True) set (IOS True) # Required as of cmake 2.8.10 set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE) # Determine the cmake host system version so we know where to find the iOS SDKs find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin) if (CMAKE_UNAME) exec_program(uname ARGS -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION) string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}") endif (CMAKE_UNAME) set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) set(CMAKE_AR ar CACHE FILEPATH "" FORCE) set (CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") if (NOT DEFINED IOS_DISABLE_BITCODE) set (EMBED_OPTIONS "-fembed-bitcode") endif(NOT DEFINED IOS_DISABLE_BITCODE) if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR ENABLE_DEBUG) set(IOS_DEBUG_OPTIONS "-glldb -gmodules") else() set(IOS_DEBUG_OPTIONS "-fvisibility=hidden -fvisibility-inlines-hidden") endif() set (CMAKE_C_FLAGS_INIT "${IOS_DEBUG_OPTIONS} ${EMBED_OPTIONS}") set (CMAKE_CXX_FLAGS_INIT "${IOS_DEBUG_OPTIONS} ${EMBED_OPTIONS}") set (CMAKE_C_LINK_FLAGS "-Wl,-search_paths_first ${EMBED_OPTIONS} ${CMAKE_C_LINK_FLAGS}") set (CMAKE_CXX_LINK_FLAGS "-Wl,-search_paths_first ${EMBED_OPTIONS} ${CMAKE_CXX_LINK_FLAGS}") set (CMAKE_PLATFORM_HAS_INSTALLNAME 1) set (CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib") set (CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle") set (CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") set (CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") set (CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".so" ".a") # Specify install_name_tool and pkg-config since it outside of SDK path and therefore can't be found by CMake if (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) find_program(CMAKE_INSTALL_NAME_TOOL install_name_tool) endif (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) if (NOT DEFINED PKG_CONFIG_EXECUTABLE) find_program(PKG_CONFIG_EXECUTABLE NAMES pkg-config) if (DEFINED PKG_CONFIG_EXECUTABLE) execute_process(COMMAND pkg-config --version OUTPUT_VARIABLE PKG_CONFIG_VERSION_STRING) endif(DEFINED PKG_CONFIG_EXECUTABLE) endif(NOT DEFINED PKG_CONFIG_EXECUTABLE) # fffio Specify path to install shared library on device set (CMAKE_INSTALL_NAME_DIR "@executable_path/Frameworks") set (CMAKE_BUILD_WITH_INSTALL_NAME_DIR TRUE) # Setup iOS platform unless specified manually with IOS_PLATFORM if (NOT DEFINED IOS_PLATFORM) set (IOS_PLATFORM "OS") endif (NOT DEFINED IOS_PLATFORM) set (IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform") # Check the platform selection and setup for developer root if (${IOS_PLATFORM} STREQUAL OS) set (IOS_PLATFORM_LOCATION "iPhoneOS.platform") # This causes the installers to properly locate the output libraries set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos") elseif (${IOS_PLATFORM} STREQUAL SIMULATOR) set (SIMULATOR true) set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") # This causes the installers to properly locate the output libraries set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator") elseif (${IOS_PLATFORM} STREQUAL SIMULATOR64) set (SIMULATOR true) set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") # This causes the installers to properly locate the output libraries set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator") else (${IOS_PLATFORM} STREQUAL OS) message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS or SIMULATOR") endif (${IOS_PLATFORM} STREQUAL OS) # Setup iOS developer location unless specified manually with CMAKE_IOS_DEVELOPER_ROOT if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) exec_program(/usr/bin/xcode-select ARGS -print-path OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR) set (CMAKE_IOS_DEVELOPER_ROOT "${CMAKE_XCODE_DEVELOPER_DIR}/Platforms/${IOS_PLATFORM_LOCATION}/Developer") endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform") # Find and use the most recent iOS sdk unless specified manually with CMAKE_IOS_SDK_ROOT if (NOT DEFINED CMAKE_IOS_SDK_ROOT) file (GLOB _CMAKE_IOS_SDKS "${CMAKE_IOS_DEVELOPER_ROOT}/SDKs/*") if (_CMAKE_IOS_SDKS) list (SORT _CMAKE_IOS_SDKS) list (REVERSE _CMAKE_IOS_SDKS) list (GET _CMAKE_IOS_SDKS 0 CMAKE_IOS_SDK_ROOT) else (_CMAKE_IOS_SDKS) message (FATAL_ERROR "No iOS SDK's found in default search path ${CMAKE_IOS_DEVELOPER_ROOT}. Manually set CMAKE_IOS_SDK_ROOT or install the iOS SDK.") endif (_CMAKE_IOS_SDKS) message (STATUS "Toolchain using default iOS SDK: ${CMAKE_IOS_SDK_ROOT}") endif (NOT DEFINED CMAKE_IOS_SDK_ROOT) set (CMAKE_IOS_SDK_ROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Location of the selected iOS SDK") # Set the sysroot default to the most recent SDK set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS support") # set the architecture for iOS if (NOT DEFINED IOS_ARCH) if (${IOS_PLATFORM} STREQUAL OS) set (IOS_ARCH arm64) elseif (${IOS_PLATFORM} STREQUAL SIMULATOR) set (IOS_ARCH i386) elseif (${IOS_PLATFORM} STREQUAL SIMULATOR64) set (IOS_ARCH x86_64) endif (${IOS_PLATFORM} STREQUAL OS) endif(NOT DEFINED IOS_ARCH) set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE STRING "Build architecture for iOS") # Set the find root to the iOS developer roots and to user defined paths set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE STRING "iOS find search path root") # default to searching for frameworks first set (CMAKE_FIND_FRAMEWORK FIRST) # set up the default search directories for frameworks set (CMAKE_SYSTEM_FRAMEWORK_PATH ${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks ${CMAKE_IOS_SDK_ROOT}/System/Library/PrivateFrameworks ${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks ) # only search the iOS sdks, not the remainder of the host filesystem set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) srt-1.4.4/scripts/maf.vim000066400000000000000000000015511412557703600153130ustar00rootroot00000000000000" " SRT - Secure, Reliable, Transport " Copyright (c) 2020 Haivision Systems Inc. " " This Source Code Form is subject to the terms of the Mozilla Public " License, v. 2.0. If a copy of the MPL was not distributed with this " file, You can obtain one at http://mozilla.org/MPL/2.0/. " " This file describes MAF ("manifest") file syntax used by " SRT project. " if exists("b:current_syntax") finish endif " conditionals syn match mafCondition contained " - [!A-Za-z].*"hs=s+2 " section syn match mafSection "^[A-Z][0-9A-Za-z_].*$" contains=mafCondition syn match mafsection "^ .*$" contains=mafCondition " comments syn match mafComment "^\s*\zs#.*$" syn match mafComment "\s\zs#.*$" syn match mafComment contained "#.*$" " hilites hi def link mafComment Comment hi def link mafSection Statement hi def link mafCondition Number let b:current_syntax = "maf" srt-1.4.4/scripts/mafread.tcl000077500000000000000000000022761412557703600161460ustar00rootroot00000000000000#!/usr/bin/tclsh proc is-section line { return [regexp {^[A-Z ]+$} $line] } # First argument is Manifest file, others are sections. set sections [lassign $argv maffile] if { $sections == "" } { puts stderr "Usage: [file tail $argv0]
" exit 1 } # NOTE: If the file doesn't exist, simply print nothing. # If there's no manifest file under this name, it means that # there are no files that satisfy given manifest and section. if { [catch {set fd [open $maffile r]}] } { exit } set extracted "" set insection 0 while { [gets $fd line] >= 0 } { set oline [string trim $line] if { $oline == "" } { continue } if { [string index $oline 0] == "#" } { continue } if { !$insection } { # An opportunity to see if this is a section name if { ![is-section $line] } { continue } # If it is, then check if this is OUR section if { $oline in $sections } { set insection 1 continue } } else { # We are inside the interesting section, so collect filenames # Check if this is a next section name - if it is, stop reading. if { [is-section $line] } { continue } # Otherwise read the current filename lappend extracted $oline } } puts $extracted srt-1.4.4/scripts/set-version-metadata.ps1000066400000000000000000000044331412557703600205160ustar00rootroot00000000000000# Script for reading generated version values and updating metadata properties #read major / minor version values from version.h (generated by cmake via version.h.in) $majorVer=99 $minorVer=99 $patchVer=0 $buildNum=0 #define regular expressions to be used when checking for #define statements $versionSniffingRegex = "(\s*#define\s+(\S+)\s+)(\d+)" #read generated file, load values from this with regular expression Get-Content ".\version.h" | Where-Object { $_ -match $versionSniffingRegex } | ForEach-Object { switch ($Matches[2]) { "SRT_VERSION_MAJOR" { $majorVer = $Matches[3] } "SRT_VERSION_MINOR" { $minorVer = $Matches[3] } "SRT_VERSION_PATCH" { $patchVer = $Matches[3] } "SRT_VERSION_BUILD" { $buildNum = $Matches[3] } } } $FileDescriptionBranchCommitValue = "SRT Local Build" if($Env:APPVEYOR){ #make AppVeyor update with this new version number Update-AppveyorBuild -Version "$majorVer.$minorVer.$patchVer.$buildNum" $FileDescriptionBranchCommitValue = "$Env:APPVEYOR_REPO_NAME - $($Env:APPVEYOR_REPO_BRANCH) ($($Env:APPVEYOR_REPO_COMMIT.substring(0,8)))" } if($Env:TEAMCITY_VERSION){ #make TeamCity update with this new version number Write-Output "##teamcity[buildNumber '$majorVer.$minorVer.$patchVer.$buildNum']" Write-Output "##teamcity[setParameter name='MajorVersion' value='$majorVer']" Write-Output "##teamcity[setParameter name='MinorVersion' value='$minorVer']" Write-Output "##teamcity[setParameter name='PatchVersion' value='$patchVer']" Write-Output "##teamcity[setParameter name='BuildVersion' value='$buildNum']" $FileDescriptionBranchCommitValue = "$majorVer.$minorVer.$patchVer.$buildNum - ($($Env:BUILD_VCS_NUMBER.substring(0,8)))" } #find C++ resource files and update file description with branch / commit details $FileDescriptionStringRegex = '(\bVALUE\s+\"FileDescription\"\s*\,\s*\")([^\"]*\\\")*[^\"]*(\")' Get-ChildItem -Path "../srtcore/srt_shared.rc" | ForEach-Object { $fileName = $_ Write-Output "Processing metadata changes for file: $fileName" $FileLines = Get-Content -path $fileName for($i=0;$i -lt $FileLines.Count;$i++) { $FileLines[$i] = $FileLines[$i] -Replace $FileDescriptionStringRegex, "`${1}$FileDescriptionBranchCommitValue`${3}" } [System.IO.File]::WriteAllLines($fileName.FullName, $FileLines) } srt-1.4.4/scripts/srt-dev.lua000066400000000000000000001115071412557703600161250ustar00rootroot00000000000000-- @brief srt-dev Protocol dissector plugin -- create a new dissector local NAME = "SRT-dev" local srt_dev = Proto(NAME, "SRT-dev Protocol") -- create a preference of a Protocol srt_dev.prefs["srt_udp_port"] = Pref.uint("SRT UDP Port", 1935, "SRT UDP Port") -- create fields of srt_dev -- Base.HEX, Base.DEC, Base.OCT, Base.UNIT_STRING, Base.NONE local fields = srt_dev.fields -- General field local pack_type_select = { [0] = "Data Packet", [1] = "Control Packet" } fields.pack_type_tree = ProtoField.uint32(NAME .. ".pack_type_tree", "Packet Type", base.HEX) fields.pack_type = ProtoField.uint16("srt_dev.pack_type", "Packet Type", base.HEX, pack_type_select, 0x8000) fields.reserve = ProtoField.uint16("srt_dev.reserve", "Reserve", base.DEC) fields.additional_info = ProtoField.uint32("srt_dev.additional_info", "Additional Information", base.DEC) fields.time_stamp = ProtoField.uint32("srt_dev.time_stamp", "Time Stamp", base.DEC) fields.dst_sock = ProtoField.uint32("srt_dev.dst_sock", "Destination Socket ID", base.DEC) fields.none = ProtoField.none("srt_dev.none", "none", base.NONE) -- Data packet fields fields.data_flag_info_tree = ProtoField.uint8("srt_dev.data_flag_info_tree", "Data Flag Info", base.HEX) local FF_state_select = { [0] = "[Middle packet]", [1] = "[Last packet]", [2] = "[First packet]", [3] = "[Single packet]" } fields.FF_state = ProtoField.uint8("srt_dev.FF_state", "FF state", base.HEX, FF_state_select, 0xC0) local O_state_select = { [0] = "[ORD_RELAX]", [1] = "[ORD_REQUIRED]" } fields.O_state = ProtoField.uint8("srt_dev.O_state", "O state", base.HEX, O_state_select, 0x20) local KK_state_select = { [0] = "[Not encrypted]", [1] = "[Data encrypted with even key]", [2] = "[Data encrypted with odd key]" } fields.KK_state = ProtoField.uint8("srt_dev.KK_state", "KK state", base.HEX, KK_state_select, 0x18) local R_state_select = { [0] = "[ORIGINAL]", [1] = "[RETRANSMITTED]" } fields.R_state = ProtoField.uint8("srt_dev.R_state", "R state", base.HEX, R_state_select, 0x04) fields.seq_num = ProtoField.uint32("srt_dev.seq_num", "Sequence Number", base.DEC) fields.msg_num = ProtoField.uint32("srt_dev.msg_num", "Message Number", base.DEC)--, nil, 0x3FFFFFF) -- control packet fields local msg_type_select = { [0] = "[HANDSHAKE]", [1] = "[KEEPALIVE]", [2] = "[ACK]", [3] = "[NAK(Loss Report)]", [4] = "[Congestion Warning]", [5] = "[Shutdown]", [6] = "[ACKACK]", [7] = "[Drop Request]", [8] = "[Peer Error]", [0x7FFF] = "[Message Extension Type]" } fields.msg_type = ProtoField.uint16("srt_dev.msg_type", "Message Type", base.HEX, msg_type_select, 0x7FFF) fields.msg_ext_type = ProtoField.uint16("srt_dev.msg_ext_type", "Message Extented Type", base.DEC) local flag_state_select = { [0] = "Unset", [1] = "Set" } -- Handshake packet fields fields.UDT_version = ProtoField.uint32("srt_dev.UDT_version", "UDT Version", base.DEC) fields.sock_type = ProtoField.uint32("srt_dev.sock_type", "Socket Type", base.DEC) fields.ency_fld = ProtoField.uint16("srt_dev.ency_fld", "Encryption Field", base.DEC) fields.ext_fld = ProtoField.uint16("srt_dev.ext_fld", "Extension Fields", base.HEX) fields.ext_fld_tree = ProtoField.uint16("srt_dev.ext_fld_tree", "Extension Fields Tree", base.HEX) fields.hsreq = ProtoField.uint16("srt_dev.hsreq", "HS_EXT_HSREQ", base.HEX, flag_state_select, 0x1) fields.kmreq = ProtoField.uint16("srt_dev.kmreq", "HS_EXT_KMREQ", base.HEX, flag_state_select, 0x2) fields.config = ProtoField.uint16("srt_dev.config", "HS_EXT_CONFIG", base.HEX, flag_state_select, 0x4) fields.isn = ProtoField.uint32("srt_dev.isn", "Initial packet sequence number", base.DEC) fields.mss = ProtoField.uint32("srt_dev.mss", "Max Packet Size", base.DEC) fields.fc = ProtoField.uint32("srt_dev.fc", "Maximum Flow Window Size", base.DEC) fields.conn_type = ProtoField.int32("srt_dev.conn_type", "Connection Type", base.DEC) fields.sock_id = ProtoField.uint32("srt_dev.sock_id", "Socket ID", base.DEC) fields.syn_cookie = ProtoField.uint32("srt_dev.syn_cookie", "SYN cookie", base.DEC) fields.peer_ipaddr = ProtoField.none("srt_dev.peer_ipaddr", "Peer IP address", base.NONE) fields.peer_ipaddr_4 = ProtoField.ipv4("srt_dev.peer_ipaddr", "Peer IP address") fields.peer_ipaddr_6 = ProtoField.ipv6("srt_dev.peer_ipaddr", "Peer IP address") local ext_type_select = { [-1] = "SRT_CMD_NONE", [0] = "SRT_CMD_REJECT", [1] = "SRT_CMD_HSREQ", [2] = "SRT_CMD_HSRSP", [3] = "SRT_CMD_KMREQ", [4] = "SRT_CMD_KMRSP", [5] = "SRT_CMD_SID", [6] = "SRT_CMD_CONGESTION", [7] = "SRT_CMD_FILTER", [8] = "SRT_CMD_GROUP" } fields.ext_type_msg_tree = ProtoField.none("srt_dev.ext_type", "Extension Type Message", base.NONE) fields.ext_type = ProtoField.uint16("srt_dev.ext_type", "Extension Type", base.HEX, ext_type_select, 0xF) fields.ext_size = ProtoField.uint16("srt_dev.ext_size", "Extension Size", base.DEC) -- Handshake packet, ext type == SRT_CMD_HSREQ or SRT_CMD_HSRSP field fields.srt_version = ProtoField.uint32("srt_dev.srt_version", "SRT Version", base.HEX) fields.srt_flags = ProtoField.uint32("srt_dev.srt_flags", "SRT Flags", base.HEX) fields.tsbpb_resv = ProtoField.uint16("srt_dev.tsbpb_resv", "TsbPb Receive", base.DEC) fields.tsbpb_delay = ProtoField.uint16("srt_dev.tsbpb_delay", "TsbPb Delay", base.DEC) fields.tsbpd_delay = ProtoField.uint16("srt_dev.tsbpd_delay", "TsbPd Delay", base.DEC) fields.rcv_tsbpd_delay = ProtoField.uint16("srt_dev.rcv_tsbpd_delay", "Receiver TsbPd Delay", base.DEC) fields.snd_tsbpd_delay = ProtoField.uint16("srt_dev.snd_tsbpd_delay", "Sender TsbPd Delay", base.DEC) -- V adn PT status flag local V_state_select = { [1] = "Initial version" } fields.V_state = ProtoField.uint8("srt_dev.V_state", "V", base.HEX, V_state_select, 0x70) local PT_state_select = { [0] = "Reserved", [1] = "MSmsg", [2] = "KMmsg", [7] = "Reserved to discriminate MPEG-TS packet(0x47=sync byte)" } fields.PT_state = ProtoField.uint8("srt_dev.PT_state", "PT", base.HEX, state_table, 0xF) fields.sign = ProtoField.uint16("srt_dev.sign", "Signature", base.HEX) local resv_select = { [0] = "Reserved for flag extension or other usage" } fields.resv = ProtoField.uint8("srt_dev.resv", "Resv", base.DEC, state_table, 0xFC) fields.ext_KK_state = ProtoField.uint8("srt_dev.ext_KK_state", "KK_state", base.HEX, KK_state_select, 0x3) fields.KEKI = ProtoField.uint32("srt_dev.KEKI", "KEKI", base.DEC) fields.cipher = ProtoField.uint8("srt_dev.cipher", "Cipher", base.DEC) fields.auth = ProtoField.uint8("srt_dev.auth", "auth", base.DEC) fields.SE = ProtoField.uint8("srt_dev.SE", "SE", base.DEC) fields.resv1 = ProtoField.uint8("srt_dev.resv1", "resv1", base.DEC) fields.resv2 = ProtoField.uint16("srt_dev.resv2", "resv2", base.DEC) fields.slen = ProtoField.uint8("srt_dev.slen", "Salt length(bytes)/4", base.DEC) fields.klen = ProtoField.uint8("srt_dev.klen", "SEK length(bytes)/4", base.DEC) fields.salt = ProtoField.uint32("srt_dev.salt", "Salt key", base.DEC) fields.wrap = ProtoField.none("srt_dev.wrap", "Wrap key(s)", base.NONE) -- Wrap Field fields.ICV = ProtoField.uint64("srt_dev.ICV", "Integerity Check Vector", base.HEX) fields.odd_key = ProtoField.stringz("srt_dev.odd_key", "Odd key", base.ASCII) fields.even_key = ProtoField.stringz("srt_dev.even_key", "Even key", base.ASCII) -- ext_type == SRT_CMD_SID field fields.sid = ProtoField.string("srt_dev.sid", "Stream ID", base.ASCII) -- ext_type == SRT_CMD_CONGESTION field fields.congestion = ProtoField.string("srt_dev.congestion", "Congestion Controller", base.ASCII) -- ext_type == SRT_CMD_FILTER field fields.filter = ProtoField.string("srt_dev.filter", "Filter", base.ASCII) -- ext_type == SRT_CMD_GROUP field fields.group = ProtoField.string("srt_dev.group", "Group Data", base.ASCII) -- SRT flags fields.srt_opt_tsbpdsnd = ProtoField.uint32("srt_dev.srt_opt_tsbpdsnd", "SRT_OPT_TSBPDSND", base.HEX, flag_state_select, 0x1) fields.srt_opt_tsbpdrcv = ProtoField.uint32("srt_dev.srt_opt_tsbpdrcv", "SRT_OPT_TSBPDRCV", base.HEX, flag_state_select, 0x2) fields.srt_opt_haicrypt = ProtoField.uint32("srt_dev.srt_opt_haicrypt", "SRT_OPT_HAICRYPT", base.HEX, flag_state_select, 0x4) fields.srt_opt_tlpktdrop = ProtoField.uint32("srt_dev.srt_opt_tlpktdrop", "SRT_OPT_TLPKTDROP", base.HEX, flag_state_select, 0x8) fields.srt_opt_nakreport = ProtoField.uint32("srt_dev.srt_opt_nakreport", "SRT_OPT_NAKREPORT", base.HEX, flag_state_select, 0x10) fields.srt_opt_rexmitflg = ProtoField.uint32("srt_dev.srt_opt_rexmitflg", "SRT_OPT_REXMITFLG", base.HEX, flag_state_select, 0x20) fields.srt_opt_stream = ProtoField.uint32("srt_dev.srt_opt_stream", "SRT_OPT_STREAM", base.HEX, flag_state_select, 0x40) -- ACK fields fields.last_ack_pack = ProtoField.uint32("srt_dev.last_ack_pack", "Last ACK Packet Sequence Number", base.DEC) fields.rtt = ProtoField.int32("srt_dev.rtt", "Round Trip Time", base.DEC) fields.rtt_variance = ProtoField.int32("srt_dev.rtt_variance", "Round Trip Time Variance", base.DEC) fields.buf_size = ProtoField.uint32("srt_dev.buf_size", "Available Buffer Size", base.DEC) fields.pack_rcv_rate = ProtoField.uint32("srt_dev.pack_rcv_rate", "Packet Receiving Rate", base.DEC) fields.est_link_capacity = ProtoField.uint32("srt_dev.est_link_capacity", "Estimated Link Capacity", base.DEC) fields.rcv_rate = ProtoField.uint32("srt_dev.rcv_rate", "Receiving Rate", base.DEC) -- ACKACK fields fields.ack_num = ProtoField.uint32("srt_dev.ack_num", "ACK number", base.DEC) fields.ctl_info = ProtoField.uint32("srt_dev.ctl_info", "Control Information", base.DEC) -- KMRSP fields local srt_km_state_select = { [0] = "[SRT_KM_UNSECURED]", [1] = "[SRT_KM_SECURING]", [2] = "[SRT_KM_SECURED]", [3] = "[SRT_KM_NOSECRET]", [4] = "[SRT_KM_BADSECRET]" } fields.km_err = ProtoField.uint32("srt_dev.km_err", "Key Message Error", base.HEX, srt_km_state_select, 0xF) -- NAK Control Packet fields fields.lost_list_tree = ProtoField.none("srt_dev.lost_list_tree", "Lost Packet List", base.NONE) fields.lost_pack_seq = ProtoField.uint32("srt_dev.lost_pack_seq", "Lost Packet Sequence Number", base.DEC) fields.lost_pack_range_tree = ProtoField.none("srt_dev.lost_pack_range_tree", "Lost Packet Range", base.NONE) fields.lost_start = ProtoField.uint32("srt_dev.lost_start", "Lost Starting Sequence", base.DEC) fields.lost_up_to = ProtoField.uint32("srt_dev.lost_up_to", "Lost Up To(including)", base.DEC) -- Dissect packet function srt_dev.dissector (tvb, pinfo, tree) -- Packet is based on UDP, so the data can be processed directly after UDP local subtree = tree:add(srt_dev, tvb()) local offset = 0 -- Changes the protocol name pinfo.cols.protocol = srt_dev.name -- Take out the first bit of package -- 0 -> Data Packet -- 1 -> Control Packet local typebit = bit.rshift(tvb(offset, 1):uint(), 7) pack_type_tree = subtree:add(fields.pack_type_tree, tvb(offset, 4)) if typebit == 1 then -- Handle Control Packet pack_type_tree:add(fields.pack_type, tvb(offset, 2)) local msg_type = tvb(offset, 2):uint() if msg_type ~= 0xFFFF then -- If type field isn't '0x7FFF',it means packet is normal data packet, then handle type field msg_type = bit.band(msg_type, 0x7FFF) function parse_three_param() -- Ignore Additional Info (this field is not defined in this packet type) subtree:add(fields.additional_info, tvb(offset, 4)):append_text(" [undefined]") offset = offset + 4 -- Handle Time Stamp subtree:add(fields.time_stamp, tvb(offset, 4)):append_text(" μs") offset = offset + 4 -- Handle Destination Socket subtree:add(fields.dst_sock, tvb(offset, 4)) offset = offset + 4 end local switch = { [0] = function() pinfo.cols.info:append(" [HANDSHAKE]") pack_type_tree:append_text(" [HANDSHAKE]") pack_type_tree:add(fields.msg_type, tvb(offset, 2)) pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") offset = offset + 4 -- Handle Additional Info, Timestamp and Destination Socket parse_three_param() -- Handle UDT version field local UDT_version = tvb(offset, 4):uint() subtree:add(fields.UDT_version, tvb(offset, 4)) offset = offset + 4 if UDT_version == 4 then -- UDT version is 4, packet is diffrent from UDT version 5 -- Handle sock type local sock_type = tvb(offset, 4):uint() if sock_type == 1 then subtree:add(fields.sock_type, tvb(offset, 4)):append_text(" [SRT_STREAM]") elseif sock_type == 2 then subtree:add(fields.sock_type, tvb(offset, 4)):append_text(" [SRT_DRAGAM]") end offset = offset + 4 elseif UDT_version == 5 then -- Handle Encryption Field local encr_fld = tvb(offset, 2):int() if encr_fld == 0 then subtree:add(fields.ency_fld, tvb(offset, 2)):append_text(" (PBKEYLEN not advertised)") elseif encr_fld == 2 then subtree:add(fields.ency_fld, tvb(offset, 2)):append_text(" (AES-128)") elseif encr_fld == 3 then subtree:add(fields.ency_fld, tvb(offset, 2)):append_text(" (AES-192)") else subtree:add(fields.ency_fld, tvb(offset, 2)):append_text(" (AES-256)") end offset = offset + 2 -- Handle Extension Field local ext_fld = tvb(offset, 2):int() if ext_fld == 0x4A17 then subtree:add(fields.ext_fld, tvb(offset, 2)):append_text(" [HSv5 MAGIC]") else -- Extension Field is HS_EXT_prefix -- The define is in fiel handshake.h local ext_fld_tree = subtree:add(fields.ext_fld_tree, tvb(offset, 2)) local str_table = { " [" } ext_fld_tree:add(fields.hsreq, tvb(offset, 2)) if bit.band(tvb(offset, 2):uint(), 0x1) == 1 then table.insert(str_table, "HS_EXT_HSREQ") table.insert(str_table, " | ") end ext_fld_tree:add(fields.kmreq, tvb(offset, 2)):append_text(" [HS_EXT_KMREQ]") if bit.band(tvb(offset, 2):uint(), 0x2) == 2 then table.insert(str_table, "HS_EXT_KMREQ") table.insert(str_table, " | ") end ext_fld_tree:add(fields.config, tvb(offset, 2)):append_text(" [HS_EXT_CONFIG]") if bit.band(tvb(offset, 2):uint(), 0x4) == 4 then table.insert(str_table, "HS_EXT_CONFIG") table.insert(str_table, " | ") end table.remove(str_table) table.insert(str_table, "]") if ext_fld ~= 0 then ext_fld_tree:append_text(table.concat(str_table)) end end offset = offset + 2 end -- Handle Initial packet sequence number subtree:add(fields.isn, tvb(offset, 4)) offset = offset + 4 -- Handle Maximum Packet Size subtree:add(fields.mss, tvb(offset, 4)) offset = offset + 4 -- Handle Maximum Flow Window Size subtree:add(fields.fc, tvb(offset, 4)) offset = offset + 4 -- Handle Connection Type local conn_type = tvb(offset, 4):int() local conn_type_tree = subtree:add(fields.conn_type, tvb(offset, 4)) if conn_type == 0 then conn_type_tree:append_text(" [WAVEAHAND] (Rendezvous Mode)") pinfo.cols.info:append(" [WAVEAHAND] (Rendezvous Mode)") elseif conn_type == 1 then conn_type_tree:append_text(" [INDUCTION]") elseif conn_type == -1 then conn_type_tree:append_text(" [CONCLUSION]") elseif conn_type == -2 then conn_type_tree:append_text(" [AGREEMENT] (Rendezvous Mode)") pinfo.cols.info:append(" [AGREEMENT] (Rendezvous Mode)") end offset = offset + 4 -- Handle Socket ID subtree:add(fields.sock_id, tvb(offset, 4)) offset = offset + 4 -- Handle SYN cookie local syn_cookie = tvb(offset, 4):int() subtree:add(fields.syn_cookie, tvb(offset, 4)) if syn_cookie == 0 then conn_type_tree:append_text(" (Caller to Listener)") pinfo.cols.info:append(" (Caller to Listener)") else if conn_type == 1 then -- reports cookie from listener conn_type_tree:append_text(" (Listener to Caller)") pinfo.cols.info:append(" (Listener to Caller)") end end offset = offset + 4 -- Handle Peer IP address -- Note the network byte order local the_last_96_bits = 0 the_last_96_bits = the_last_96_bits + math.floor(tvb(offset + 4, 4):int() * (2 ^ 16)) the_last_96_bits = the_last_96_bits + math.floor(tvb(offset + 8, 4):int() * (2 ^ 8)) the_last_96_bits = the_last_96_bits + tvb(offset + 12, 4):int() if the_last_96_bits == 0 then subtree:add_le(fields.peer_ipaddr_4, tvb(offset, 4)) else subtree:add_le(fields.peer_ipaddr, tvb(offset, 16)) end offset = offset + 16 -- UDT version is 4, packet handle finish if UDT_version == 4 or offset == tvb:len() then return end function process_ext_type() -- Handle Ext Type, processing by type local ext_type = tvb(offset, 2):int() if ext_type == 1 or ext_type == 2 then local ext_type_msg_tree = subtree:add(fields.ext_type_msg_tree, tvb(offset, 16)) if ext_type == 1 then ext_type_msg_tree:append_text(" [SRT_CMD_HSREQ]") ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) conn_type_tree:append_text(" (Caller to Listener)") pinfo.cols.info:append(" (Caller to Listener)") else ext_type_msg_tree:append_text(" [SRT_CMD_HSRSP]") ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) conn_type_tree:append_text(" (Listener to Caller)") pinfo.cols.info:append(" (Listener to Caller)") end offset = offset + 2 -- Handle Ext Size ext_type_msg_tree:add(fields.ext_size, tvb(offset, 2)) offset = offset + 2 -- Handle SRT Version ext_type_msg_tree:add(fields.srt_version, tvb(offset, 4)) offset = offset + 4 -- Handle SRT Flags local SRT_flags_tree = ext_type_msg_tree:add(fields.srt_flags, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_tsbpdsnd, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_tsbpdrcv, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_haicrypt, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_tlpktdrop, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_nakreport, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_rexmitflg, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_stream, tvb(offset, 4)) offset = offset + 4 -- Handle Recv TsbPd Delay and Snd TsbPd Delay if UDT_version == 4 then ext_type_msg_tree:add(fields.tsbpd_delay, tvb(offset, 2)):append_text(" [Unused in HSv4]") offset = offset + 2 ext_type_msg_tree:add(fields.tsbpb_delay, tvb(offset, 2)) offset = offset + 2 else ext_type_msg_tree:add(fields.rcv_tsbpd_delay, tvb(offset, 2)) offset = offset + 2 ext_type_msg_tree:add(fields.snd_tsbpd_delay, tvb(offset, 2)) offset = offset + 2 end elseif ext_type == 3 or ext_type == 4 then local ext_type_msg_tree = subtree:add(fields.ext_type_msg_tree, tvb(offset, 16)) if ext_type == 3 then ext_type_msg_tree:append_text(" [SRT_CMD_KMREQ]") ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) conn_type_tree:append_text(" (Listener to Caller)") else ext_type_msg_tree:append_text(" [SRT_CMD_KMRSP]") ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) end offset = offset + 2 -- Handle Ext Size local km_len = tvb(offset, 2):uint() ext_type_msg_tree:add(fields.ext_size, tvb(offset, 2)):append_text(" (byte/4)") offset = offset + 2 -- Handle SRT_CMD_KMREQ message -- V and PT status flag ext_type_msg_tree:add(fields.V_state, tvb(offset, 1)) ext_type_msg_tree:add(fields.PT_state, tvb(offset, 1)) offset = offset + 1 -- Handle sign ext_type_msg_tree:add(fields.sign, tvb(offset, 2)):append_text(" (/'HAI/' PnP Vendor ID in big endian order)") offset = offset + 2 -- Handle resv ext_type_msg_tree:add(fields.resv, tvb(offset, 1)) -- Handle KK flag local KK = tvb(offset, 1):uint() ext_type_msg_tree:add(fields.ext_KK_state, tvb(offset, 1)) offset = offset + 1 -- Handle KEKI if tvb(offset, 4):uint() == 0 then ext_type_msg_tree:add(fields.KEKI, tvb(offset, 4)):append_text(" (Default stream associated key(stream/system default))") else ext_type_msg_tree:add(fields.KEKI, tvb(offset, 4)):append_text(" (Reserved for manually indexed keys)") end offset = offset + 4 -- Handle Cipher local cipher_node = ext_type_msg_tree:add(fields.cipher, tvb(offset, 1)) local cipher = tvb(offset, 1):uint() if cipher == 0 then elseif cipher == 1 then cipher_node:append_text(" (AES-ECB(potentially for VF 2.0 compatible message))") elseif cipher == 2 then cipher_node:append_text(" (AES-CTR[FP800-38A])") else cipher_node:append_text(" (AES-CCM or AES-GCM)") end offset = offset + 1 -- Handle Auth if tvb(offset, 1):uint() == 0 then ext_type_msg_tree:add(fields.auth, tvb(offset, 1)):append_text(" (None or KEKI indexed crypto context)") else ext_type_msg_tree:add(fields.auth, tvb(offset, 1)) end offset = offset + 1 -- Handle SE local SE_node = ext_type_msg_tree:add(fields.SE, tvb(offset, 1)) local SE = tvb(offset, 1):uint() if SE == 0 then SE_node:append_text( " (Unspecified or KEKI indexed crypto context)") elseif SE == 1 then SE_node:append_text( " (MPEG-TS/UDP)") elseif SE == 2 then SE_node:append_text( " (MPEG-TS/SRT)") end offset = offset + 1 -- Handle resv1 ext_type_msg_tree:add(fields.resv1, tvb(offset, 1)) offset = offset + 1 -- Handle resv2 ext_type_msg_tree:add(fields.resv2, tvb(offset, 2)) offset = offset + 2 -- Handle slen ext_type_msg_tree:add(fields.slen, tvb(offset, 1)) offset = offset + 1 -- Handle klen local klen = tvb(offset, 1):uint() ext_type_msg_tree:add(fields.klen, tvb(offset, 1)) offset = offset + 1 -- Handle salt key ext_type_msg_tree:add(fields.salt, tvb(offset, slen * 4)) offset = offset + slen * 4 -- Handle wrap -- Handle ICV local wrap_len = 8 + KK * klen local wrap_tree = ext_type_msg_tree:add(fields.wrap, tvb(offset, wrap_len)) wrap_tree:add(fields.ICV, tvb(offset, 8)) offset = offset + 8 -- If KK == 2, first key is Even key if KK == 2 then wrap_tree:add(fields.even_key, tvb(offset, klen)) offset = offset + klen; end -- Handle Odd key wrap_tree:add(fields.odd_key, tvb(offset, klen)) offset = offset + klen; elseif ext_type >= 5 and ext_type <= 8 then local value_size = tvb(offset + 2, 2):uint() * 4 local ext_msg_size = 2 + 2 + value_size local type_array = { " [SRT_CMD_SID]", " [SRT_CMD_CONGESTION]", " [SRT_CMD_FILTER]", " [SRT_CMD_GROUP]" } local field_array = { fields.sid, fields.congestion, fields.filter, fields.group } local ext_type_msg_tree = subtree:add(fields.ext_type_msg_tree, tvb(offset, ext_msg_size)):append_text(type_array[ext_type - 4]) ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) offset = offset + 2 -- Handle Ext Msg Value Size ext_type_msg_tree:add(fields.ext_size, tvb(offset, 2)):append_text(" (byte/4)") offset = offset + 2 -- Value local value_table = {} for pos = 0, value_size - 4, 4 do table.insert(value_table, string.char(tvb(offset + pos + 3, 1):uint())) table.insert(value_table, string.char(tvb(offset + pos + 2, 1):uint())) table.insert(value_table, string.char(tvb(offset + pos + 1, 1):uint())) table.insert(value_table, string.char(tvb(offset + pos, 1):uint())) end local value = table.concat(value_table) ext_type_msg_tree:add(field_array[ext_type - 4], tvb(offset, value_size), value) offset = offset + value_size elseif ext_type == -1 then local ext_type_msg_tree = subtree:add(fields.ext_type_msg_tree, tvb(offset, tvb:len() - offset)):append_text(" [SRT_CMD_NONE]") ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) offset = offset + 2 -- none if offset == tvb:len() then return end ext_type_msg_tree:add(fields.none, tvb(offset, tvb:len() - offset)) offset = tvb:len() end if offset == tvb:len() then return else process_ext_type() end end process_ext_type() end, [1] = function() pinfo.cols.info:append(" [KEEPALIVE]") pack_type_tree:append_text(" [KEEPALIVE]") pack_type_tree:add(fields.msg_type, tvb(offset, 2)):append_text(" [KEEPALIVE]") pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") offset = offset + 4 -- Handle Additional Info, Time Stamp and Destination Socket parse_three_param() end, [2] = function() pinfo.cols.info:append(" [ACK]") pack_type_tree:append_text(" [ACK]") pack_type_tree:add(fields.msg_type, tvb(offset, 2)) pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") offset = offset + 4 -- Handle ACK Number subtree:add(fields.ack_num, tvb(offset, 4)) offset = offset + 4 -- Handle Time Stamp subtree:add(fields.time_stamp, tvb(offset, 4)):append_text(" μs") offset = offset + 4 -- Handle Destination Socket subtree:add(fields.dst_sock, tvb(offset, 4)) offset = offset + 4 -- Handle Last Ack Packet Sequence local last_ack_pack = tvb(offset, 4):uint() pinfo.cols.info:append(" (Last ACK Seq:" .. last_ack_pack .. ")") subtree:add(fields.last_ack_pack, tvb(offset, 4)) offset = offset + 4 -- Handle RTT local rtt = tvb(offset, 4):int() subtree:add(fields.rtt, tvb(offset, 4)):append_text(" μs") offset = offset + 4 -- Handle RTT variance if rtt < 0 then subtree:add(fields.rtt_variance, tvb(offset, 4), -tvb(offset, 4):int()) else subtree:add(fields.rtt_variance, tvb(offset, 4)) end offset = offset + 4 -- Handle Available Buffer Size(pkts) subtree:add(fields.buf_size, tvb(offset, 4)):append_text(" pkts") offset = offset + 4 -- Handle Packets Receiving Rate(Pkts/sec) subtree:add(fields.pack_rcv_rate, tvb(offset, 4)):append_text(" pkts/sec") offset = offset + 4 -- Handle Estmated Link Capacity subtree:add(fields.est_link_capacity, tvb(offset, 4)):append_text(" pkts/sec") offset = offset + 4 -- Handle Receiving Rate(bps) subtree:add(fields.rcv_rate, tvb(offset, 4)):append_text(" bps") offset = offset + 4 end, [3] = function() pinfo.cols.info:append(" [NAK(loss Report)]") pack_type_tree:append_text(" [NAK(loss Report)]") pack_type_tree:add(fields.msg_type, tvb(offset, 2)) pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") offset = offset + 4 -- Handle Additional Info, Timestamp and Destination Socket parse_three_param() -- Handle lost packet sequence -- lua does not support changing loop variables within loops, but in the form of closures -- https://blog.csdn.net/Ai102iA/article/details/75371239 local start = offset local ending = tvb:len() local lost_list_tree = subtree:add(fields.lost_list_tree, tvb(offset, ending - offset)) for start in function() local first_bit = bit.rshift(tvb(start, 1):uint(), 7) if first_bit == 1 then local lost_pack_range_tree = lost_list_tree:add(fields.lost_pack_range_tree, tvb(start, 8)) local lost_start = bit.band(tvb(start, 4):uint(), 0x7FFFFFFF) lost_pack_range_tree:append_text(" (" .. lost_start .. " -> " .. tvb(start + 4, 4):uint() .. ")") lost_pack_range_tree:add(fields.lost_start, tvb(start, 4), lost_start) start = start + 4 lost_pack_range_tree:add(fields.lost_up_to, tvb(start, 4)) start = start + 4 else lost_list_tree:add(fields.lost_pack_seq, tvb(start, 4)) start = start + 4 end return start end do if start == ending then break end end end, [4] = function() pinfo.cols.info:append(" [Congestion Warning]") pack_type_tree:append_text(" [Congestion Warning]") pack_type_tree:add(fields.msg_type, tvb(offset, 2)) pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") offset = offset + 4 end, [5] = function() pinfo.cols.info:append(" [Shutdown]") pack_type_tree:append_text(" [Shutdown]") pack_type_tree:add(fields.msg_type, tvb(offset, 2)) pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") offset = offset + 4 -- Handle Additional Info, Timestamp and Destination Socket parse_three_param() end, [6] = function() pinfo.cols.info:append(" [ACKACK]") pack_type_tree:append_text(" [ACKACK]") pack_type_tree:add(fields.msg_type, tvb(offset, 2)) pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") offset = offset + 4 -- Handle ACK sequence number subtree:add(fields.ack_num, tvb(offset, 4)) offset = offset + 4 -- Handle Time Stamp subtree:add(fields.time_stamp, tvb(offset, 4)):append_text(" μs") offset = offset + 4 -- Handle Destination Socket subtree:add(fields.dst_sock, tvb(offset, 4)) offset = offset + 4 -- Handle Control Information subtree:add(fields.ctl_info, tvb(offset, 4)) offset = offset + 4 end, [7] = function() pinfo.cols.info:append(" [Drop Request]") pack_type_tree:append_text(" [Drop Request]") pack_type_tree:add(fields.msg_type, tvb(offset, 2)):append_text(" [Drop Request]") pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") offset = offset + 4 end, [8] = function() pinfo.cols.info:append(" [Peer Error]") pack_type_tree:append_text(" [Peer Error]") pack_type_tree:add(fields.msg_type, tvb(offset, 2)):append_text(" [Peer Error]") pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") offset = offset + 4 end } -- Handle based on msg_type local case = switch[msg_type] if case then case() else -- default case subtree:add(fields.msg_type, tvb(offset, 2)):append_text(" [Unknown Message Type]") offset = offset + 4 end else -- If type field is '0x7FFF', it means an extended type, Handle Reserve field offset = offset + 2 local msg_ext_type = tvb(offset, 2):uint() if msg_ext_type == 0 then pinfo.cols.info:append(" [Message Extension]") pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [Message Extension]") offset = offset + 2 -- Handle Additional Info, Time Stamp and Destination Socket parse_three_param() -- Control information: defined by user elseif msg_ext_type == 1 or ext_type == 2 then if msg_ext_type == 1 then pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [SRT Handshake Request]") pinfo.cols.info:append(" [SRT Handshake Request]") elseif msg_ext_type == 2 then pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [SRT Handshake Response]") pinfo.cols.info:append(" [SRT Handshake Response]") end offset = offset + 2 -- Ignore additional info (this field is not defined in this packet type) subtree:add(fields.additional_info, tvb(offset, 4)):append_text(" [undefined]") offset = offset + 4 -- Handle Time Stamp subtree:add(fields.time_stamp, tvb(offset, 4)):append_text("μs") offset = offset + 4 -- Handle Destination Socket subtree:add(fields.dst_sock, tvb(offset, 4)) offset = offset + 4 -- Handle SRT Version field subtree:add(fields.srt_version, tvb(offset, 4)) offset = ofssset + 4 -- Handle SRT Flags local SRT_flags_tree = subtree:add(fields.srt_flags, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_tsbpdsnd, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_tsbpdrcv, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_haicrypt, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_tlpktdrop, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_nakreport, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_rexmitflg, tvb(offset, 4)) SRT_flags_tree:add(fields.srt_opt_stream, tvb(offset, 4)) offset = offset + 4 -- Handle TsbPd Resv subtree:add(fields.tsbpb_resv, tvb(offset, 2)) offset = offset + 2 -- Handle TsbPb Delay subtree:add(fields.tsbpb_delay, tvb(offset, 2)) offset = offset + 2 -- Handle Reserved field subtree:add(fields.reserve, tvb(offset, 4)) offset = offset + 4 elseif msg_ext_type == 3 or msg_ext_type == 4 then if msg_ext_type == 3 then pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [Encryption Keying Material Request]") pinfo.cols.info:append(" [Encryption Keying Material Request]") elseif msg_ext_type == 4 then pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [Encryption Keying Material Response]") pinfo.cols.info:append(" [Encryption Keying Material Response]") end offset = offset + 2 -- Ignore additional info (this field is not defined in this packet type) subtree:add(fields.additional_info, tvb(offset, 4)):append_text(" [undefined]") offset = offset + 4 -- Handle Timestamp subtree:add(fields.time_stamp, tvb(offset, 4)):append_text("μs") offset = offset + 4 -- Handle Destination Socket subtree:add(fields.dst_sock, tvb(offset, 4)) offset = offset + 4 -- Handle KmErr if msg_ext_type == 4 then subtree:add(fields.km_err, tvb(offset, 4)) offset = offset + 4 return end -- The encrypted message is not handled end end else -- 0 -> Data Packet pack_type_tree:add(fields.pack_type, tvb(offset, 2)) pack_type_tree:append_text(" (Data Packet)") local seq_num = tvb(offset, 4):uint() pinfo.cols.info:append(" (Data Packet)(Seq Num:" .. seq_num .. ")") -- The first 4 bytes are the package sequence number subtree:add(fields.seq_num, tvb(offset, 4)) offset = offset + 4 data_flag_info_tree = subtree:add(fields.data_flag_info_tree, tvb(offset, 1)) -- Handle FF flag local FF_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0xC0), 6) if FF_state == 0 then data_flag_info_tree:append_text(" [Middle packet]") elseif FF_state == 1 then data_flag_info_tree:append_text(" [Last packet]") elseif FF_state == 2 then data_flag_info_tree:append_text(" [First packet]") else data_flag_info_tree:append_text(" [Single packet]") end data_flag_info_tree:add(fields.FF_state, tvb(offset, 1)) -- Handle O flag local O_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0x20), 5) if O_state == 0 then data_flag_info_tree:append_text(" [Data delivered unordered]") else data_flag_info_tree:append_text(" [Data delivered in order]") end data_flag_info_tree:add(fields.O_state, tvb(offset, 1)) -- Handle KK flag local KK_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0x18), 3) if KK_state == 1 then data_flag_info_tree:append_text(" [Encrypted with even key]") elseif KK_state == 2 then data_flag_info_tree:append_text(" [Encrypted with odd key]") end data_flag_info_tree:add(fields.KK_state, tvb(offset, 1)) -- Handle R flag local R_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0x04), 2) if R_state == 1 then data_flag_info_tree:append_text(" [Retransmit packet]") pinfo.cols.info:append(" [Retransmit packet]") end data_flag_info_tree:add(fields.R_state, tvb(offset, 1)) -- Handle message number local msg_num = tvb(offset, 4):uint() msg_num = bit.band(tvb(offset, 4):uint(), 0x03FFFFFF) -- subtree:add(fields.msg_num, bit.band(tvb(offset, 4):uint(), 0x03FFFFFF)) subtree:add(fields.msg_num, tvb(offset, 4), msg_num) offset = offset + 4 -- Handle Timestamp subtree:add(fields.time_stamp, tvb(offset, 4)):append_text(" μs") offset = offset + 4 -- Handle destination socket subtree:add(fields.dst_sock, tvb(offset, 4)) offset = offset + 4 end end -- Add the protocol into udp table local port = 1935 local function enable_dissector() DissectorTable.get("udp.port"):add(port, srt_dev) end -- Call it now - enabled by default enable_dissector() local function disable_dissector() DissectorTable.get("udp.port"):remove(port, srt_dev) end -- Prefs changed will listen at new port function srt_dev.prefs_changed() if port ~= srt_dev.prefs.srt_udp_port then if port ~= 0 then disable_dissector() end port = srt_dev.prefs.srt_udp_port if port ~= 0 then enable_dissector() end end end srt-1.4.4/scripts/srt-ffplay000077500000000000000000000014351412557703600160510ustar00rootroot00000000000000#!/bin/bash # # SRT - Secure, Reliable, Transport # Copyright (c) 2018 Haivision Systems Inc. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # 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/srt-live-transmit ]]; then echo >&2 "ERROR: you need 'srt-live-transmit' 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/srt-live-transmit "$1" file://con/ | ffplay - srt-1.4.4/scripts/srt.pc.in000066400000000000000000000006401412557703600155720ustar00rootroot00000000000000prefix=@INSTALLDIR@ exec_prefix=${prefix} libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ Name: srt Description: SRT library set Version: @SRT_VERSION@ Libs: -L${libdir} -l@TARGET_srt@ @IFNEEDED_LINK_HAICRYPT@ @IFNEEDED_SRTBASE@ @IFNEEDED_SRT_LDFLAGS@ Libs.private: @SRT_LIBS_PRIVATE@ Cflags: -I${includedir} -I${includedir}/srt Requires.private: @SSL_REQUIRED_MODULES@ srt-1.4.4/scripts/tcp-echo-client.tcl000066400000000000000000000044151412557703600175170ustar00rootroot00000000000000#!/usr/bin/tclsh set read_running 0 set write_running 0 set read_eof 0 set theend 0 set nread 0 set nwritten 0 proc ReadBack {fd} { if { !$::write_running } { puts stderr "ERROR: connection closed unexpectedly!" set ::theend 1 return } set r [read $fd 4096] if {$r == ""} { if {[eof $fd]} { puts stderr "EOF on socket" set ::read_running 0 return } # --- puts stderr "SPURIOUS, not reading" return } # --- puts stderr "REPRINTING [string bytelength $r] bytes" puts -nonewline stdout $r incr ::nwritten [string bytelength $r] # --- puts stderr "DONE" set remain [expr {$::nread - $::nwritten}] if { $::read_eof } { puts stderr "Finishing... read=$::nread written=$::nwritten diff=[expr {$::nwritten - $::nread}] - [expr {100.0*$remain/$::nread}]%" } # Nothing more to read if {$remain == 0} { puts stderr "NOTHING MORE TO BE WRITTEN - exiting" set ::theend 1 return } after idle "ReadBack $fd" } proc SendToSocket {fd} { global theend if { !$::write_running } { # --- puts stderr "SERVER DOWN, not reading" fileevent stdin readable {} return } if { $::read_eof } { # Don't read, already EOF. } # --- puts stderr "READING cin" set r [read stdin 4096] if {$r == ""} { if {[eof stdin]} { if {!$::read_eof} { puts stderr "EOF, setting server off" set ::read_eof 1 } # Just enough when the next SendToSocket will # not be scheduled. return } # --- puts stderr "SPURIOUS, not reading" return } # --- puts stderr "SENDING [string bytelength $r] bytes" # Set blocking for a short moment of sending # in order to prevent losing data that must wait fconfigure $fd -blocking yes puts -nonewline $fd $r incr ::nread [string bytelength $r] fconfigure $fd -blocking no # --- if {[fblocked stdin]} { # --- # Nothing more to read # --- return # --- } after idle "SendToSocket $fd" } set fd [socket {*}$argv] fconfigure $fd -encoding binary -translation binary -blocking no -buffering none fileevent $fd readable "ReadBack $fd" fconfigure stdin -encoding binary -translation binary -blocking no fconfigure stdout -encoding binary -translation binary fileevent stdin readable "SendToSocket $fd" # --- puts stderr "READY, sending" set read_running 1 set write_running 1 vwait theend close $fd srt-1.4.4/scripts/tcp-echo-server.tcl000066400000000000000000000017361412557703600175520ustar00rootroot00000000000000#!/usr/bin/tclsh proc SpawnEchoServer {fd host port} { fconfigure $fd -encoding binary -translation binary -blocking no -buffering none fileevent $fd readable "EchoBack $fd" # --- puts stderr "Connected: [fconfigure $fd -peername]" } proc EchoBack {fd} { # --- puts stderr "READ-READY" while 1 { # --- puts stderr "READING 4096" set r [read $fd 4096] if {$r == ""} { if {[eof $fd]} { # --- puts stderr "EOF. Closing" close $fd return } # --- puts stderr "SPURIOUS, giving up read" return } # Set blocking for a short moment of sending # in order to prevent losing data that must wait # --- puts stderr "SENDING [string bytelength $r] bytes" fconfigure $fd -blocking yes puts -nonewline $fd $r fconfigure $fd -blocking no if {[fblocked $fd]} { # --- puts stderr "NO MORE DATA" # Nothing more to read return } # --- puts stderr "AGAIN" } } socket -server SpawnEchoServer $argv puts stderr "SERVER READY" vwait tk srt-1.4.4/scripts/win-installer/000077500000000000000000000000001412557703600166215ustar00rootroot00000000000000srt-1.4.4/scripts/win-installer/.gitignore000066400000000000000000000000761412557703600206140ustar00rootroot00000000000000tmp installers *.exe *~ ~* .#* *.bak *.autosave .DS_Store ._* srt-1.4.4/scripts/win-installer/README.md000066400000000000000000000124621412557703600201050ustar00rootroot00000000000000# SRT Static Libraries Installer for Windows This directory contains scripts to build a binary installer for libsrt on Windows systems for Visual Studio applications using SRT. ## SRT developer: Building the libsrt installer ### Prerequisites These first two steps need to be executed once only. - Prerequisite 1: Install OpenSSL for Windows, both 64 and 32 bits. This can be done automatically by running the PowerShell script `install-openssl.ps1`. - Prerequisite 2: Install NSIS, the NullSoft Installation Scripting system. This can be done automatically by running the PowerShell script `install-nsis.ps1`. ### Building the libsrt installer To build the libsrt installer, simply run the PowerShell script `build-win-installer.ps1`. Running it without parameters, for instance launching it from the Windows Explorer, is sufficient to build the installer. Optional parameters: - `-Version name` : Use the specified string as version number for libsrt. By default, if the current commit has a tag, use that tag (initial "v" removed, for instance `1.4.3`). Otherwise, the defaut version is a detailed version number (most recent version, number of commits since then, short commit SHA, for instance `1.4.3-32-g22cc924`). Use that option if necessary to specify some other non-standard form of version string. - `-NoPause` : Do not wait for the user to press `` at end of execution. By default, execute a `pause` instruction at the end of execution, which is useful when the script was launched from Windows Explorer. Use that option when the script is invoked from another PowerShell script. The installer is then available in the directory `installers`. The name of the installer is `libsrt-VERS.exe` where `VERS` is the SRT version number (see the `-Version` option). The installer shall then be published as a release asset in the `srt` repository on GitHub, either as `libsrt-VERS.exe` or `libsrt-VERS-win-installer.zip`. In the latter case, the archive shall contain `libsrt-VERS.exe`. ## SRT user: Using the libsrt installer ### Installing the SRT libraries To install the SRT libraries, simply run the `libsrt-VERS.exe` installer which is available in the [SRT release area](https://github.com/Haivision/srt/releases). After installing the libsrt binaries, an environment variable named `LIBSRT` is defined with the installation root (typically `C:\Program Files (x86)\libsrt`). If there is a need for automation, in a CI/CD pipeline for instance, the download of the latest `libsrt-VERS.exe` and its installation can be automated using the sample PowerShell script `install-libsrt.ps1` which is available in this directory. This script may be freely copied in the user's build environment. When run without parameters (for instance from the Windows explorer), this script downloads and installs the latest version of libsrt. Optional parameters: - `-Destination path` : Specify a local directory where the libsrt package will be downloaded. By default, use the `tmp` subdirectory from this script's directory. - `-ForceDownload` : Force a download even if the package is already downloaded in the destination path. Note that the latest version is always checked. If a older package is already present but a newer one is available online, the newer one is always downloaded, even without this option. - `-GitHubActions` : When used in a GitHub Actions workflow, make sure that the `LIBSRT` environment variable is propagated to subsequent jobs. In your GitHub workflow, in the initial setup phase, use `script-dir\install-libsrt.ps1 -GitHubActions -NoPause`. - `-NoInstall` : Do not install the package, only download it. By default, libsrt is installed. - `-NoPause` : Do not wait for the user to press `` at end of execution. By default, execute a `pause` instruction at the end of execution, which is useful when the script was launched from Windows Explorer. Use that option when the script is invoked from another PowerShell script. ### Building Windows applications with libsrt In the SRT installation root directory (specified in environment variable `LIBSRT`), there is a Visual Studio property file named `libsrt.props`. Simply reference this property file in your Visual Studio project to use libsrt. You can also do that manually by editing the application project file (the XML file named with a `.vcxproj` extension). Add the following line just before the end of the file: ~~~ ~~~ With this setup, just compile your application normally, either using the Visual Studio IDE or the MSBuild command line tool. ## Files reference This directory contains the following files: | File name | Usage | ----------------------- | ----- | build-win-installer.ps1 | PowerShell script to build the libsrt installer. | install-libsrt.ps1 | Sample PowerShell script to automatically install libsrt (for user's projects). | install-openssl.ps1 | PowerShell script to install OpenSSL (prerequisite to build the installer). | install-nsis.ps1 | PowerShell script to install NSIS (prerequisite to build the installer). | libsrt.nsi | NSIS installation script (used to build the installer). | libsrt.props | Visual Studio property files to use libsrt (embedded in the installer). | README.md | This text file. srt-1.4.4/scripts/win-installer/build-win-installer.ps1000066400000000000000000000170721412557703600231420ustar00rootroot00000000000000#----------------------------------------------------------------------------- # # SRT - Secure, Reliable, Transport # Copyright (c) 2021, Thierry Lelegard # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # #----------------------------------------------------------------------------- <# .SYNOPSIS Build the SRT static libraries installer for Windows. .PARAMETER Version Use the specified string as version number from libsrt. By default, if the current commit has a tag, use that tag (initial 'v' removed). Otherwise, the defaut version is a detailed version number (most recent version, number of commits since then, short commit SHA). .PARAMETER NoPause Do not wait for the user to press at end of execution. By default, execute a "pause" instruction at the end of execution, which is useful when the script was run from Windows Explorer. #> [CmdletBinding()] param( [string]$Version = "", [switch]$NoPause = $false ) Write-Output "Building the SRT static libraries installer for Windows" # Directory containing this script: $ScriptDir = $PSScriptRoot # The root of the srt repository is two levels up. $RepoDir = (Split-Path -Parent (Split-Path -Parent $ScriptDir)) # Output directory for final installers: $OutDir = "$ScriptDir\installers" # Temporary directory for build operations: $TmpDir = "$ScriptDir\tmp" #----------------------------------------------------------------------------- # A function to exit this script with optional error message, using -NoPause #----------------------------------------------------------------------------- function Exit-Script([string]$Message = "") { $Code = 0 if ($Message -ne "") { Write-Output "ERROR: $Message" $Code = 1 } if (-not $NoPause) { pause } exit $Code } #----------------------------------------------------------------------------- # Build SRT version strings #----------------------------------------------------------------------------- # By default, let git format a decent version number. if (-not $Version) { $Version = (git describe --tags ) -replace '-g','-' } $Version = $Version -replace '^v','' # Split version string in pieces and make sure to get at least four elements. $VField = ($Version -split "[-\. ]") + @("0", "0", "0", "0") | Select-String -Pattern '^\d*$' $VersionInfo = "$($VField[0]).$($VField[1]).$($VField[2]).$($VField[3])" Write-Output "SRT version: $Version" Write-Output "Windows version info: $VersionInfo" #----------------------------------------------------------------------------- # Initialization phase, verify prerequisites #----------------------------------------------------------------------------- # Locate OpenSSL root from local installation. $SslRoot = @{ "x64" = "C:\Program Files\OpenSSL-Win64"; "Win32" = "C:\Program Files (x86)\OpenSSL-Win32" } # Verify OpenSSL directories. $Missing = 0 foreach ($file in @($SslRoot["x64"], $SslRoot["Win32"])) { if (-not (Test-Path $file)) { Write-Output "**** Missing $file" $Missing = $Missing + 1 } } if ($Missing -gt 0) { Exit-Script "Missing $Missing OpenSSL files, use install-openssl.ps1 to install OpenSSL" } # Locate MSBuild and CMake, regardless of Visual Studio version. Write-Output "Searching MSBuild ..." $MSRoots = @("C:\Program Files*\MSBuild", "C:\Program Files*\Microsoft Visual Studio", "C:\Program Files*\CMake*") $MSBuild = Get-ChildItem -Recurse -Path $MSRoots -Include MSBuild.exe -ErrorAction Ignore | ForEach-Object { (Get-Command $_).FileVersionInfo } | Sort-Object -Unique -Property FileVersion | ForEach-Object { $_.FileName} | Select-Object -Last 1 if (-not $MSBuild) { Exit-Script "MSBuild not found" } Write-Output "Searching CMake ..." $CMake = Get-ChildItem -Recurse -Path $MSRoots -Include cmake.exe -ErrorAction Ignore | ForEach-Object { (Get-Command $_).FileVersionInfo } | Sort-Object -Unique -Property FileVersion | ForEach-Object { $_.FileName} | Select-Object -Last 1 if (-not $CMake) { Exit-Script "CMake not found, check option 'C++ CMake tools for Windows' in Visual Studio installer" } # Locate NSIS, the Nullsoft Scriptable Installation System. Write-Output "Searching NSIS ..." $NSIS = Get-Item "C:\Program Files*\NSIS\makensis.exe" | ForEach-Object { $_.FullName} | Select-Object -Last 1 if (-not $NSIS) { Exit-Script "NSIS not found, use install-nsis.ps1 to install NSIS" } Write-Output "MSBuild: $MSBuild" Write-Output "CMake: $CMake" Write-Output "NSIS: $NSIS" # Create the directories for builds when necessary. [void](New-Item -Path $TmpDir -ItemType Directory -Force) [void](New-Item -Path $OutDir -ItemType Directory -Force) #----------------------------------------------------------------------------- # Configure and build SRT library using CMake on two architectures. #----------------------------------------------------------------------------- foreach ($Platform in @("x64", "Win32")) { # Build directory. Cleanup to force a fresh cmake config. $BuildDir = "$TmpDir\build.$Platform" Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $BuildDir [void](New-Item -Path $BuildDir -ItemType Directory -Force) # Run CMake. Write-Output "Configuring build for platform $Platform ..." $SRoot = $SslRoot[$Platform] & $CMake -S $RepoDir -B $BuildDir -A $Platform ` -DENABLE_STDCXX_SYNC=ON ` -DOPENSSL_ROOT_DIR="$SRoot" ` -DOPENSSL_LIBRARIES="$SRoot\lib\libssl_static.lib;$SRoot\lib\libcrypto_static.lib" ` -DOPENSSL_INCLUDE_DIR="$SRoot\include" # Patch version string in version.h Get-Content "$BuildDir\version.h" | ForEach-Object { $_ -replace "#define *SRT_VERSION_STRING .*","#define SRT_VERSION_STRING `"$Version`"" } | Out-File "$BuildDir\version.new" -Encoding ascii Move-Item "$BuildDir\version.new" "$BuildDir\version.h" -Force # Compile SRT. Write-Output "Building for platform $Platform ..." foreach ($Conf in @("Release", "Debug")) { & $MSBuild "$BuildDir\SRT.sln" /nologo /maxcpucount /property:Configuration=$Conf /property:Platform=$Platform /target:srt_static } } # Verify the presence of compiled libraries. Write-Output "Checking compiled libraries ..." $Missing = 0 foreach ($Conf in @("Release", "Debug")) { foreach ($Platform in @("x64", "Win32")) { $Path = "$TmpDir\build.$Platform\$Conf\srt_static.lib" if (-not (Test-Path $Path)) { Write-Output "**** Missing $Path" $Missing = $Missing + 1 } } } if ($Missing -gt 0) { Exit-Script "Missing $Missing files" } #----------------------------------------------------------------------------- # Build the binary installer. #----------------------------------------------------------------------------- $InstallExe = "$OutDir\libsrt-$Version.exe" $InstallZip = "$OutDir\libsrt-$Version-win-installer.zip" Write-Output "Building installer ..." & $NSIS /V2 ` /DVersion="$Version" ` /DVersionInfo="$VersionInfo" ` /DOutDir="$OutDir" ` /DBuildRoot="$TmpDir" ` /DRepoDir="$RepoDir" ` "$ScriptDir\libsrt.nsi" if (-not (Test-Path $InstallExe)) { Exit-Script "**** Missing $InstallExe" } Write-Output "Building installer archive ..." Remove-Item -Force -ErrorAction SilentlyContinue $InstallZip Compress-Archive -Path $InstallExe -DestinationPath $InstallZip -CompressionLevel Optimal if (-not (Test-Path $InstallZip)) { Exit-Script "**** Missing $InstallZip" } Exit-Script srt-1.4.4/scripts/win-installer/install-libsrt.ps1000066400000000000000000000075651412557703600222260ustar00rootroot00000000000000# SRT library download and install for Windows. # Copyright (c) 2021, Thierry Lelegard # All rights reserved. <# .SYNOPSIS Download and install the libsrt library for Windows. This script is provided to automate the build of Windows applications using libsrt. .PARAMETER Destination Specify a local directory where the libsrt package will be downloaded. By default, use "tmp" subdirectory from this script. .PARAMETER ForceDownload Force a download even if the package is already downloaded. .PARAMETER GitHubActions When used in a GitHub Actions workflow, make sure that the LIBSRT environment variable is propagated to subsequent jobs. .PARAMETER NoInstall Do not install the package. By default, libsrt is installed. .PARAMETER NoPause Do not wait for the user to press at end of execution. By default, execute a "pause" instruction at the end of execution, which is useful when the script was run from Windows Explorer. #> [CmdletBinding(SupportsShouldProcess=$true)] param( [string]$Destination = "", [switch]$ForceDownload = $false, [switch]$GitHubActions = $false, [switch]$NoInstall = $false, [switch]$NoPause = $false ) Write-Output "libsrt download and installation procedure" # Default directory for downloaded products. if (-not $Destination) { $Destination = "$PSScriptRoot\tmp" } # A function to exit this script. function Exit-Script([string]$Message = "") { $Code = 0 if ($Message -ne "") { Write-Output "ERROR: $Message" $Code = 1 } if (-not $NoPause) { pause } exit $Code } # Without this, Invoke-WebRequest is awfully slow. $ProgressPreference = 'SilentlyContinue' # Get the URL of the latest libsrt installer. $URL = (Invoke-RestMethod "https://api.github.com/repos/Haivision/srt/releases?per_page=20" | ForEach-Object { $_.assets } | ForEach-Object { $_.browser_download_url } | Select-String @("/libsrt-.*\.exe$", "/libsrt-.*-win-installer\.zip$") | Select-Object -First 1) if (-not $URL) { Exit-Script "Could not find a libsrt installer on GitHub" } if (-not ($URL -match "\.zip$") -and -not ($URL -match "\.exe$")) { Exit-Script "Unexpected URL, not .exe, not .zip: $URL" } # Installer name and path. $InstName = (Split-Path -Leaf $URL) $InstPath = "$Destination\$InstName" # Create the directory for downloaded products when necessary. [void](New-Item -Path $Destination -ItemType Directory -Force) # Download installer if (-not $ForceDownload -and (Test-Path $InstPath)) { Write-Output "$InstName already downloaded, use -ForceDownload to download again" } else { Write-Output "Downloading $URL ..." Invoke-WebRequest $URL.ToString() -UseBasicParsing -UserAgent Download -OutFile $InstPath if (-not (Test-Path $InstPath)) { Exit-Script "$URL download failed" } } # If installer is an archive, expect an exe with same name inside. if ($InstName -match "\.zip$") { # Expected installer name in archive. $ZipName = $InstName $ZipPath = $InstPath $InstName = $ZipName -replace '-win-installer.zip','.exe' $InstPath = "$Destination\$InstName" # Extract the installer. Remove-Item -Force $InstPath -ErrorAction SilentlyContinue Write-Output "Expanding $ZipName ..." Expand-Archive $ZipPath -DestinationPath $Destination if (-not (Test-Path $InstPath)) { Exit-Script "$InstName not found in $ZipName" } } # Install libsrt if (-not $NoInstall) { Write-Output "Installing $InstName" Start-Process -FilePath $InstPath -ArgumentList @("/S") -Wait } # Propagate LIBSRT in next jobs for GitHub Actions. if ($GitHubActions -and (-not -not $env:GITHUB_ENV) -and (Test-Path $env:GITHUB_ENV)) { $libsrt = [System.Environment]::GetEnvironmentVariable("LIBSRT","Machine") Write-Output "LIBSRT=$libsrt" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append } Exit-Script srt-1.4.4/scripts/win-installer/install-nsis.ps1000066400000000000000000000067411412557703600216760ustar00rootroot00000000000000#----------------------------------------------------------------------------- # # SRT - Secure, Reliable, Transport # Copyright (c) 2021, Thierry Lelegard # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # #----------------------------------------------------------------------------- <# .SYNOPSIS Download, expand and install NSIS, the NullSoft Installer Scripting. .PARAMETER ForceDownload Force a download even if NSIS is already downloaded. .PARAMETER NoInstall Do not install the NSIS package. By default, NSIS is installed. .PARAMETER NoPause Do not wait for the user to press at end of execution. By default, execute a "pause" instruction at the end of execution, which is useful when the script was run from Windows Explorer. #> [CmdletBinding(SupportsShouldProcess=$true)] param( [switch]$ForceDownload = $false, [switch]$NoInstall = $false, [switch]$NoPause = $false ) Write-Output "NSIS download and installation procedure" $NSISPage = "https://nsis.sourceforge.io/Download" $FallbackURL = "http://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download" # A function to exit this script. function Exit-Script([string]$Message = "") { $Code = 0 if ($Message -ne "") { Write-Output "ERROR: $Message" $Code = 1 } if (-not $NoPause) { pause } exit $Code } # Local file names. $RootDir = $PSScriptRoot $TmpDir = "$RootDir\tmp" # Create the directory for external products when necessary. [void] (New-Item -Path $TmpDir -ItemType Directory -Force) # Without this, Invoke-WebRequest is awfully slow. $ProgressPreference = 'SilentlyContinue' # Get the HTML page for NSIS downloads. $status = 0 $message = "" $Ref = $null try { $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $NSISPage $status = [int] [Math]::Floor($response.StatusCode / 100) } catch { $message = $_.Exception.Message } if ($status -ne 1 -and $status -ne 2) { # Error fetch NSIS download page. if ($message -eq "" -and (Test-Path variable:response)) { Write-Output "Status code $($response.StatusCode), $($response.StatusDescription)" } else { Write-Output "#### Error accessing ${NSISPage}: $message" } } else { # Parse HTML page to locate the latest installer. $Ref = $response.Links.href | Where-Object { $_ -like "*/nsis-*-setup.exe?download" } | Select-Object -First 1 } if (-not $Ref) { # Could not find a reference to NSIS installer. $Url = [System.Uri]$FallbackURL } else { # Build the absolute URL's from base URL (the download page) and href links. $Url = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$NSISPage, $Ref) } $InstallerName = (Split-Path -Leaf $Url.LocalPath) $InstallerPath = "$TmpDir\$InstallerName" # Download installer if (-not $ForceDownload -and (Test-Path $InstallerPath)) { Write-Output "$InstallerName already downloaded, use -ForceDownload to download again" } else { Write-Output "Downloading $Url ..." Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $InstallerPath if (-not (Test-Path $InstallerPath)) { Exit-Script "$Url download failed" } } # Install NSIS if (-not $NoInstall) { Write-Output "Installing $InstallerName" Start-Process -FilePath $InstallerPath -ArgumentList @("/S") -Wait } Exit-Script srt-1.4.4/scripts/win-installer/install-openssl.ps1000066400000000000000000000071521412557703600224020ustar00rootroot00000000000000#----------------------------------------------------------------------------- # # SRT - Secure, Reliable, Transport # Copyright (c) 2021, Thierry Lelegard # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # #----------------------------------------------------------------------------- <# .SYNOPSIS Download, expand and install OpenSSL for Windows. .PARAMETER ForceDownload Force a download even if the OpenSSL installers are already downloaded. .PARAMETER NoInstall Do not install the OpenSSL packages. By default, OpenSSL is installed. .PARAMETER NoPause Do not wait for the user to press at end of execution. By default, execute a "pause" instruction at the end of execution, which is useful when the script was run from Windows Explorer. #> [CmdletBinding(SupportsShouldProcess=$true)] param( [switch]$ForceDownload = $false, [switch]$NoInstall = $false, [switch]$NoPause = $false ) Write-Output "OpenSSL download and installation procedure" $OpenSSLHomePage = "http://slproweb.com/products/Win32OpenSSL.html" # A function to exit this script. function Exit-Script([string]$Message = "") { $Code = 0 if ($Message -ne "") { Write-Output "ERROR: $Message" $Code = 1 } if (-not $NoPause) { pause } exit $Code } # Local file names. $RootDir = $PSScriptRoot $TmpDir = "$RootDir\tmp" # Create the directory for external products when necessary. [void] (New-Item -Path $TmpDir -ItemType Directory -Force) # Without this, Invoke-WebRequest is awfully slow. $ProgressPreference = 'SilentlyContinue' # Get the HTML page for OpenSSL downloads. $status = 0 $message = "" try { $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $OpenSSLHomePage $status = [int] [Math]::Floor($response.StatusCode / 100) } catch { $message = $_.Exception.Message } if ($status -ne 1 -and $status -ne 2) { if ($message -eq "" -and (Test-Path variable:response)) { Exit-Script "Status code $($response.StatusCode), $($response.StatusDescription)" } else { Exit-Script "#### Error accessing ${OpenSSLHomePage}: $message" } } # Parse HTML page to locate the latest MSI files. $Ref32 = $response.Links.href | Where-Object { $_ -like "*/Win32OpenSSL-*.msi" } | Select-Object -First 1 $Ref64 = $response.Links.href | Where-Object { $_ -like "*/Win64OpenSSL-*.msi" } | Select-Object -First 1 # Build the absolute URL's from base URL (the download page) and href links. $Url32 = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$OpenSSLHomePage, $Ref32) $Url64 = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$OpenSSLHomePage, $Ref64) # Download and install one MSI package. function Download-Install([string]$Url) { $MsiName = (Split-Path -Leaf $Url.toString()) $MsiPath = "$TmpDir\$MsiName" if (-not $ForceDownload -and (Test-Path $MsiPath)) { Write-Output "$MsiName already downloaded, use -ForceDownload to download again" } else { Write-Output "Downloading $Url ..." Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $MsiPath } if (-not (Test-Path $MsiPath)) { Exit-Script "$Url download failed" } if (-not $NoInstall) { Write-Output "Installing $MsiName" Start-Process msiexec.exe -ArgumentList @("/i", $MsiPath, "/qn", "/norestart") -Wait } } # Download and install the two MSI packages. Download-Install $Url32 Download-Install $Url64 Exit-Script srt-1.4.4/scripts/win-installer/libsrt.nsi000066400000000000000000000164221412557703600206400ustar00rootroot00000000000000;----------------------------------------------------------------------------- ; ; SRT - Secure, Reliable, Transport ; Copyright (c) 2021, Thierry Lelegard ; ; This Source Code Form is subject to the terms of the Mozilla Public ; License, v. 2.0. If a copy of the MPL was not distributed with this ; file, You can obtain one at http://mozilla.org/MPL/2.0/. ; ;----------------------------------------------------------------------------- ; ; NSIS script to build the SRT binary installer for Windows. ; Do not invoke NSIS directly, use PowerShell script build-win-installer.ps1 ; to ensure that all parameters are properly passed. ; ;----------------------------------------------------------------------------- Name "SRT" Caption "SRT Libraries Installer" !verbose push !verbose 0 !include "MUI2.nsh" !include "Sections.nsh" !include "TextFunc.nsh" !include "FileFunc.nsh" !include "WinMessages.nsh" !include "x64.nsh" !verbose pop !define ProductName "libsrt" !define Build32Dir "${BuildRoot}\build.Win32" !define Build64Dir "${BuildRoot}\build.x64" !define SSL32Dir "C:\Program Files (x86)\OpenSSL-Win32" !define SSL64Dir "C:\Program Files\OpenSSL-Win64" ; Installer file information. VIProductVersion ${VersionInfo} VIAddVersionKey ProductName "${ProductName}" VIAddVersionKey ProductVersion "${Version}" VIAddVersionKey Comments "The SRT static libraries for Visual C++ on Windows" VIAddVersionKey CompanyName "Haivision" VIAddVersionKey LegalCopyright "Copyright (c) 2021 Haivision Systems Inc." VIAddVersionKey FileVersion "${VersionInfo}" VIAddVersionKey FileDescription "SRT Installer" ; Name of binary installer file. OutFile "${OutDir}\${ProductName}-${Version}.exe" ; Generate a Unicode installer (default is ANSI). Unicode true ; Registry key for environment variables !define EnvironmentKey '"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' ; Registry entry for product info and uninstallation info. !define ProductKey "Software\${ProductName}" !define UninstallKey "Software\Microsoft\Windows\CurrentVersion\Uninstall\${ProductName}" ; Use XP manifest. XPStyle on ; Request administrator privileges for Windows Vista and higher. RequestExecutionLevel admin ; "Modern User Interface" (MUI) settings. !define MUI_ABORTWARNING ; Default installation folder. InstallDir "$PROGRAMFILES\${ProductName}" ; Get installation folder from registry if available from a previous installation. InstallDirRegKey HKLM "${ProductKey}" "InstallDir" ; Installer pages. !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES ; Uninstaller pages. !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES ; Languages. !insertmacro MUI_LANGUAGE "English" ; Installation initialization. function .onInit ; In 64-bit installers, don't use registry redirection. ${If} ${RunningX64} SetRegView 64 ${EndIf} functionEnd ; Uninstallation initialization. function un.onInit ; In 64-bit installers, don't use registry redirection. ${If} ${RunningX64} SetRegView 64 ${EndIf} functionEnd ; Installation section Section "Install" ; Work on "all users" context, not current user. SetShellVarContext all ; Delete obsolete files from previous versions. Delete "$INSTDIR\LICENSE.pthread.txt" Delete "$INSTDIR\include\srt\srt4udt.h" Delete "$INSTDIR\include\srt\udt.h" Delete "$INSTDIR\lib\Release-x64\pthread.lib" Delete "$INSTDIR\lib\Release-Win32\pthread.lib" Delete "$INSTDIR\lib\Debug-x64\srt.pdb" Delete "$INSTDIR\lib\Debug-x64\pthread.pdb" Delete "$INSTDIR\lib\Debug-x64\pthread.lib" Delete "$INSTDIR\lib\Debug-Win32\srt.pdb" Delete "$INSTDIR\lib\Debug-Win32\pthread.pdb" Delete "$INSTDIR\lib\Debug-Win32\pthread.lib" SetOutPath "$INSTDIR" File /oname=LICENSE.txt "${RepoDir}\LICENSE" File "libsrt.props" ; Header files. CreateDirectory "$INSTDIR\include\srt" SetOutPath "$INSTDIR\include\srt" File "${RepoDir}\srtcore\logging_api.h" File "${RepoDir}\srtcore\platform_sys.h" File "${RepoDir}\srtcore\srt.h" File "${Build64Dir}\version.h" CreateDirectory "$INSTDIR\include\win" SetOutPath "$INSTDIR\include\win" File "${RepoDir}\common\win\syslog_defs.h" ; Libraries. CreateDirectory "$INSTDIR\lib" CreateDirectory "$INSTDIR\lib\Release-x64" SetOutPath "$INSTDIR\lib\Release-x64" File /oname=srt.lib "${Build64Dir}\Release\srt_static.lib" File /oname=libcrypto.lib "${SSL64Dir}\lib\VC\static\libcrypto64MD.lib" File /oname=libssl.lib "${SSL64Dir}\lib\VC\static\libssl64MD.lib" CreateDirectory "$INSTDIR\lib\Debug-x64" SetOutPath "$INSTDIR\lib\Debug-x64" File /oname=srt.lib "${Build64Dir}\Debug\srt_static.lib" File /oname=libcrypto.lib "${SSL64Dir}\lib\VC\static\libcrypto64MDd.lib" File /oname=libssl.lib "${SSL64Dir}\lib\VC\static\libssl64MDd.lib" CreateDirectory "$INSTDIR\lib\Release-Win32" SetOutPath "$INSTDIR\lib\Release-Win32" File /oname=srt.lib "${Build32Dir}\Release\srt_static.lib" File /oname=libcrypto.lib "${SSL32Dir}\lib\VC\static\libcrypto32MD.lib" File /oname=libssl.lib "${SSL32Dir}\lib\VC\static\libssl32MD.lib" CreateDirectory "$INSTDIR\lib\Debug-Win32" SetOutPath "$INSTDIR\lib\Debug-Win32" File /oname=srt.lib "${Build32Dir}\Debug\srt_static.lib" File /oname=libcrypto.lib "${SSL32Dir}\lib\VC\static\libcrypto32MDd.lib" File /oname=libssl.lib "${SSL32Dir}\lib\VC\static\libssl32MDd.lib" ; Add an environment variable to installation root. WriteRegStr HKLM ${EnvironmentKey} "LIBSRT" "$INSTDIR" ; Store installation folder in registry. WriteRegStr HKLM "${ProductKey}" "InstallDir" $INSTDIR ; Create uninstaller WriteUninstaller "$INSTDIR\Uninstall.exe" ; Declare uninstaller in "Add/Remove Software" control panel WriteRegStr HKLM "${UninstallKey}" "DisplayName" "${ProductName}" WriteRegStr HKLM "${UninstallKey}" "Publisher" "Haivision" WriteRegStr HKLM "${UninstallKey}" "URLInfoAbout" "https://github.com/Haivision/srt" WriteRegStr HKLM "${UninstallKey}" "DisplayVersion" "${Version}" WriteRegStr HKLM "${UninstallKey}" "DisplayIcon" "$INSTDIR\Uninstall.exe" WriteRegStr HKLM "${UninstallKey}" "UninstallString" "$INSTDIR\Uninstall.exe" ; Get estimated size of installed files ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKLM "${UninstallKey}" "EstimatedSize" "$0" ; Notify applications of environment modifications SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 SectionEnd ; Uninstallation section Section "Uninstall" ; Work on "all users" context, not current user. SetShellVarContext all ; Get installation folder from registry ReadRegStr $0 HKLM "${ProductKey}" "InstallDir" ; Delete product registry entries DeleteRegKey HKCU "${ProductKey}" DeleteRegKey HKLM "${ProductKey}" DeleteRegKey HKLM "${UninstallKey}" DeleteRegValue HKLM ${EnvironmentKey} "LIBSRT" ; Delete product files. RMDir /r "$0\include" RMDir /r "$0\lib" Delete "$0\libsrt.props" Delete "$0\LICENSE*" Delete "$0\Uninstall.exe" RMDir "$0" ; Notify applications of environment modifications SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 SectionEnd srt-1.4.4/scripts/win-installer/libsrt.props000066400000000000000000000026001412557703600212030ustar00rootroot00000000000000 Win32 x64 $(Platform) $(LIBSRT)\include;%(AdditionalIncludeDirectories) srt.lib;libssl.lib;libcrypto.lib;crypt32.lib;%(AdditionalDependencies) $(LIBSRT)\lib\$(Configuration)-$(SrtPlatform);%(AdditionalLibraryDirectories) /ignore:4099 %(AdditionalOptions) srt-1.4.4/sonar-project.properties000066400000000000000000000021571412557703600172530ustar00rootroot00000000000000# must be unique in a given SonarQube instance sonar.projectKey=srt sonar.organization=haivision # --- optional properties --- # This is the name and version displayed in the SonarCloud UI. #sonar.projectName=srt #sonar.projectVersion=1.0 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. #sonar.sources=. # Encoding of the source code. Default is default system encoding #sonar.sourceEncoding=UTF-8 # ===================================================== # Meta-data for the project # ===================================================== sonar.links.homepage=https://github.com/Haivision/srt sonar.links.ci=https://travis-ci.org/Haivision/srt sonar.links.scm=https://github.com/Haivision/srt sonar.links.issue=https://github.com/Haivision/srt/issues # ===================================================== # Properties that will be shared amongst all modules # ===================================================== # SQ standard properties sonar.sources=. # Properties specific to the C/C++ analyzer: sonar.cfamily.build-wrapper-output=bw-output sonar.cfamily.gcov.reportsPath=. srt-1.4.4/srt-ffplay000077700000000000000000000000001412557703600200502scripts/srt-ffplayustar00rootroot00000000000000srt-1.4.4/srtcore/000077500000000000000000000000001412557703600140235ustar00rootroot00000000000000srt-1.4.4/srtcore/README.md000066400000000000000000000044211412557703600153030ustar00rootroot00000000000000SRT Core ======== These files are contents of the SRT library. Beside files that are used exclusively and internally by the library, this directory also contains: - common files: usually header files, which can be used also by other projects, even if they don't link against SRT - public and protected header files - header files for the library, which will be picked up from here Which header files are public, protected and private, it's defined in the manifest file together with all source files that the SRT library comprises of: `filelist.maf`. Common files ============ This directory holds the files that may be used separately by both SRT library itself and the internal applications. Source files are added to SRT library, so apps don't have to use them. However these source files might be used by some internal applications that do not link against SRT library. Header files contained here might be required by internal applications no matter if they link against SRT or not. They are here because simultaneously they are used also by the SRT library. Utilities ========= 1. threadname.h This is a utility that is useful for debugging and it allows a thread to be given a name. This name is used in the logging messages, as well as you can see it also inside the debugger. This is currently supported only on Linux; some more portable and more reliable way is needed. 2. utilities.h A set of various reusable components, all defined as C++ classes or C++ inline functions. 3. `netinet_any.h` This defines a `sockaddr_any` type, which simplifies dealing with the BSD socket API using `sockaddr`, `sockaddr_in` and `sockaddr_in6` structures. Compat and portability ====================== 1. `srt_compat.h` This part contains some portability problem resolutions, including: - `strerror` in a version that is both portable and thread safe - `localtime` in a version that is both portable and thread safe 2. win directory This contains various header files that are used on Windows platform only. They provide various facilities available OOTB on POSIX systems. 3. `platform_sys.h` This is a file that is responsible to include whatever system include files must be included for whatever system API must be provided for the needs of SRT library. This is a part of public headers. srt-1.4.4/srtcore/access_control.h000066400000000000000000000120661412557703600172020ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2020 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_F_ACCESS_CONTROL_H #define INC_F_ACCESS_CONTROL_H // A list of rejection codes that are SRT specific. #define SRT_REJX_FALLBACK 1000 // A code used in case when the application wants to report some problem, but can't precisely specify it. #define SRT_REJX_KEY_NOTSUP 1001 // The key used in the StreamID keyed string is not supported by the service. #define SRT_REJX_FILEPATH 1002 // The resource type designates a file and the path is either wrong syntax or not found #define SRT_REJX_HOSTNOTFOUND 1003 // The `h` host specification was not recognized by the service // The list of http codes adopted for SRT. // An example C++ header for HTTP codes can be found at: // https://github.com/j-ulrich/http-status-codes-cpp // Some of the unused code can be revived in the future, if there // happens to be a good reason for it. #define SRT_REJX_BAD_REQUEST 1400 // General syntax error in the SocketID specification (also a fallback code for undefined cases) #define SRT_REJX_UNAUTHORIZED 1401 // Authentication failed, provided that the user was correctly identified and access to the required resource would be granted #define SRT_REJX_OVERLOAD 1402 // The server is too heavily loaded, or you have exceeded credits for accessing the service and the resource. #define SRT_REJX_FORBIDDEN 1403 // Access denied to the resource by any kind of reason. #define SRT_REJX_NOTFOUND 1404 // Resource not found at this time. #define SRT_REJX_BAD_MODE 1405 // The mode specified in `m` key in StreamID is not supported for this request. #define SRT_REJX_UNACCEPTABLE 1406 // The requested parameters specified in SocketID cannot be satisfied for the requested resource. Also when m=publish and the data format is not acceptable. // CODE NOT IN USE 407: unused: proxy functionality not predicted // CODE NOT IN USE 408: unused: no timeout predicted for listener callback #define SRT_REJX_CONFLICT 1409 // The resource being accessed is already locked for modification. This is in case of m=publish and the specified resource is currently read-only. // CODE NOT IN USE 410: unused: treated as a specific case of 404 // CODE NOT IN USE 411: unused: no reason to include lenght in the protocol // CODE NOT IN USE 412: unused: preconditions not predicted in AC // CODE NOT IN USE 413: unused: AC size is already defined as 512 // CODE NOT IN USE 414: unused: AC size is already defined as 512 #define SRT_REJX_NOTSUP_MEDIA 1415 // The media type is not supported by the application. This is the `t` key that specifies the media type as stream, file and auth, possibly extended by the application. // CODE NOT IN USE 416: unused: no detailed specification defined // CODE NOT IN USE 417: unused: expectations not supported // CODE NOT IN USE 418: unused: sharks do not drink tea // CODE NOT IN USE 419: not defined in HTTP // CODE NOT IN USE 420: not defined in HTTP // CODE NOT IN USE 421: unused: misdirection not supported // CODE NOT IN USE 422: unused: aligned to general 400 #define SRT_REJX_LOCKED 1423 // The resource being accessed is locked for any access. #define SRT_REJX_FAILED_DEPEND 1424 // The request failed because it specified a dependent session ID that has been disconnected. // CODE NOT IN USE 425: unused: replaying not supported // CODE NOT IN USE 426: unused: tempting, but it requires resend in connected // CODE NOT IN USE 427: not defined in HTTP // CODE NOT IN USE 428: unused: renders to 409 // CODE NOT IN USE 429: unused: renders to 402 // CODE NOT IN USE 451: unused: renders to 403 #define SRT_REJX_ISE 1500 // Unexpected internal server error #define SRT_REJX_UNIMPLEMENTED 1501 // The request was recognized, but the current version doesn't support it. #define SRT_REJX_GW 1502 // The server acts as a gateway and the target endpoint rejected the connection. #define SRT_REJX_DOWN 1503 // The service has been temporarily taken over by a stub reporting this error. The real service can be down for maintenance or crashed. // CODE NOT IN USE 504: unused: timeout not supported #define SRT_REJX_VERSION 1505 // SRT version not supported. This might be either unsupported backward compatibility, or an upper value of a version. // CODE NOT IN USE 506: unused: negotiation and references not supported #define SRT_REJX_NOROOM 1507 // The data stream cannot be archived due to lacking storage space. This is in case when the request type was to send a file or the live stream to be archived. // CODE NOT IN USE 508: unused: no redirection supported // CODE NOT IN USE 509: not defined in HTTP // CODE NOT IN USE 510: unused: extensions not supported // CODE NOT IN USE 511: unused: intercepting proxies not supported #endif srt-1.4.4/srtcore/api.cpp000066400000000000000000004323541412557703600153130ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 07/09/2011 modified by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include #include #include #include #include #include #include "utilities.h" #include "netinet_any.h" #include "api.h" #include "core.h" #include "epoll.h" #include "logging.h" #include "threadname.h" #include "srt.h" #include "udt.h" #ifdef _WIN32 #include #endif #ifdef _MSC_VER #pragma warning(error: 4530) #endif using namespace std; using namespace srt_logging; using namespace srt::sync; extern LogConfig srt_logger_config; void srt::CUDTSocket::construct() { #if ENABLE_EXPERIMENTAL_BONDING m_GroupOf = NULL; m_GroupMemberData = NULL; #endif setupMutex(m_AcceptLock, "Accept"); setupCond(m_AcceptCond, "Accept"); setupMutex(m_ControlLock, "Control"); } srt::CUDTSocket::~CUDTSocket() { releaseMutex(m_AcceptLock); releaseCond(m_AcceptCond); releaseMutex(m_ControlLock); } SRT_SOCKSTATUS srt::CUDTSocket::getStatus() { // TTL in CRendezvousQueue::updateConnStatus() will set m_bConnecting to false. // Although m_Status is still SRTS_CONNECTING, the connection is in fact to be closed due to TTL expiry. // In this case m_bConnected is also false. Both checks are required to avoid hitting // a regular state transition from CONNECTING to CONNECTED. if (m_UDT.m_bBroken) return SRTS_BROKEN; // Connecting timed out if ((m_Status == SRTS_CONNECTING) && !m_UDT.m_bConnecting && !m_UDT.m_bConnected) return SRTS_BROKEN; return m_Status; } // [[using locked(m_GlobControlLock)]] void srt::CUDTSocket::breakSocket_LOCKED() { // This function is intended to be called from GC, // under a lock of m_GlobControlLock. m_UDT.m_bBroken = true; m_UDT.m_iBrokenCounter = 0; HLOGC(smlog.Debug, log << "@" << m_SocketID << " CLOSING AS SOCKET"); m_UDT.closeInternal(); setClosed(); } void srt::CUDTSocket::setClosed() { m_Status = SRTS_CLOSED; // a socket will not be immediately 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 m_tsClosureTimeStamp = steady_clock::now(); } void srt::CUDTSocket::setBrokenClosed() { m_UDT.m_iBrokenCounter = 60; m_UDT.m_bBroken = true; setClosed(); } bool srt::CUDTSocket::readReady() { if (m_UDT.m_bConnected && m_UDT.m_pRcvBuffer->isRcvDataReady()) return true; if (m_UDT.m_bListening) return !m_QueuedSockets.empty(); return broken(); } bool srt::CUDTSocket::writeReady() const { return (m_UDT.m_bConnected && (m_UDT.m_pSndBuffer->getCurrBufSize() < m_UDT.m_config.iSndBufSize)) || broken(); } bool srt::CUDTSocket::broken() const { return m_UDT.m_bBroken || !m_UDT.m_bConnected; } //////////////////////////////////////////////////////////////////////////////// srt::CUDTUnited::CUDTUnited(): m_Sockets(), m_GlobControlLock(), m_IDLock(), m_mMultiplexer(), m_MultiplexerLock(), m_pCache(NULL), m_bClosing(false), m_GCStopCond(), m_InitLock(), m_iInstanceCount(0), m_bGCStatus(false), m_ClosedSockets() { // Socket ID MUST start from a random value m_SocketIDGenerator = genRandomInt(1, MAX_SOCKET_VAL); m_SocketIDGenerator_init = m_SocketIDGenerator; // XXX An unlikely exception thrown from the below calls // might destroy the application before `main`. This shouldn't // be a problem in general. setupMutex(m_GlobControlLock, "GlobControl"); setupMutex(m_IDLock, "ID"); setupMutex(m_InitLock, "Init"); m_pCache = new CCache; } srt::CUDTUnited::~CUDTUnited() { // Call it if it wasn't called already. // This will happen at the end of main() of the application, // when the user didn't call srt_cleanup(). if (m_bGCStatus) { cleanup(); } releaseMutex(m_GlobControlLock); releaseMutex(m_IDLock); releaseMutex(m_InitLock); delete m_pCache; } string srt::CUDTUnited::CONID(SRTSOCKET sock) { if ( sock == 0 ) return ""; std::ostringstream os; os << "@" << sock << ":"; return os.str(); } int srt::CUDTUnited::startup() { ScopedLock gcinit(m_InitLock); if (m_iInstanceCount++ > 0) return 1; // 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 PacketFilter::globalInit(); if (m_bGCStatus) return 1; m_bClosing = false; try { setupMutex(m_GCStopLock, "GCStop"); setupCond(m_GCStopCond, "GCStop"); } catch (...) { return -1; } if (!StartThread(m_GCThread, garbageCollect, this, "SRT:GC")) return -1; m_bGCStatus = true; HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR); return 0; } int srt::CUDTUnited::cleanup() { // IMPORTANT!!! // In this function there must be NO LOGGING AT ALL. This function may // potentially be called from within the global program destructor, and // therefore some of the facilities used by the logging system - including // the default std::cerr object bound to it by default, but also a different // stream that the user's app has bound to it, and which got destroyed // together with already exited main() - may be already deleted when // executing this procedure. ScopedLock gcinit(m_InitLock); if (--m_iInstanceCount > 0) return 0; if (!m_bGCStatus) return 0; m_bClosing = true; // NOTE: we can do relaxed signaling here because // waiting on m_GCStopCond has a 1-second timeout, // after which the m_bClosing flag is cheched, which // is set here above. Worst case secenario, this // pthread_join() call will block for 1 second. CSync::signal_relaxed(m_GCStopCond); m_GCThread.join(); // XXX There's some weird bug here causing this // to hangup on Windows. This might be either something // bigger, or some problem in pthread-win32. As this is // the application cleanup section, this can be temporarily // tolerated with simply exit the application without cleanup, // counting on that the system will take care of it anyway. #ifndef _WIN32 releaseCond(m_GCStopCond); #endif m_bGCStatus = false; // Global destruction code #ifdef _WIN32 WSACleanup(); #endif return 0; } SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) { ScopedLock guard(m_IDLock); int sockval = m_SocketIDGenerator - 1; // First problem: zero-value should be avoided by various reasons. if (sockval <= 0) { // We have a rollover on the socket value, so // definitely we haven't made the Columbus mistake yet. m_SocketIDGenerator = MAX_SOCKET_VAL; } // Check all sockets if any of them has this value. // Socket IDs are begin created this way: // // Initial random // | // | // | // | // ... // The only problem might be if the number rolls over // and reaches the same value from the opposite side. // This is still a valid socket value, but this time // we have to check, which sockets have been used already. if ( sockval == m_SocketIDGenerator_init ) { // Mark that since this point on the checks for // whether the socket ID is in use must be done. m_SocketIDGenerator_init = 0; } // This is when all socket numbers have been already used once. // This may happen after many years of running an application // constantly when the connection breaks and gets restored often. if ( m_SocketIDGenerator_init == 0 ) { int startval = sockval; for (;;) // Roll until an unused value is found { enterCS(m_GlobControlLock); const bool exists = #if ENABLE_EXPERIMENTAL_BONDING for_group ? m_Groups.count(sockval | SRTGROUP_MASK) : #endif m_Sockets.count(sockval); leaveCS(m_GlobControlLock); if (exists) { // The socket value is in use. --sockval; if (sockval <= 0) sockval = MAX_SOCKET_VAL; // Before continuing, check if we haven't rolled back to start again // This is virtually impossible, so just make an RTI error. if (sockval == startval) { // Of course, we don't lack memory, but actually this is so impossible // that a complete memory extinction is much more possible than this. // So treat this rather as a formal fallback for something that "should // never happen". This should make the socket creation functions, from // socket_create and accept, return this error. m_SocketIDGenerator = sockval+1; // so that any next call will cause the same error throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } // try again, if this is a free socket continue; } // No socket found, this ID is free to use m_SocketIDGenerator = sockval; break; } } else { m_SocketIDGenerator = sockval; } // The socket value counter remains with the value rolled // without the group bit set; only the returned value may have // the group bit set. if (for_group) sockval = m_SocketIDGenerator | SRTGROUP_MASK; else sockval = m_SocketIDGenerator; LOGC(smlog.Debug, log << "generateSocketID: " << (for_group ? "(group)" : "") << ": @" << sockval); return sockval; } SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) { // XXX consider using some replacement of std::unique_ptr // so that exceptions will clean up the object without the // need for a dedicated code. CUDTSocket* ns = NULL; try { ns = new CUDTSocket; } catch (...) { delete ns; throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } try { ns->m_SocketID = generateSocketID(); } catch (...) { delete ns; throw; } ns->m_Status = SRTS_INIT; ns->m_ListenSocket = 0; ns->core().m_SocketID = ns->m_SocketID; ns->core().m_pCache = m_pCache; try { HLOGC(smlog.Debug, log << CONID(ns->m_SocketID) << "newSocket: mapping socket " << ns->m_SocketID); // protect the m_Sockets structure. ScopedLock cs(m_GlobControlLock); m_Sockets[ns->m_SocketID] = ns; } catch (...) { //failure and rollback delete ns; ns = NULL; } if (!ns) throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); if (pps) *pps = ns; return ns->m_SocketID; } int srt::CUDTUnited::newConnection(const SRTSOCKET listen, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs, int& w_error, CUDT*& w_acpu) { CUDTSocket* ns = NULL; w_acpu = NULL; w_error = SRT_REJ_IPE; // Can't manage this error through an exception because this is // running in the listener loop. CUDTSocket* ls = locateSocket(listen); if (!ls) { LOGC(cnlog.Error, log << "IPE: newConnection by listener socket id=" << listen << " which DOES NOT EXIST."); return -1; } HLOGC(cnlog.Debug, log << "newConnection: creating new socket after listener @" << listen << " contacted with backlog=" << ls->m_uiBackLog); // if this connection has already been processed if ((ns = locatePeer(peer, w_hs.m_iID, w_hs.m_iISN)) != NULL) { if (ns->core().m_bBroken) { // last connection from the "peer" address has been broken ns->setClosed(); ScopedLock acceptcg(ls->m_AcceptLock); ls->m_QueuedSockets.erase(ns->m_SocketID); } else { // connection already exist, this is a repeated connection request // respond with existing HS information HLOGC(cnlog.Debug, log << "newConnection: located a WORKING peer @" << w_hs.m_iID << " - ADAPTING."); w_hs.m_iISN = ns->core().m_iISN; w_hs.m_iMSS = ns->core().MSS(); w_hs.m_iFlightFlagSize = ns->core().m_config.iFlightFlagSize; w_hs.m_iReqType = URQ_CONCLUSION; w_hs.m_iID = ns->m_SocketID; // Report the original UDT because it will be // required to complete the HS data for conclusion response. w_acpu = &ns->core(); return 0; //except for this situation a new connection should be started } } else { HLOGC(cnlog.Debug, log << "newConnection: NOT located any peer @" << w_hs.m_iID << " - resuming with initial connection."); } // exceeding backlog, refuse the connection request if (ls->m_QueuedSockets.size() >= ls->m_uiBackLog) { w_error = SRT_REJ_BACKLOG; LOGC(cnlog.Note, log << "newConnection: listen backlog=" << ls->m_uiBackLog << " EXCEEDED"); return -1; } try { ns = new CUDTSocket(*ls); // No need to check the peer, this is the address from which the request has come. ns->m_PeerAddr = peer; } catch (...) { w_error = SRT_REJ_RESOURCE; delete ns; LOGC(cnlog.Error, log << "IPE: newConnection: unexpected exception (probably std::bad_alloc)"); return -1; } ns->core().m_RejectReason = SRT_REJ_UNKNOWN; // pre-set a universal value try { ns->m_SocketID = generateSocketID(); } catch (const CUDTException&) { LOGF(cnlog.Fatal, "newConnection: IPE: all sockets occupied? Last gen=%d", m_SocketIDGenerator); // generateSocketID throws exception, which can be naturally handled // when the call is derived from the API call, but here it's called // internally in response to receiving a handshake. It must be handled // here and turned into an erroneous return value. delete ns; return -1; } ns->m_ListenSocket = listen; ns->core().m_SocketID = ns->m_SocketID; ns->m_PeerID = w_hs.m_iID; ns->m_iISN = w_hs.m_iISN; HLOGC(cnlog.Debug, log << "newConnection: DATA: lsnid=" << listen << " id=" << ns->core().m_SocketID << " peerid=" << ns->core().m_PeerID << " ISN=" << ns->m_iISN); int error = 0; bool should_submit_to_accept = true; // Set the error code for all prospective problems below. // It won't be interpreted when result was successful. w_error = SRT_REJ_RESOURCE; // 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. HLOGF(cnlog.Debug, "newConnection: incoming %s, mapping socket %d", peer.str().c_str(), ns->m_SocketID); { ScopedLock cg(m_GlobControlLock); m_Sockets[ns->m_SocketID] = ns; } if (ls->core().m_cbAcceptHook) { if (!ls->core().runAcceptHook(&ns->core(), peer.get(), w_hs, hspkt)) { w_error = ns->core().m_RejectReason; error = 1; goto ERR_ROLLBACK; } } // bind to the same addr of listening socket ns->core().open(); updateListenerMux(ns, ls); ns->core().acceptAndRespond(ls->m_SelfAddr, peer, hspkt, (w_hs)); } catch (...) { // Extract the error that was set in this new failed entity. w_error = ns->core().m_RejectReason; error = 1; goto ERR_ROLLBACK; } ns->m_Status = SRTS_CONNECTED; // copy address information of local node // Precisely, what happens here is: // - Get the IP address and port from the system database ns->core().m_pSndQueue->m_pChannel->getSockAddr((ns->m_SelfAddr)); // - OVERWRITE just the IP address itself by a value taken from piSelfIP // (the family is used exactly as the one taken from what has been returned // by getsockaddr) CIPAddress::pton((ns->m_SelfAddr), ns->core().m_piSelfIP, peer); { // protect the m_PeerRec structure (and group existence) ScopedLock glock (m_GlobControlLock); try { HLOGF(cnlog.Debug, "newConnection: mapping peer %d to that socket (%d)\n", ns->m_PeerID, ns->m_SocketID); m_PeerRec[ns->getPeerSpec()].insert(ns->m_SocketID); } catch (...) { LOGC(cnlog.Error, log << "newConnection: error when mapping peer!"); error = 2; } // The access to m_GroupOf should be also protected, as the group // could be requested deletion in the meantime. This will hold any possible // removal from group and resetting m_GroupOf field. #if ENABLE_EXPERIMENTAL_BONDING if (ns->m_GroupOf) { // XXX this might require another check of group type. // For redundancy group, at least, update the status in the group CUDTGroup* g = ns->m_GroupOf; ScopedLock glock (g->m_GroupLock); if (g->m_bClosing) { error = 1; // "INTERNAL REJECTION" goto ERR_ROLLBACK; } // Check if this is the first socket in the group. // If so, give it up to accept, otherwise just do nothing // The client will be informed about the newly added connection at the // first moment when attempting to get the group status. for (CUDTGroup::gli_t gi = g->m_Group.begin(); gi != g->m_Group.end(); ++gi) { if (gi->laststatus == SRTS_CONNECTED) { HLOGC(cnlog.Debug, log << "Found another connected socket in the group: $" << gi->id << " - socket will be NOT given up for accepting"); should_submit_to_accept = false; break; } } // Update the status in the group so that the next // operation can include the socket in the group operation. CUDTGroup::SocketData* gm = ns->m_GroupMemberData; HLOGC(cnlog.Debug, log << "newConnection(GROUP): Socket @" << ns->m_SocketID << " BELONGS TO $" << g->id() << " - will " << (should_submit_to_accept? "" : "NOT ") << "report in accept"); gm->sndstate = SRT_GST_IDLE; gm->rcvstate = SRT_GST_IDLE; gm->laststatus = SRTS_CONNECTED; if (!g->m_bConnected) { HLOGC(cnlog.Debug, log << "newConnection(GROUP): First socket connected, SETTING GROUP CONNECTED"); g->m_bConnected = true; } // XXX PROLBEM!!! These events are subscribed here so that this is done once, lazily, // but groupwise connections could be accepted from multiple listeners for the same group! // m_listener MUST BE A CONTAINER, NOT POINTER!!! // ALSO: Maybe checking "the same listener" is not necessary as subscruption may be done // multiple times anyway? if (!g->m_listener) { // Newly created group from the listener, which hasn't yet // the listener set. g->m_listener = ls; // Listen on both first connected socket and continued sockets. // This might help with jump-over situations, and in regular continued // sockets the IN event won't be reported anyway. int listener_modes = SRT_EPOLL_ACCEPT | SRT_EPOLL_UPDATE; epoll_add_usock_INTERNAL(g->m_RcvEID, ls, &listener_modes); // This listening should be done always when a first connected socket // appears as accepted off the listener. This is for the sake of swait() calls // inside the group receiving and sending functions so that they get // interrupted when a new socket is connected. } // Add also per-direction subscription for the about-to-be-accepted socket. // Both first accepted socket that makes the group-accept and every next // socket that adds a new link. int read_modes = SRT_EPOLL_IN | SRT_EPOLL_ERR; int write_modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; epoll_add_usock_INTERNAL(g->m_RcvEID, ns, &read_modes); epoll_add_usock_INTERNAL(g->m_SndEID, ns, &write_modes); // With app reader, do not set groupPacketArrival (block the // provider array feature completely for now). /* SETUP HERE IF NEEDED ns->core().m_cbPacketArrival.set(ns->m_pUDT, &CUDT::groupPacketArrival); */ } else { HLOGC(cnlog.Debug, log << "newConnection: Socket @" << ns->m_SocketID << " is not in a group"); } #endif } if (should_submit_to_accept) { enterCS(ls->m_AcceptLock); try { ls->m_QueuedSockets.insert(ns->m_SocketID); } catch (...) { LOGC(cnlog.Error, log << "newConnection: error when queuing socket!"); error = 3; } leaveCS(ls->m_AcceptLock); HLOGC(cnlog.Debug, log << "ACCEPT: new socket @" << ns->m_SocketID << " submitted for acceptance"); // acknowledge users waiting for new connections on the listening socket m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, true); CGlobEvent::triggerEvent(); // XXX the exact value of 'error' is ignored if (error > 0) { goto ERR_ROLLBACK; } // wake up a waiting accept() call CSync::lock_signal(ls->m_AcceptCond, ls->m_AcceptLock); } else { HLOGC(cnlog.Debug, log << "ACCEPT: new socket @" << ns->m_SocketID << " NOT submitted to acceptance, another socket in the group is already connected"); // acknowledge INTERNAL users waiting for new connections on the listening socket // that are reported when a new socket is connected within an already connected group. m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_UPDATE, true); CGlobEvent::triggerEvent(); } ERR_ROLLBACK: // XXX the exact value of 'error' is ignored if (error > 0) { #if ENABLE_LOGGING static const char* why [] = { "UNKNOWN ERROR", "INTERNAL REJECTION", "IPE when mapping a socket", "IPE when inserting a socket" }; LOGC(cnlog.Warn, log << CONID(ns->m_SocketID) << "newConnection: connection rejected due to: " << why[error] << " - " << RequestTypeStr(URQFailure(w_error))); #endif SRTSOCKET id = ns->m_SocketID; ns->core().closeInternal(); ns->setClosed(); // The mapped socket should be now unmapped to preserve the situation that // was in the original UDT code. // In SRT additionally the acceptAndRespond() function (it was called probably // connect() in UDT code) may fail, in which case this socket should not be // further processed and should be removed. { ScopedLock cg(m_GlobControlLock); #if ENABLE_EXPERIMENTAL_BONDING if (ns->m_GroupOf) { HLOGC(smlog.Debug, log << "@" << ns->m_SocketID << " IS MEMBER OF $" << ns->m_GroupOf->id() << " - REMOVING FROM GROUP"); ns->removeFromGroup(true); } #endif m_Sockets.erase(id); m_ClosedSockets[id] = ns; } return -1; } return 1; } // static forwarder int srt::CUDT::installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { return s_UDTUnited.installAcceptHook(lsn, hook, opaq); } int srt::CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { try { CUDTSocket* s = locateSocket(lsn, ERH_THROW); s->core().installAcceptHook(hook, opaq); } catch (CUDTException& e) { SetThreadLocalError(e); return SRT_ERROR; } return 0; } int srt::CUDT::installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) { return s_UDTUnited.installConnectHook(lsn, hook, opaq); } int srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_fn* hook, void* opaq) { try { #if ENABLE_EXPERIMENTAL_BONDING if (u & SRTGROUP_MASK) { GroupKeeper k (*this, u, ERH_THROW); k.group->installConnectHook(hook, opaq); return 0; } #endif CUDTSocket* s = locateSocket(u, ERH_THROW); s->core().installConnectHook(hook, opaq); } catch (CUDTException& e) { SetThreadLocalError(e); return SRT_ERROR; } return 0; } SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u) { // protects the m_Sockets structure ScopedLock cg(m_GlobControlLock); sockets_t::const_iterator i = m_Sockets.find(u); if (i == m_Sockets.end()) { if (m_ClosedSockets.find(u) != m_ClosedSockets.end()) return SRTS_CLOSED; return SRTS_NONEXIST; } return i->second->getStatus(); } int srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) { ScopedLock cg(s->m_ControlLock); // cannot bind a socket more than once if (s->m_Status != SRTS_INIT) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); s->core().open(); updateMux(s, name); s->m_Status = SRTS_OPENED; // copy address information of local node s->core().m_pSndQueue->m_pChannel->getSockAddr((s->m_SelfAddr)); return 0; } int srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) { ScopedLock cg(s->m_ControlLock); // cannot bind a socket more than once if (s->m_Status != SRTS_INIT) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); sockaddr_any name; socklen_t namelen = sizeof name; // max of inet and inet6 // This will preset the sa_family as well; the namelen is given simply large // enough for any family here. if (::getsockname(udpsock, &name.sa, &namelen) == -1) throw CUDTException(MJ_NOTSUP, MN_INVAL); // Successfully extracted, so update the size name.len = namelen; s->core().open(); updateMux(s, name, &udpsock); s->m_Status = SRTS_OPENED; // copy address information of local node s->core().m_pSndQueue->m_pChannel->getSockAddr(s->m_SelfAddr); return 0; } int srt::CUDTUnited::listen(const SRTSOCKET 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 = locateSocket(u); if (!s) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); ScopedLock 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 == SRTS_LISTENING) return 0; // a socket can listen only if is in OPENED status if (s->m_Status != SRTS_OPENED) throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0); // [[using assert(s->m_Status == OPENED)]]; // listen is not supported in rendezvous connection setup if (s->core().m_config.bRendezvous) throw CUDTException(MJ_NOTSUP, MN_ISRENDEZVOUS, 0); s->m_uiBackLog = backlog; // [[using assert(s->m_Status == OPENED)]]; // (still, unchanged) s->core().setListenState(); // propagates CUDTException, // if thrown, remains in OPENED state if so. s->m_Status = SRTS_LISTENING; return 0; } SRTSOCKET srt::CUDTUnited::accept_bond(const SRTSOCKET listeners [], int lsize, int64_t msTimeOut) { CEPollDesc* ed = 0; int eid = m_EPoll.create(&ed); // Destroy it at return - this function can be interrupted // by an exception. struct AtReturn { int eid; CUDTUnited* that; AtReturn(CUDTUnited* t, int e): eid(e), that(t) {} ~AtReturn() { that->m_EPoll.release(eid); } } l_ar(this, eid); // Subscribe all of listeners for accept int events = SRT_EPOLL_ACCEPT; for (int i = 0; i < lsize; ++i) { srt_epoll_add_usock(eid, listeners[i], &events); } CEPoll::fmap_t st; m_EPoll.swait(*ed, (st), msTimeOut, true); if (st.empty()) { // Sanity check throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); } // Theoretically we can have a situation that more than one // listener is ready for accept. In this case simply get // only the first found. int lsn = st.begin()->first; sockaddr_storage dummy; int outlen = sizeof dummy; return accept(lsn, ((sockaddr*)&dummy), (&outlen)); } SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int* pw_addrlen) { if (pw_addr && !pw_addrlen) { LOGC(cnlog.Error, log << "srt_accept: provided address, but address length parameter is missing"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } CUDTSocket* ls = locateSocket(listen); if (ls == NULL) { LOGC(cnlog.Error, log << "srt_accept: invalid listener socket ID value: " << listen); throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); } // the "listen" socket must be in LISTENING status if (ls->m_Status != SRTS_LISTENING) { LOGC(cnlog.Error, log << "srt_accept: socket @" << listen << " is not in listening state (forgot srt_listen?)"); throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0); } // no "accept" in rendezvous connection setup if (ls->core().m_config.bRendezvous) { LOGC(cnlog.Fatal, log << "CUDTUnited::accept: RENDEZVOUS flag passed through check in srt_listen when it set listen state"); // This problem should never happen because `srt_listen` function should have // checked this situation before and not set listen state in result. // Inform the user about the invalid state in the universal way. throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0); } SRTSOCKET u = CUDT::INVALID_SOCK; bool accepted = false; // !!only one conection can be set up each time!! while (!accepted) { UniqueLock accept_lock(ls->m_AcceptLock); CSync accept_sync(ls->m_AcceptCond, accept_lock); if ((ls->m_Status != SRTS_LISTENING) || ls->core().m_bBroken) { // This socket has been closed. accepted = true; } else if (ls->m_QueuedSockets.size() > 0) { set::iterator b = ls->m_QueuedSockets.begin(); u = *b; ls->m_QueuedSockets.erase(b); accepted = true; } else if (!ls->core().m_config.bSynRecving) { accepted = true; } if (!accepted && (ls->m_Status == SRTS_LISTENING)) accept_sync.wait(); if (ls->m_QueuedSockets.empty()) m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, false); } if (u == CUDT::INVALID_SOCK) { // non-blocking receiving, no connection available if (!ls->core().m_config.bSynRecving) { LOGC(cnlog.Error, log << "srt_accept: no pending connection available at the moment"); throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } LOGC(cnlog.Error, log << "srt_accept: listener socket @" << listen << " is already closed"); // listening socket is closed throw CUDTException(MJ_SETUP, MN_CLOSED, 0); } CUDTSocket* s = locateSocket(u); if (s == NULL) { LOGC(cnlog.Error, log << "srt_accept: pending connection has unexpectedly closed"); throw CUDTException(MJ_SETUP, MN_CLOSED, 0); } // Set properly the SRTO_GROUPCONNECT flag s->core().m_config.iGroupConnect = 0; // Check if LISTENER has the SRTO_GROUPCONNECT flag set, // and the already accepted socket has successfully joined // the mirror group. If so, RETURN THE GROUP ID, not the socket ID. #if ENABLE_EXPERIMENTAL_BONDING if (ls->core().m_config.iGroupConnect == 1 && s->m_GroupOf) { // Put a lock to protect the group against accidental deletion // in the meantime. ScopedLock glock (m_GlobControlLock); // Check again; it's unlikely to happen, but // it's a theoretically possible scenario if (s->m_GroupOf) { u = s->m_GroupOf->m_GroupID; s->core().m_config.iGroupConnect = 1; // should be derived from ls, but make sure // Mark the beginning of the connection at the moment // when the group ID is returned to the app caller s->m_GroupOf->m_stats.tsLastSampleTime = steady_clock::now(); } else { LOGC(smlog.Error, log << "accept: IPE: socket's group deleted in the meantime of accept process???"); } } #endif ScopedLock cg(s->m_ControlLock); if (pw_addr != NULL && pw_addrlen != NULL) { // Check if the length of the buffer to fill the name in // was large enough. const int len = s->m_PeerAddr.size(); if (*pw_addrlen < len) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); memcpy((pw_addr), &s->m_PeerAddr, len); *pw_addrlen = len; } return u; } int srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int namelen) { // Here both srcname and tarname must be specified if (!srcname || !tarname || size_t(namelen) < sizeof (sockaddr_in)) { LOGC(aclog.Error, log << "connect(with source): invalid call: srcname=" << srcname << " tarname=" << tarname << " namelen=" << namelen); throw CUDTException(MJ_NOTSUP, MN_INVAL); } sockaddr_any source_addr(srcname, namelen); if (source_addr.len == 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); sockaddr_any target_addr(tarname, namelen); if (target_addr.len == 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #if ENABLE_EXPERIMENTAL_BONDING // Check affiliation of the socket. It's now allowed for it to be // a group or socket. For a group, add automatically a socket to // the group. if (u & SRTGROUP_MASK) { GroupKeeper k (*this, u, ERH_THROW); // Note: forced_isn is ignored when connecting a group. // The group manages the ISN by itself ALWAYS, that is, // it's generated anew for the very first socket, and then // derived by all sockets in the group. SRT_SOCKGROUPCONFIG gd[1] = { srt_prepare_endpoint(srcname, tarname, namelen) }; // When connecting to exactly one target, only this very target // can be returned as a socket, so rewritten back array can be ignored. return singleMemberConnect(k.group, gd); } #endif CUDTSocket* s = locateSocket(u); if (s == NULL) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); // For a single socket, just do bind, then connect bind(s, source_addr); return connectIn(s, target_addr, SRT_SEQNO_NONE); } int srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) { sockaddr_any target_addr(name, namelen); if (target_addr.len == 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #if ENABLE_EXPERIMENTAL_BONDING // Check affiliation of the socket. It's now allowed for it to be // a group or socket. For a group, add automatically a socket to // the group. if (u & SRTGROUP_MASK) { GroupKeeper k (*this, u, ERH_THROW); // Note: forced_isn is ignored when connecting a group. // The group manages the ISN by itself ALWAYS, that is, // it's generated anew for the very first socket, and then // derived by all sockets in the group. SRT_SOCKGROUPCONFIG gd[1] = { srt_prepare_endpoint(NULL, name, namelen) }; return singleMemberConnect(k.group, gd); } #endif CUDTSocket* s = locateSocket(u); if (!s) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); return connectIn(s, target_addr, forced_isn); } #if ENABLE_EXPERIMENTAL_BONDING int srt::CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* gd) { int gstat = groupConnect(pg, gd, 1); if (gstat == -1) { // We have only one element here, so refer to it. // Sanity check if (gd->errorcode == SRT_SUCCESS) gd->errorcode = SRT_EINVPARAM; CodeMajor mj = CodeMajor(gd->errorcode / 1000); CodeMinor mn = CodeMinor(gd->errorcode % 1000); return CUDT::APIError(mj, mn); } return gstat; } // [[using assert(pg->m_iBusy > 0)]] int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, int arraysize) { CUDTGroup& g = *pg; SRT_ASSERT(g.m_iBusy > 0); // The group must be managed to use srt_connect on it, // as it must create particular socket automatically. // Non-managed groups can't be "connected" - at best you can connect // every socket individually. if (!g.managed()) throw CUDTException(MJ_NOTSUP, MN_INVAL); // Check and report errors on data brought in by srt_prepare_endpoint, // as the latter function has no possibility to report errors. for (int tii = 0; tii < arraysize; ++tii) { if (targets[tii].srcaddr.ss_family != targets[tii].peeraddr.ss_family) { LOGC(aclog.Error, log << "srt_connect/group: family differs on source and target address"); throw CUDTException(MJ_NOTSUP, MN_INVAL); } if (targets[tii].weight > CUDT::MAX_WEIGHT) { LOGC(aclog.Error, log << "srt_connect/group: weight value must be between 0 and " << (+CUDT::MAX_WEIGHT)); throw CUDTException(MJ_NOTSUP, MN_INVAL); } } // If the open state switched to OPENED, the blocking mode // must make it wait for connecting it. Doing connect when the // group is already OPENED returns immediately, regardless if the // connection is going to later succeed or fail (this will be // known in the group state information). bool block_new_opened = !g.m_bOpened && g.m_bSynRecving; const bool was_empty = g.groupEmpty(); // In case the group was retried connection, clear first all epoll readiness. const int ncleared = m_EPoll.update_events(g.id(), g.m_sPollID, SRT_EPOLL_ERR, false); if (was_empty || ncleared) { HLOGC(aclog.Debug, log << "srt_connect/group: clearing IN/OUT because was_empty=" << was_empty << " || ncleared=" << ncleared); // IN/OUT only in case when the group is empty, otherwise it would // clear out correct readiness resulting from earlier calls. // This also should happen if ERR flag was set, as IN and OUT could be set, too. m_EPoll.update_events(g.id(), g.m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT, false); } SRTSOCKET retval = -1; int eid = -1; int connect_modes = SRT_EPOLL_CONNECT | SRT_EPOLL_ERR; if (block_new_opened) { // Create this eid only to block-wait for the first // connection. eid = srt_epoll_create(); } // Use private map to avoid searching in the // overall map. map spawned; HLOGC(aclog.Debug, log << "groupConnect: will connect " << arraysize << " links and " << (block_new_opened ? "BLOCK until any is ready" : "leave the process in background")); for (int tii = 0; tii < arraysize; ++tii) { sockaddr_any target_addr(targets[tii].peeraddr); sockaddr_any source_addr(targets[tii].srcaddr); SRTSOCKET& sid_rloc = targets[tii].id; int& erc_rloc = targets[tii].errorcode; erc_rloc = SRT_SUCCESS; // preinitialized HLOGC(aclog.Debug, log << "groupConnect: taking on " << sockaddr_any(targets[tii].peeraddr).str()); CUDTSocket* ns = 0; // NOTE: After calling newSocket, the socket is mapped into m_Sockets. // It must be MANUALLY removed from this list in case we need it deleted. SRTSOCKET sid = newSocket(&ns); if (pg->m_cbConnectHook) { // Derive the connect hook by the socket, if set on the group ns->core().m_cbConnectHook = pg->m_cbConnectHook; } SRT_SocketOptionObject* config = targets[tii].config; // XXX Support non-blocking mode: // If the group has nonblocking set for connect (SNDSYN), // then it must set so on the socket. Then, the connection // process is asynchronous. The socket appears first as // GST_PENDING state, and only after the socket becomes // connected does its status in the group turn into GST_IDLE. // Set all options that were requested by the options set on a group // prior to connecting. string error_reason SRT_ATR_UNUSED; try { for (size_t i = 0; i < g.m_config.size(); ++i) { HLOGC(aclog.Debug, log << "groupConnect: OPTION @" << sid << " #" << g.m_config[i].so); error_reason = "setting group-derived option: #" + Sprint(g.m_config[i].so); ns->core().setOpt(g.m_config[i].so, &g.m_config[i].value[0], (int) g.m_config[i].value.size()); } // Do not try to set a user option if failed already. if (config) { error_reason = "user option"; ns->core().applyMemberConfigObject(*config); } error_reason = "bound address"; // We got it. Bind the socket, if the source address was set if (!source_addr.empty()) bind(ns, source_addr); } catch (CUDTException& e) { // Just notify the problem, but the loop must continue. // Set the original error as reported. targets[tii].errorcode = e.getErrorCode(); LOGC(aclog.Error, log << "srt_connect_group: failed to set " << error_reason); } catch (...) { // Set the general EINVPARAM - this error should never happen LOGC(aclog.Error, log << "IPE: CUDT::setOpt reported unknown exception"); targets[tii].errorcode = SRT_EINVPARAM; } // Add socket to the group. // Do it after setting all stored options, as some of them may // influence some group data. srt::groups::SocketData data = srt::groups::prepareSocketData(ns); if (targets[tii].token != -1) { // Reuse the token, if specified by the caller data.token = targets[tii].token; } else { // Otherwise generate and write back the token data.token = CUDTGroup::genToken(); targets[tii].token = data.token; } { ScopedLock cs(m_GlobControlLock); if (m_Sockets.count(sid) == 0) { HLOGC(aclog.Debug, log << "srt_connect_group: socket @" << sid << " deleted in process"); // Someone deleted the socket in the meantime? // Unlikely, but possible in theory. // Don't delete anyhting - it's alreay done. continue; } // There's nothing wrong with preparing the data first // even if this happens for nothing. But now, under the lock // and after checking that the socket still exists, check now // if this succeeded, and then also if the group is still usable. // The group will surely exist because it's set busy, until the // end of this function. But it might be simultaneously requested closed. bool proceed = true; if (targets[tii].errorcode != SRT_SUCCESS) { HLOGC(aclog.Debug, log << "srt_connect_group: not processing @" << sid << " due to error in setting options"); proceed = false; } if (g.m_bClosing) { HLOGC(aclog.Debug, log << "srt_connect_group: not processing @" << sid << " due to CLOSED GROUP $" << g.m_GroupID); proceed = false; } if (proceed) { CUDTGroup::SocketData* f = g.add(data); ns->m_GroupMemberData = f; ns->m_GroupOf = &g; f->weight = targets[tii].weight; LOGC(aclog.Note, log << "srt_connect_group: socket @" << sid << " added to group $" << g.m_GroupID); } else { targets[tii].id = CUDT::INVALID_SOCK; delete ns; m_Sockets.erase(sid); // If failed to set options, then do not continue // neither with binding, nor with connecting. continue; } } // XXX This should be reenabled later, this should // be probably still in use to exchange information about // packets assymetrically lost. But for no other purpose. /* ns->core().m_cbPacketArrival.set(ns->m_pUDT, &CUDT::groupPacketArrival); */ int isn = g.currentSchedSequence(); // Don't synchronize ISN in case of synch on msgno. Every link // may send their own payloads independently. if (g.synconmsgno()) { HLOGC(aclog.Debug, log << "groupConnect: NOT synchronizing sequence numbers: will sync on msgno"); isn = -1; } // Set it the groupconnect option, as all in-group sockets should have. ns->core().m_config.iGroupConnect = 1; // Every group member will have always nonblocking // (this implies also non-blocking connect/accept). // The group facility functions will block when necessary // using epoll_wait. ns->core().m_config.bSynRecving = false; ns->core().m_config.bSynSending = false; HLOGC(aclog.Debug, log << "groupConnect: NOTIFIED AS PENDING @" << sid << " both read and write"); // If this socket is not to block the current connect process, // it may still be needed for the further check if the redundant // connection succeeded or failed and whether the new socket is // ready to use or needs to be closed. epoll_add_usock_INTERNAL(g.m_SndEID, ns, &connect_modes); epoll_add_usock_INTERNAL(g.m_RcvEID, ns, &connect_modes); // Adding a socket on which we need to block to BOTH these tracking EIDs // and the blocker EID. We'll simply remove from them later all sockets that // got connected state or were broken. if (block_new_opened) { HLOGC(aclog.Debug, log << "groupConnect: WILL BLOCK on @" << sid << " until connected"); epoll_add_usock_INTERNAL(eid, ns, &connect_modes); } // And connect try { HLOGC(aclog.Debug, log << "groupConnect: connecting a new socket with ISN=" << isn); connectIn(ns, target_addr, isn); } catch (const CUDTException& e) { LOGC(aclog.Error, log << "groupConnect: socket @" << sid << " in group " << pg->id() << " failed to connect"); // We know it does belong to a group. // Remove it first because this involves a mutex, and we want // to avoid locking more than one mutex at a time. erc_rloc = e.getErrorCode(); targets[tii].errorcode = e.getErrorCode(); targets[tii].id = CUDT::INVALID_SOCK; ScopedLock cl (m_GlobControlLock); ns->removeFromGroup(false); m_Sockets.erase(ns->m_SocketID); // Intercept to delete the socket on failure. delete ns; continue; } catch (...) { LOGC(aclog.Fatal, log << "groupConnect: IPE: UNKNOWN EXCEPTION from connectIn"); targets[tii].errorcode = SRT_ESYSOBJ; targets[tii].id = CUDT::INVALID_SOCK; ScopedLock cl (m_GlobControlLock); ns->removeFromGroup(false); m_Sockets.erase(ns->m_SocketID); // Intercept to delete the socket on failure. delete ns; // Do not use original exception, it may crash off a C API. throw CUDTException(MJ_SYSTEMRES, MN_OBJECT); } SRT_SOCKSTATUS st; { ScopedLock grd (ns->m_ControlLock); st = ns->getStatus(); } { // NOTE: Not applying m_GlobControlLock because the group is now // set busy, so it won't be deleted, even if it was requested to be closed. ScopedLock grd (g.m_GroupLock); if (!ns->m_GroupOf) { // The situation could get changed between the unlock and lock of m_GroupLock. // This must be checked again. // If a socket has been removed from group, it means that some other thread is // currently trying to delete the socket. Therefore it doesn't have, and even shouldn't, // be deleted here. Just exit with error report. LOGC(aclog.Error, log << "groupConnect: self-created member socket deleted during process, SKIPPING."); // Do not report the error from here, just ignore this socket. continue; } // If m_GroupOf is not NULL, the m_IncludedIter is still valid. CUDTGroup::SocketData* f = ns->m_GroupMemberData; // Now under a group lock, we need to make sure the group isn't being closed // in order not to add a socket to a dead group. if (g.m_bClosing) { LOGC(aclog.Error, log << "groupConnect: group deleted while connecting; breaking the process"); // Set the status as pending so that the socket is taken care of later. // Note that all earlier sockets that were processed in this loop were either // set BROKEN or PENDING. f->sndstate = SRT_GST_PENDING; f->rcvstate = SRT_GST_PENDING; retval = -1; break; } HLOGC(aclog.Debug, log << "groupConnect: @" << sid << " connection successful, setting group OPEN (was " << (g.m_bOpened ? "ALREADY" : "NOT") << "), will " << (block_new_opened ? "" : "NOT ") << "block the connect call, status:" << SockStatusStr(st)); // XXX OPEN OR CONNECTED? // BLOCK IF NOT OPEN OR BLOCK IF NOT CONNECTED? // // What happens to blocking when there are 2 connections // pending, about to be broken, and srt_connect() is called again? // SHOULD BLOCK the latter, even though is OPEN. // Or, OPEN should be removed from here and srt_connect(_group) // should block always if the group doesn't have neither 1 conencted link g.m_bOpened = true; g.m_stats.tsLastSampleTime = steady_clock::now(); f->laststatus = st; // Check the socket status and update it. // Turn the group state of the socket to IDLE only if // connection is established or in progress f->agent = source_addr; f->peer = target_addr; if (st >= SRTS_BROKEN) { f->sndstate = SRT_GST_BROKEN; f->rcvstate = SRT_GST_BROKEN; epoll_remove_socket_INTERNAL(g.m_SndEID, ns); epoll_remove_socket_INTERNAL(g.m_RcvEID, ns); } else { f->sndstate = SRT_GST_PENDING; f->rcvstate = SRT_GST_PENDING; spawned[sid] = ns; sid_rloc = sid; erc_rloc = 0; retval = sid; } } } if (retval == -1) { HLOGC(aclog.Debug, log << "groupConnect: none succeeded as background-spawn, exit with error"); block_new_opened = false; // Avoid executing further while loop } vector broken; while (block_new_opened) { if (spawned.empty()) { // All were removed due to errors. retval = -1; break; } HLOGC(aclog.Debug, log << "groupConnect: first connection, applying EPOLL WAITING."); int len = (int) spawned.size(); vector ready(spawned.size()); const int estat = srt_epoll_wait(eid, NULL, NULL, // IN/ACCEPT &ready[0], &len, // OUT/CONNECT -1, // indefinitely (FIXME Check if it needs to REGARD CONNECTION TIMEOUT!) NULL, NULL, NULL, NULL ); // Sanity check. Shouldn't happen if subs are in sync with spawned. if (estat == -1) { #if ENABLE_LOGGING CUDTException& x = CUDT::getlasterror(); if (x.getErrorCode() != SRT_EPOLLEMPTY) { LOGC(aclog.Error, log << "groupConnect: srt_epoll_wait failed not because empty, unexpected IPE:" << x.getErrorMessage()); } #endif HLOGC(aclog.Debug, log << "groupConnect: srt_epoll_wait failed - breaking the wait loop"); retval = -1; break; } // At the moment when you are going to work with real sockets, // lock the groups so that no one messes up with something here // in the meantime. ScopedLock lock (*g.exp_groupLock()); // NOTE: UNDER m_GroupLock, NO API FUNCTION CALLS DARE TO HAPPEN BELOW! // Check first if a socket wasn't closed in the meantime. It will be // automatically removed from all EIDs, but there's no sense in keeping // them in 'spawned' map. for (map::iterator y = spawned.begin(); y != spawned.end(); ++y) { SRTSOCKET sid = y->first; if (y->second->getStatus() >= SRTS_BROKEN) { HLOGC(aclog.Debug, log << "groupConnect: Socket @" << sid << " got BROKEN in the meantine during the check, remove from candidates"); // Remove from spawned and try again broken.push_back(sid); epoll_remove_socket_INTERNAL(eid, y->second); epoll_remove_socket_INTERNAL(g.m_SndEID, y->second); epoll_remove_socket_INTERNAL(g.m_RcvEID, y->second); } } // Remove them outside the loop because this can't be done // while iterating over the same container. for (size_t i = 0; i < broken.size(); ++i) spawned.erase(broken[i]); // Check the sockets if they were reported due // to have connected or due to have failed. // Distill successful ones. If distilled nothing, return -1. // If not all sockets were reported in this instance, repeat // the call until you get information about all of them. for (int i = 0; i < len; ++i) { map::iterator x = spawned.find(ready[i]); if (x == spawned.end()) { // Might be removed above - ignore it. continue; } SRTSOCKET sid = x->first; CUDTSocket* s = x->second; // Check status. If failed, remove from spawned // and try again. SRT_SOCKSTATUS st = s->getStatus(); if (st >= SRTS_BROKEN) { HLOGC(aclog.Debug, log << "groupConnect: Socket @" << sid << " got BROKEN during background connect, remove & TRY AGAIN"); // Remove from spawned and try again if (spawned.erase(sid)) broken.push_back(sid); epoll_remove_socket_INTERNAL(eid, s); epoll_remove_socket_INTERNAL(g.m_SndEID, s); epoll_remove_socket_INTERNAL(g.m_RcvEID, s); continue; } if (st == SRTS_CONNECTED) { HLOGC(aclog.Debug, log << "groupConnect: Socket @" << sid << " got CONNECTED as first in the group - reporting"); retval = sid; g.m_bConnected = true; block_new_opened = false; // Interrupt also rolling epoll (outer loop) // Remove this socket from SND EID because it doesn't need to // be connection-tracked anymore. Don't remove from the RCV EID // however because RCV procedure relies on epoll also for reading // and when found this socket connected it will "upgrade" it to // read-ready tracking only. epoll_remove_socket_INTERNAL(g.m_SndEID, s); break; } // Spurious? HLOGC(aclog.Debug, log << "groupConnect: Socket @" << sid << " got spurious wakeup in " << SockStatusStr(st) << " TRY AGAIN"); } // END of m_GroupLock CS - you can safely use API functions now. } // Finished, delete epoll. if (eid != -1) { HLOGC(aclog.Debug, log << "connect FIRST IN THE GROUP finished, removing E" << eid); srt_epoll_release(eid); } for (vector::iterator b = broken.begin(); b != broken.end(); ++b) { CUDTSocket* s = locateSocket(*b, ERH_RETURN); if (!s) continue; // This will also automatically remove it from the group and all eids close(s); } // There's no possibility to report a problem on every connection // separately in case when every single connection has failed. What // is more interesting, it's only a matter of luck that all connections // fail at exactly the same time. OTOH if all are to fail, this // function will still be polling sockets to determine the last man // standing. Each one could, however, break by a different reason, // for example, one by timeout, another by wrong passphrase. Check // the `errorcode` field to determine the reaon for particular link. if (retval == -1) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); return retval; } #endif int srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, int32_t forced_isn) { ScopedLock cg(s->m_ControlLock); // a socket can "connect" only if it is in the following states: // - OPENED: assume the socket binding parameters are configured // - INIT: configure binding parameters here // - any other (meaning, already connected): report error if (s->m_Status == SRTS_INIT) { if (s->core().m_config.bRendezvous) throw CUDTException(MJ_NOTSUP, MN_ISRENDUNBOUND, 0); // If bind() was done first on this socket, then the // socket will not perform this step. This actually does the // same thing as bind() does, just with empty address so that // the binding parameters are autoselected. s->core().open(); sockaddr_any autoselect_sa (target_addr.family()); // This will create such a sockaddr_any that // will return true from empty(). updateMux(s, autoselect_sa); // <<---- updateMux // -> C(Snd|Rcv)Queue::init // -> pthread_create(...C(Snd|Rcv)Queue::worker...) s->m_Status = SRTS_OPENED; } else { if (s->m_Status != SRTS_OPENED) throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); // status = SRTS_OPENED, so family should be known already. if (target_addr.family() != s->m_SelfAddr.family()) { LOGP(cnlog.Error, "srt_connect: socket is bound to a different family than target address"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 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 = SRTS_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 */ try { // record peer address s->m_PeerAddr = target_addr; s->core().startConnect(target_addr, forced_isn); } catch (CUDTException& e) // Interceptor, just to change the state. { s->m_Status = SRTS_OPENED; throw e; } // ScopedLock destructor will delete cg and unlock s->m_ControlLock return 0; } int srt::CUDTUnited::close(const SRTSOCKET u) { #if ENABLE_EXPERIMENTAL_BONDING if (u & SRTGROUP_MASK) { GroupKeeper k (*this, u, ERH_THROW); k.group->close(); deleteGroup(k.group); return 0; } #endif CUDTSocket* s = locateSocket(u); if (!s) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); return close(s); } #if ENABLE_EXPERIMENTAL_BONDING void srt::CUDTUnited::deleteGroup(CUDTGroup* g) { using srt_logging::gmlog; srt::sync::ScopedLock cg (m_GlobControlLock); return deleteGroup_LOCKED(g); } // [[using locked(m_GlobControlLock)]] void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) { SRT_ASSERT(g->groupEmpty()); // After that the group is no longer findable by GroupKeeper m_Groups.erase(g->m_GroupID); m_ClosedGroups[g->m_GroupID] = g; // Paranoid check: since the group is in m_ClosedGroups // it may potentially be deleted. Make sure no socket points // to it. Actually all sockets should have been already removed // from the group container, so if any does, it's invalid. for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++ i) { CUDTSocket* s = i->second; if (s->m_GroupOf == g) { HLOGC(smlog.Debug, log << "deleteGroup: IPE: existing @" << s->m_SocketID << " points to a dead group!"); s->m_GroupOf = NULL; s->m_GroupMemberData = NULL; } } // Just in case, do it in closed sockets, too, although this should be // always done before moving to it. for (sockets_t::iterator i = m_ClosedSockets.begin(); i != m_ClosedSockets.end(); ++ i) { CUDTSocket* s = i->second; if (s->m_GroupOf == g) { HLOGC(smlog.Debug, log << "deleteGroup: IPE: closed @" << s->m_SocketID << " points to a dead group!"); s->m_GroupOf = NULL; s->m_GroupMemberData = NULL; } } } #endif int srt::CUDTUnited::close(CUDTSocket* s) { HLOGC(smlog.Debug, log << s->core().CONID() << " CLOSE. Acquiring control lock"); ScopedLock socket_cg(s->m_ControlLock); HLOGC(smlog.Debug, log << s->core().CONID() << " CLOSING (removing from listening, closing CUDT)"); const bool synch_close_snd = s->core().m_config.bSynSending; SRTSOCKET u = s->m_SocketID; if (s->m_Status == SRTS_LISTENING) { if (s->core().m_bBroken) return 0; s->m_tsClosureTimeStamp = steady_clock::now(); s->core().m_bBroken = true; // Change towards original UDT: // Leave all the closing activities for garbageCollect to happen, // however remove the listener from the RcvQueue IMMEDIATELY. // Even though garbageCollect would eventually remove the listener // as well, there would be some time interval between now and the // moment when it's done, and during this time the application will // be unable to bind to this port that the about-to-delete listener // is currently occupying (due to blocked slot in the RcvQueue). HLOGC(smlog.Debug, log << s->core().CONID() << " CLOSING (removing listener immediately)"); s->core().notListening(); // broadcast all "accept" waiting CSync::lock_broadcast(s->m_AcceptCond, s->m_AcceptLock); } else { // Note: this call may be done on a socket that hasn't finished // sending all packets scheduled for sending, which means, this call // may block INDEFINITELY. As long as it's acceptable to block the // call to srt_close(), and all functions in all threads where this // very socket is used, this shall not block the central database. s->core().closeInternal(); // synchronize with garbage collection. HLOGC(smlog.Debug, log << "@" << u << "U::close done. GLOBAL CLOSE: " << s->core().CONID() << ". Acquiring GLOBAL control lock"); ScopedLock manager_cg(m_GlobControlLock); // since "s" is located before m_GlobControlLock, locate it again in case // it became invalid // XXX This is very weird; if we state that the CUDTSocket object // could not be deleted between locks, then definitely it couldn't // also change the pointer value. There's no other reason for getting // this iterator but to obtain the 's' pointer, which is impossible to // be different than previous 's' (m_Sockets is a map that stores pointers // transparently). This iterator isn't even later used to delete the socket // from the container, though it would be more efficient. // FURTHER RESEARCH REQUIRED. sockets_t::iterator i = m_Sockets.find(u); if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED)) { HLOGC(smlog.Debug, log << "@" << u << "U::close: NOT AN ACTIVE SOCKET, returning."); return 0; } s = i->second; s->setClosed(); #if ENABLE_EXPERIMENTAL_BONDING if (s->m_GroupOf) { HLOGC(smlog.Debug, log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP"); s->removeFromGroup(true); } #endif m_Sockets.erase(s->m_SocketID); m_ClosedSockets[s->m_SocketID] = s; HLOGC(smlog.Debug, log << "@" << u << "U::close: Socket MOVED TO CLOSED for collecting later."); CGlobEvent::triggerEvent(); } HLOGC(smlog.Debug, log << "@" << u << ": GLOBAL: CLOSING DONE"); // Check if the ID is still in closed sockets before you access it // (the last triggerEvent could have deleted it). if ( synch_close_snd ) { #if SRT_ENABLE_CLOSE_SYNCH HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: sync-waiting for releasing sender resources..."); for (;;) { CSndBuffer* sb = s->core().m_pSndBuffer; // Disconnected from buffer - nothing more to check. if (!sb) { HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: sending buffer disconnected. Allowed to close."); break; } // Sender buffer empty if (sb->getCurrBufSize() == 0) { HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: sending buffer depleted. Allowed to close."); break; } // Ok, now you are keeping GC thread hands off the internal data. // You can check then if it has already deleted the socket or not. // The socket is either in m_ClosedSockets or is already gone. // Done the other way, but still done. You can stop waiting. bool isgone = false; { ScopedLock manager_cg(m_GlobControlLock); isgone = m_ClosedSockets.count(u) == 0; } if (!isgone) { isgone = !s->core().m_bOpened; } if (isgone) { HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: ... gone in the meantime, whatever. Exiting close()."); break; } HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: ... still waiting for any update."); // How to handle a possible error here? CGlobEvent::waitForEvent(); // Continue waiting in case when an event happened or 1s waiting time passed for checkpoint. } #endif } /* This code is PUT ASIDE for now. Most likely this will be never required. It had to hold the closing activity until the time when the receiver buffer is depleted. However the closing of the socket should only happen when the receiver has received an information about that the reading is no longer possible (error report from recv/recvfile). When this happens, the receiver buffer is definitely depleted already and there's no need to check anything. Should there appear any other conditions in future under which the closing process should be delayed until the receiver buffer is empty, this code can be filled here. if ( synch_close_rcv ) { ... } */ return 0; } void srt::CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) { if (!pw_name || !pw_namelen) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); if (getStatus(u) != SRTS_CONNECTED) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); CUDTSocket* s = locateSocket(u); if (!s) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); if (!s->core().m_bConnected || s->core().m_bBroken) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); const int len = s->m_PeerAddr.size(); if (*pw_namelen < len) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); memcpy((pw_name), &s->m_PeerAddr.sa, len); *pw_namelen = len; } void srt::CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) { if (!pw_name || !pw_namelen) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); CUDTSocket* s = locateSocket(u); if (!s) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); if (s->core().m_bBroken) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); if (s->m_Status == SRTS_INIT) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); const int len = s->m_SelfAddr.size(); if (*pw_namelen < len) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); memcpy((pw_name), &s->m_SelfAddr.sa, len); *pw_namelen = len; } int srt::CUDTUnited::select( UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) { const steady_clock::time_point entertime = steady_clock::now(); const int64_t timeo_us = timeout ? static_cast(timeout->tv_sec) * 1000000 + timeout->tv_usec : -1; const steady_clock::duration timeo(microseconds_from(timeo_us)); // 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 (getStatus(*i1) == SRTS_BROKEN) { rs.insert(*i1); ++ count; } else if (!(s = locateSocket(*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 (getStatus(*i2) == SRTS_BROKEN) { ws.insert(*i2); ++ count; } else if (!(s = locateSocket(*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 (getStatus(*i3) == SRTS_BROKEN) { es.insert(*i3); ++ count; } else if (!(s = locateSocket(*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->readReady() || s->m_Status == SRTS_CLOSED) { rs.insert(s->m_SocketID); ++ count; } } // query write sockets for (vector::iterator j2 = wu.begin(); j2 != wu.end(); ++ j2) { s = *j2; if (s->writeReady() || s->m_Status == SRTS_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; CGlobEvent::waitForEvent(); } while (timeo > steady_clock::now() - entertime); if (readfds) *readfds = rs; if (writefds) *writefds = ws; if (exceptfds) *exceptfds = es; return count; } int srt::CUDTUnited::selectEx( const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, int64_t msTimeOut) { const steady_clock::time_point entertime = steady_clock::now(); const int64_t timeo_us = msTimeOut >= 0 ? msTimeOut * 1000 : -1; const steady_clock::duration timeo(microseconds_from(timeo_us)); // 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 = locateSocket(*i); if ((!s) || s->core().m_bBroken || (s->m_Status == SRTS_CLOSED)) { if (exceptfds) { exceptfds->push_back(*i); ++ count; } continue; } if (readfds) { if ((s->core().m_bConnected && s->core().m_pRcvBuffer->isRcvDataReady() ) || (s->core().m_bListening && (s->m_QueuedSockets.size() > 0))) { readfds->push_back(s->m_SocketID); ++ count; } } if (writefds) { if (s->core().m_bConnected && (s->core().m_pSndBuffer->getCurrBufSize() < s->core().m_config.iSndBufSize)) { writefds->push_back(s->m_SocketID); ++ count; } } } if (count > 0) break; CGlobEvent::waitForEvent(); } while (timeo > steady_clock::now() - entertime); return count; } int srt::CUDTUnited::epoll_create() { return m_EPoll.create(); } int srt::CUDTUnited::epoll_clear_usocks(int eid) { return m_EPoll.clear_usocks(eid); } int srt::CUDTUnited::epoll_add_usock( const int eid, const SRTSOCKET u, const int* events) { int ret = -1; #if ENABLE_EXPERIMENTAL_BONDING if (u & SRTGROUP_MASK) { GroupKeeper k (*this, u, ERH_THROW); ret = m_EPoll.update_usock(eid, u, events); k.group->addEPoll(eid); return 0; } #endif CUDTSocket* s = locateSocket(u); if (s) { ret = epoll_add_usock_INTERNAL(eid, s, events); } else { throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); } return ret; } // NOTE: WILL LOCK (serially): // - CEPoll::m_EPollLock // - CUDT::m_RecvLock int srt::CUDTUnited::epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events) { int ret = m_EPoll.update_usock(eid, s->m_SocketID, events); s->core().addEPoll(eid); return ret; } int srt::CUDTUnited::epoll_add_ssock( const int eid, const SYSSOCKET s, const int* events) { return m_EPoll.add_ssock(eid, s, events); } int srt::CUDTUnited::epoll_update_ssock( const int eid, const SYSSOCKET s, const int* events) { return m_EPoll.update_ssock(eid, s, events); } template int srt::CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent) { // XXX Not sure if this is anyhow necessary because setting readiness // to false doesn't actually trigger any action. Further research needed. HLOGC(ealog.Debug, log << "epoll_remove_usock: CLEARING readiness on E" << eid << " of @" << ent->id()); ent->removeEPollEvents(eid); // First remove the EID from the subscribed in the socket so that // a possible call to update_events: // - if happens before this call, can find the epoll bit update possible // - if happens after this call, will not strike this EID HLOGC(ealog.Debug, log << "epoll_remove_usock: REMOVING E" << eid << " from back-subscirbers in @" << ent->id()); ent->removeEPollID(eid); HLOGC(ealog.Debug, log << "epoll_remove_usock: CLEARING subscription on E" << eid << " of @" << ent->id()); int no_events = 0; int ret = m_EPoll.update_usock(eid, ent->id(), &no_events); return ret; } // Needed internal access! int srt::CUDTUnited::epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* s) { return epoll_remove_entity(eid, &s->core()); } #if ENABLE_EXPERIMENTAL_BONDING int srt::CUDTUnited::epoll_remove_group_INTERNAL(const int eid, CUDTGroup* g) { return epoll_remove_entity(eid, g); } #endif int srt::CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) { CUDTSocket* s = 0; #if ENABLE_EXPERIMENTAL_BONDING CUDTGroup* g = 0; if (u & SRTGROUP_MASK) { GroupKeeper k (*this, u, ERH_THROW); g = k.group; return epoll_remove_entity(eid, g); } else #endif { s = locateSocket(u); if (s) return epoll_remove_entity(eid, &s->core()); } LOGC(ealog.Error, log << "remove_usock: @" << u << " not found as either socket or group. Removing only from epoll system."); int no_events = 0; return m_EPoll.update_usock(eid, u, &no_events); } int srt::CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s) { return m_EPoll.remove_ssock(eid, s); } int srt::CUDTUnited::epoll_uwait( const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { return m_EPoll.uwait(eid, fdsSet, fdsSize, msTimeOut); } int32_t srt::CUDTUnited::epoll_set(int eid, int32_t flags) { return m_EPoll.setflags(eid, flags); } int srt::CUDTUnited::epoll_release(const int eid) { return m_EPoll.release(eid); } srt::CUDTSocket* srt::CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling erh) { ScopedLock cg (m_GlobControlLock); CUDTSocket* s = locateSocket_LOCKED(u); if (!s) { if (erh == ERH_RETURN) return NULL; throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); } return s; } // [[using locked(m_GlobControlLock)]]; srt::CUDTSocket* srt::CUDTUnited::locateSocket_LOCKED(SRTSOCKET u) { sockets_t::iterator i = m_Sockets.find(u); if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED)) { return NULL; } return i->second; } #if ENABLE_EXPERIMENTAL_BONDING srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling erh) { ScopedLock cg (m_GlobControlLock); const groups_t::iterator i = m_Groups.find(u); if ( i == m_Groups.end() ) { if (erh == ERH_THROW) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); return NULL; } ScopedLock cgroup (*i->second->exp_groupLock()); i->second->apiAcquire(); return i->second; } srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s) { ScopedLock cg (m_GlobControlLock); CUDTGroup* g = s->m_GroupOf; if (!g) return NULL; // With m_GlobControlLock locked, we are sure the group // still exists, if it wasn't removed from this socket. g->apiAcquire(); return g; } #endif srt::CUDTSocket* srt::CUDTUnited::locatePeer( const sockaddr_any& peer, const SRTSOCKET id, int32_t isn) { ScopedLock cg(m_GlobControlLock); 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) { sockets_t::iterator k = m_Sockets.find(*j); // this socket might have been closed and moved m_ClosedSockets if (k == m_Sockets.end()) continue; if (k->second->m_PeerAddr == peer) { return k->second; } } return NULL; } void srt::CUDTUnited::checkBrokenSockets() { ScopedLock cg(m_GlobControlLock); // set of sockets To Be Closed and To Be Removed vector tbc; vector tbr; #if ENABLE_EXPERIMENTAL_BONDING vector delgids; for (groups_t::iterator i = m_ClosedGroups.begin(); i != m_ClosedGroups.end(); ++i) { // isStillBusy requires lock on the group, so only after an API // function that uses it returns, and so clears the busy flag, // a new API function won't be called anyway until it can acquire // GlobControlLock, and all functions that have already seen this // group as closing will not continue with the API and return. // If we caught some API function still using the closed group, // it's not going to wait, will be checked next time. if (i->second->isStillBusy()) continue; delgids.push_back(i->first); delete i->second; i->second = NULL; // just for a case, avoid a dangling pointer } for (vector::iterator di = delgids.begin(); di != delgids.end(); ++di) { m_ClosedGroups.erase(*di); } #endif for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++ i) { CUDTSocket* s = i->second; // check broken connection if (s->core().m_bBroken) { if (s->m_Status == SRTS_LISTENING) { const steady_clock::duration elapsed = steady_clock::now() - s->m_tsClosureTimeStamp; // for a listening socket, it should wait an extra 3 seconds // in case a client is connecting if (elapsed < milliseconds_from(CUDT::COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS)) { continue; } } else if ((s->core().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->core().m_pRcvBuffer->isRcvDataAvailable()) { const int bc = s->core().m_iBrokenCounter.load(); if (bc > 0) { // HLOGF(smlog.Debug, "STILL KEEPING socket (still have data): // %d\n", i->first); // if there is still data in the receiver buffer, wait longer s->core().m_iBrokenCounter.store(bc - 1); continue; } } #if ENABLE_EXPERIMENTAL_BONDING if (s->m_GroupOf) { LOGC(smlog.Note, log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP"); s->removeFromGroup(true); } #endif HLOGC(smlog.Debug, log << "checkBrokenSockets: moving BROKEN socket to CLOSED: @" << i->first); //close broken connections and start removal timer s->setClosed(); tbc.push_back(i->first); m_ClosedSockets[i->first] = s; // remove from listener's queue sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket); if (ls == m_Sockets.end()) { ls = m_ClosedSockets.find(s->m_ListenSocket); if (ls == m_ClosedSockets.end()) continue; } enterCS(ls->second->m_AcceptLock); ls->second->m_QueuedSockets.erase(s->m_SocketID); leaveCS(ls->second->m_AcceptLock); } } for (sockets_t::iterator j = m_ClosedSockets.begin(); j != m_ClosedSockets.end(); ++ j) { // HLOGF(smlog.Debug, "checking CLOSED socket: %d\n", j->first); if (!is_zero(j->second->core().m_tsLingerExpiration)) { // asynchronous close: if ((!j->second->core().m_pSndBuffer) || (0 == j->second->core().m_pSndBuffer->getCurrBufSize()) || (j->second->core().m_tsLingerExpiration <= steady_clock::now())) { HLOGC(smlog.Debug, log << "checkBrokenSockets: marking CLOSED qualified @" << j->second->m_SocketID); j->second->core().m_tsLingerExpiration = steady_clock::time_point(); j->second->core().m_bClosing = true; j->second->m_tsClosureTimeStamp = steady_clock::now(); } } // timeout 1 second to destroy a socket AND it has been removed from // RcvUList const steady_clock::time_point now = steady_clock::now(); const steady_clock::duration closed_ago = now - j->second->m_tsClosureTimeStamp; if (closed_ago > seconds_from(1)) { CRNode* rnode = j->second->core().m_pRNode; if (!rnode || !rnode->m_bOnList) { HLOGC(smlog.Debug, log << "checkBrokenSockets: @" << j->second->m_SocketID << " closed " << FormatDuration(closed_ago) << " ago and removed from RcvQ - will remove"); // HLOGF(smlog.Debug, "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); } // [[using locked(m_GlobControlLock)]] void srt::CUDTUnited::removeSocket(const SRTSOCKET u) { sockets_t::iterator i = m_ClosedSockets.find(u); // invalid socket ID if (i == m_ClosedSockets.end()) return; CUDTSocket* const s = i->second; // The socket may be in the trashcan now, but could // still be under processing in the sender/receiver worker // threads. If that's the case, SKIP IT THIS TIME. The // socket will be checked next time the GC rollover starts. CSNode* sn = s->core().m_pSNode; if (sn && sn->m_iHeapLoc != -1) return; CRNode* rn = s->core().m_pRNode; if (rn && rn->m_bOnList) return; #if ENABLE_EXPERIMENTAL_BONDING if (s->m_GroupOf) { HLOGC(smlog.Debug, log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP"); s->removeFromGroup(true); } #endif // decrease multiplexer reference count, and remove it if necessary const int mid = s->m_iMuxID; { ScopedLock cg(s->m_AcceptLock); // if it is a listener, close all un-accepted sockets in its queue // and remove them later for (set::iterator q = s->m_QueuedSockets.begin(); q != s->m_QueuedSockets.end(); ++ q) { sockets_t::iterator si = m_Sockets.find(*q); if (si == m_Sockets.end()) { // gone in the meantime LOGC(smlog.Error, log << "removeSocket: IPE? socket @" << (*q) << " being queued for listener socket @" << s->m_SocketID << " is GONE in the meantime ???"); continue; } CUDTSocket* as = si->second; as->breakSocket_LOCKED(); m_ClosedSockets[*q] = as; m_Sockets.erase(*q); } } // remove from peer rec map >::iterator j = m_PeerRec.find( s->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, s->core().m_sPollID, SRT_EPOLL_IN|SRT_EPOLL_OUT|SRT_EPOLL_ERR, false); // delete this one m_ClosedSockets.erase(i); HLOGC(smlog.Debug, log << "GC/removeSocket: closing associated UDT @" << u); s->core().closeInternal(); HLOGC(smlog.Debug, log << "GC/removeSocket: DELETING SOCKET @" << u); delete s; if (mid == -1) return; map::iterator m; m = m_mMultiplexer.find(mid); if (m == m_mMultiplexer.end()) { LOGC(smlog.Fatal, log << "IPE: For socket @" << u << " MUXER id=" << mid << " NOT FOUND!"); return; } CMultiplexer& mx = m->second; mx.m_iRefCount --; // HLOGF(smlog.Debug, "unrefing underlying socket for %u: %u\n", // u, mx.m_iRefCount); if (0 == mx.m_iRefCount) { HLOGC(smlog.Debug, log << "MUXER id=" << mid << " lost last socket @" << u << " - deleting muxer bound to port " << mx.m_pChannel->bindAddressAny().hport()); // The channel has no access to the queues and // it looks like the multiplexer is the master of all of them. // The queues must be silenced before closing the channel // because this will cause error to be returned in any operation // being currently done in the queues, if any. mx.m_pSndQueue->setClosing(); mx.m_pRcvQueue->setClosing(); mx.destroy(); m_mMultiplexer.erase(m); } } void srt::CUDTUnited::configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af) { w_m.m_mcfg = s->core().m_config; w_m.m_iIPversion = af; w_m.m_iRefCount = 1; w_m.m_iID = s->m_SocketID; } uint16_t srt::CUDTUnited::installMuxer(CUDTSocket* w_s, CMultiplexer& fw_sm) { w_s->core().m_pSndQueue = fw_sm.m_pSndQueue; w_s->core().m_pRcvQueue = fw_sm.m_pRcvQueue; w_s->m_iMuxID = fw_sm.m_iID; sockaddr_any sa; fw_sm.m_pChannel->getSockAddr((sa)); w_s->m_SelfAddr = sa; // Will be also completed later, but here it's needed for later checks return sa.hport(); } bool srt::CUDTUnited::channelSettingsMatch(const CMultiplexer& m, const CUDTSocket* s) { return m.m_mcfg.bReuseAddr && m.m_mcfg == s->core().m_config; } void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* udpsock /*[[nullable]]*/) { ScopedLock cg(m_GlobControlLock); // If udpsock is provided, then this socket will be simply // taken for binding as a good deal. It would be nice to make // a sanity check to see if this UDP socket isn't already installed // in some multiplexer, but we state this UDP socket isn't accessible // anyway so this wouldn't be possible. if (!udpsock) { // If not, we need to see if there exist already a multiplexer bound // to the same endpoint. const int port = addr.hport(); bool reuse_attempt = false; for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++ i) { CMultiplexer& m = i->second; // First, we need to find a multiplexer with the same port. if (m.m_iPort != port) { HLOGC(smlog.Debug, log << "bind: muxer @" << m.m_iID << " found, but for port " << m.m_iPort << " (requested port: " << port << ")"); continue; } // If this is bound to the wildcard address, it can be reused if: // - addr is also a wildcard // - channel settings match // Otherwise it's a conflict. sockaddr_any sa; m.m_pChannel->getSockAddr((sa)); HLOGC(smlog.Debug, log << "bind: Found existing muxer @" << m.m_iID << " : " << sa.str() << " - check against " << addr.str()); if (sa.isany()) { if (!addr.isany()) { LOGC(smlog.Error, log << "bind: Address: " << addr.str() << " conflicts with existing wildcard binding: " << sa.str()); throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); } // Still, for ANY you need either the same family, or open // for families. if (m.m_mcfg.iIpV6Only != -1 && m.m_mcfg.iIpV6Only != s->core().m_config.iIpV6Only) { LOGC(smlog.Error, log << "bind: Address: " << addr.str() << " conflicts with existing IPv6 wildcard binding: " << sa.str()); throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); } if ((m.m_mcfg.iIpV6Only == 0 || s->core().m_config.iIpV6Only == 0) && m.m_iIPversion != addr.family()) { LOGC(smlog.Error, log << "bind: Address: " << addr.str() << " conflicts with IPv6 wildcard binding: " << sa.str() << " : family " << (m.m_iIPversion == AF_INET ? "IPv4" : "IPv6") << " vs. " << (addr.family() == AF_INET ? "IPv4" : "IPv6")); throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); } reuse_attempt = true; HLOGC(smlog.Debug, log << "bind: wildcard address - multiplexer reusable"); } else if (addr.isany() && addr.family() == sa.family()) { LOGC(smlog.Error, log << "bind: Wildcard address: " << addr.str() << " conflicts with existting IP binding: " << sa.str()); throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); } // If this is bound to a certain address, AND: else if (sa.equal_address(addr)) { // - the address is the same as addr reuse_attempt = true; HLOGC(smlog.Debug, log << "bind: same IP address - multiplexer reusable"); } else { HLOGC(smlog.Debug, log << "bind: IP addresses differ - ALLOWED to create a new multiplexer"); } // Otherwise: // - the address is different than addr // - the address can't be reused, but this can go on with new one. // If this is a reusage attempt: if (reuse_attempt) { // - if the channel settings match, it can be reused if (channelSettingsMatch(m, s)) { HLOGC(smlog.Debug, log << "bind: reusing multiplexer for port " << port); // reuse the existing multiplexer ++ i->second.m_iRefCount; installMuxer((s), (i->second)); return; } else { // - if not, it's a conflict LOGC(smlog.Error, log << "bind: Address: " << addr.str() << " conflicts with binding: " << sa.str() << " due to channel settings"); throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); } } // If not, proceed to the next one, and when there are no reusage // candidates, proceed with creating a new multiplexer. // Note that a binding to a different IP address is not treated // as a candidate for either reuseage or conflict. } } // a new multiplexer is needed CMultiplexer m; configureMuxer((m), s, addr.family()); try { m.m_pChannel = new CChannel(); m.m_pChannel->setConfig(m.m_mcfg); if (udpsock) { // In this case, addr contains the address // that has been extracted already from the // given socket m.m_pChannel->attach(*udpsock, addr); } else if (addr.empty()) { // The case of previously used case of a NULL address. // This here is used to pass family only, in this case // just automatically bind to the "0" address to autoselect // everything. m.m_pChannel->open(addr.family()); } else { // If at least the IP address is specified, then bind to that // address, but still possibly autoselect the outgoing port, if the // port was specified as 0. m.m_pChannel->open(addr); } 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->core().maxPayloadSize(), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer); // Rewrite the port here, as it might be only known upon return // from CChannel::open. m.m_iPort = installMuxer((s), m); m_mMultiplexer[m.m_iID] = m; } catch (const CUDTException&) { m.destroy(); throw; } catch (...) { m.destroy(); throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } HLOGC(smlog.Debug, log << "bind: creating new multiplexer for port " << m.m_iPort); } // This function is going to find a multiplexer for the port contained // in the 'ls' listening socket. The multiplexer must exist when the listener // exists, otherwise the dispatching procedure wouldn't even call this // function. By historical reasons there's also a fallback for a case when the // multiplexer wasn't found by id, the search by port number continues. bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) { ScopedLock cg(m_GlobControlLock); const int port = ls->m_SelfAddr.hport(); HLOGC(smlog.Debug, log << "updateListenerMux: finding muxer of listener socket @" << ls->m_SocketID << " muxid=" << ls->m_iMuxID << " bound=" << ls->m_SelfAddr.str() << " FOR @" << s->m_SocketID << " addr=" << s->m_SelfAddr.str() << "_->_" << s->m_PeerAddr.str()); // First thing that should be certain here is that there should exist // a muxer with the ID written in the listener socket's mux ID. CMultiplexer* mux = map_getp(m_mMultiplexer, ls->m_iMuxID); // NOTE: // THIS BELOW CODE is only for a highly unlikely, and probably buggy, // situation when the Multiplexer wasn't found by ID recorded in the listener. CMultiplexer* fallback = NULL; if (!mux) { LOGC(smlog.Error, log << "updateListenerMux: IPE? listener muxer not found by ID, trying by port"); // To be used as first found with different IP version // find the listener's address for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++ i) { CMultiplexer& m = i->second; #if ENABLE_HEAVY_LOGGING ostringstream that_muxer; that_muxer << "id=" << m.m_iID << " port=" << m.m_iPort << " ip=" << (m.m_iIPversion == AF_INET ? "v4" : "v6"); #endif if (m.m_iPort == port) { HLOGC(smlog.Debug, log << "updateListenerMux: reusing muxer: " << that_muxer.str()); if (m.m_iIPversion == s->m_PeerAddr.family()) { mux = &m; // best match break; } else { fallback = &m; } } else { HLOGC(smlog.Debug, log << "updateListenerMux: SKIPPING muxer: " << that_muxer.str()); } } if (!mux && fallback) { // It is allowed to reuse this multiplexer, but the socket must allow both IPv4 and IPv6 if (fallback->m_mcfg.iIpV6Only == 0) { HLOGC(smlog.Warn, log << "updateListenerMux: reusing multiplexer from different family"); mux = fallback; } } } // Checking again because the above procedure could have set it if (mux) { // reuse the existing multiplexer ++ mux->m_iRefCount; s->core().m_pSndQueue = mux->m_pSndQueue; s->core().m_pRcvQueue = mux->m_pRcvQueue; s->m_iMuxID = mux->m_iID; return true; } return false; } void* srt::CUDTUnited::garbageCollect(void* p) { CUDTUnited* self = (CUDTUnited*)p; THREAD_STATE_INIT("SRT:GC"); UniqueLock gclock(self->m_GCStopLock); while (!self->m_bClosing) { INCREMENT_THREAD_ITERATIONS(); self->checkBrokenSockets(); HLOGC(inlog.Debug, log << "GC: sleep 1 s"); self->m_GCStopCond.wait_for(gclock, seconds_from(1)); } // remove all sockets and multiplexers HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock..."); { ScopedLock glock (self->m_GlobControlLock); for (sockets_t::iterator i = self->m_Sockets.begin(); i != self->m_Sockets.end(); ++ i) { CUDTSocket* s = i->second; s->breakSocket_LOCKED(); #if ENABLE_EXPERIMENTAL_BONDING if (s->m_GroupOf) { HLOGC(smlog.Debug, log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " (IPE?) - REMOVING FROM GROUP"); s->removeFromGroup(false); } #endif self->m_ClosedSockets[i->first] = s; // remove from listener's queue sockets_t::iterator ls = self->m_Sockets.find( s->m_ListenSocket); if (ls == self->m_Sockets.end()) { ls = self->m_ClosedSockets.find(s->m_ListenSocket); if (ls == self->m_ClosedSockets.end()) continue; } enterCS(ls->second->m_AcceptLock); ls->second->m_QueuedSockets.erase(s->m_SocketID); leaveCS(ls->second->m_AcceptLock); } self->m_Sockets.clear(); for (sockets_t::iterator j = self->m_ClosedSockets.begin(); j != self->m_ClosedSockets.end(); ++ j) { j->second->m_tsClosureTimeStamp = steady_clock::time_point(); } } HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets."); while (true) { self->checkBrokenSockets(); enterCS(self->m_GlobControlLock); bool empty = self->m_ClosedSockets.empty(); leaveCS(self->m_GlobControlLock); if (empty) break; srt::sync::this_thread::sleep_for(milliseconds_from(1)); } THREAD_EXIT(); return NULL; } //////////////////////////////////////////////////////////////////////////////// int srt::CUDT::startup() { return s_UDTUnited.startup(); } int srt::CUDT::cleanup() { return s_UDTUnited.cleanup(); } SRTSOCKET srt::CUDT::socket() { if (!s_UDTUnited.m_bGCStatus) s_UDTUnited.startup(); try { return s_UDTUnited.newSocket(); } catch (const CUDTException& e) { SetThreadLocalError(e); return INVALID_SOCK; } catch (const bad_alloc&) { SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); return INVALID_SOCK; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "socket: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); return INVALID_SOCK; } } srt::CUDT::APIError::APIError(const CUDTException& e) { SetThreadLocalError(e); } srt::CUDT::APIError::APIError(CodeMajor mj, CodeMinor mn, int syserr) { SetThreadLocalError(CUDTException(mj, mn, syserr)); } #if ENABLE_EXPERIMENTAL_BONDING // This is an internal function; 'type' should be pre-checked if it has a correct value. // This doesn't have argument of GroupType due to header file conflicts. // [[using locked(s_UDTUnited.m_GlobControlLock)]] srt::CUDTGroup& srt::CUDT::newGroup(const int type) { const SRTSOCKET id = s_UDTUnited.generateSocketID(true); // Now map the group return s_UDTUnited.addGroup(id, SRT_GROUP_TYPE(type)).set_id(id); } SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) { // Doing the same lazy-startup as with srt_create_socket() if (!s_UDTUnited.m_bGCStatus) s_UDTUnited.startup(); try { srt::sync::ScopedLock globlock (s_UDTUnited.m_GlobControlLock); return newGroup(gt).id(); // Note: potentially, after this function exits, the group // could be deleted, immediately, from a separate thread (tho // unlikely because the other thread would need some handle to // keep it). But then, the first call to any API function would // return invalid ID error. } catch (const CUDTException& e) { return APIError(e); } catch (...) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } return SRT_INVALID_SOCK; } int srt::CUDT::addSocketToGroup(SRTSOCKET socket, SRTSOCKET group) { // Check if socket and group have been set correctly. int32_t sid = socket & ~SRTGROUP_MASK; int32_t gm = group & SRTGROUP_MASK; if ( sid != socket || gm == 0 ) return APIError(MJ_NOTSUP, MN_INVAL, 0); // Find the socket and the group CUDTSocket* s = s_UDTUnited.locateSocket(socket); CUDTUnited::GroupKeeper k (s_UDTUnited, group, s_UDTUnited.ERH_RETURN); if (!s || !k.group) return APIError(MJ_NOTSUP, MN_INVAL, 0); // Check if the socket is already IN SOME GROUP. if (s->m_GroupOf) return APIError(MJ_NOTSUP, MN_INVAL, 0); CUDTGroup* g = k.group; if (g->managed()) { // This can be changed as long as the group is empty. if (!g->groupEmpty()) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } g->set_managed(false); } ScopedLock cg (s->m_ControlLock); ScopedLock cglob (s_UDTUnited.m_GlobControlLock); if (g->closing()) return APIError(MJ_NOTSUP, MN_INVAL, 0); // Check if the socket already is in the group srt::groups::SocketData* f; if (g->contains(socket, (f))) { // XXX This is internal error. Report it, but continue LOGC(aclog.Error, log << "IPE (non-fatal): the socket is in the group, but has no clue about it!"); s->m_GroupMemberData = f; s->m_GroupOf = g; return 0; } s->m_GroupMemberData = g->add(srt::groups::prepareSocketData(s)); s->m_GroupOf = g; return 0; } // dead function as for now. This is only for non-managed // groups. int srt::CUDT::removeSocketFromGroup(SRTSOCKET socket) { CUDTSocket* s = s_UDTUnited.locateSocket(socket); if (!s) return APIError(MJ_NOTSUP, MN_INVAL, 0); if (!s->m_GroupOf) return APIError(MJ_NOTSUP, MN_INVAL, 0); ScopedLock cg (s->m_ControlLock); ScopedLock glob_grd (s_UDTUnited.m_GlobControlLock); s->removeFromGroup(false); return 0; } // [[using locked(m_ControlLock)]] // [[using locked(CUDT::s_UDTUnited.m_GlobControlLock)]] void srt::CUDTSocket::removeFromGroup(bool broken) { CUDTGroup* g = m_GroupOf; if (g) { // Reset group-related fields immediately. They won't be accessed // in the below calls, while the iterator will be invalidated for // a short moment between removal from the group container and the end, // while the GroupLock would be already taken out. It is safer to reset // it to a NULL iterator before removal. m_GroupOf = NULL; m_GroupMemberData = NULL; bool still_have = g->remove(m_SocketID); if (broken) { // Activate the SRT_EPOLL_UPDATE event on the group // if it was because of a socket that was earlier connected // and became broken. This is not to be sent in case when // it is a failure during connection, or the socket was // explicitly removed from the group. g->activateUpdateEvent(still_have); } HLOGC(smlog.Debug, log << "removeFromGroup: socket @" << m_SocketID << " NO LONGER A MEMBER of $" << g->id() << "; group is " << (still_have ? "still ACTIVE" : "now EMPTY")); } } SRTSOCKET srt::CUDT::getGroupOfSocket(SRTSOCKET socket) { // Lock this for the whole function as we need the group // to persist the call. ScopedLock glock (s_UDTUnited.m_GlobControlLock); CUDTSocket* s = s_UDTUnited.locateSocket_LOCKED(socket); if (!s || !s->m_GroupOf) return APIError(MJ_NOTSUP, MN_INVAL, 0); return s->m_GroupOf->id(); } int srt::CUDT::configureGroup(SRTSOCKET groupid, const char* str) { if ( (groupid & SRTGROUP_MASK) == 0) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } CUDTUnited::GroupKeeper k (s_UDTUnited, groupid, s_UDTUnited.ERH_RETURN); if (!k.group) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } return k.group->configure(str); } int srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize) { if ((groupid & SRTGROUP_MASK) == 0 || !psize) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } CUDTUnited::GroupKeeper k (s_UDTUnited, groupid, s_UDTUnited.ERH_RETURN); if (!k.group) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } // To get only the size of the group pdata=NULL can be used return k.group->getGroupData(pdata, psize); } #endif int srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) { try { sockaddr_any sa (name, namelen); if (sa.len == 0) { // This happens if the namelen check proved it to be // too small for particular family, or that family is // not recognized (is none of AF_INET, AF_INET6). // This is a user error. return APIError(MJ_NOTSUP, MN_INVAL, 0); } CUDTSocket* s = s_UDTUnited.locateSocket(u); if (!s) return APIError(MJ_NOTSUP, MN_INVAL, 0); return s_UDTUnited.bind(s, sa); } catch (const CUDTException& e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "bind: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) { try { CUDTSocket* s = s_UDTUnited.locateSocket(u); if (!s) return APIError(MJ_NOTSUP, MN_INVAL, 0); return s_UDTUnited.bind(s, udpsock); } catch (const CUDTException& e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "bind/udp: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::listen(SRTSOCKET u, int backlog) { try { return s_UDTUnited.listen(u, backlog); } catch (const CUDTException& e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "listen: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } SRTSOCKET srt::CUDT::accept_bond(const SRTSOCKET listeners [], int lsize, int64_t msTimeOut) { try { return s_UDTUnited.accept_bond(listeners, lsize, msTimeOut); } catch (const CUDTException& e) { SetThreadLocalError(e); return INVALID_SOCK; } catch (bad_alloc&) { SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); return INVALID_SOCK; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "accept_bond: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); return INVALID_SOCK; } } SRTSOCKET srt::CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) { try { return s_UDTUnited.accept(u, addr, addrlen); } catch (const CUDTException& e) { SetThreadLocalError(e); return INVALID_SOCK; } catch (const bad_alloc&) { SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); return INVALID_SOCK; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "accept: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); return INVALID_SOCK; } } int srt::CUDT::connect( SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen) { try { return s_UDTUnited.connect(u, name, tname, namelen); } catch (const CUDTException& e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (std::exception& ee) { LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } #if ENABLE_EXPERIMENTAL_BONDING int srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets [], int arraysize) { if (arraysize <= 0) return APIError(MJ_NOTSUP, MN_INVAL, 0); if ( (grp & SRTGROUP_MASK) == 0) { // connectLinks accepts only GROUP id, not socket id. return APIError(MJ_NOTSUP, MN_SIDINVAL, 0); } try { CUDTUnited::GroupKeeper k(s_UDTUnited, grp, s_UDTUnited.ERH_THROW); return s_UDTUnited.groupConnect(k.group, targets, arraysize); } catch (CUDTException& e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (std::exception& ee) { LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } #endif int srt::CUDT::connect( SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) { try { return s_UDTUnited.connect(u, name, namelen, forced_isn); } catch (const CUDTException &e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::close(SRTSOCKET u) { try { return s_UDTUnited.close(u); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "close: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) { try { s_UDTUnited.getpeername(u, name, namelen); return 0; } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "getpeername: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) { try { s_UDTUnited.getsockname(u, name, namelen); return 0; } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "getsockname: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::getsockopt( SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval, int* pw_optlen) { if (!pw_optval || !pw_optlen) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } try { #if ENABLE_EXPERIMENTAL_BONDING if (u & SRTGROUP_MASK) { CUDTUnited::GroupKeeper k(s_UDTUnited, u, s_UDTUnited.ERH_THROW); k.group->getOpt(optname, (pw_optval), (*pw_optlen)); return 0; } #endif CUDT& udt = s_UDTUnited.locateSocket(u, s_UDTUnited.ERH_THROW)->core(); udt.getOpt(optname, (pw_optval), (*pw_optlen)); return 0; } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "getsockopt: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* optval, int optlen) { if (!optval) return APIError(MJ_NOTSUP, MN_INVAL, 0); try { #if ENABLE_EXPERIMENTAL_BONDING if (u & SRTGROUP_MASK) { CUDTUnited::GroupKeeper k(s_UDTUnited, u, s_UDTUnited.ERH_THROW); k.group->setOpt(optname, optval, optlen); return 0; } #endif CUDT& udt = s_UDTUnited.locateSocket(u, s_UDTUnited.ERH_THROW)->core(); udt.setOpt(optname, optval, optlen); return 0; } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "setsockopt: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::send(SRTSOCKET u, const char* buf, int len, int) { SRT_MSGCTRL mctrl = srt_msgctrl_default; return sendmsg2(u, buf, len, (mctrl)); } // --> CUDT::recv moved down int srt::CUDT::sendmsg( SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; mctrl.msgttl = ttl; mctrl.inorder = inorder; mctrl.srctime = srctime; return sendmsg2(u, buf, len, (mctrl)); } int srt::CUDT::sendmsg2( SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) { try { #if ENABLE_EXPERIMENTAL_BONDING if (u & SRTGROUP_MASK) { CUDTUnited::GroupKeeper k (s_UDTUnited, u, s_UDTUnited.ERH_THROW); return k.group->send(buf, len, (w_m)); } #endif return s_UDTUnited.locateSocket(u, CUDTUnited::ERH_THROW)->core().sendmsg2(buf, len, (w_m)); } catch (const CUDTException& e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "sendmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::recv(SRTSOCKET u, char* buf, int len, int) { SRT_MSGCTRL mctrl = srt_msgctrl_default; int ret = recvmsg2(u, buf, len, (mctrl)); return ret; } int srt::CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; int ret = recvmsg2(u, buf, len, (mctrl)); srctime = mctrl.srctime; return ret; } int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) { try { #if ENABLE_EXPERIMENTAL_BONDING if (u & SRTGROUP_MASK) { CUDTUnited::GroupKeeper k(s_UDTUnited, u, s_UDTUnited.ERH_THROW); return k.group->recv(buf, len, (w_m)); } #endif return s_UDTUnited.locateSocket(u, CUDTUnited::ERH_THROW)->core().recvmsg2(buf, len, (w_m)); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "recvmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int64_t srt::CUDT::sendfile( SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) { try { CUDT& udt = s_UDTUnited.locateSocket(u, s_UDTUnited.ERH_THROW)->core(); return udt.sendfile(ifs, offset, size, block); } catch (const CUDTException& e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "sendfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int64_t srt::CUDT::recvfile( SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) { try { return s_UDTUnited.locateSocket(u, CUDTUnited::ERH_THROW)->core().recvfile(ofs, offset, size, block); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "recvfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::select( int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) { if ((!readfds) && (!writefds) && (!exceptfds)) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } try { return s_UDTUnited.select(readfds, writefds, exceptfds, timeout); } catch (const CUDTException& e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "select: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::selectEx( const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, int64_t msTimeOut) { if ((!readfds) && (!writefds) && (!exceptfds)) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } try { return s_UDTUnited.selectEx(fds, readfds, writefds, exceptfds, msTimeOut); } catch (const CUDTException& e) { return APIError(e); } catch (bad_alloc&) { return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "selectEx: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN); } } int srt::CUDT::epoll_create() { try { return s_UDTUnited.epoll_create(); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_create: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_clear_usocks(int eid) { try { return s_UDTUnited.epoll_clear_usocks(eid); } catch (const CUDTException& e) { return APIError(e); } catch (std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_clear_usocks: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) { try { return s_UDTUnited.epoll_add_usock(eid, u, events); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_add_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) { try { return s_UDTUnited.epoll_add_ssock(eid, s, events); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_add_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_update_usock( const int eid, const SRTSOCKET u, const int* events) { try { return s_UDTUnited.epoll_add_usock(eid, u, events); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_update_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_update_ssock( const int eid, const SYSSOCKET s, const int* events) { try { return s_UDTUnited.epoll_update_ssock(eid, s, events); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_update_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) { try { return s_UDTUnited.epoll_remove_usock(eid, u); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_remove_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) { try { return s_UDTUnited.epoll_remove_ssock(eid, s); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_remove_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_wait( const int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) { try { return s_UDTUnited.epoll_ref().wait( eid, readfds, writefds, msTimeOut, lrfds, lwfds); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_wait: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_uwait( const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { try { return s_UDTUnited.epoll_uwait(eid, fdsSet, fdsSize, msTimeOut); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_uwait: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int32_t srt::CUDT::epoll_set( const int eid, int32_t flags) { try { return s_UDTUnited.epoll_set(eid, flags); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_set: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } int srt::CUDT::epoll_release(const int eid) { try { return s_UDTUnited.epoll_release(eid); } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_release: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } CUDTException& srt::CUDT::getlasterror() { return GetThreadLocalError(); } int srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous) { #if ENABLE_EXPERIMENTAL_BONDING if (u & SRTGROUP_MASK) return groupsockbstats(u, perf, clear); #endif try { CUDT& udt = s_UDTUnited.locateSocket(u, s_UDTUnited.ERH_THROW)->core(); udt.bstats(perf, clear, instantaneous); return 0; } catch (const CUDTException& e) { return APIError(e); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "bstats: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); return APIError(MJ_UNKNOWN, MN_NONE, 0); } } #if ENABLE_EXPERIMENTAL_BONDING int srt::CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear) { try { CUDTUnited::GroupKeeper k(s_UDTUnited, u, s_UDTUnited.ERH_THROW); k.group->bstatsSocket(perf, clear); return 0; } catch (const CUDTException& e) { SetThreadLocalError(e); return ERROR; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "bstats: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); return ERROR; } } #endif srt::CUDT* srt::CUDT::getUDTHandle(SRTSOCKET u) { try { return &s_UDTUnited.locateSocket(u, s_UDTUnited.ERH_THROW)->core(); } catch (const CUDTException& e) { SetThreadLocalError(e); return NULL; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "getUDTHandle: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); return NULL; } } vector srt::CUDT::existingSockets() { vector out; for (CUDTUnited::sockets_t::iterator i = s_UDTUnited.m_Sockets.begin(); i != s_UDTUnited.m_Sockets.end(); ++i) { out.push_back(i->first); } return out; } SRT_SOCKSTATUS srt::CUDT::getsockstate(SRTSOCKET u) { try { #if ENABLE_EXPERIMENTAL_BONDING if (isgroup(u)) { CUDTUnited::GroupKeeper k(s_UDTUnited, u, s_UDTUnited.ERH_THROW); return k.group->getStatus(); } #endif return s_UDTUnited.getStatus(u); } catch (const CUDTException& e) { SetThreadLocalError(e); return SRTS_NONEXIST; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "getsockstate: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); return SRTS_NONEXIST; } } //////////////////////////////////////////////////////////////////////////////// namespace UDT { int startup() { return srt::CUDT::startup(); } int cleanup() { return srt::CUDT::cleanup(); } int bind(SRTSOCKET u, const struct sockaddr* name, int namelen) { return srt::CUDT::bind(u, name, namelen); } int bind2(SRTSOCKET u, UDPSOCKET udpsock) { return srt::CUDT::bind(u, udpsock); } int listen(SRTSOCKET u, int backlog) { return srt::CUDT::listen(u, backlog); } SRTSOCKET accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen) { return srt::CUDT::accept(u, addr, addrlen); } int connect(SRTSOCKET u, const struct sockaddr* name, int namelen) { return srt::CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); } int close(SRTSOCKET u) { return srt::CUDT::close(u); } int getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen) { return srt::CUDT::getpeername(u, name, namelen); } int getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen) { return srt::CUDT::getsockname(u, name, namelen); } int getsockopt( SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen) { return srt::CUDT::getsockopt(u, level, optname, optval, optlen); } int setsockopt( SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen) { return srt::CUDT::setsockopt(u, level, optname, optval, optlen); } // DEVELOPER API int connect_debug( SRTSOCKET u, const struct sockaddr* name, int namelen, int32_t forced_isn) { return srt::CUDT::connect(u, name, namelen, forced_isn); } int send(SRTSOCKET u, const char* buf, int len, int flags) { return srt::CUDT::send(u, buf, len, flags); } int recv(SRTSOCKET u, char* buf, int len, int flags) { return srt::CUDT::recv(u, buf, len, flags); } int sendmsg( SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime) { return srt::CUDT::sendmsg(u, buf, len, ttl, inorder, srctime); } int recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) { return srt::CUDT::recvmsg(u, buf, len, srctime); } int recvmsg(SRTSOCKET u, char* buf, int len) { int64_t srctime; return srt::CUDT::recvmsg(u, buf, len, srctime); } int64_t sendfile( SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) { return srt::CUDT::sendfile(u, ifs, offset, size, block); } int64_t recvfile( SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) { return srt::CUDT::recvfile(u, ofs, offset, size, block); } int64_t sendfile2( SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block) { fstream ifs(path, ios::binary | ios::in); int64_t ret = srt::CUDT::sendfile(u, ifs, *offset, size, block); ifs.close(); return ret; } int64_t recvfile2( SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block) { fstream ofs(path, ios::binary | ios::out); int64_t ret = srt::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 srt::CUDT::select(nfds, readfds, writefds, exceptfds, timeout); } int selectEx( const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, int64_t msTimeOut) { return srt::CUDT::selectEx(fds, readfds, writefds, exceptfds, msTimeOut); } int epoll_create() { return srt::CUDT::epoll_create(); } int epoll_clear_usocks(int eid) { return srt::CUDT::epoll_clear_usocks(eid); } int epoll_add_usock(int eid, SRTSOCKET u, const int* events) { return srt::CUDT::epoll_add_usock(eid, u, events); } int epoll_add_ssock(int eid, SYSSOCKET s, const int* events) { return srt::CUDT::epoll_add_ssock(eid, s, events); } int epoll_update_usock(int eid, SRTSOCKET u, const int* events) { return srt::CUDT::epoll_update_usock(eid, u, events); } int epoll_update_ssock(int eid, SYSSOCKET s, const int* events) { return srt::CUDT::epoll_update_ssock(eid, s, events); } int epoll_remove_usock(int eid, SRTSOCKET u) { return srt::CUDT::epoll_remove_usock(eid, u); } int epoll_remove_ssock(int eid, SYSSOCKET s) { return srt::CUDT::epoll_remove_ssock(eid, s); } int epoll_wait( int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) { return srt::CUDT::epoll_wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); } template inline void set_result(set* val, int* num, SOCKTYPE* fds) { if ( !val || !num || !fds ) return; if (*num > int(val->size())) *num = int(val->size()); // will get 0 if val->empty() int count = 0; // This loop will run 0 times if val->empty() for (typename set::const_iterator it = val->begin(); it != val->end(); ++ it) { if (count >= *num) break; fds[count ++] = *it; } } int epoll_wait2( int eid, SRTSOCKET* readfds, int* rnum, SRTSOCKET* 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 = srt::CUDT::epoll_wait(eid, rval, wval, msTimeOut, lrval, lwval); if (ret > 0) { //set::const_iterator i; //SET_RESULT(rval, rnum, readfds, i); set_result(rval, rnum, readfds); //SET_RESULT(wval, wnum, writefds, i); set_result(wval, wnum, writefds); //set::const_iterator j; //SET_RESULT(lrval, lrnum, lrfds, j); set_result(lrval, lrnum, lrfds); //SET_RESULT(lwval, lwnum, lwfds, j); set_result(lwval, lwnum, lwfds); } return ret; } int epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { return srt::CUDT::epoll_uwait(eid, fdsSet, fdsSize, msTimeOut); } int epoll_release(int eid) { return srt::CUDT::epoll_release(eid); } ERRORINFO& getlasterror() { return srt::CUDT::getlasterror(); } int getlasterror_code() { return srt::CUDT::getlasterror().getErrorCode(); } const char* getlasterror_desc() { return srt::CUDT::getlasterror().getErrorMessage(); } int getlasterror_errno() { return srt::CUDT::getlasterror().getErrno(); } // Get error string of a given error code const char* geterror_desc(int code, int err) { CUDTException e (CodeMajor(code/1000), CodeMinor(code%1000), err); return(e.getErrorMessage()); } int bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear) { return srt::CUDT::bstats(u, perf, clear); } SRT_SOCKSTATUS getsockstate(SRTSOCKET u) { return srt::CUDT::getsockstate(u); } } // namespace UDT namespace srt { void setloglevel(LogLevel::type ll) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.max_level = ll; } void addlogfa(LogFA fa) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.enabled_fa.set(fa, true); } void dellogfa(LogFA fa) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.enabled_fa.set(fa, false); } void resetlogfa(set fas) { ScopedLock gg(srt_logger_config.mutex); for (int i = 0; i <= SRT_LOGFA_LASTNONE; ++i) srt_logger_config.enabled_fa.set(i, fas.count(i)); } void resetlogfa(const int* fara, size_t fara_size) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.enabled_fa.reset(); for (const int* i = fara; i != fara + fara_size; ++i) srt_logger_config.enabled_fa.set(*i, true); } void setlogstream(std::ostream& stream) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.log_stream = &stream; } void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.loghandler_opaque = opaque; srt_logger_config.loghandler_fn = handler; } void setlogflags(int flags) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.flags = flags; } SRT_API bool setstreamid(SRTSOCKET u, const std::string& sid) { return CUDT::setstreamid(u, sid); } SRT_API std::string getstreamid(SRTSOCKET u) { return CUDT::getstreamid(u); } int getrejectreason(SRTSOCKET u) { return CUDT::rejectReason(u); } int setrejectreason(SRTSOCKET u, int value) { return CUDT::rejectReason(u, value); } } // namespace srt srt-1.4.4/srtcore/api.h000066400000000000000000000430211412557703600147450ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 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 INC_SRT_API_H #define INC_SRT_API_H #include #include #include #include "netinet_any.h" #include "udt.h" #include "packet.h" #include "queue.h" #include "cache.h" #include "epoll.h" #include "handshake.h" #include "core.h" #if ENABLE_EXPERIMENTAL_BONDING #include "group.h" #endif // Please refer to structure and locking information provided in the // docs/dev/low-level-info.md document. namespace srt { class CUDT; /// @brief Class CUDTSocket is a control layer on top of the CUDT core functionality layer. /// CUDTSocket owns CUDT. class CUDTSocket { public: CUDTSocket() : m_Status(SRTS_INIT) , m_SocketID(0) , m_ListenSocket(0) , m_PeerID(0) #if ENABLE_EXPERIMENTAL_BONDING , m_GroupMemberData() , m_GroupOf() #endif , m_iISN(0) , m_UDT(this) , m_AcceptCond() , m_AcceptLock() , m_uiBackLog(0) , m_iMuxID(-1) { construct(); } CUDTSocket(const CUDTSocket& ancestor) : m_Status(SRTS_INIT) , m_SocketID(0) , m_ListenSocket(0) , m_PeerID(0) #if ENABLE_EXPERIMENTAL_BONDING , m_GroupMemberData() , m_GroupOf() #endif , m_iISN(0) , m_UDT(this, ancestor.m_UDT) , m_AcceptCond() , m_AcceptLock() , m_uiBackLog(0) , m_iMuxID(-1) { construct(); } ~CUDTSocket(); void construct(); srt::sync::atomic m_Status; //< current socket state /// Time when the socket is closed. /// When the socket is closed, it is not removed immediately from the list /// of sockets in order to prevent other methods from accessing invalid address. /// A timer is started and the socket will be removed after approximately /// 1 second (see CUDTUnited::checkBrokenSockets()). sync::steady_clock::time_point m_tsClosureTimeStamp; sockaddr_any m_SelfAddr; //< local address of the socket sockaddr_any m_PeerAddr; //< peer address of the socket SRTSOCKET m_SocketID; //< socket ID SRTSOCKET m_ListenSocket; //< ID of the listener socket; 0 means this is an independent socket SRTSOCKET m_PeerID; //< peer socket ID #if ENABLE_EXPERIMENTAL_BONDING groups::SocketData* m_GroupMemberData; //< Pointer to group member data, or NULL if not a group member CUDTGroup* m_GroupOf; //< Group this socket is a member of, or NULL if it isn't #endif int32_t m_iISN; //< initial sequence number, used to tell different connection from same IP:port private: CUDT m_UDT; //< internal SRT socket logic public: std::set m_QueuedSockets; //< set of connections waiting for accept() sync::Condition m_AcceptCond; //< used to block "accept" call sync::Mutex m_AcceptLock; //< mutex associated to m_AcceptCond unsigned int m_uiBackLog; //< maximum number of connections in queue // XXX A refactoring might be needed here. // There are no reasons found why the socket can't contain a list iterator to a // multiplexer INSTEAD of m_iMuxID. There's no danger in this solution because // the multiplexer is never deleted until there's at least one socket using it. // // The multiplexer may even physically be contained in the CUDTUnited object, // just track the multiple users of it (the listener and the accepted sockets). // When deleting, you simply "unsubscribe" yourself from the multiplexer, which // will unref it and remove the list element by the iterator kept by the // socket. int m_iMuxID; //< multiplexer ID sync::Mutex m_ControlLock; //< lock this socket exclusively for control APIs: bind/listen/connect CUDT& core() { return m_UDT; } const CUDT& core() const { return m_UDT; } static int64_t getPeerSpec(SRTSOCKET id, int32_t isn) { return (int64_t(id) << 30) + isn; } int64_t getPeerSpec() { return getPeerSpec(m_PeerID, m_iISN); } SRT_SOCKSTATUS getStatus(); /// This function shall be called always wherever /// you'd like to call cudtsocket->m_pUDT->close(), /// from within the GC thread only (that is, only when /// the socket should be no longer visible in the /// connection, including for sending remaining data). void breakSocket_LOCKED(); /// This makes the socket no longer capable of performing any transmission /// operation, but continues to be responsive in the connection in order /// to finish sending the data that were scheduled for sending so far. void setClosed(); /// This does the same as setClosed, plus sets the m_bBroken to true. /// Such a socket can still be read from so that remaining data from /// the receiver buffer can be read, but no longer sends anything. void setBrokenClosed(); void removeFromGroup(bool broken); // Instrumentally used by select() and also required for non-blocking // mode check in groups bool readReady(); bool writeReady() const; bool broken() const; private: CUDTSocket& operator=(const CUDTSocket&); }; //////////////////////////////////////////////////////////////////////////////// class CUDTUnited { friend class CUDT; friend class CUDTGroup; friend class CRendezvousQueue; public: CUDTUnited(); ~CUDTUnited(); // Public constants static const int32_t MAX_SOCKET_VAL = SRTGROUP_MASK - 1; // maximum value for a regular socket public: enum ErrorHandling { ERH_RETURN, ERH_THROW, ERH_ABORT }; static std::string CONID(SRTSOCKET sock); /// 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 [out] pps Variable (optional) to which the new socket will be written, if succeeded /// @return The new UDT socket ID, or INVALID_SOCK. SRTSOCKET newSocket(CUDTSocket** pps = NULL); /// 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); /// @param [out] w_error error code when failed /// @param [out] w_acpu entity of accepted socket, if connection already exists /// @return If the new connection is successfully created: 1 success, 0 already exist, -1 error. int newConnection(const SRTSOCKET listen, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs, int& w_error, CUDT*& w_acpu); int installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); int installConnectHook(const SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); /// Check the status of the UDT socket. /// @param [in] u the UDT socket ID. /// @return UDT socket status, or NONEXIST if not found. SRT_SOCKSTATUS getStatus(const SRTSOCKET u); // socket APIs int bind(CUDTSocket* u, const sockaddr_any& name); int bind(CUDTSocket* u, UDPSOCKET udpsock); int listen(const SRTSOCKET u, int backlog); SRTSOCKET accept(const SRTSOCKET listen, sockaddr* addr, int* addrlen); SRTSOCKET accept_bond(const SRTSOCKET listeners [], int lsize, int64_t msTimeOut); int connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int tarlen); int connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); int connectIn(CUDTSocket* s, const sockaddr_any& target, int32_t forced_isn); #if ENABLE_EXPERIMENTAL_BONDING int groupConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG targets [], int arraysize); int singleMemberConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG* target); #endif int close(const SRTSOCKET u); int close(CUDTSocket* s); void getpeername(const SRTSOCKET u, sockaddr* name, int* namelen); void getsockname(const SRTSOCKET u, sockaddr* name, int* namelen); int select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* 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_clear_usocks(int eid); int epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); int epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events); int epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); int epoll_remove_usock(const int eid, const SRTSOCKET u); template int epoll_remove_entity(const int eid, EntityType* ent); int epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* ent); #if ENABLE_EXPERIMENTAL_BONDING int epoll_remove_group_INTERNAL(const int eid, CUDTGroup* ent); #endif int epoll_remove_ssock(const int eid, const SYSSOCKET s); int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); int32_t epoll_set(const int eid, int32_t flags); int epoll_release(const int eid); #if ENABLE_EXPERIMENTAL_BONDING // [[using locked(m_GlobControlLock)]] CUDTGroup& addGroup(SRTSOCKET id, SRT_GROUP_TYPE type) { // This only ensures that the element exists. // If the element was newly added, it will be NULL. CUDTGroup*& g = m_Groups[id]; if (!g) { // This is a reference to the cell, so it will // rewrite it into the map. g = new CUDTGroup(type); } // Now we are sure that g is not NULL, // and persistence of this object is in the map. // The reference to the object can be safely returned here. return *g; } void deleteGroup(CUDTGroup* g); void deleteGroup_LOCKED(CUDTGroup* g); // [[using locked(m_GlobControlLock)]] CUDTGroup* findPeerGroup_LOCKED(SRTSOCKET peergroup) { for (groups_t::iterator i = m_Groups.begin(); i != m_Groups.end(); ++i) { if (i->second->peerid() == peergroup) return i->second; } return NULL; } #endif CEPoll& epoll_ref() { return m_EPoll; } private: /// Generates a new socket ID. This function starts from a randomly /// generated value (at initialization time) and goes backward with /// with next calls. The possible values come from the range without /// the SRTGROUP_MASK bit, and the group bit is set when the ID is /// generated for groups. It is also internally checked if the /// newly generated ID isn't already used by an existing socket or group. /// /// Socket ID value range. /// - [0]: reserved for handshake procedure. If the destination Socket ID is 0 /// (destination Socket ID unknown) the packet will be sent to the listening socket /// or to a socket that is in the rendezvous connection phase. /// - [1; 2 ^ 30): single socket ID range. /// - (2 ^ 30; 2 ^ 31): group socket ID range. Effectively any positive number /// from [1; 2 ^ 30) with bit 30 set to 1. Bit 31 is zero. /// The most significant bit 31 (sign bit) is left unused so that checking for a value <= 0 identifies an invalid socket ID. /// /// @param group The socket id should be for socket group. /// @return The new socket ID. /// @throw CUDTException if after rolling over all possible ID values nothing can be returned SRTSOCKET generateSocketID(bool group = false); private: typedef std::map sockets_t; // stores all the socket structures sockets_t m_Sockets; #if ENABLE_EXPERIMENTAL_BONDING typedef std::map groups_t; groups_t m_Groups; #endif sync::Mutex m_GlobControlLock; // used to synchronize UDT API sync::Mutex m_IDLock; // used to synchronize ID generation SRTSOCKET m_SocketIDGenerator; // seed to generate a new unique socket ID SRTSOCKET m_SocketIDGenerator_init; // Keeps track of the very first one std::map > m_PeerRec;// record sockets from peers to avoid repeated connection request, int64_t = (socker_id << 30) + isn private: friend struct FLookupSocketWithEvent_LOCKED; CUDTSocket* locateSocket(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); // This function does the same as locateSocket, except that: // - lock on m_GlobControlLock is expected (so that you don't unlock between finding and using) // - only return NULL if not found CUDTSocket* locateSocket_LOCKED(SRTSOCKET u); CUDTSocket* locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn); #if ENABLE_EXPERIMENTAL_BONDING CUDTGroup* locateAcquireGroup(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); CUDTGroup* acquireSocketsGroup(CUDTSocket* s); struct GroupKeeper { CUDTGroup* group; // This is intended for API functions to lock the group's existence // for the lifetime of their call. GroupKeeper(CUDTUnited& glob, SRTSOCKET id, ErrorHandling erh) { group = glob.locateAcquireGroup(id, erh); } // This is intended for TSBPD thread that should lock the group's // existence until it exits. GroupKeeper(CUDTUnited& glob, CUDTSocket* s) { group = glob.acquireSocketsGroup(s); } ~GroupKeeper() { if (group) { // We have a guarantee that if `group` was set // as non-NULL here, it is also acquired and will not // be deleted until this busy flag is set back to false. sync::ScopedLock cgroup (*group->exp_groupLock()); group->apiRelease(); // Only now that the group lock is lifted, can the // group be now deleted and this pointer potentially dangling } } }; #endif void updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* = NULL); bool updateListenerMux(CUDTSocket* s, const CUDTSocket* ls); // Utility functions for updateMux void configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af); uint16_t installMuxer(CUDTSocket* w_s, CMultiplexer& sm); bool channelSettingsMatch(const CMultiplexer& m, const CUDTSocket* s); private: std::map m_mMultiplexer; // UDP multiplexer sync::Mutex m_MultiplexerLock; private: CCache* m_pCache; // UDT network information cache private: srt::sync::atomic m_bClosing; sync::Mutex m_GCStopLock; sync::Condition m_GCStopCond; sync::Mutex m_InitLock; int m_iInstanceCount; // number of startup() called by application bool m_bGCStatus; // if the GC thread is working (true) sync::CThread m_GCThread; static void* garbageCollect(void*); sockets_t m_ClosedSockets; // temporarily store closed sockets #if ENABLE_EXPERIMENTAL_BONDING groups_t m_ClosedGroups; #endif void checkBrokenSockets(); void removeSocket(const SRTSOCKET u); CEPoll m_EPoll; // handling epoll data structures and events private: CUDTUnited(const CUDTUnited&); CUDTUnited& operator=(const CUDTUnited&); }; } // namespace srt #endif srt-1.4.4/srtcore/atomic.h000066400000000000000000000254031412557703600154540ustar00rootroot00000000000000//---------------------------------------------------------------------------- // This is free and unencumbered software released into the public domain. // // Anyone is free to copy, modify, publish, use, compile, sell, or distribute // this software, either in source code form or as a compiled binary, for any // purpose, commercial or non-commercial, and by any means. // // In jurisdictions that recognize copyright laws, the author or authors of // this software dedicate any and all copyright interest in the software to the // public domain. We make this dedication for the benefit of the public at // large and to the detriment of our heirs and successors. We intend this // dedication to be an overt act of relinquishment in perpetuity of all present // and future rights to this software under copyright law. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // For more information, please refer to //----------------------------------------------------------------------------- // SRT Project information: // This file was adopted from a Public Domain project from // https://github.com/mbitsnbites/atomic // Only namespaces were changed to adopt it for SRT project. #ifndef SRT_SYNC_ATOMIC_H_ #define SRT_SYNC_ATOMIC_H_ // Macro for disallowing copying of an object. #if __cplusplus >= 201103L #define ATOMIC_DISALLOW_COPY(T) \ T(const T&) = delete; \ T& operator=(const T&) = delete; #else #define ATOMIC_DISALLOW_COPY(T) \ T(const T&); \ T& operator=(const T&); #endif // A portable static assert. #if __cplusplus >= 201103L #define ATOMIC_STATIC_ASSERT(condition, message) \ static_assert((condition), message) #else // Based on: http://stackoverflow.com/a/809465/5778708 #define ATOMIC_STATIC_ASSERT(condition, message) \ _impl_STATIC_ASSERT_LINE(condition, __LINE__) #define _impl_PASTE(a, b) a##b #ifdef __GNUC__ #define _impl_UNUSED __attribute__((__unused__)) #else #define _impl_UNUSED #endif #define _impl_STATIC_ASSERT_LINE(condition, line) \ typedef char _impl_PASTE( \ STATIC_ASSERT_failed_, \ line)[(2 * static_cast(!!(condition))) - 1] _impl_UNUSED #endif #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) // NOTE: Defined at the top level. #elif defined(__APPLE__) && (__cplusplus >= 201103L) // NOTE: Does support c++11 std::atomic, but the compiler may or // may not support GCC atomic intrinsics. So go ahead and use the // std::atomic implementation. #define ATOMIC_USE_CPP11_ATOMIC #elif (defined(__clang__) && defined(__clang_major__) && (__clang_major__ > 5)) \ || defined(__xlc__) // NOTE: Clang <6 does not support GCC __atomic_* intrinsics. I am unsure // about Clang6. Since Clang sets __GNUC__ and __GNUC_MINOR__ of this era // to <4.5, older Clang will catch the setting below to use the // POSIX Mutex Implementation. #define ATOMIC_USE_GCC_INTRINSICS #elif defined(__GNUC__) \ && ( (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) ) // NOTE: The __atomic_* family of intrisics were introduced in GCC-4.7.0. // NOTE: This follows #if defined(__clang__), because most if, not all, // versions of Clang define __GNUC__ and __GNUC_MINOR__ but often define // them to 4.4 or an even earlier version. Most of the newish versions // of Clang also support GCC Atomic Intrisics even if they set GCC version // macros to <4.7. #define ATOMIC_USE_GCC_INTRINSICS #elif defined(__GNUC__) && !defined(ATOMIC_USE_SRT_SYNC_MUTEX) // NOTE: GCC compiler built-ins for atomic operations are pure // compiler extensions prior to GCC-4.7 and were grouped into the // the __sync_* family of functions. GCC-4.7, both the c++11 and C11 // standards had been finalized, and GCC updated their built-ins to // better reflect the new memory model and the new functions grouped // into the __atomic_* family. Also the memory models were defined // differently, than in pre 4.7. // TODO: PORT to the pre GCC-4.7 __sync_* intrinsics. In the meantime use // the POSIX Mutex Implementation. #define ATOMIC_USE_SRT_SYNC_MUTEX 1 #elif defined(_MSC_VER) #define ATOMIC_USE_MSVC_INTRINSICS #include "atomic_msvc.h" #elif __cplusplus >= 201103L #define ATOMIC_USE_CPP11_ATOMIC #else #error Unsupported compiler / system. #endif // Include any necessary headers for the selected Atomic Implementation. #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) #include "sync.h" #endif #if defined(ATOMIC_USE_CPP11_ATOMIC) #include #endif namespace srt { namespace sync { template class atomic { public: ATOMIC_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Only types of size 1, 2, 4 or 8 are supported"); atomic() : value_(static_cast(0)) #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) , mutex_() #endif { // No-Op } explicit atomic(const T value) : value_(value) #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) , mutex_() #endif { // No-Op } ~atomic() { // No-Op } /// @brief Performs an atomic increment operation (value + 1). /// @returns The new value of the atomic object. T operator++() { #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) ScopedLock lg_(mutex_); const T t = ++value_; return t; #elif defined(ATOMIC_USE_GCC_INTRINSICS) return __atomic_add_fetch(&value_, 1, __ATOMIC_SEQ_CST); #elif defined(ATOMIC_USE_MSVC_INTRINSICS) return msvc::interlocked::increment(&value_); #elif defined(ATOMIC_USE_CPP11_ATOMIC) return ++value_; #else #error "Implement Me." #endif } /// @brief Performs an atomic decrement operation (value - 1). /// @returns The new value of the atomic object. T operator--() { #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) ScopedLock lg_(mutex_); const T t = --value_; return t; #elif defined(ATOMIC_USE_GCC_INTRINSICS) return __atomic_sub_fetch(&value_, 1, __ATOMIC_SEQ_CST); #elif defined(ATOMIC_USE_MSVC_INTRINSICS) return msvc::interlocked::decrement(&value_); #elif defined(ATOMIC_USE_CPP11_ATOMIC) return --value_; #else #error "Implement Me." #endif } /// @brief Performs an atomic compare-and-swap (CAS) operation. /// /// The value of the atomic object is only updated to the new value if the /// old value of the atomic object matches @c expected_val. /// /// @param expected_val The expected value of the atomic object. /// @param new_val The new value to write to the atomic object. /// @returns True if new_value was written to the atomic object. bool compare_exchange(const T expected_val, const T new_val) { #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) ScopedLock lg_(mutex_); bool result = false; if (expected_val == value_) { value_ = new_val; result = true; } return result; #elif defined(ATOMIC_USE_GCC_INTRINSICS) T e = expected_val; return __atomic_compare_exchange_n( &value_, &e, new_val, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); #elif defined(ATOMIC_USE_MSVC_INTRINSICS) const T old_val = msvc::interlocked::compare_exchange(&value_, new_val, expected_val); return (old_val == expected_val); #elif defined(ATOMIC_USE_CPP11_ATOMIC) T e = expected_val; return value_.compare_exchange_weak(e, new_val); #else #error "Implement Me." #endif } /// @brief Performs an atomic set operation. /// /// The value of the atomic object is unconditionally updated to the new /// value. /// /// @param new_val The new value to write to the atomic object. void store(const T new_val) { #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) ScopedLock lg_(mutex_); value_ = new_val; #elif defined(ATOMIC_USE_GCC_INTRINSICS) __atomic_store_n(&value_, new_val, __ATOMIC_SEQ_CST); #elif defined(ATOMIC_USE_MSVC_INTRINSICS) (void)msvc::interlocked::exchange(&value_, new_val); #elif defined(ATOMIC_USE_CPP11_ATOMIC) value_.store(new_val); #else #error "Implement Me." #endif } /// @returns the current value of the atomic object. /// @note Be careful about how this is used, since any operations on the /// returned value are inherently non-atomic. T load() const { #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) ScopedLock lg_(mutex_); const T t = value_; return t; #elif defined(ATOMIC_USE_GCC_INTRINSICS) return __atomic_load_n(&value_, __ATOMIC_SEQ_CST); #elif defined(ATOMIC_USE_MSVC_INTRINSICS) // TODO(m): Is there a better solution for MSVC? return value_; #elif defined(ATOMIC_USE_CPP11_ATOMIC) return value_; #else #error "Implement Me." #endif } /// @brief Performs an atomic exchange operation. /// /// The value of the atomic object is unconditionally updated to the new /// value, and the old value is returned. /// /// @param new_val The new value to write to the atomic object. /// @returns the old value. T exchange(const T new_val) { #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) ScopedLock lg_(mutex_); const T t = value_; value_ = new_val; return t; #elif defined(ATOMIC_USE_GCC_INTRINSICS) return __atomic_exchange_n(&value_, new_val, __ATOMIC_SEQ_CST); #elif defined(ATOMIC_USE_MSVC_INTRINSICS) return msvc::interlocked::exchange(&value_, new_val); #elif defined(ATOMIC_USE_CPP11_ATOMIC) return value_.exchange(new_val); #else #error "Implement Me." #endif } T operator=(const T new_value) { store(new_value); return new_value; } operator T() const { return load(); } private: #if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) T value_; mutable Mutex mutex_; #elif defined(ATOMIC_USE_GCC_INTRINSICS) volatile T value_; #elif defined(ATOMIC_USE_MSVC_INTRINSICS) volatile T value_; #elif defined(ATOMIC_USE_CPP11_ATOMIC) std::atomic value_; #else #error "Implement Me. (value_ type)" #endif ATOMIC_DISALLOW_COPY(atomic) }; } // namespace sync } // namespace srt // Undef temporary defines. #undef ATOMIC_USE_GCC_INTRINSICS #undef ATOMIC_USE_MSVC_INTRINSICS #undef ATOMIC_USE_CPP11_ATOMIC #endif // ATOMIC_ATOMIC_H_ srt-1.4.4/srtcore/atomic_clock.h000066400000000000000000000035201412557703600166230ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2021 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_SYNC_ATOMIC_CLOCK_H #define INC_SRT_SYNC_ATOMIC_CLOCK_H #include "sync.h" #include "atomic.h" namespace srt { namespace sync { template class AtomicDuration { atomic dur; typedef typename Clock::duration duration_type; typedef typename Clock::time_point time_point_type; public: AtomicDuration() ATR_NOEXCEPT : dur(0) {} duration_type load() { int64_t val = dur.load(); return duration_type(val); } void store(const duration_type& d) { dur.store(d.count()); } AtomicDuration& operator=(const duration_type& s) { dur = s.count(); return *this; } operator duration_type() const { return duration_type(dur); } }; template class AtomicClock { atomic dur; typedef typename Clock::duration duration_type; typedef typename Clock::time_point time_point_type; public: AtomicClock() ATR_NOEXCEPT : dur(0) {} time_point_type load() const { int64_t val = dur.load(); return time_point_type(duration_type(val)); } void store(const time_point_type& d) { dur.store(uint64_t(d.time_since_epoch().count())); } AtomicClock& operator=(const time_point_type& s) { dur = s.time_since_epoch().count(); return *this; } operator time_point_type() const { return time_point_type(duration_type(dur.load())); } }; } // namespace sync } // namespace srt #endif // INC_SRT_SYNC_ATOMIC_CLOCK_H srt-1.4.4/srtcore/atomic_msvc.h000066400000000000000000000216661412557703600165130ustar00rootroot00000000000000//----------------------------------------------------------------------------- // This is free and unencumbered software released into the public domain. // // Anyone is free to copy, modify, publish, use, compile, sell, or distribute // this software, either in source code form or as a compiled binary, for any // purpose, commercial or non-commercial, and by any means. // // In jurisdictions that recognize copyright laws, the author or authors of // this software dedicate any and all copyright interest in the software to the // public domain. We make this dedication for the benefit of the public at // large and to the detriment of our heirs and successors. We intend this // dedication to be an overt act of relinquishment in perpetuity of all present // and future rights to this software under copyright law. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // For more information, please refer to //----------------------------------------------------------------------------- // SRT Project information: // This file was adopted from a Public Domain project from // https://github.com/mbitsnbites/atomic // Only namespaces were changed to adopt it for SRT project. #ifndef SRT_SYNC_ATOMIC_MSVC_H_ #define SRT_SYNC_ATOMIC_MSVC_H_ // Define which functions we need (don't include ). extern "C" { short _InterlockedIncrement16(short volatile*); long _InterlockedIncrement(long volatile*); __int64 _InterlockedIncrement64(__int64 volatile*); short _InterlockedDecrement16(short volatile*); long _InterlockedDecrement(long volatile*); __int64 _InterlockedDecrement64(__int64 volatile*); char _InterlockedExchange8(char volatile*, char); short _InterlockedExchange16(short volatile*, short); long __cdecl _InterlockedExchange(long volatile*, long); __int64 _InterlockedExchange64(__int64 volatile*, __int64); char _InterlockedCompareExchange8(char volatile*, char, char); short _InterlockedCompareExchange16(short volatile*, short, short); long __cdecl _InterlockedCompareExchange(long volatile*, long, long); __int64 _InterlockedCompareExchange64(__int64 volatile*, __int64, __int64); }; // Define which functions we want to use as inline intriniscs. #pragma intrinsic(_InterlockedIncrement) #pragma intrinsic(_InterlockedIncrement16) #pragma intrinsic(_InterlockedDecrement) #pragma intrinsic(_InterlockedDecrement16) #pragma intrinsic(_InterlockedCompareExchange) #pragma intrinsic(_InterlockedCompareExchange8) #pragma intrinsic(_InterlockedCompareExchange16) #pragma intrinsic(_InterlockedExchange) #pragma intrinsic(_InterlockedExchange8) #pragma intrinsic(_InterlockedExchange16) #if defined(_M_X64) #pragma intrinsic(_InterlockedIncrement64) #pragma intrinsic(_InterlockedDecrement64) #pragma intrinsic(_InterlockedCompareExchange64) #pragma intrinsic(_InterlockedExchange64) #endif // _M_X64 namespace srt { namespace sync { namespace msvc { template struct interlocked { }; template struct interlocked { static inline T increment(T volatile* x) { // There's no _InterlockedIncrement8(). char old_val, new_val; do { old_val = static_cast(*x); new_val = old_val + static_cast(1); } while (_InterlockedCompareExchange8(reinterpret_cast(x), new_val, old_val) != old_val); return static_cast(new_val); } static inline T decrement(T volatile* x) { // There's no _InterlockedDecrement8(). char old_val, new_val; do { old_val = static_cast(*x); new_val = old_val - static_cast(1); } while (_InterlockedCompareExchange8(reinterpret_cast(x), new_val, old_val) != old_val); return static_cast(new_val); } static inline T compare_exchange(T volatile* x, const T new_val, const T expected_val) { return static_cast( _InterlockedCompareExchange8(reinterpret_cast(x), static_cast(new_val), static_cast(expected_val))); } static inline T exchange(T volatile* x, const T new_val) { return static_cast(_InterlockedExchange8( reinterpret_cast(x), static_cast(new_val))); } }; template struct interlocked { static inline T increment(T volatile* x) { return static_cast( _InterlockedIncrement16(reinterpret_cast(x))); } static inline T decrement(T volatile* x) { return static_cast( _InterlockedDecrement16(reinterpret_cast(x))); } static inline T compare_exchange(T volatile* x, const T new_val, const T expected_val) { return static_cast( _InterlockedCompareExchange16(reinterpret_cast(x), static_cast(new_val), static_cast(expected_val))); } static inline T exchange(T volatile* x, const T new_val) { return static_cast( _InterlockedExchange16(reinterpret_cast(x), static_cast(new_val))); } }; template struct interlocked { static inline T increment(T volatile* x) { return static_cast( _InterlockedIncrement(reinterpret_cast(x))); } static inline T decrement(T volatile* x) { return static_cast( _InterlockedDecrement(reinterpret_cast(x))); } static inline T compare_exchange(T volatile* x, const T new_val, const T expected_val) { return static_cast( _InterlockedCompareExchange(reinterpret_cast(x), static_cast(new_val), static_cast(expected_val))); } static inline T exchange(T volatile* x, const T new_val) { return static_cast(_InterlockedExchange( reinterpret_cast(x), static_cast(new_val))); } }; template struct interlocked { static inline T increment(T volatile* x) { #if defined(_M_X64) return static_cast( _InterlockedIncrement64(reinterpret_cast(x))); #else // There's no _InterlockedIncrement64() for 32-bit x86. __int64 old_val, new_val; do { old_val = static_cast<__int64>(*x); new_val = old_val + static_cast<__int64>(1); } while (_InterlockedCompareExchange64( reinterpret_cast(x), new_val, old_val) != old_val); return static_cast(new_val); #endif // _M_X64 } static inline T decrement(T volatile* x) { #if defined(_M_X64) return static_cast( _InterlockedDecrement64(reinterpret_cast(x))); #else // There's no _InterlockedDecrement64() for 32-bit x86. __int64 old_val, new_val; do { old_val = static_cast<__int64>(*x); new_val = old_val - static_cast<__int64>(1); } while (_InterlockedCompareExchange64( reinterpret_cast(x), new_val, old_val) != old_val); return static_cast(new_val); #endif // _M_X64 } static inline T compare_exchange(T volatile* x, const T new_val, const T expected_val) { return static_cast(_InterlockedCompareExchange64( reinterpret_cast(x), static_cast(new_val), static_cast(expected_val))); } static inline T exchange(T volatile* x, const T new_val) { #if defined(_M_X64) return static_cast( _InterlockedExchange64(reinterpret_cast(x), static_cast(new_val))); #else // There's no _InterlockedExchange64 for 32-bit x86. __int64 old_val; do { old_val = static_cast<__int64>(*x); } while (_InterlockedCompareExchange64( reinterpret_cast(x), new_val, old_val) != old_val); return static_cast(old_val); #endif // _M_X64 } }; } // namespace msvc } // namespace sync } // namespace srt #endif // ATOMIC_ATOMIC_MSVC_H_ srt-1.4.4/srtcore/buffer.cpp000066400000000000000000002365311412557703600160120ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 03/12/2011 modified by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include #include #include "buffer.h" #include "packet.h" #include "core.h" // provides some constants #include "logging.h" using namespace std; using namespace srt_logging; using namespace srt; using namespace srt::sync; // You can change this value at build config by using "ENFORCE" options. #if !defined(SRT_MAVG_SAMPLING_RATE) #define SRT_MAVG_SAMPLING_RATE 40 #endif bool AvgBufSize::isTimeToUpdate(const time_point& now) const { const int usMAvgBasePeriod = 1000000; // 1s in microseconds const int us2ms = 1000; const int msMAvgPeriod = (usMAvgBasePeriod / SRT_MAVG_SAMPLING_RATE) / us2ms; const uint64_t elapsed_ms = count_milliseconds(now - m_tsLastSamplingTime); // ms since last sampling return (elapsed_ms >= msMAvgPeriod); } void AvgBufSize::update(const steady_clock::time_point& now, int pkts, int bytes, int timespan_ms) { const uint64_t elapsed_ms = count_milliseconds(now - m_tsLastSamplingTime); // ms since last sampling m_tsLastSamplingTime = now; const uint64_t one_second_in_ms = 1000; if (elapsed_ms > one_second_in_ms) { // No sampling in last 1 sec, initialize average m_dCountMAvg = pkts; m_dBytesCountMAvg = bytes; m_dTimespanMAvg = timespan_ms; return; } // // weight last average value between -1 sec and last sampling time (LST) // and new value between last sampling time and now // |elapsed_ms| // +----------------------------------+-------+ // -1 LST 0(now) // m_dCountMAvg = avg_iir_w<1000, double>(m_dCountMAvg, pkts, elapsed_ms); m_dBytesCountMAvg = avg_iir_w<1000, double>(m_dBytesCountMAvg, bytes, elapsed_ms); m_dTimespanMAvg = avg_iir_w<1000, double>(m_dTimespanMAvg, timespan_ms, elapsed_ms); } int round_val(double val) { return static_cast(round(val)); } 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) , m_iBytesCount(0) , m_iInRatePktsCount(0) , m_iInRateBytesCount(0) , m_InRatePeriod(INPUTRATE_FAST_START_US) // 0.5 sec (fast start) , m_iInRateBps(INPUTRATE_INITIAL_BYTESPS) { // 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; setupMutex(m_BufLock, "Buf"); } 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; } releaseMutex(m_BufLock); } void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) { int32_t& w_msgno = w_mctrl.msgno; int32_t& w_seqno = w_mctrl.pktseq; int64_t& w_srctime = w_mctrl.srctime; const int& ttl = w_mctrl.msgttl; int size = len / m_iMSS; if ((len % m_iMSS) != 0) size++; HLOGC(bslog.Debug, log << "addBuffer: size=" << m_iCount << " reserved=" << m_iSize << " needs=" << size << " buffers for " << len << " bytes"); // dynamically increase sender buffer while (size + m_iCount >= m_iSize) { HLOGC(bslog.Debug, log << "addBuffer: ... still lacking " << (size + m_iCount - m_iSize) << " buffers..."); increase(); } const steady_clock::time_point time = steady_clock::now(); const int32_t inorder = w_mctrl.inorder ? MSGNO_PACKET_INORDER::mask : 0; HLOGC(bslog.Debug, log << CONID() << "addBuffer: adding " << size << " packets (" << len << " bytes) to send, msgno=" << (w_msgno > 0 ? w_msgno : m_iNextMsgNo) << (inorder ? "" : " NOT") << " in order"); // The sequence number passed to this function is the sequence number // that the very first packet from the packet series should get here. // If there's more than one packet, this function must increase it by itself // and then return the accordingly modified sequence number in the reference. Block* s = m_pLastBlock; if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied { HLOGC(bslog.Debug, log << "addBuffer: using internally managed msgno=" << m_iNextMsgNo); w_msgno = m_iNextMsgNo; } else { HLOGC(bslog.Debug, log << "addBuffer: OVERWRITTEN by msgno supplied by caller: msgno=" << w_msgno); m_iNextMsgNo = w_msgno; } for (int i = 0; i < size; ++i) { int pktlen = len - i * m_iMSS; if (pktlen > m_iMSS) pktlen = m_iMSS; HLOGC(bslog.Debug, log << "addBuffer: %" << w_seqno << " #" << w_msgno << " spreading from=" << (i * m_iMSS) << " size=" << pktlen << " TO BUFFER:" << (void*)s->m_pcData); memcpy((s->m_pcData), data + i * m_iMSS, pktlen); s->m_iLength = pktlen; s->m_iSeqNo = w_seqno; w_seqno = CSeqNo::incseq(w_seqno); 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 s->m_llSourceTime_us = w_srctime; s->m_tsOriginTime = time; s->m_tsRexmitTime = time_point(); s->m_iTTL = ttl; // Rewrite the actual sending time back into w_srctime // so that the calling facilities can reuse it if (!w_srctime) w_srctime = count_microseconds(s->m_tsOriginTime.time_since_epoch()); // XXX unchecked condition: s->m_pNext == NULL. // Should never happen, as the call to increase() should ensure enough buffers. SRT_ASSERT(s->m_pNext); s = s->m_pNext; } m_pLastBlock = s; enterCS(m_BufLock); m_iCount += size; m_iBytesCount += len; m_tsLastOriginTime = time; updateInputRate(time, size, len); updAvgBufSize(time); leaveCS(m_BufLock); // MSGNO_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. const int nextmsgno = ++MsgNo(m_iNextMsgNo); HLOGC(bslog.Debug, log << "CSndBuffer::addBuffer: updating msgno: #" << m_iNextMsgNo << " -> #" << nextmsgno); m_iNextMsgNo = nextmsgno; } void CSndBuffer::setInputRateSmpPeriod(int period) { m_InRatePeriod = (uint64_t)period; //(usec) 0=no input rate calculation } void CSndBuffer::updateInputRate(const steady_clock::time_point& time, int pkts, int bytes) { // no input rate calculation if (m_InRatePeriod == 0) return; if (is_zero(m_tsInRateStartTime)) { m_tsInRateStartTime = time; return; } m_iInRatePktsCount += pkts; m_iInRateBytesCount += bytes; // Trigger early update in fast start mode const bool early_update = (m_InRatePeriod < INPUTRATE_RUNNING_US) && (m_iInRatePktsCount > INPUTRATE_MAX_PACKETS); const uint64_t period_us = count_microseconds(time - m_tsInRateStartTime); if (early_update || period_us > m_InRatePeriod) { // Required Byte/sec rate (payload + headers) m_iInRateBytesCount += (m_iInRatePktsCount * srt::CPacket::SRT_DATA_HDR_SIZE); m_iInRateBps = (int)(((int64_t)m_iInRateBytesCount * 1000000) / period_us); HLOGC(bslog.Debug, log << "updateInputRate: pkts:" << m_iInRateBytesCount << " bytes:" << m_iInRatePktsCount << " rate=" << (m_iInRateBps * 8) / 1000 << "kbps interval=" << period_us); m_iInRatePktsCount = 0; m_iInRateBytesCount = 0; m_tsInRateStartTime = time; setInputRateSmpPeriod(INPUTRATE_RUNNING_US); } } int CSndBuffer::addBufferFromFile(fstream& ifs, int len) { int size = len / m_iMSS; if ((len % m_iMSS) != 0) size++; HLOGC(bslog.Debug, log << "addBufferFromFile: size=" << m_iCount << " reserved=" << m_iSize << " needs=" << size << " buffers for " << len << " bytes"); // dynamically increase sender buffer while (size + m_iCount >= m_iSize) { HLOGC(bslog.Debug, log << "addBufferFromFile: ... still lacking " << (size + m_iCount - m_iSize) << " buffers..."); increase(); } HLOGC(bslog.Debug, log << CONID() << "addBufferFromFile: adding " << size << " packets (" << len << " bytes) to send, msgno=" << m_iNextMsgNo); 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; HLOGC(bslog.Debug, log << "addBufferFromFile: reading from=" << (i * m_iMSS) << " size=" << pktlen << " TO BUFFER:" << (void*)s->m_pcData); ifs.read(s->m_pcData, pktlen); if ((pktlen = int(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 = SRT_MSGTTL_INF; s = s->m_pNext; total += pktlen; } m_pLastBlock = s; enterCS(m_BufLock); m_iCount += size; m_iBytesCount += total; leaveCS(m_BufLock); m_iNextMsgNo++; if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) m_iNextMsgNo = 1; return total; } steady_clock::time_point CSndBuffer::getSourceTime(const CSndBuffer::Block& block) { if (block.m_llSourceTime_us) { return steady_clock::time_point() + microseconds_from(block.m_llSourceTime_us); } return block.m_tsOriginTime; } int CSndBuffer::readData(srt::CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs) { // No data to read if (m_pCurrBlock == m_pLastBlock) return 0; // Make the packet REFLECT the data stored in the buffer. w_packet.m_pcData = m_pCurrBlock->m_pcData; int readlen = m_pCurrBlock->m_iLength; w_packet.setLength(readlen); w_packet.m_iSeqNo = m_pCurrBlock->m_iSeqNo; // XXX This is probably done because the encryption should happen // just once, and so this sets the encryption flags to both msgno bitset // IN THE PACKET and IN THE BLOCK. This is probably to make the encryption // happen at the time when scheduling a new packet to send, but the packet // must remain in the send buffer until it's ACKed. For the case of rexmit // the packet will be taken "as is" (that is, already encrypted). // // The problem is in the order of things: // 0. When the application stores the data, some of the flags for PH_MSGNO are set. // 1. The readData() is called to get the original data sent by the application. // 2. The data are original and must be encrypted. They WILL BE encrypted, later. // 3. So far we are in readData() so the encryption flags must be updated NOW because // later we won't have access to the block's data. // 4. After exiting from readData(), the packet is being encrypted. It's immediately // sent, however the data must remain in the sending buffer until they are ACKed. // 5. In case when rexmission is needed, the second overloaded version of readData // is being called, and the buffer + PH_MSGNO value is extracted. All interesting // flags must be present and correct at that time. // // The only sensible way to fix this problem is to encrypt the packet not after // extracting from here, but when the packet is stored into CSndBuffer. The appropriate // flags for PH_MSGNO will be applied directly there. Then here the value for setting // PH_MSGNO will be set as is. if (kflgs == -1) { HLOGC(bslog.Debug, log << CONID() << " CSndBuffer: ERROR: encryption required and not possible. NOT SENDING."); readlen = 0; } else { m_pCurrBlock->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); } w_packet.m_iMsgNo = m_pCurrBlock->m_iMsgNoBitset; w_srctime = getSourceTime(*m_pCurrBlock); m_pCurrBlock = m_pCurrBlock->m_pNext; HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send"); return readlen; } int32_t CSndBuffer::getMsgNoAt(const int offset) { ScopedLock bufferguard(m_BufLock); Block* p = m_pFirstBlock; if (p) { HLOGC(bslog.Debug, log << "CSndBuffer::getMsgNoAt: FIRST MSG: size=" << p->m_iLength << " %" << p->m_iSeqNo << " #" << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); } if (offset >= m_iCount) { // Prevent accessing the last "marker" block LOGC(bslog.Error, log << "CSndBuffer::getMsgNoAt: IPE: offset=" << offset << " not found, max offset=" << m_iCount); return SRT_MSGNO_CONTROL; } // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. int i; Block* ee SRT_ATR_UNUSED = 0; for (i = 0; i < offset && p; ++i) { ee = p; p = p->m_pNext; } if (!p) { LOGC(bslog.Error, log << "CSndBuffer::getMsgNoAt: IPE: offset=" << offset << " not found, stopped at " << i << " with #" << (ee ? ee->getMsgSeq() : SRT_MSGNO_NONE)); return SRT_MSGNO_CONTROL; } HLOGC(bslog.Debug, log << "CSndBuffer::getMsgNoAt: offset=" << offset << " found, size=" << p->m_iLength << " %" << p->m_iSeqNo << " #" << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); return p->getMsgSeq(); } int CSndBuffer::readData(const int offset, srt::CPacket& w_packet, steady_clock::time_point& w_srctime, int& w_msglen) { int32_t& msgno_bitset = w_packet.m_iMsgNo; ScopedLock bufferguard(m_BufLock); Block* p = m_pFirstBlock; // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. for (int i = 0; i < offset && p != m_pLastBlock; ++i) { p = p->m_pNext; } if (p == m_pLastBlock) { LOGC(qslog.Error, log << "CSndBuffer::readData: offset " << offset << " too large!"); return 0; } #if ENABLE_HEAVY_LOGGING const int32_t first_seq = p->m_iSeqNo; int32_t last_seq = p->m_iSeqNo; #endif // 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 // (This is for messages that have declared TTL - messages that fail to be sent // before the TTL defined time comes, will be dropped). if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - p->m_tsOriginTime) > p->m_iTTL)) { int32_t msgno = p->getMsgSeq(); w_msglen = 1; p = p->m_pNext; bool move = false; while (p != m_pLastBlock && msgno == p->getMsgSeq()) { #if ENABLE_HEAVY_LOGGING last_seq = p->m_iSeqNo; #endif if (p == m_pCurrBlock) move = true; p = p->m_pNext; if (move) m_pCurrBlock = p; w_msglen++; } HLOGC(qslog.Debug, log << "CSndBuffer::readData: due to TTL exceeded, SEQ " << first_seq << " - " << last_seq << ", " << w_msglen << " packets to drop, msgno=" << msgno); // 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; } w_packet.m_pcData = p->m_pcData; int readlen = p->m_iLength; w_packet.setLength(readlen); // XXX Here the value predicted to be applied to PH_MSGNO field is extracted. // As this function is predicted to extract the data to send as a rexmited packet, // the packet must be in the form ready to send - so, in case of encryption, // encrypted, and with all ENC flags already set. So, the first call to send // the packet originally (the other overload of this function) must set these // flags. w_packet.m_iMsgNo = p->m_iMsgNoBitset; w_srctime = getSourceTime(*p); // This function is called when packet retransmission is triggered. // Therefore we are setting the rexmit time. p->m_tsRexmitTime = steady_clock::now(); HLOGC(qslog.Debug, log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.m_iSeqNo << " size=" << readlen << " to send [REXMIT]"); return readlen; } srt::sync::steady_clock::time_point CSndBuffer::getPacketRexmitTime(const int offset) { ScopedLock bufferguard(m_BufLock); const Block* p = m_pFirstBlock; // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. for (int i = 0; i < offset; ++i) { SRT_ASSERT(p); p = p->m_pNext; } SRT_ASSERT(p); return p->m_tsRexmitTime; } void CSndBuffer::ackData(int offset) { ScopedLock bufferguard(m_BufLock); 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; m_iCount -= offset; updAvgBufSize(steady_clock::now()); } int CSndBuffer::getCurrBufSize() const { return m_iCount; } int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) { ScopedLock bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */ /* update stats in case there was no add/ack activity lately */ updAvgBufSize(steady_clock::now()); // Average number of packets and timespan could be small, // so rounding is beneficial, while for the number of // bytes in the buffer is a higher value, so rounding can be omitted, // but probably better to round all three values. w_bytes = round_val(m_mavg.bytes()); w_tsp = round_val(m_mavg.timespan_ms()); return round_val(m_mavg.pkts()); } void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now) { if (!m_mavg.isTimeToUpdate(now)) return; int bytes = 0; int timespan_ms = 0; const int pkts = getCurrBufSize((bytes), (timespan_ms)); m_mavg.update(now, pkts, bytes, timespan_ms); } int CSndBuffer::getCurrBufSize(int& w_bytes, int& w_timespan) { w_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. */ w_timespan = 0 < m_iCount ? count_milliseconds(m_tsLastOriginTime - m_pFirstBlock->m_tsOriginTime) + 1 : 0; return m_iCount; } int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_clock::time_point& too_late_time) { int dpkts = 0; int dbytes = 0; bool move = false; int32_t msgno = 0; ScopedLock bufferguard(m_BufLock); for (int i = 0; i < m_iCount && m_pFirstBlock->m_tsOriginTime < too_late_time; ++i) { dpkts++; dbytes += m_pFirstBlock->m_iLength; msgno = m_pFirstBlock->getMsgSeq(); 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; w_bytes = dbytes; // We report the increased number towards the last ever seen // by the loop, as this last one is the last received. So remained // (even if "should remain") is the first after the last removed one. w_first_msgno = ++MsgNo(msgno); updAvgBufSize(steady_clock::now()); return (dpkts); } 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; HLOGC(bslog.Debug, log << "CSndBuffer: BUFFER FULL - adding " << (unitsize * m_iMSS) << " bytes spread to " << unitsize << " blocks" << " (total size: " << m_iSize << " bytes)"); } //////////////////////////////////////////////////////////////////////////////// /* * 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. // 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 CRcvBuffer::CRcvBuffer(CUnitQueue* queue, int bufsize_pkts) : m_pUnit(NULL) , m_iSize(bufsize_pkts) , m_pUnitQueue(queue) , m_iStartPos(0) , m_iLastAckPos(0) , m_iMaxPos(0) , m_iNotch(0) , m_BytesCountLock() , m_iBytesCount(0) , m_iAckedPktsCount(0) , m_iAckedBytesCount(0) , m_uAvgPayloadSz(7 * 188) { 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 setupMutex(m_BytesCountLock, "BytesCount"); } CRcvBuffer::~CRcvBuffer() { for (int i = 0; i < m_iSize; ++i) { if (m_pUnit[i] != NULL) { m_pUnitQueue->makeUnitFree(m_pUnit[i]); } } delete[] m_pUnit; releaseMutex(m_BytesCountLock); } 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) */ ScopedLock 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_uAvgPayloadSz = ((m_uAvgPayloadSz * (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 */ } } int CRcvBuffer::addData(CUnit* unit, int offset) { SRT_ASSERT(unit != NULL); if (offset >= getAvailBufSize()) return -1; const int pos = (m_iLastAckPos + offset) % m_iSize; if (offset >= m_iMaxPos) m_iMaxPos = offset + 1; if (m_pUnit[pos] != NULL) { HLOGC(qrlog.Debug, log << "addData: unit %" << unit->m_Packet.m_iSeqNo << " rejected, already exists"); return -1; } m_pUnit[pos] = unit; countBytes(1, (int)unit->m_Packet.getLength()); m_pUnitQueue->makeUnitGood(unit); HLOGC(qrlog.Debug, log << "addData: unit %" << unit->m_Packet.m_iSeqNo << " accepted, off=" << offset << " POS=" << pos); return 0; } int CRcvBuffer::readBuffer(char* data, int len) { int p = m_iStartPos; int lastack = m_iLastAckPos; int rs = len; IF_HEAVY_LOGGING(char* begin = data); const bool bTsbPdEnabled = m_tsbpd.isEnabled(); const steady_clock::time_point now = (bTsbPdEnabled ? steady_clock::now() : steady_clock::time_point()); HLOGC(brlog.Debug, log << CONID() << "readBuffer: start=" << p << " lastack=" << lastack); while ((p != lastack) && (rs > 0)) { if (m_pUnit[p] == NULL) { LOGC(brlog.Error, log << CONID() << " IPE readBuffer on null packet pointer"); return -1; } const srt::CPacket& pkt = m_pUnit[p]->m_Packet; if (bTsbPdEnabled) { HLOGC(brlog.Debug, log << CONID() << "readBuffer: chk if time2play:" << " NOW=" << FormatTime(now) << " PKT TS=" << FormatTime(getPktTsbPdTime(pkt.getMsgTimeStamp()))); if ((getPktTsbPdTime(pkt.getMsgTimeStamp()) > now)) break; /* too early for this unit, return whatever was copied */ } const int pktlen = (int) pkt.getLength(); const int remain_pktlen = pktlen - m_iNotch; const int unitsize = std::min(remain_pktlen, rs); HLOGC(brlog.Debug, log << CONID() << "readBuffer: copying buffer #" << p << " targetpos=" << int(data - begin) << " sourcepos=" << m_iNotch << " size=" << unitsize << " left=" << (unitsize - rs)); memcpy((data), pkt.m_pcData + m_iNotch, unitsize); data += unitsize; if (rs >= remain_pktlen) { freeUnitAt(p); p = shiftFwd(p); m_iNotch = 0; } else m_iNotch += rs; rs -= unitsize; } /* we removed acked bytes form receive buffer */ countBytes(-1, -(len - rs), true); m_iStartPos = p; return len - rs; } int CRcvBuffer::readBufferToFile(fstream& ofs, int len) { int p = m_iStartPos; int lastack = m_iLastAckPos; int rs = len; int32_t trace_seq SRT_ATR_UNUSED = SRT_SEQNO_NONE; int trace_shift SRT_ATR_UNUSED = -1; while ((p != lastack) && (rs > 0)) { #if ENABLE_LOGGING ++trace_shift; #endif // Skip empty units. Note that this shouldn't happen // in case of a file transfer. if (!m_pUnit[p]) { p = shiftFwd(p); LOGC(brlog.Error, log << "readBufferToFile: IPE: NULL unit found in file transmission, last good %" << trace_seq << " + " << trace_shift); continue; } const srt::CPacket& pkt = m_pUnit[p]->m_Packet; #if ENABLE_LOGGING trace_seq = pkt.getSeqNo(); #endif const int pktlen = (int) pkt.getLength(); const int remain_pktlen = pktlen - m_iNotch; const int unitsize = std::min(remain_pktlen, rs); ofs.write(pkt.m_pcData + m_iNotch, unitsize); if (ofs.fail()) break; if (rs >= remain_pktlen) { freeUnitAt(p); p = shiftFwd(p); m_iNotch = 0; } else m_iNotch += rs; rs -= unitsize; } /* we removed acked bytes form receive buffer */ countBytes(-1, -(len - rs), true); m_iStartPos = p; return len - rs; } int CRcvBuffer::ackData(int len) { SRT_ASSERT(len < m_iSize); SRT_ASSERT(len > 0); int end = shift(m_iLastAckPos, len); { int pkts = 0; int bytes = 0; for (int i = m_iLastAckPos; i != end; i = shiftFwd(i)) { if (m_pUnit[i] == NULL) continue; pkts++; bytes += (int)m_pUnit[i]->m_Packet.getLength(); } if (pkts > 0) countBytes(pkts, bytes, true); } HLOGC(brlog.Debug, log << "ackData: shift by " << len << ", start=" << m_iStartPos << " end=" << m_iLastAckPos << " -> " << end); m_iLastAckPos = end; m_iMaxPos -= len; if (m_iMaxPos < 0) m_iMaxPos = 0; // Returned value is the distance towards the starting // position from m_iLastAckPos, which is in sync with CUDT::m_iRcvLastSkipAck. // This should help determine the sequence number at first read-ready position. const int dist = m_iLastAckPos - m_iStartPos; if (dist < 0) return dist + m_iSize; return dist; } 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; } size_t CRcvBuffer::dropData(int len) { // This function does the same as skipData, although skipData // should work in the condition of absence of data, so no need // to force the units in the range to be freed. This function // works in more general condition where we don't know if there // are any data in the given range, but want to remove these // "sequence positions" from the buffer, whether there are data // at them or not. size_t stats_bytes = 0; int p = m_iStartPos; int past_q = shift(p, len); while (p != past_q) { if (m_pUnit[p] && m_pUnit[p]->m_iFlag == CUnit::GOOD) { stats_bytes += m_pUnit[p]->m_Packet.getLength(); freeUnitAt(p); } p = shiftFwd(p); } m_iStartPos = past_q; return stats_bytes; } bool CRcvBuffer::getRcvFirstMsg(steady_clock::time_point& w_tsbpdtime, bool& w_passack, int32_t& w_skipseqno, int32_t& w_curpktseq, int32_t base_seq) { HLOGC(brlog.Debug, log << "getRcvFirstMsg: base_seq=" << base_seq); w_skipseqno = SRT_SEQNO_NONE; w_passack = false; // tsbpdtime will be retrieved by the below call // Returned values: // - tsbpdtime: real time when the packet is ready to play (whether ready to play or not) // - w_passack: false (the report concerns a packet with an exactly next sequence) // - w_skipseqno == SRT_SEQNO_NONE: no packets to skip towards the first RTP // - w_curpktseq: that exactly packet that is reported (for debugging purposes) // - @return: whether the reported packet is ready to play /* Check the acknowledged packets */ // getRcvReadyMsg returns true if the time to play for the first message // that larger than base_seq is in the past. if (getRcvReadyMsg((w_tsbpdtime), (w_curpktseq), -1, base_seq)) { HLOGC(brlog.Debug, log << "getRcvFirstMsg: ready CONTIG packet: %" << w_curpktseq); return true; } else if (!is_zero(w_tsbpdtime)) { HLOGC(brlog.Debug, log << "getRcvFirstMsg: packets found, but in future"); // This means that a message next to be played, has been found, // but the time to play is in future. return false; } // Falling here means that there are NO PACKETS in the ACK-ed region // (m_iStartPos - m_iLastAckPos), but we may have something in the // region (m_iLastAckPos - (m_iLastAckPos+m_iMaxPos)), that is, packets // that may be separated from the last ACK-ed by lost ones. // Below this line we have only two options: // - m_iMaxPos == 0, which means that no more packets are in the buffer // - returned: tsbpdtime=0, w_passack=true, w_skipseqno=SRT_SEQNO_NONE, w_curpktseq=, @return false // - m_iMaxPos > 0, which means that there are packets arrived after a lost packet: // - returned: tsbpdtime=PKT.TS, w_passack=true, w_skipseqno=PKT.SEQ, w_curpktseq=PKT, @return LOCAL(PKT.TS) <= // NOW /* * 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; int last_ready_pos = -1; steady_clock::time_point tsbpdtime = steady_clock::time_point(); w_tsbpdtime = steady_clock::time_point(); w_passack = true; // XXX SUSPECTED ISSUE with this algorithm: // The above call to getRcvReadyMsg() should report as to whether: // - there is an EXACTLY NEXT SEQUENCE packet // - this packet is ready to play. // // Situations handled after the call are when: // - there's the next sequence packet available and it is ready to play // - there are no packets at all, ready to play or not // // So, the remaining situation is that THERE ARE PACKETS that follow // the current sequence, but they are not ready to play. This includes // packets that have the exactly next sequence and packets that jump // over a lost packet. // // As the getRcvReadyMsg() function walks through the incoming units // to see if there's anything that satisfies these conditions, it *SHOULD* // be also capable of checking if the next available packet, if it is // there, is the next sequence packet or not. Retrieving this exactly // packet would be most useful, as the test for play-readiness and // sequentiality can be done on it directly. // // When done so, the below loop would be completely unnecessary. // Logical description of the below algorithm: // 1. update w_tsbpdtime and w_curpktseq if found one packet ready to play // - keep check the next packet if still smaller than base_seq // 2. set w_skipseqno if found packets before w_curpktseq lost // if no packets larger than base_seq ready to play, return the largest RTP // else return the first one that larger than base_seq and rady to play for (int i = m_iLastAckPos, n = shift(m_iLastAckPos, m_iMaxPos); i != n; i = shiftFwd(i)) { if (!m_pUnit[i] || m_pUnit[i]->m_iFlag != CUnit::GOOD) { /* There are packets in the sequence not received yet */ haslost = true; HLOGC(brlog.Debug, log << "getRcvFirstMsg: empty hole at *" << i); } else { tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); /* Packet ready to play */ if (tsbpdtime <= steady_clock::now()) { // If the last ready-to-play packet exists, free it. if (!is_zero(w_tsbpdtime)) { HLOGC(brlog.Debug, log << "getRcvFirstMsg: found next ready packet, free last %" << w_curpktseq << " POS=" << last_ready_pos); SRT_ASSERT(w_curpktseq != SRT_SEQNO_NONE); freeUnitAt(last_ready_pos); } w_tsbpdtime = tsbpdtime; w_curpktseq = m_pUnit[i]->m_Packet.m_iSeqNo; last_ready_pos = i; if (haslost) w_skipseqno = w_curpktseq; if (base_seq != SRT_SEQNO_NONE && CSeqNo::seqcmp(w_curpktseq, base_seq) <= 0) { HLOGC(brlog.Debug, log << "getRcvFirstMsg: found ready packet %" << w_curpktseq << " but not larger than base_seq, try next"); continue; } HLOGC(brlog.Debug, log << "getRcvFirstMsg: found ready packet, nSKIPPED: " << ((i - m_iLastAckPos + m_iSize) % m_iSize)); // NOTE: if haslost is not set, it means that this is the VERY FIRST // packet, that is, packet currently at pos = m_iLastAckPos. There's no // possibility that it is so otherwise because: // - if this first good packet is ready to play, THIS HERE RETURNS NOW. // ... return true; } if (!is_zero(w_tsbpdtime)) { return true; } HLOGC(brlog.Debug, log << "getRcvFirstMsg: found NOT READY packet, nSKIPPED: " << ((i - m_iLastAckPos + m_iSize) % m_iSize)); // ... and if this first good packet WASN'T ready to play, THIS HERE RETURNS NOW, TOO, // just states that there's no ready packet to play. // ... return false; } // ... and if this first packet WASN'T GOOD, the loop continues, however since now // the 'haslost' is set, which means that it continues only to find the first valid // packet after stating that the very first packet isn't valid. } if (!is_zero(w_tsbpdtime)) { return true; } HLOGC(brlog.Debug, log << "getRcvFirstMsg: found NO PACKETS"); return false; } steady_clock::time_point CRcvBuffer::debugGetDeliveryTime(int offset) { int i; if (offset > 0) i = shift(m_iStartPos, offset); else i = m_iStartPos; CUnit* u = m_pUnit[i]; if (!u || u->m_iFlag != CUnit::GOOD) return steady_clock::time_point(); return getPktTsbPdTime(u->m_Packet.getMsgTimeStamp()); } int32_t CRcvBuffer::getTopMsgno() const { if (m_iStartPos == m_iLastAckPos) return SRT_MSGNO_NONE; // No message is waiting if (!m_pUnit[m_iStartPos]) return SRT_MSGNO_NONE; // pity return m_pUnit[m_iStartPos]->m_Packet.getMsgSeq(); } bool CRcvBuffer::getRcvReadyMsg(steady_clock::time_point& w_tsbpdtime, int32_t& w_curpktseq, int upto, int base_seq) { const bool havelimit = upto != -1; int end = -1, past_end = -1; if (havelimit) { int stretch = (m_iSize + m_iStartPos - m_iLastAckPos) % m_iSize; if (upto > stretch) { HLOGC(brlog.Debug, log << "position back " << upto << " exceeds stretch " << stretch); // Do nothing. This position is already gone. return false; } end = m_iLastAckPos - upto; if (end < 0) end += m_iSize; past_end = shiftFwd(end); // For in-loop comparison HLOGC(brlog.Debug, log << "getRcvReadyMsg: will read from position " << end); } // NOTE: position m_iLastAckPos in the buffer represents the sequence number of // CUDT::m_iRcvLastSkipAck. Therefore 'upto' contains a positive value that should // be decreased from m_iLastAckPos to get the position in the buffer that represents // the sequence number up to which we'd like to read. IF_HEAVY_LOGGING(const char* reason = "NOT RECEIVED"); for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = shiftFwd(i)) { // In case when we want to read only up to given sequence number, stop // the loop if this number was reached. This number must be extracted from // the buffer and any following must wait here for "better times". Note // that the unit that points to the requested sequence must remain in // the buffer, unless there is no valid packet at that position, in which // case it is allowed to point to the NEXT sequence towards it, however // if it does, this cell must remain in the buffer for prospective recovery. if (havelimit && i == past_end) break; bool freeunit = false; /* Skip any invalid skipped/dropped packets */ if (m_pUnit[i] == NULL) { HLOGC(brlog.Debug, log << "getRcvReadyMsg: POS=" << i << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) << " SKIPPED - no unit there"); m_iStartPos = shiftFwd(m_iStartPos); continue; } w_curpktseq = m_pUnit[i]->m_Packet.getSeqNo(); if (m_pUnit[i]->m_iFlag != CUnit::GOOD) { HLOGC(brlog.Debug, log << "getRcvReadyMsg: POS=" << i << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) << " SKIPPED - unit not good"); freeunit = true; } else { // This does: // 1. Get the TSBPD time of the unit. Stop and return false if this unit // is not yet ready to play. // 2. If it's ready to play, check also if it's decrypted. If not, skip it. // 3. Check also if it's larger than base_seq, if not, skip it. // 4. If it's ready to play, decrypted and larger than base, stop and return it. if (!havelimit) { w_tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); const steady_clock::duration towait = (w_tsbpdtime - steady_clock::now()); if (towait.count() > 0) { HLOGC(brlog.Debug, log << "getRcvReadyMsg: POS=" << i << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) << " pkt %" << w_curpktseq << " NOT ready to play (only in " << count_milliseconds(towait) << "ms)"); return false; } if (m_pUnit[i]->m_Packet.getMsgCryptoFlags() != EK_NOENC) { IF_HEAVY_LOGGING(reason = "DECRYPTION FAILED"); freeunit = true; /* packet not decrypted */ } else if (base_seq != SRT_SEQNO_NONE && CSeqNo::seqcmp(w_curpktseq, base_seq) <= 0) { IF_HEAVY_LOGGING(reason = "smaller than base_seq"); w_tsbpdtime = steady_clock::time_point(); freeunit = true; } else { HLOGC(brlog.Debug, log << "getRcvReadyMsg: POS=" << i << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) << " pkt %" << w_curpktseq << " ready to play (delayed " << count_milliseconds(towait) << "ms)"); return true; } } // In this case: // 1. We don't even look into the packet if this is not the requested sequence. // All packets that are earlier than the required sequence will be dropped. // 2. When found the packet with expected sequence number, and the condition for // good unit is passed, we get the timestamp. // 3. If the packet is not decrypted, we allow it to be removed // 4. If we reached the required sequence, and the packet is good, KEEP IT in the buffer, // and return with the pointer pointing to this very buffer. Only then return true. else { // We have a limit up to which the reading will be done, // no matter if the time has come or not - although retrieve it. if (i == end) { HLOGC(brlog.Debug, log << "CAUGHT required seq position " << i); // We have the packet we need. Extract its data. w_tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); // If we have a decryption failure, allow the unit to be released. if (m_pUnit[i]->m_Packet.getMsgCryptoFlags() != EK_NOENC) { IF_HEAVY_LOGGING(reason = "DECRYPTION FAILED"); freeunit = true; /* packet not decrypted */ } else { // Stop here and keep the packet in the buffer, so it will be // next extracted. HLOGC(brlog.Debug, log << "getRcvReadyMsg: packet seq=" << w_curpktseq << " ready for extraction"); return true; } } else { HLOGC(brlog.Debug, log << "SKIPPING position " << i); // Continue the loop and remove the current packet because // its sequence number is too old. freeunit = true; } } } if (freeunit) { HLOGC(brlog.Debug, log << "getRcvReadyMsg: POS=" << i << " FREED: " << reason); /* removed skipped, dropped, undecryptable bytes from rcv buffer */ const int rmbytes = (int)m_pUnit[i]->m_Packet.getLength(); countBytes(-1, -rmbytes, true); freeUnitAt(i); m_iStartPos = shiftFwd(m_iStartPos); } } HLOGC(brlog.Debug, log << "getRcvReadyMsg: nothing to deliver: " << reason); return false; } /* * Return receivable data status (packet timestamp_us 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_us 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(steady_clock::time_point& w_tsbpdtime, int32_t& w_curpktseq, int32_t seqdistance) { w_tsbpdtime = steady_clock::time_point(); if (m_tsbpd.isEnabled()) { const srt::CPacket* pkt = getRcvReadyPacket(seqdistance); if (!pkt) { HLOGC(brlog.Debug, log << "isRcvDataReady: packet NOT extracted."); return false; } /* * Acknowledged data is available, * Only say ready if time to deliver. * Report the timestamp, ready or not. */ w_curpktseq = pkt->getSeqNo(); w_tsbpdtime = getPktTsbPdTime(pkt->getMsgTimeStamp()); // If seqdistance was passed, then return true no matter what the // TSBPD time states. if (seqdistance != -1 || w_tsbpdtime <= steady_clock::now()) { HLOGC(brlog.Debug, log << "isRcvDataReady: packet extracted seqdistance=" << seqdistance << " TsbPdTime=" << FormatTime(w_tsbpdtime)); return true; } HLOGC(brlog.Debug, log << "isRcvDataReady: packet extracted, but NOT READY"); return false; } return isRcvDataAvailable(); } // XXX This function may be called only after checking // if m_bTsbPdMode. CPacket* CRcvBuffer::getRcvReadyPacket(int32_t seqdistance) { // If asked for readiness of a packet at given sequence distance // (that is, we need to extract the packet with given sequence number), // only check if this cell is occupied in the buffer, and if so, // if it's occupied with a "good" unit. That's all. It doesn't // matter whether it's ready to play. if (seqdistance != -1) { // Note: seqdistance is the value to to go BACKWARDS from m_iLastAckPos, // which is the position that is in sync with CUDT::m_iRcvLastSkipAck. This // position is the sequence number of a packet that is NOT received, but it's // expected to be received as next. So the minimum value of seqdistance is 1. // SANITY CHECK if (seqdistance == 0) { LOGC(brlog.Fatal, log << "IPE: trying to extract packet past the last ACK-ed!"); return 0; } if (seqdistance > getRcvDataSize()) { HLOGC(brlog.Debug, log << "getRcvReadyPacket: Sequence offset=" << seqdistance << " is in the past (start=" << m_iStartPos << " end=" << m_iLastAckPos << ")"); return 0; } int i = shift(m_iLastAckPos, -seqdistance); if (m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD) { HLOGC(brlog.Debug, log << "getRcvReadyPacket: FOUND PACKET %" << m_pUnit[i]->m_Packet.getSeqNo()); return &m_pUnit[i]->m_Packet; } HLOGC(brlog.Debug, log << "getRcvReadyPacket: Sequence offset=" << seqdistance << " IS NOT RECEIVED."); return 0; } IF_HEAVY_LOGGING(int nskipped = 0); for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = shiftFwd(i)) { /* * Skip missing packets that did not arrive in time. */ if (m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD) { HLOGC(brlog.Debug, log << "getRcvReadyPacket: Found next packet seq=%" << m_pUnit[i]->m_Packet.getSeqNo() << " (" << nskipped << " empty cells skipped)"); return &m_pUnit[i]->m_Packet; } IF_HEAVY_LOGGING(++nskipped); } return 0; } #if ENABLE_HEAVY_LOGGING // This function is for debug purposes only and it's called only // from within HLOG* macros. void CRcvBuffer::reportBufferStats() const { int nmissing = 0; int32_t low_seq = SRT_SEQNO_NONE, high_seq = SRT_SEQNO_NONE; int32_t low_ts = 0, high_ts = 0; for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = (i + 1) % m_iSize) { if (m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD) { low_seq = m_pUnit[i]->m_Packet.m_iSeqNo; low_ts = m_pUnit[i]->m_Packet.m_iTimeStamp; break; } ++nmissing; } // Not sure if a packet MUST BE at the last ack pos position, so check, just in case. int n = m_iLastAckPos; if (m_pUnit[n] && m_pUnit[n]->m_iFlag == CUnit::GOOD) { high_ts = m_pUnit[n]->m_Packet.m_iTimeStamp; high_seq = m_pUnit[n]->m_Packet.m_iSeqNo; } else { // Possibilities are: // m_iStartPos == m_iLastAckPos, high_ts == low_ts, defined. // No packet: low_ts == 0, so high_ts == 0, too. high_ts = low_ts; } // The 32-bit timestamps are relative and roll over oftten; what // we really need is the timestamp difference. The only place where // we can ask for the time base is the upper time because when trying // to receive the time base for the lower time we'd break the requirement // for monotonic clock. uint64_t upper_time = high_ts; uint64_t lower_time = low_ts; if (lower_time > upper_time) upper_time += uint64_t(srt::CPacket::MAX_TIMESTAMP) + 1; int32_t timespan = upper_time - lower_time; int seqspan = 0; if (low_seq != SRT_SEQNO_NONE && high_seq != SRT_SEQNO_NONE) { seqspan = CSeqNo::seqoff(low_seq, high_seq); } LOGC(brlog.Debug, log << "RCV BUF STATS: seqspan=%(" << low_seq << "-" << high_seq << ":" << seqspan << ") missing=" << nmissing << "pkts"); LOGC(brlog.Debug, log << "RCV BUF STATS: timespan=" << timespan << "us (lo=" << lower_time << " hi=" << upper_time << ")"); } #endif // ENABLE_HEAVY_LOGGING bool CRcvBuffer::isRcvDataReady() { steady_clock::time_point tsbpdtime; int32_t seq; return isRcvDataReady((tsbpdtime), (seq), -1); } 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; } int CRcvBuffer::debugGetSize() const { // Does exactly the same as getRcvDataSize, but // it should be used FOR INFORMATIONAL PURPOSES ONLY. // The source values might be changed in another thread // during the calculation, although worst case the // resulting value may differ to the real buffer size by 1. int from = m_iStartPos, to = m_iLastAckPos; int size = to - from; if (size < 0) size += m_iSize; return size; } /* Return moving average of acked data pkts, bytes, and timespan (ms) of the receive buffer */ int CRcvBuffer::getRcvAvgDataSize(int& bytes, int& timespan) { // Average number of packets and timespan could be small, // so rounding is beneficial, while for the number of // bytes in the buffer is a higher value, so rounding can be omitted, // but probably better to round all three values. timespan = round_val(m_mavg.timespan_ms()); bytes = round_val(m_mavg.bytes()); return round_val(m_mavg.pkts()); } /* Update moving average of acked data pkts, bytes, and timespan (ms) of the receive buffer */ void CRcvBuffer::updRcvAvgDataSize(const steady_clock::time_point& now) { if (!m_mavg.isTimeToUpdate(now)) return; int bytes = 0; int timespan_ms = 0; const int pkts = getRcvDataSize(bytes, timespan_ms); m_mavg.update(now, pkts, bytes, timespan_ms); } /* Return acked data pkts, bytes, and timespan (ms) of the receive buffer */ int CRcvBuffer::getRcvDataSize(int& bytes, int& timespan) { timespan = 0; if (m_tsbpd.isEnabled()) { // Get a valid startpos. // Skip invalid entries in the beginning, if any. int startpos = m_iStartPos; for (; startpos != m_iLastAckPos; startpos = shiftFwd(startpos)) { if ((NULL != m_pUnit[startpos]) && (CUnit::GOOD == m_pUnit[startpos]->m_iFlag)) break; } int endpos = m_iLastAckPos; 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])) { const steady_clock::time_point startstamp = getPktTsbPdTime(m_pUnit[startpos]->m_Packet.getMsgTimeStamp()); const steady_clock::time_point 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 = count_milliseconds(endstamp - startstamp); } /* * 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; } } HLOGF(brlog.Debug, "getRcvDataSize: %6d %6d %6d ms\n", m_iAckedPktsCount, m_iAckedBytesCount, timespan); bytes = m_iAckedBytesCount; return m_iAckedPktsCount; } unsigned CRcvBuffer::getRcvAvgPayloadSize() const { return m_uAvgPayloadSz; } CRcvBuffer::ReadingState CRcvBuffer::debugGetReadingState() const { ReadingState readstate; readstate.iNumAcknowledged = 0; readstate.iNumUnacknowledged = m_iMaxPos; if ((NULL != m_pUnit[m_iStartPos]) && (m_pUnit[m_iStartPos]->m_iFlag == CUnit::GOOD)) { if (m_tsbpd.isEnabled()) readstate.tsStart = m_tsbpd.getPktTsbPdTime(m_pUnit[m_iStartPos]->m_Packet.getMsgTimeStamp()); readstate.iNumAcknowledged = m_iLastAckPos > m_iStartPos ? m_iLastAckPos - m_iStartPos : m_iLastAckPos + (m_iSize - m_iStartPos); } // All further stats are valid if TSBPD is enabled. if (!m_tsbpd.isEnabled()) return readstate; // m_iLastAckPos points to the first unacknowledged packet const int iLastAckPos = (m_iLastAckPos - 1) % m_iSize; if (m_iLastAckPos != m_iStartPos && (NULL != m_pUnit[iLastAckPos]) && (m_pUnit[iLastAckPos]->m_iFlag == CUnit::GOOD)) { readstate.tsLastAck = m_tsbpd.getPktTsbPdTime(m_pUnit[iLastAckPos]->m_Packet.getMsgTimeStamp()); } const int iEndPos = (m_iLastAckPos + m_iMaxPos - 1) % m_iSize; if (m_iMaxPos == 0) { readstate.tsEnd = readstate.tsLastAck; } else if ((NULL != m_pUnit[iEndPos]) && (m_pUnit[iEndPos]->m_iFlag == CUnit::GOOD)) { readstate.tsEnd = m_tsbpd.getPktTsbPdTime(m_pUnit[iEndPos]->m_Packet.getMsgTimeStamp()); } return readstate; } string CRcvBuffer::strFullnessState(const time_point& tsNow) const { const ReadingState bufstate = debugGetReadingState(); stringstream ss; ss << "Space avail " << getAvailBufSize() << "/" << m_iSize; ss << " pkts. Packets ACKed: " << bufstate.iNumAcknowledged; if (!is_zero(bufstate.tsStart) && !is_zero(bufstate.tsLastAck)) { ss << " (TSBPD ready in "; ss << count_milliseconds(bufstate.tsStart - tsNow); ss << " : "; ss << count_milliseconds(bufstate.tsLastAck - tsNow); ss << " ms)"; } ss << ", not ACKed: " << bufstate.iNumUnacknowledged; if (!is_zero(bufstate.tsStart) && !is_zero(bufstate.tsEnd)) { ss << ", timespan "; ss << count_milliseconds(bufstate.tsEnd - bufstate.tsStart); ss << " ms"; } ss << ". " SRT_SYNC_CLOCK_STR " drift " << getDrift() / 1000 << " ms."; return ss.str(); } void CRcvBuffer::dropMsg(int32_t msgno, bool using_rexmit_flag) { for (int i = m_iStartPos, n = shift(m_iLastAckPos, m_iMaxPos); i != n; i = shiftFwd(i)) if ((m_pUnit[i] != NULL) && (m_pUnit[i]->m_Packet.getMsgSeq(using_rexmit_flag) == msgno)) m_pUnit[i]->m_iFlag = CUnit::DROPPED; } void CRcvBuffer::applyGroupTime(const steady_clock::time_point& timebase, bool wrp, uint32_t delay, const steady_clock::duration& udrift) { m_tsbpd.applyGroupTime(timebase, wrp, delay, udrift); } void CRcvBuffer::applyGroupDrift(const steady_clock::time_point& timebase, bool wrp, const steady_clock::duration& udrift) { m_tsbpd.applyGroupDrift(timebase, wrp, udrift); } void CRcvBuffer::getInternalTimeBase(steady_clock::time_point& w_timebase, bool& w_wrp, steady_clock::duration& w_udrift) { return m_tsbpd.getInternalTimeBase(w_timebase, w_wrp, w_udrift); } steady_clock::time_point CRcvBuffer::getPktTsbPdTime(uint32_t usPktTimestamp) { // Updating TSBPD time here is not very accurate and prevents from making the function constant. // For now preserving the existing behavior. m_tsbpd.updateTsbPdTimeBase(usPktTimestamp); return m_tsbpd.getPktTsbPdTime(usPktTimestamp); } void CRcvBuffer::setRcvTsbPdMode(const steady_clock::time_point& timebase, const steady_clock::duration& delay) { const bool no_wrap_check = false; m_tsbpd.setTsbPdMode(timebase, no_wrap_check, delay); } bool CRcvBuffer::addRcvTsbPdDriftSample(uint32_t timestamp_us, int rtt) { return m_tsbpd.addDriftSample(timestamp_us, rtt); } int CRcvBuffer::readMsg(char* data, int len) { SRT_MSGCTRL dummy = srt_msgctrl_default; return readMsg(data, len, (dummy), -1); } // NOTE: The order of ref-arguments is odd because: // - data and len shall be close to one another // - upto is last because it's a kind of unusual argument that has a default value int CRcvBuffer::readMsg(char* data, int len, SRT_MSGCTRL& w_msgctl, int upto) { int p = -1, q = -1; bool passack; bool empty = accessMsg((p), (q), (passack), (w_msgctl.srctime), upto); if (empty) return 0; // This should happen just once. By 'empty' condition // we have a guarantee that m_pUnit[p] exists and is valid. CPacket& pkt1 = m_pUnit[p]->m_Packet; // This returns the sequence number and message number to // the API caller. w_msgctl.pktseq = pkt1.getSeqNo(); w_msgctl.msgno = pkt1.getMsgSeq(); return extractData((data), len, p, q, passack); } #ifdef SRT_DEBUG_TSBPD_OUTJITTER void CRcvBuffer::debugTraceJitter(time_point playtime) { uint64_t ms = count_microseconds(steady_clock::now() - playtime); if (ms / 10 < 10) m_ulPdHisto[0][ms / 10]++; else if (ms / 100 < 10) m_ulPdHisto[1][ms / 100]++; else if (ms / 1000 < 10) m_ulPdHisto[2][ms / 1000]++; else m_ulPdHisto[3][1]++; } #endif /* SRT_DEBUG_TSBPD_OUTJITTER */ bool CRcvBuffer::accessMsg(int& w_p, int& w_q, bool& w_passack, int64_t& w_playtime, int upto) { // This function should do the following: // 1. Find the first packet starting the next message (or just next packet) // 2. When found something ready for extraction, return true. // 3. w_p and w_q point the index range for extraction // 4. passack decides if this range shall be removed after extraction bool empty = true; if (m_tsbpd.isEnabled()) { w_passack = false; int seq = 0; steady_clock::time_point play_time; const bool isReady = getRcvReadyMsg(play_time, (seq), upto); w_playtime = count_microseconds(play_time.time_since_epoch()); if (isReady) { empty = false; // In TSBPD mode you always read one message // at a time and a message always fits in one UDP packet, // so in one "unit". w_p = w_q = m_iStartPos; debugTraceJitter(play_time); } } else { w_playtime = 0; if (scanMsg((w_p), (w_q), (w_passack))) empty = false; } return empty; } int CRcvBuffer::extractData(char* data, int len, int p, int q, bool passack) { SRT_ASSERT(len > 0); int rs = len > 0 ? len : 0; const int past_q = shiftFwd(q); while (p != past_q) { const int pktlen = (int)m_pUnit[p]->m_Packet.getLength(); // When unitsize is less than pktlen, only a fragment is copied to the output 'data', // but still the whole packet is removed from the receiver buffer. if (pktlen > 0) countBytes(-1, -pktlen, true); const int unitsize = ((rs >= 0) && (pktlen > rs)) ? rs : pktlen; HLOGC(brlog.Debug, log << "readMsg: checking unit POS=" << p); if (unitsize > 0) { memcpy((data), m_pUnit[p]->m_Packet.m_pcData, unitsize); data += unitsize; rs -= unitsize; IF_HEAVY_LOGGING(readMsgHeavyLogging(p)); } else { HLOGC(brlog.Debug, log << CONID() << "readMsg: SKIPPED POS=" << p << " - ZERO SIZE UNIT"); } // Note special case for live mode (one packet per message and TSBPD=on): // - p == q (that is, this loop passes only once) // - no passack (the unit is always removed from the buffer) if (!passack) { HLOGC(brlog.Debug, log << CONID() << "readMsg: FREEING UNIT POS=" << p); freeUnitAt(p); } else { HLOGC(brlog.Debug, log << CONID() << "readMsg: PASSACK UNIT POS=" << p); m_pUnit[p]->m_iFlag = CUnit::PASSACK; } p = shiftFwd(p); } if (!passack) m_iStartPos = past_q; HLOGC(brlog.Debug, log << "rcvBuf/extractData: begin=" << m_iStartPos << " reporting extraction size=" << (len - rs)); return len - rs; } string CRcvBuffer::debugTimeState(size_t first_n_pkts) const { stringstream ss; int ipos = m_iStartPos; for (size_t i = 0; i < first_n_pkts; ++i, ipos = CSeqNo::incseq(ipos)) { const CUnit* unit = m_pUnit[ipos]; if (!unit) { ss << "pkt[" << i << "] missing, "; continue; } const CPacket& pkt = unit->m_Packet; ss << "pkt[" << i << "] ts=" << pkt.getMsgTimeStamp() << ", "; } return ss.str(); } #if ENABLE_HEAVY_LOGGING void CRcvBuffer::readMsgHeavyLogging(int p) { static steady_clock::time_point prev_now; static steady_clock::time_point prev_srctime; const CPacket& pkt = m_pUnit[p]->m_Packet; const int32_t seq = pkt.m_iSeqNo; steady_clock::time_point nowtime = steady_clock::now(); steady_clock::time_point srctime = getPktTsbPdTime(m_pUnit[p]->m_Packet.getMsgTimeStamp()); const int64_t timediff_ms = count_milliseconds(nowtime - srctime); const int64_t nowdiff_ms = is_zero(prev_now) ? count_milliseconds(nowtime - prev_now) : 0; const int64_t srctimediff_ms = is_zero(prev_srctime) ? count_milliseconds(srctime - prev_srctime) : 0; const int next_p = shiftFwd(p); CUnit* u = m_pUnit[next_p]; string next_playtime; if (u && u->m_iFlag == CUnit::GOOD) { next_playtime = FormatTime(getPktTsbPdTime(u->m_Packet.getMsgTimeStamp())); } else { next_playtime = "NONE"; } LOGC(brlog.Debug, log << CONID() << "readMsg: DELIVERED seq=" << seq << " T=" << FormatTime(srctime) << " in " << timediff_ms << "ms - TIME-PREVIOUS: PKT: " << srctimediff_ms << " LOCAL: " << nowdiff_ms << " !" << BufferStamp(pkt.data(), pkt.size()) << " NEXT pkt T=" << next_playtime); prev_now = nowtime; prev_srctime = srctime; } #endif bool CRcvBuffer::scanMsg(int& w_p, int& w_q, bool& w_passack) { // empty buffer if ((m_iStartPos == m_iLastAckPos) && (m_iMaxPos <= 0)) { HLOGC(brlog.Debug, log << "scanMsg: empty buffer"); return false; } int rmpkts = 0; int rmbytes = 0; // skip all bad msgs at the beginning // This loop rolls until the "buffer is empty" (head == tail), // in particular, there's no unit accessible for the reader. while (m_iStartPos != m_iLastAckPos) { // Roll up to the first valid unit if (!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 // We expect to see either of: // [PB_FIRST] [PB_SUBSEQUENT] [PB_SUBSEQUENT] [PB_LAST] // [PB_SOLO] // but not: // [PB_FIRST] NULL ... // [PB_FIRST] FREE/PASSACK/DROPPED... // If the message didn't look as expected, interrupt this. // This begins with a message starting at m_iStartPos // up to m_iLastAckPos OR until the PB_LAST message is found. // If any of the units on this way isn't good, this OUTER loop // will be interrupted. 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; } rmpkts++; rmbytes += (int) freeUnitAt((size_t) m_iStartPos); m_iStartPos = shiftFwd(m_iStartPos); } /* we removed bytes form receive buffer */ countBytes(-rmpkts, -rmbytes, true); // Not sure if this is correct, but this above 'while' loop exits // under the following conditions only: // - m_iStartPos == m_iLastAckPos (that makes passack = true) // - found at least GOOD unit with PB_FIRST and not all messages up to PB_LAST are good, // in which case it returns with m_iStartPos <% m_iLastAckPos (earlier) // Also all units that lied before m_iStartPos are removed. w_p = -1; // message head w_q = m_iStartPos; // message tail w_passack = m_iStartPos == m_iLastAckPos; bool found = false; // looking for the first message //>>m_pUnit[size + m_iMaxPos] is not valid // XXX Would be nice to make some very thorough refactoring here. // This rolls by q variable from m_iStartPos up to m_iLastAckPos, // actually from the first message up to the one with PB_LAST // or PB_SOLO boundary. // The 'i' variable used in this loop is just a stub and it's // even hard to define the unit here. It is "shift towards // m_iStartPos", so the upper value is m_iMaxPos + size. // m_iMaxPos is itself relative to m_iLastAckPos, so // the upper value is m_iMaxPos + difference between // m_iLastAckPos and m_iStartPos, so that this value is relative // to m_iStartPos. // // The 'i' value isn't used anywhere, although the 'q' value rolls // in this loop in sync with 'i', with the difference that 'q' is // wrapped around, and 'i' is just incremented normally. // // This makes that this loop rolls in the range by 'q' from // m_iStartPos to m_iStartPos + UPPER, // where UPPER = m_iLastAckPos -% m_iStartPos + m_iMaxPos // This embraces the range from the current reading head up to // the last packet ever received. // // 'passack' is set to true when the 'q' has passed through // the border of m_iLastAckPos and fallen into the range // of unacknowledged packets. for (int i = 0, n = m_iMaxPos + getRcvDataSize(); i < n; ++i) { if (m_pUnit[w_q] && m_pUnit[w_q]->m_iFlag == CUnit::GOOD) { // Equivalent pseudocode: // PacketBoundary bound = m_pUnit[w_q]->m_Packet.getMsgBoundary(); // if ( IsSet(bound, PB_FIRST) ) // w_p = w_q; // if ( IsSet(bound, PB_LAST) && w_p != -1 ) // found = true; // // Not implemented this way because it uselessly check w_p for -1 // also after setting it explicitly. switch (m_pUnit[w_q]->m_Packet.getMsgBoundary()) { case PB_SOLO: // 11 w_p = w_q; found = true; break; case PB_FIRST: // 10 w_p = w_q; break; case PB_LAST: // 01 if (w_p != -1) found = true; break; case PB_SUBSEQUENT:; // do nothing (caught first, rolling for last) } } else { // a hole in this message, not valid, restart search w_p = -1; } // 'found' is set when the current iteration hit a message with PB_LAST // (including PB_SOLO since the very first message). if (found) { // the msg has to be ack'ed or it is allowed to read out of order, and was not read before if (!w_passack || !m_pUnit[w_q]->m_Packet.getMsgOrderFlag()) { HLOGC(brlog.Debug, log << "scanMsg: found next-to-broken message, delivering OUT OF ORDER."); break; } found = false; } if (++w_q == m_iSize) w_q = 0; if (w_q == m_iLastAckPos) w_passack = true; } // no msg found if (!found) { // NOTE: // This situation may only happen if: // - Found a packet with PB_FIRST, so w_p = w_q at the moment when it was found // - Possibly found following components of that message up to shifted w_q // - Found no terminal packet (PB_LAST) for that message. // if the message is larger than the receiver buffer, return part of the message if ((w_p != -1) && (shiftFwd(w_q) == w_p)) { HLOGC(brlog.Debug, log << "scanMsg: BUFFER FULL and message is INCOMPLETE. Returning PARTIAL MESSAGE."); found = true; } else { HLOGC(brlog.Debug, log << "scanMsg: PARTIAL or NO MESSAGE found: p=" << w_p << " q=" << w_q); } } else { HLOGC(brlog.Debug, log << "scanMsg: extracted message p=" << w_p << " q=" << w_q << " (" << ((w_q - w_p + m_iSize + 1) % m_iSize) << " packets)"); } return found; } srt-1.4.4/srtcore/buffer.h000066400000000000000000000555201412557703600154540ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 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 INC_SRT_BUFFER_H #define INC_SRT_BUFFER_H #include "udt.h" #include "list.h" #include "queue.h" #include "tsbpd_time.h" #include "utilities.h" // The notation used for "circular numbers" in comments: // The "cicrular numbers" are numbers that when increased up to the // maximum become zero, and similarly, when the zero value is decreased, // it turns into the maximum value minus one. This wrapping works the // same for adding and subtracting. Circular numbers cannot be multiplied. // Operations done on these numbers are marked with additional % character: // a %> b : a is later than b // a ++% (++%a) : shift a by 1 forward // a +% b : shift a by b // a == b : equality is same as for just numbers /// The AvgBufSize class is used to calculate moving average of the buffer (RCV or SND) class AvgBufSize { typedef srt::sync::steady_clock::time_point time_point; public: AvgBufSize() : m_dBytesCountMAvg(0.0) , m_dCountMAvg(0.0) , m_dTimespanMAvg(0.0) { } public: bool isTimeToUpdate(const time_point& now) const; void update(const time_point& now, int pkts, int bytes, int timespan_ms); public: inline double pkts() const { return m_dCountMAvg; } inline double timespan_ms() const { return m_dTimespanMAvg; } inline double bytes() const { return m_dBytesCountMAvg; } private: time_point m_tsLastSamplingTime; double m_dBytesCountMAvg; double m_dCountMAvg; double m_dTimespanMAvg; }; class CSndBuffer { typedef srt::sync::steady_clock::time_point time_point; typedef srt::sync::steady_clock::duration duration; 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(); public: /// Insert a user buffer into the sending list. /// For @a w_mctrl the following fields are used: /// INPUT: /// - msgttl: timeout for retransmitting the message, if lost /// - inorder: request to deliver the message in order of sending /// - srctime: local time as a base for packet's timestamp (0 if unused) /// - pktseq: sequence number to be stamped on the packet (-1 if unused) /// - msgno: message number to be stamped on the packet (-1 if unused) /// OUTPUT: /// - srctime: local time stamped on the packet (same as input, if input wasn't 0) /// - pktseq: sequence number to be stamped on the next packet /// - msgno: message number stamped on the packet /// @param [in] data pointer to the user data block. /// @param [in] len size of the block. /// @param [inout] w_mctrl Message control data void addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl); /// 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. int readData(srt::CPacket& w_packet, time_point& w_origintime, int kflgs); /// 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 (backward sequence number difference) /// @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 (return 0 if offset too large, -1 if TTL exceeded). int readData(const int offset, srt::CPacket& w_packet, time_point& w_origintime, int& w_msglen); /// Get the time of the last retransmission (if any) of the DATA packet. /// @param [in] offset offset from the last ACK point (backward sequence number difference) /// /// @return Last time of the last retransmission event for the corresponding DATA packet. time_point getPacketRexmitTime(const int offset); /// Update the ACK point and may release/unmap/return the user data according to the flag. /// @param [in] offset number of packets acknowledged. int32_t getMsgNoAt(const int offset); 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; int dropLateData(int& bytes, int32_t& w_first_msgno, const time_point& too_late_time); void updAvgBufSize(const time_point& time); int getAvgBufSize(int& bytes, int& timespan); int getCurrBufSize(int& bytes, int& timespan); uint64_t getInRatePeriod() const { return m_InRatePeriod; } /// Retrieve input bitrate in bytes per second int getInputRate() const { return m_iInRateBps; } /// Update input rate calculation. /// @param [in] time current time in microseconds /// @param [in] pkts number of packets newly added to the buffer /// @param [in] bytes number of payload bytes in those newly added packets /// /// @return Current size of the data in the sending list. void updateInputRate(const time_point& time, int pkts = 0, int bytes = 0); void resetInputRateSmpPeriod(bool disable = false) { setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); } private: void increase(); void setInputRateSmpPeriod(int period); struct Block; // Defined below static time_point getSourceTime(const CSndBuffer::Block& block); private: // Constants static const uint64_t INPUTRATE_FAST_START_US = 500000; // 500 ms static const uint64_t INPUTRATE_RUNNING_US = 1000000; // 1000 ms static const int64_t INPUTRATE_MAX_PACKETS = 2000; // ~ 21 Mbps of 1316 bytes payload static const int INPUTRATE_INITIAL_BYTESPS = BW_INFINITE; private: srt::sync::Mutex 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 int32_t m_iSeqNo; // sequence number for scheduling time_point m_tsOriginTime; // original request time time_point m_tsRexmitTime; // packet retransmission time uint64_t m_llSourceTime_us; 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 int m_iBytesCount; // number of payload bytes in queue time_point m_tsLastOriginTime; AvgBufSize m_mavg; int m_iInRatePktsCount; // number of payload bytes added since InRateStartTime int m_iInRateBytesCount; // number of payload bytes added since InRateStartTime time_point m_tsInRateStartTime; uint64_t m_InRatePeriod; // usec int m_iInRateBps; // Input Rate in Bytes/sec int m_iAvgPayloadSz; // Average packet payload size private: CSndBuffer(const CSndBuffer&); CSndBuffer& operator=(const CSndBuffer&); }; //////////////////////////////////////////////////////////////////////////////// class CRcvBuffer { typedef srt::sync::steady_clock::time_point time_point; typedef srt::sync::steady_clock::duration duration; 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 ""; } static const int DEFAULT_SIZE = 65536; /// Construct the buffer. /// @param [in] queue CUnitQueue that actually holds the units (packets) /// @param [in] bufsize_pkts in units (packets) CRcvBuffer(srt::CUnitQueue* queue, int bufsize_pkts = DEFAULT_SIZE); ~CRcvBuffer(); public: /// 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(srt::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 number of units to be acknowledged. /// @return 1 if a user buffer is fulfilled, otherwise 0. int ackData(int len); /// Query how many buffer space left for data receiving. /// Actually only acknowledged packets, that are still in the buffer, /// are considered to take buffer space. /// /// @return size of available buffer space (including user buffer) for data receiving. /// Not counting unacknowledged packets. int getAvailBufSize() const; /// Query how many data has been continuously received (for reading) and ready to play (tsbpdtime < now). /// @return size of valid (continous) data for reading. int getRcvDataSize() const; /// Query how many data was received and acknowledged. /// @param [out] bytes bytes /// @param [out] spantime spantime /// @return size in pkts of acked data. int getRcvDataSize(int& bytes, int& spantime); /// Query a 1 sec moving average of how many data was received and acknowledged. /// @param [out] bytes bytes /// @param [out] spantime 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 [in] now current time in us. /// @return none. void updRcvAvgDataSize(const time_point& now); /// Query the received average payload size. /// @return size (bytes) of payload size unsigned getRcvAvgPayloadSize() const; struct ReadingState { time_point tsStart; time_point tsLastAck; time_point tsEnd; int iNumAcknowledged; int iNumUnacknowledged; }; ReadingState debugGetReadingState() const; /// Form a string of the current buffer fullness state. /// number of packets acknowledged, TSBPD readiness, etc. std::string strFullnessState(const time_point& tsNow) const; /// 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); #if ENABLE_HEAVY_LOGGING void readMsgHeavyLogging(int p); #endif /// 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, SRT_MSGCTRL& w_mctrl, int upto); /// 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. /// @param [out] curpktseq Sequence number of the packet if there is one ready to play /// @return true if ready to play, false otherwise (tsbpdtime may be !0 in /// both cases). bool isRcvDataReady(time_point& w_tsbpdtime, int32_t& w_curpktseq, int32_t seqdistance); #ifdef SRT_DEBUG_TSBPD_OUTJITTER void debugTraceJitter(time_point t); #else void debugTraceJitter(time_point) {} #endif /* SRT_DEBUG_TSBPD_OUTJITTER */ bool isRcvDataReady(); bool isRcvDataAvailable() { return m_iLastAckPos != m_iStartPos; } srt::CPacket* getRcvReadyPacket(int32_t seqdistance); /// 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 void setRcvTsbPdMode(const time_point& timebase, const duration& delay); /// Add packet timestamp for drift caclculation and compensation /// @param [in] timestamp packet time stamp /// @param [in] rtt RTT sample bool addRcvTsbPdDriftSample(uint32_t timestamp, int rtt); #ifdef SRT_DEBUG_TSBPD_DRIFT void printDriftHistogram(int64_t iDrift); void printDriftOffset(int tsbPdOffset, int tsbPdDriftAvg); #endif /// Get information on the 1st message in queue. // Parameters (of the 1st packet queue, ready to play or not): /// @param [out] w_tsbpdtime localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 /// if none /// @param [out] w_passack true if 1st ready packet is not yet acknowleged (allowed to be delivered to the app) /// @param [out] w_skipseqno SRT_SEQNO_NONE or seq number of 1st unacknowledged pkt ready to play preceeded by /// missing packets. /// @param base_seq SRT_SEQNO_NONE or desired, ignore seq smaller than base if exist packet ready-to-play /// and larger than base /// @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 != SRT_SEQNO_NONE, packet ready to play preceeded by missing packets.; /// IF skipseqno == SRT_SEQNO_NONE, no missing packet but 1st not ready to play. bool getRcvFirstMsg(time_point& w_tsbpdtime, bool& w_passack, int32_t& w_skipseqno, int32_t& w_curpktseq, int32_t base_seq = SRT_SEQNO_NONE); /// Update the ACK point of the buffer. /// @param [in] len size of data to be skip & acknowledged. void skipData(int len); #if ENABLE_HEAVY_LOGGING void reportBufferStats() const; // Heavy logging Debug only #endif bool empty() const { // This will not always return the intended value, // that is, it may return false when the buffer really is // empty - but it will return true then in one of next calls. // This function will be always called again at some point // if it returned false, and on true the connection // is going to be broken - so this behavior is acceptable. return m_iStartPos == m_iLastAckPos; } bool full() const { return m_iStartPos == (m_iLastAckPos + 1) % m_iSize; } int capacity() const { return m_iSize; } private: /// This gives up unit at index p. The unit is given back to the /// free unit storage for further assignment for the new incoming /// data. size_t freeUnitAt(size_t p) { srt::CUnit* u = m_pUnit[p]; m_pUnit[p] = NULL; size_t rmbytes = u->m_Packet.getLength(); m_pUnitQueue->makeUnitFree(u); return rmbytes; } /// Adjust receive queue to 1st ready to play message (tsbpdtime < now). /// Parameters (of the 1st packet queue, ready to play or not): /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 if /// none /// @param base_seq SRT_SEQNO_NONE or desired, ignore seq smaller than base /// @retval true 1st packet ready to play without discontinuity (no hole) /// @retval false tsbpdtime = 0: no packet ready to play bool getRcvReadyMsg(time_point& w_tsbpdtime, int32_t& w_curpktseq, int upto, int base_seq = SRT_SEQNO_NONE); public: /// @brief Get clock drift in microseconds. int64_t getDrift() const { return m_tsbpd.drift(); } public: int32_t getTopMsgno() const; void getInternalTimeBase(time_point& w_tb, bool& w_wrp, duration& w_udrift); void applyGroupTime(const time_point& timebase, bool wrapcheck, uint32_t delay, const duration& udrift); void applyGroupDrift(const time_point& timebase, bool wrapcheck, const duration& udrift); time_point getPktTsbPdTime(uint32_t timestamp); int debugGetSize() const; time_point debugGetDeliveryTime(int offset); size_t dropData(int len); private: int extractData(char* data, int len, int p, int q, bool passack); bool accessMsg(int& w_p, int& w_q, bool& w_passack, int64_t& w_playtime, int upto); /// Describes the state of the first N packets std::string debugTimeState(size_t first_n_pkts) const; /// thread safe bytes counter of the Recv & Ack buffer /// @param [in] pkts acked or removed pkts from rcv buffer (used with acked = true) /// @param [in] bytes number of bytes added/delete (if negative) to/from rcv buffer. /// @param [in] acked true when adding new pkt in RcvBuffer; false when acking/removing pkts to/from buffer void countBytes(int pkts, int bytes, bool acked = false); private: bool scanMsg(int& w_start, int& w_end, bool& w_passack); int shift(int basepos, int shift) const { return (basepos + shift) % m_iSize; } /// Simplified versions with ++ and --; avoid using division instruction int shiftFwd(int basepos) const { if (++basepos == m_iSize) return 0; return basepos; } int shiftBack(int basepos) const { if (basepos == 0) return m_iSize - 1; return --basepos; } private: srt::CUnit** m_pUnit; // Array of pointed units collected in the buffer const int m_iSize; // Size of the internal array of CUnit* items srt::CUnitQueue* m_pUnitQueue; // the shared unit queue int m_iStartPos; // HEAD: first packet available for reading int m_iLastAckPos; // the last ACKed position (exclusive), follows the last readable // EMPTY: m_iStartPos = m_iLastAckPos FULL: m_iStartPos = m_iLastAckPos + 1 int m_iMaxPos; // delta between acked-TAIL and reception-TAIL int m_iNotch; // the starting read point of the first unit // (this is required for stream reading mode; it's // the position in the first unit in the list // up to which data are already retrieved; // in message reading mode it's unused and always 0) srt::sync::Mutex 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 unsigned m_uAvgPayloadSz; // Average payload size for dropped bytes estimation srt::CTsbpdTime m_tsbpd; AvgBufSize m_mavg; private: CRcvBuffer(); CRcvBuffer(const CRcvBuffer&); CRcvBuffer& operator=(const CRcvBuffer&); }; #endif srt-1.4.4/srtcore/cache.cpp000066400000000000000000000072251412557703600156000ustar00rootroot00000000000000/***************************************************************************** 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 *****************************************************************************/ #include "platform_sys.h" #include #include "cache.h" #include "core.h" using namespace std; CInfoBlock& CInfoBlock::copyFrom(const CInfoBlock& obj) { std::copy(obj.m_piIP, obj.m_piIP + 4, m_piIP); m_iIPversion = obj.m_iIPversion; m_ullTimeStamp = obj.m_ullTimeStamp; m_iSRTT = obj.m_iSRTT; 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 + 4, obj->m_piIP); obj->m_iIPversion = m_iIPversion; obj->m_ullTimeStamp = m_ullTimeStamp; obj->m_iSRTT = m_iSRTT; 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_any& addr, uint32_t aw_ip[4]) { if (addr.family() == AF_INET) { aw_ip[0] = addr.sin.sin_addr.s_addr; aw_ip[1] = aw_ip[2] = aw_ip[3] = 0; } else { memcpy((aw_ip), addr.sin6.sin6_addr.s6_addr, sizeof addr.sin6.sin6_addr.s6_addr); } } srt-1.4.4/srtcore/cache.h000066400000000000000000000175201412557703600152440ustar00rootroot00000000000000/***************************************************************************** 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 INC_SRT_CACHE_H #define INC_SRT_CACHE_H #include #include #include "sync.h" #include "netinet_any.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); // Exception: -> CUDTUnited ctor srt::sync::setupMutex(m_Lock, "Cache"); } ~CCache() { clear(); } 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) { srt::sync::ScopedLock 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) { srt::sync::ScopedLock 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; ItemPtrList& last_item_list = m_vHashPtr[last_key]; for (typename ItemPtrList::iterator i = last_item_list.begin(); i != last_item_list.end(); ++ i) { if (*last_data == ***i) { last_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; srt::sync::Mutex 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; // Address family: AF_INET or AF_INET6. uint64_t m_ullTimeStamp; // Last update time. int m_iSRTT; // Smoothed RTT. int m_iBandwidth; // Estimated link 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: CInfoBlock() {} // NOTE: leaves uninitialized CInfoBlock& copyFrom(const CInfoBlock& obj); CInfoBlock(const CInfoBlock& src) { copyFrom(src); } CInfoBlock& operator=(const CInfoBlock& src) { return copyFrom(src); } bool operator==(const CInfoBlock& obj); CInfoBlock* clone(); int getKey(); 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_any& addr, uint32_t ip[4]); }; #endif srt-1.4.4/srtcore/channel.cpp000066400000000000000000000666661412557703600161630ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ****************************************************************************/ /**************************************************************************** written by Yunhong Gu, last updated 01/27/2011 modified by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include #include // Logging #include #include #include "channel.h" #include "core.h" // srt_logging:kmlog #include "packet.h" #include "logging.h" #include "netinet_any.h" #include "utilities.h" #ifdef _WIN32 typedef int socklen_t; #endif using namespace std; using namespace srt_logging; namespace srt { #ifdef _WIN32 // use INVALID_SOCKET, as provided #else static const int INVALID_SOCKET = -1; #endif #if ENABLE_SOCK_CLOEXEC #ifndef _WIN32 #if defined(_AIX) || defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || \ defined(__FreeBSD_kernel__) || defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) // Set the CLOEXEC flag using ioctl() function static int set_cloexec(int fd, int set) { int r; do r = ioctl(fd, set ? FIOCLEX : FIONCLEX); while (r == -1 && errno == EINTR); if (r) return errno; return 0; } #else // Set the CLOEXEC flag using fcntl() function static int set_cloexec(int fd, int set) { int flags; int r; do r = fcntl(fd, F_GETFD); while (r == -1 && errno == EINTR); if (r == -1) return errno; /* Bail out now if already set/clear. */ if (!!(r & FD_CLOEXEC) == !!set) return 0; if (set) flags = r | FD_CLOEXEC; else flags = r & ~FD_CLOEXEC; do r = fcntl(fd, F_SETFD, flags); while (r == -1 && errno == EINTR); if (r) return errno; return 0; } #endif // if defined(_AIX) ... #endif // ifndef _WIN32 #endif // if ENABLE_CLOEXEC } // namespace srt srt::CChannel::CChannel() : m_iSocket(INVALID_SOCKET) { } srt::CChannel::~CChannel() {} void srt::CChannel::createSocket(int family) { #if ENABLE_SOCK_CLOEXEC bool cloexec_flag = false; // construct an socket #if defined(SOCK_CLOEXEC) m_iSocket = ::socket(family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); if (m_iSocket == INVALID_SOCKET) { m_iSocket = ::socket(family, SOCK_DGRAM, IPPROTO_UDP); cloexec_flag = true; } #else m_iSocket = ::socket(family, SOCK_DGRAM, IPPROTO_UDP); cloexec_flag = true; #endif #else // ENABLE_SOCK_CLOEXEC m_iSocket = ::socket(family, SOCK_DGRAM, IPPROTO_UDP); #endif // ENABLE_SOCK_CLOEXEC if (m_iSocket == INVALID_SOCKET) throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); #if ENABLE_SOCK_CLOEXEC #ifdef _WIN32 // XXX ::SetHandleInformation(hInputWrite, HANDLE_FLAG_INHERIT, 0) #else if (cloexec_flag) { if (0 != set_cloexec(m_iSocket, 1)) { throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); } } #endif #endif // ENABLE_SOCK_CLOEXEC if ((m_mcfg.iIpV6Only != -1) && (family == AF_INET6)) // (not an error if it fails) { const int res SRT_ATR_UNUSED = ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&m_mcfg.iIpV6Only, sizeof m_mcfg.iIpV6Only); #if ENABLE_LOGGING if (res == -1) { int err = errno; char msg[160]; LOGC(kmlog.Error, log << "::setsockopt: failed to set IPPROTO_IPV6/IPV6_V6ONLY = " << m_mcfg.iIpV6Only << ": " << SysStrError(err, msg, 159)); } #endif // ENABLE_LOGGING } } void srt::CChannel::open(const sockaddr_any& addr) { createSocket(addr.family()); socklen_t namelen = addr.size(); if (::bind(m_iSocket, &addr.sa, namelen) == -1) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); m_BindAddr = addr; LOGC(kmlog.Debug, log << "CHANNEL: Bound to local address: " << m_BindAddr.str()); setUDPSockOpt(); } void srt::CChannel::open(int family) { createSocket(family); // 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 = family; hints.ai_socktype = SOCK_DGRAM; const int eai = ::getaddrinfo(NULL, "0", &hints, &res); if (eai != 0) { // Controversial a little bit because this function occasionally // doesn't use errno (here: NET_ERROR for portability), instead // it returns 0 if succeeded or an error code. This error code // is passed here then. A controversy is around the fact that // the receiver of this error has completely no ability to know // what this error code's domain is, and it definitely isn't // the same as for errno. throw CUDTException(MJ_SETUP, MN_NORES, eai); } // On Windows ai_addrlen has type size_t (unsigned), while bind takes int. if (0 != ::bind(m_iSocket, res->ai_addr, (socklen_t)res->ai_addrlen)) { ::freeaddrinfo(res); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } m_BindAddr = sockaddr_any(res->ai_addr, (sockaddr_any::len_t)res->ai_addrlen); ::freeaddrinfo(res); HLOGC(kmlog.Debug, log << "CHANNEL: Bound to local address: " << m_BindAddr.str()); setUDPSockOpt(); } void srt::CChannel::attach(UDPSOCKET udpsock, const sockaddr_any& udpsocks_addr) { // The getsockname() call is done before calling it and the // result is placed into udpsocks_addr. m_iSocket = udpsock; m_BindAddr = udpsocks_addr; setUDPSockOpt(); } void srt::CChannel::setUDPSockOpt() { #if defined(BSD) || TARGET_OS_MAC // BSD system will fail setsockopt if the requested buffer size exceeds system maximum value int maxsize = 64000; if (0 != ::setsockopt( m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&maxsize, sizeof maxsize); if (0 != ::setsockopt( m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)) ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&maxsize, sizeof maxsize); #else // for other systems, if requested is greated than maximum, the maximum value will be automactally used if ((0 != ::setsockopt( m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) || (0 != ::setsockopt( m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #endif if (m_mcfg.iIpTTL != -1) { if (m_BindAddr.family() == AF_INET) { if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } else { // If IPv6 address is unspecified, set BOTH IP_TTL and IPV6_UNICAST_HOPS. // For specified IPv6 address, set IPV6_UNICAST_HOPS ONLY UNLESS it's an IPv4-mapped-IPv6 if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) { if (0 != ::setsockopt( m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) { throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } } // For specified IPv6 address, set IP_TTL ONLY WHEN it's an IPv4-mapped-IPv6 if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) { if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) { throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } } } } if (m_mcfg.iIpToS != -1) { if (m_BindAddr.family() == AF_INET) { if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } else { // If IPv6 address is unspecified, set BOTH IP_TOS and IPV6_TCLASS. #ifdef IPV6_TCLASS // For specified IPv6 address, set IPV6_TCLASS ONLY UNLESS it's an IPv4-mapped-IPv6 if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) { if (0 != ::setsockopt( m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) { throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } } #endif // For specified IPv6 address, set IP_TOS ONLY WHEN it's an IPv4-mapped-IPv6 if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) { if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) { throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } } } } #ifdef SRT_ENABLE_BINDTODEVICE if (!m_mcfg.sBindToDevice.empty()) { if (m_BindAddr.family() != AF_INET) { LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE can only be set with AF_INET connections"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } if (0 != ::setsockopt( m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, m_mcfg.sBindToDevice.c_str(), m_mcfg.sBindToDevice.size())) { #if ENABLE_LOGGING char buf[255]; const char* err = SysStrError(NET_ERROR, buf, 255); LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << err); #endif // ENABLE_LOGGING throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } } #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) u_long nonBlocking = 1; if (0 != ioctlsocket(m_iSocket, FIONBIO, &nonBlocking)) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #else timeval tv; tv.tv_sec = 0; #if defined(BSD) || TARGET_OS_MAC // 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 // 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 srt::CChannel::close() const { #ifndef _WIN32 ::close(m_iSocket); #else ::closesocket(m_iSocket); #endif } int srt::CChannel::getSndBufSize() { socklen_t size = (socklen_t)sizeof m_mcfg.iUDPSndBufSize; ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_mcfg.iUDPSndBufSize, &size); return m_mcfg.iUDPSndBufSize; } int srt::CChannel::getRcvBufSize() { socklen_t size = (socklen_t)sizeof m_mcfg.iUDPRcvBufSize; ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&m_mcfg.iUDPRcvBufSize, &size); return m_mcfg.iUDPRcvBufSize; } void srt::CChannel::setConfig(const CSrtMuxerConfig& config) { m_mcfg = config; } int srt::CChannel::getIpTTL() const { if (m_iSocket == INVALID_SOCKET) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); socklen_t size = (socklen_t)sizeof m_mcfg.iIpTTL; if (m_BindAddr.family() == AF_INET) { ::getsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (char*)&m_mcfg.iIpTTL, &size); } else if (m_BindAddr.family() == AF_INET6) { ::getsockopt(m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char*)&m_mcfg.iIpTTL, &size); } else { // If family is unspecified, the socket probably doesn't exist. LOGC(kmlog.Error, log << "IPE: CChannel::getIpTTL called with unset family"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } return m_mcfg.iIpTTL; } int srt::CChannel::getIpToS() const { if (m_iSocket == INVALID_SOCKET) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); socklen_t size = (socklen_t)sizeof m_mcfg.iIpToS; if (m_BindAddr.family() == AF_INET) { ::getsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (char*)&m_mcfg.iIpToS, &size); } else if (m_BindAddr.family() == AF_INET6) { #ifdef IPV6_TCLASS ::getsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (char*)&m_mcfg.iIpToS, &size); #endif } else { // If family is unspecified, the socket probably doesn't exist. LOGC(kmlog.Error, log << "IPE: CChannel::getIpToS called with unset family"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } return m_mcfg.iIpToS; } #ifdef SRT_ENABLE_BINDTODEVICE bool srt::CChannel::getBind(char* dst, size_t len) { if (m_iSocket == INVALID_SOCKET) return false; // No socket to get data from // Try to obtain it directly from the function. If not possible, // then return from internal data. socklen_t length = len; int res = ::getsockopt(m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, dst, &length); if (res == -1) return false; // Happens on Linux v < 3.8 // For any case dst[length] = 0; return true; } #endif int srt::CChannel::ioctlQuery(int type SRT_ATR_UNUSED) const { #if defined(unix) || defined(__APPLE__) int value = 0; int res = ::ioctl(m_iSocket, type, &value); if (res != -1) return value; #endif return -1; } int srt::CChannel::sockoptQuery(int level SRT_ATR_UNUSED, int option SRT_ATR_UNUSED) const { #if defined(unix) || defined(__APPLE__) int value = 0; socklen_t len = sizeof(int); int res = ::getsockopt(m_iSocket, level, option, &value, &len); if (res != -1) return value; #endif return -1; } void srt::CChannel::getSockAddr(sockaddr_any& w_addr) const { // The getsockname function requires only to have enough target // space to copy the socket name, it doesn't have to be correlated // with the address family. So the maximum space for any name, // regardless of the family, does the job. socklen_t namelen = (socklen_t)w_addr.storage_size(); ::getsockname(m_iSocket, (w_addr.get()), (&namelen)); w_addr.len = namelen; } void srt::CChannel::getPeerAddr(sockaddr_any& w_addr) const { socklen_t namelen = (socklen_t)w_addr.storage_size(); ::getpeername(m_iSocket, (w_addr.get()), (&namelen)); w_addr.len = namelen; } int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet) const { HLOGC(kslog.Debug, log << "CChannel::sendto: SENDING NOW DST=" << addr.str() << " target=@" << packet.m_iID << " size=" << packet.getLength() << " pkt.ts=" << packet.m_iTimeStamp << " " << packet.Info()); #ifdef SRT_TEST_FAKE_LOSS #define FAKELOSS_STRING_0(x) #x #define FAKELOSS_STRING(x) FAKELOSS_STRING_0(x) const char* fakeloss_text = FAKELOSS_STRING(SRT_TEST_FAKE_LOSS); #undef FAKELOSS_STRING #undef FAKELOSS_WRAP static int dcounter = 0; static int flwcounter = 0; struct FakelossConfig { pair config; FakelossConfig(const char* f) { vector out; Split(f, '+', back_inserter(out)); config.first = atoi(out[0].c_str()); config.second = out.size() > 1 ? atoi(out[1].c_str()) : 8; } }; static FakelossConfig fakeloss = fakeloss_text; if (!packet.isControl()) { ++dcounter; if (flwcounter) { // This is a counter of how many packets in a row shall be lost --flwcounter; HLOGC(kslog.Debug, log << "CChannel: TEST: FAKE LOSS OF %" << packet.getSeqNo() << " (" << flwcounter << " more to drop)"); return packet.getLength(); // fake successful sendinf } if (dcounter > 8) { // Make a random number in the range between 8 and 24 const int rnd = srt::sync::getRandomInt(8, 24); if (dcounter > rnd) { dcounter = 1; HLOGC(kslog.Debug, log << "CChannel: TEST: FAKE LOSS OF %" << packet.getSeqNo() << " (will drop " << fakeloss.config.first << " more)"); flwcounter = fakeloss.config.first; return packet.getLength(); // fake successful sendinf } } } #endif // convert control information into network order packet.toNL(); #ifndef _WIN32 msghdr mh; mh.msg_name = (sockaddr*)&addr; mh.msg_namelen = addr.size(); mh.msg_iov = (iovec*)packet.m_PacketVector; mh.msg_iovlen = 2; mh.msg_control = NULL; mh.msg_controllen = 0; mh.msg_flags = 0; const int res = ::sendmsg(m_iSocket, &mh, 0); #else DWORD size = (DWORD)(CPacket::HDR_SIZE + packet.getLength()); int addrsize = addr.size(); int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr.get(), addrsize, NULL, NULL); res = (0 == res) ? size : -1; #endif packet.toHL(); return res; } EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet) const { EReadStatus status = RST_OK; int msg_flags = 0; int recv_size = -1; #if defined(UNIX) || defined(_WIN32) fd_set set; timeval tv; FD_ZERO(&set); FD_SET(m_iSocket, &set); tv.tv_sec = 0; tv.tv_usec = 10000; const int select_ret = ::select((int)m_iSocket + 1, &set, NULL, &set, &tv); #else const int select_ret = 1; // the socket is expected to be in the blocking mode itself #endif if (select_ret == 0) // timeout { w_packet.setLength(-1); return RST_AGAIN; } #ifndef _WIN32 if (select_ret > 0) { msghdr mh; mh.msg_name = (w_addr.get()); mh.msg_namelen = w_addr.size(); mh.msg_iov = (w_packet.m_PacketVector); mh.msg_iovlen = 2; mh.msg_control = NULL; mh.msg_controllen = 0; mh.msg_flags = 0; recv_size = ::recvmsg(m_iSocket, (&mh), 0); msg_flags = mh.msg_flags; } // Note that there are exactly four groups of possible errors // reported by recvmsg(): // 1. Temporary error, can't get the data, but you can try again. // Codes: EAGAIN/EWOULDBLOCK, EINTR, ECONNREFUSED // Return: RST_AGAIN. // // 2. Problems that should never happen due to unused configurations. // Codes: ECONNREFUSED, ENOTCONN // Return: RST_ERROR, just formally treat this as IPE. // // 3. Unexpected runtime errors: // Codes: EINVAL, EFAULT, ENOMEM, ENOTSOCK // Return: RST_ERROR. Except ENOMEM, this can only be an IPE. ENOMEM // should make the program stop as lacking memory will kill the program anyway soon. // // 4. Expected socket closed in the meantime by another thread. // Codes: EBADF // Return: RST_ERROR. This will simply make the worker thread exit, which is // expected to happen after CChannel::close() is called by another thread. // We do not handle <= SOCKET_ERROR as they are handled further by checking the recv_size if (select_ret == -1 || recv_size == -1) { const int err = NET_ERROR; if (err == EAGAIN || err == EINTR || err == ECONNREFUSED) // For EAGAIN, this isn't an error, just a useless call. { status = RST_AGAIN; } else { HLOGC(krlog.Debug, log << CONID() << "(sys)recvmsg: " << SysStrError(err) << " [" << err << "]"); status = RST_ERROR; } goto Return_error; } #else // XXX REFACTORING NEEDED! // This procedure uses the WSARecvFrom function that just reads // into one buffer. On Windows, the equivalent for recvmsg, WSARecvMsg // uses the equivalent of msghdr - WSAMSG, which has different field // names and also uses the equivalet of iovec - WSABUF, which has different // field names and layout. It is important that this code be translated // to the "proper" solution, however this requires that CPacket::m_PacketVector // also uses the "platform independent" (or, better, platform-suitable) type // which can be appropriate for the appropriate system function, not just iovec // (see a specifically provided definition for iovec for windows in packet.h). // // For the time being, the msg_flags variable is defined in both cases // so that it can be checked independently, however it won't have any other // value one Windows than 0, unless this procedure below is rewritten // to use WSARecvMsg(). int recv_ret = SOCKET_ERROR; DWORD flag = 0; if (select_ret > 0) // the total number of socket handles that are ready { DWORD size = (DWORD)(CPacket::HDR_SIZE + w_packet.getLength()); int addrsize = w_addr.size(); recv_ret = ::WSARecvFrom(m_iSocket, ((LPWSABUF)w_packet.m_PacketVector), 2, (&size), (&flag), (w_addr.get()), (&addrsize), NULL, NULL); if (recv_ret == 0) recv_size = size; } // We do not handle <= SOCKET_ERROR as they are handled further by checking the recv_size if (select_ret == SOCKET_ERROR || recv_ret == SOCKET_ERROR) // == SOCKET_ERROR { recv_size = -1; // On Windows this is a little bit more complicated, so simply treat every error // as an "again" situation. This should still be probably fixed, but it needs more // thorough research. For example, the problem usually reported from here is // WSAETIMEDOUT, which isn't mentioned in the documentation of WSARecvFrom at all. // // These below errors are treated as "fatal", all others are treated as "again". static const int fatals[] = {WSAEFAULT, WSAEINVAL, WSAENETDOWN, WSANOTINITIALISED, WSA_OPERATION_ABORTED}; static const int* fatals_end = fatals + Size(fatals); const int err = NET_ERROR; if (std::find(fatals, fatals_end, err) != fatals_end) { HLOGC(krlog.Debug, log << CONID() << "(sys)WSARecvFrom: " << SysStrError(err) << " [" << err << "]"); status = RST_ERROR; } else { status = RST_AGAIN; } goto Return_error; } // Not sure if this problem has ever occurred on Windows, just a sanity check. if (flag & MSG_PARTIAL) msg_flags = 1; #endif // Sanity check for a case when it didn't fill in even the header if (size_t(recv_size) < CPacket::HDR_SIZE) { status = RST_AGAIN; HLOGC(krlog.Debug, log << CONID() << "POSSIBLE ATTACK: received too short packet with " << recv_size << " bytes"); goto Return_error; } // Fix for an issue with Linux Kernel found during tests at Tencent. // // There was a bug in older Linux Kernel which caused that when the internal // buffer was depleted during reading from the network, not the whole buffer // was copied from the packet, EVEN THOUGH THE GIVEN BUFFER WAS OF ENOUGH SIZE. // It was still very kind of the buggy procedure, though, that at least // they inform the caller about that this has happened by setting MSG_TRUNC // flag. // // Normally this flag should be set only if there was too small buffer given // by the caller, so as this code knows that the size is enough, it never // predicted this to happen. Just for a case then when you run this on a buggy // system that suffers of this problem, the fix for this case is left here. // // When this happens, then you have at best a fragment of the buffer and it's // useless anyway. This is solved by dropping the packet and fake that no // packet was received, so the packet will be then retransmitted. if (msg_flags != 0) { HLOGC(krlog.Debug, log << CONID() << "NET ERROR: packet size=" << recv_size << " msg_flags=0x" << hex << msg_flags << ", possibly MSG_TRUNC (0x" << hex << int(MSG_TRUNC) << ")"); status = RST_AGAIN; goto Return_error; } w_packet.setLength(recv_size - CPacket::HDR_SIZE); // convert back into local host order // XXX use NtoHLA(). // for (int i = 0; i < 4; ++ i) // w_packet.m_nHeader[i] = ntohl(w_packet.m_nHeader[i]); { uint32_t* p = w_packet.m_nHeader; for (size_t i = 0; i < SRT_PH_E_SIZE; ++i) { *p = ntohl(*p); ++p; } } if (w_packet.isControl()) { for (size_t j = 0, n = w_packet.getLength() / sizeof(uint32_t); j < n; ++j) *((uint32_t*)w_packet.m_pcData + j) = ntohl(*((uint32_t*)w_packet.m_pcData + j)); } return RST_OK; Return_error: w_packet.setLength(-1); return status; } srt-1.4.4/srtcore/channel.h000066400000000000000000000124271412557703600156120ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 01/27/2011 modified by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_CHANNEL_H #define INC_SRT_CHANNEL_H #include "platform_sys.h" #include "udt.h" #include "packet.h" #include "socketconfig.h" #include "netinet_any.h" namespace srt { class CChannel { void createSocket(int family); 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(); /// Open a UDP channel. /// @param [in] addr The local address that UDP will use. void open(const sockaddr_any& addr); void open(int family); /// Open a UDP channel based on an existing UDP socket. /// @param [in] udpsock UDP socket descriptor. void attach(UDPSOCKET udpsock, const sockaddr_any& adr); /// 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(); /// Query the socket address that the channel is using. /// @param [out] addr pointer to store the returned socket address. void getSockAddr(sockaddr_any& addr) const; /// Query the peer side socket address that the channel is connect to. /// @param [out] addr pointer to store the returned socket address. void getPeerAddr(sockaddr_any& addr) const; /// Send a packet to the given address. /// @param [in] addr pointer to the destination address. /// @param [in] packet reference to a CPacket entity. /// @return Actual size of data sent. int sendto(const sockaddr_any& addr, srt::CPacket& packet) const; /// Receive a packet from the channel and record the source address. /// @param [in] addr pointer to the source address. /// @param [in] packet reference to a CPacket entity. /// @return Actual size of data received. EReadStatus recvfrom(sockaddr_any& addr, srt::CPacket& packet) const; void setConfig(const CSrtMuxerConfig& config); /// 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; #ifdef SRT_ENABLE_BINDTODEVICE bool getBind(char* dst, size_t len); #endif int ioctlQuery(int type) const; int sockoptQuery(int level, int option) const; const sockaddr* bindAddress() { return m_BindAddr.get(); } const sockaddr_any& bindAddressAny() { return m_BindAddr; } private: void setUDPSockOpt(); private: UDPSOCKET m_iSocket; // socket descriptor // Mutable because when querying original settings // this comprises the cache for extracted values, // although the object itself isn't considered modified. mutable CSrtMuxerConfig m_mcfg; // Note: ReuseAddr is unused and ineffective. sockaddr_any m_BindAddr; }; } // namespace srt #endif srt-1.4.4/srtcore/common.cpp000066400000000000000000000460361412557703600160300ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 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. *****************************************************************************/ #define SRT_IMPORT_TIME 1 #include "platform_sys.h" #include #include #include #include #include #include #include #include "udt.h" #include "md5.h" #include "common.h" #include "netinet_any.h" #include "logging.h" #include "packet.h" #include "threadname.h" #include // SysStrError using namespace srt; using namespace srt::sync; namespace srt_logging { extern Logger inlog; } using namespace srt_logging; CUDTException::CUDTException(CodeMajor major, CodeMinor minor, int err): m_iMajor(major), m_iMinor(minor) { if (err == -1) m_iErrno = NET_ERROR; else m_iErrno = err; } namespace srt { const char* strerror_get_message(size_t major, size_t minor); } const char* CUDTException::getErrorMessage() const ATR_NOTHROW { return srt::strerror_get_message(m_iMajor, m_iMinor); } std::string CUDTException::getErrorString() const { return getErrorMessage(); } #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; } #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_any& addr, uint32_t ip[4]) { if (addr.family() == AF_INET) { // SRT internal format of IPv4 address. // The IPv4 address is in the first field. The rest is 0. ip[0] = addr.sin.sin_addr.s_addr; ip[1] = ip[2] = ip[3] = 0; } else { const sockaddr_in6* a = &addr.sin6; 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]; } } bool checkMappedIPv4(const uint16_t* addr) { static const uint16_t ipv4on6_model [8] = { 0, 0, 0, 0, 0, 0xFFFF, 0, 0 }; // Compare only first 6 words. Remaining 2 words // comprise the IPv4 address, if these first 6 match. const uint16_t* mbegin = ipv4on6_model; const uint16_t* mend = ipv4on6_model + 6; return std::equal(mbegin, mend, addr); } // XXX This has void return and the first argument is passed by reference. // Consider simply returning sockaddr_any by value. void CIPAddress::pton(sockaddr_any& w_addr, const uint32_t ip[4], const sockaddr_any& peer) { uint32_t* target_ipv4_addr = NULL; if (peer.family() == AF_INET) { sockaddr_in* a = (&w_addr.sin); target_ipv4_addr = (uint32_t*) &a->sin_addr.s_addr; } else // AF_INET6 { // Check if the peer address is a model of IPv4-mapped-on-IPv6. // If so, it means that the `ip` array should be interpreted as IPv4. const bool is_mapped_ipv4 = checkMappedIPv4((uint16_t*)peer.sin6.sin6_addr.s6_addr); sockaddr_in6* a = (&w_addr.sin6); // This whole above procedure was only in order to EXCLUDE the // possibility of IPv4-mapped-on-IPv6. This below may only happen // if BOTH peers are IPv6. Otherwise we have a situation of cross-IP // version connection in which case the address in question is always // IPv4 in various mapping formats. if (!is_mapped_ipv4) { // Here both agent and peer use IPv6, in which case // `ip` contains the full IPv6 address, so just copy // it as is. // XXX Possibly, a simple // memcpy( (a->sin6_addr.s6_addr), ip, 16); // would do the same thing, and faster. The address in `ip`, // even though coded here as uint32_t, is still big endian. for (int i = 0; i < 4; ++ i) { a->sin6_addr.s6_addr[i * 4 + 0] = 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); } return; // The address is written, nothing left to do. } // // IPv4 mapped on IPv6 // Here agent uses IPv6 with IPPROTO_IPV6/IPV6_V6ONLY == 0 // In this case, the address in `ip` is always an IPv4, // although we are not certain as to whether it's using the // IPv6 encoding (0::FFFF:IPv4) or SRT encoding (IPv4::0); // this must be extra determined. // // Unfortunately, sockaddr_in6 doesn't give any straightforward // method for it, although the official size of a single element // of the IPv6 address is 16-bit. memset((a->sin6_addr.s6_addr), 0, sizeof a->sin6_addr.s6_addr); // The sin6_addr.s6_addr32 is non that portable to use here. uint32_t* paddr32 = (uint32_t*)a->sin6_addr.s6_addr; uint16_t* paddr16 = (uint16_t*)a->sin6_addr.s6_addr; // layout: of IPv4 address 192.168.128.2 // 16-bit: // [0000: 0000: 0000: 0000: 0000: FFFF: 192.168:128.2] // 8-bit // [00/00/00/00/00/00/00/00/00/00/FF/FF/192/168/128/2] // 32-bit // [00000000 && 00000000 && 0000FFFF && 192.168.128.2] // Spreading every 16-bit word separately to avoid endian dilemmas paddr16[2 * 2 + 1] = 0xFFFF; target_ipv4_addr = &paddr32[3]; } // Now we have two possible formats of encoding the IPv4 address: // 1. If peer is IPv4, it's IPv4::0 // 2. If peer is IPv6, it's 0::FFFF:IPv4. // // Has any other possibility happen here, copy an empty address, // which will be the only sign of an error. const uint16_t* peeraddr16 = (uint16_t*)ip; const bool is_mapped_ipv4 = checkMappedIPv4(peeraddr16); if (is_mapped_ipv4) { *target_ipv4_addr = ip[3]; HLOGC(inlog.Debug, log << "pton: Handshake address: " << w_addr.str() << " provided in IPv6 mapping format"); } // Check SRT IPv4 format. else if ((ip[1] | ip[2] | ip[3]) == 0) { *target_ipv4_addr = ip[0]; HLOGC(inlog.Debug, log << "pton: Handshake address: " << w_addr.str() << " provided in SRT IPv4 format"); } else { LOGC(inlog.Error, log << "pton: IPE or net error: can't determine IPv4 carryover format: " << std::hex << peeraddr16[0] << ":" << peeraddr16[1] << ":" << peeraddr16[2] << ":" << peeraddr16[3] << ":" << peeraddr16[4] << ":" << peeraddr16[5] << ":" << peeraddr16[6] << ":" << peeraddr16[7] << std::dec); *target_ipv4_addr = 0; if (peer.family() != AF_INET) { // Additionally overwrite the 0xFFFF that has been // just written 50 lines above. w_addr.sin6.sin6_addr.s6_addr[10] = 0; w_addr.sin6.sin6_addr.s6_addr[11] = 0; } } } 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, (int) strlen(input)); md5_finish(&state, result); } std::string MessageTypeStr(UDTMessageType mt, uint32_t extt) { using std::string; static const char* const udt_types [] = { "handshake", "keepalive", "ack", "lossreport", "cgwarning", //4 "shutdown", "ackack", "dropreq", "peererror", //8 }; static const char* const srt_types [] = { "EXT:none", "EXT:hsreq", "EXT:hsrsp", "EXT:kmreq", "EXT:kmrsp", "EXT:sid", "EXT:congctl", "EXT:filter", "EXT:group" }; if ( mt == UMSG_EXT ) { if ( extt >= Size(srt_types) ) return "EXT:unknown"; return srt_types[extt]; } if ( size_t(mt) > Size(udt_types) ) return "unknown"; return udt_types[mt]; } std::string ConnectStatusStr(EConnectStatus cst) { return cst == CONN_CONTINUE ? "INDUCED/CONCLUDING" : cst == CONN_RUNNING ? "RUNNING" : cst == CONN_ACCEPT ? "ACCEPTED" : cst == CONN_RENDEZVOUS ? "RENDEZVOUS (HSv5)" : cst == CONN_AGAIN ? "AGAIN" : cst == CONN_CONFUSED ? "MISSING HANDSHAKE" : "REJECTED"; } std::string TransmissionEventStr(ETransmissionEvent ev) { static const char* const vals [] = { "init", "ack", "ackack", "lossreport", "checktimer", "send", "receive", "custom" }; size_t vals_size = Size(vals); if (size_t(ev) >= vals_size) return "UNKNOWN"; return vals[ev]; } extern const char* const srt_rejectreason_msg [] = { "Unknown or erroneous", "Error in system calls", "Peer rejected connection", "Resource allocation failure", "Rogue peer or incorrect parameters", "Listener's backlog exceeded", "Internal Program Error", "Socket is being closed", "Peer version too old", "Rendezvous-mode cookie collision", "Incorrect passphrase", "Password required or unexpected", "MessageAPI/StreamAPI collision", "Congestion controller type collision", "Packet Filter settings error", "Group settings collision", "Connection timeout" }; const char* srt_rejectreason_str(int id) { if (id >= SRT_REJC_PREDEFINED) { return "Application-defined rejection reason"; } static const size_t ra_size = Size(srt_rejectreason_msg); if (size_t(id) >= ra_size) return srt_rejectreason_msg[0]; return srt_rejectreason_msg[id]; } bool SrtParseConfig(string s, SrtConfig& w_config) { using namespace std; vector parts; Split(s, ',', back_inserter(parts)); w_config.type = parts[0]; for (vector::iterator i = parts.begin()+1; i != parts.end(); ++i) { vector keyval; Split(*i, ':', back_inserter(keyval)); if (keyval.size() != 2) return false; if (keyval[1] != "") w_config.parameters[keyval[0]] = keyval[1]; } return true; } uint64_t PacketMetric::fullBytes() { static const int PKT_HDR_SIZE = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; return bytes + pkts * PKT_HDR_SIZE; } namespace srt_logging { // Value display utilities // (also useful for applications) std::string SockStatusStr(SRT_SOCKSTATUS s) { if (int(s) < int(SRTS_INIT) || int(s) > int(SRTS_NONEXIST)) return "???"; static struct AutoMap { // Values start from 1, so do -1 to avoid empty cell std::string names[int(SRTS_NONEXIST)-1+1]; AutoMap() { #define SINI(statename) names[SRTS_##statename-1] = #statename SINI(INIT); SINI(OPENED); SINI(LISTENING); SINI(CONNECTING); SINI(CONNECTED); SINI(BROKEN); SINI(CLOSING); SINI(CLOSED); SINI(NONEXIST); #undef SINI } } names; return names.names[int(s)-1]; } #if ENABLE_EXPERIMENTAL_BONDING std::string MemberStatusStr(SRT_MEMBERSTATUS s) { if (int(s) < int(SRT_GST_PENDING) || int(s) > int(SRT_GST_BROKEN)) return "???"; static struct AutoMap { std::string names[int(SRT_GST_BROKEN)+1]; AutoMap() { #define SINI(statename) names[SRT_GST_##statename] = #statename SINI(PENDING); SINI(IDLE); SINI(RUNNING); SINI(BROKEN); #undef SINI } } names; return names.names[int(s)]; } #endif // Logging system implementation #if ENABLE_LOGGING LogDispatcher::Proxy::Proxy(LogDispatcher& guy) : that(guy), that_enabled(that.CheckEnabled()) { if (that_enabled) { i_file = ""; i_line = 0; flags = that.src_config->flags; // Create logger prefix that.CreateLogLinePrefix(os); } } LogDispatcher::Proxy LogDispatcher::operator()() { return Proxy(*this); } void LogDispatcher::CreateLogLinePrefix(std::ostringstream& serr) { using namespace std; SRT_STATIC_ASSERT(ThreadName::BUFSIZE >= sizeof("hh:mm:ss.") * 2, // multiply 2 for some margin "ThreadName::BUFSIZE is too small to be used for strftime"); char tmp_buf[ThreadName::BUFSIZE]; if ( !isset(SRT_LOGF_DISABLE_TIME) ) { // Not necessary if sending through the queue. timeval tv; gettimeofday(&tv, NULL); struct tm tm = SysLocalTime((time_t) tv.tv_sec); if (strftime(tmp_buf, sizeof(tmp_buf), "%X.", &tm)) { serr << tmp_buf << setw(6) << setfill('0') << tv.tv_usec; } } string out_prefix; if ( !isset(SRT_LOGF_DISABLE_SEVERITY) ) { out_prefix = prefix; } // Note: ThreadName::get needs a buffer of size min. ThreadName::BUFSIZE if ( !isset(SRT_LOGF_DISABLE_THREADNAME) && ThreadName::get(tmp_buf) ) { serr << "/" << tmp_buf << out_prefix << ": "; } else { serr << out_prefix << ": "; } } std::string LogDispatcher::Proxy::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); } #endif } // (end namespace srt_logging) srt-1.4.4/srtcore/common.h000066400000000000000000001236001412557703600154660ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 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 INC_SRT_COMMON_H #define INC_SRT_COMMON_H #define _CRT_SECURE_NO_WARNINGS 1 // silences windows complaints for sscanf #include #include #include #ifndef _WIN32 #include #include #else // #include //#include #endif #include "srt.h" #include "utilities.h" #include "sync.h" #include "netinet_any.h" #include "packetfilter_api.h" // System-independent errno #ifndef _WIN32 #define NET_ERROR errno #else #define NET_ERROR WSAGetLastError() #endif #ifdef _DEBUG #include #define SRT_ASSERT(cond) assert(cond) #else #define SRT_ASSERT(cond) #endif #if HAVE_FULL_CXX11 #define SRT_STATIC_ASSERT(cond, msg) static_assert(cond, msg) #else #define SRT_STATIC_ASSERT(cond, msg) #endif #include // 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 CUDTException: public std::exception { public: CUDTException(CodeMajor major = MJ_SUCCESS, CodeMinor minor = MN_NONE, int err = -1); virtual ~CUDTException() ATR_NOTHROW {} /// Get the description of the exception. /// @return Text message for the exception description. const char* getErrorMessage() const ATR_NOTHROW; virtual const char* what() const ATR_NOTHROW ATR_OVERRIDE { return getErrorMessage(); } std::string getErrorString() const; /// Get the system errno for the exception. /// @return errno. int getErrorCode() const; /// Get the system network errno for the exception. /// @return errno. int getErrno() const; /// 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 mutable std::string m_strMsg; // text error message (cache) 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: // Legacy Error Code static const int EUNKNOWN = SRT_EUNKNOWN; static const int SUCCESS = SRT_SUCCESS; static const int ECONNSETUP = SRT_ECONNSETUP; static const int ENOSERVER = SRT_ENOSERVER; static const int ECONNREJ = SRT_ECONNREJ; static const int ESOCKFAIL = SRT_ESOCKFAIL; static const int ESECFAIL = SRT_ESECFAIL; static const int ECONNFAIL = SRT_ECONNFAIL; static const int ECONNLOST = SRT_ECONNLOST; static const int ENOCONN = SRT_ENOCONN; static const int ERESOURCE = SRT_ERESOURCE; static const int ETHREAD = SRT_ETHREAD; static const int ENOBUF = SRT_ENOBUF; static const int EFILE = SRT_EFILE; static const int EINVRDOFF = SRT_EINVRDOFF; static const int ERDPERM = SRT_ERDPERM; static const int EINVWROFF = SRT_EINVWROFF; static const int EWRPERM = SRT_EWRPERM; static const int EINVOP = SRT_EINVOP; static const int EBOUNDSOCK = SRT_EBOUNDSOCK; static const int ECONNSOCK = SRT_ECONNSOCK; static const int EINVPARAM = SRT_EINVPARAM; static const int EINVSOCK = SRT_EINVSOCK; static const int EUNBOUNDSOCK = SRT_EUNBOUNDSOCK; static const int ESTREAMILL = SRT_EINVALMSGAPI; static const int EDGRAMILL = SRT_EINVALBUFFERAPI; static const int ENOLISTEN = SRT_ENOLISTEN; static const int ERDVNOSERV = SRT_ERDVNOSERV; static const int ERDVUNBOUND = SRT_ERDVUNBOUND; static const int EINVALMSGAPI = SRT_EINVALMSGAPI; static const int EINVALBUFFERAPI = SRT_EINVALBUFFERAPI; static const int EDUPLISTEN = SRT_EDUPLISTEN; static const int ELARGEMSG = SRT_ELARGEMSG; static const int EINVPOLLID = SRT_EINVPOLLID; static const int EASYNCFAIL = SRT_EASYNCFAIL; static const int EASYNCSND = SRT_EASYNCSND; static const int EASYNCRCV = SRT_EASYNCRCV; static const int ETIMEOUT = SRT_ETIMEOUT; static const int ECONGEST = SRT_ECONGEST; static const int EPEERERR = SRT_EPEERERR; }; 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 (NAK). 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. }; // This side's role is: INITIATOR prepares the environment first, and sends // appropriate information to the peer. The peer must be RESPONDER and be ready // to receive it. It's important for the encryption: the INITIATOR side generates // the KM, and sends it to RESPONDER. RESPONDER awaits KM received from the // INITIATOR. Note that in bidirectional mode - that is always with HSv5 - the // INITIATOR creates both sending and receiving contexts, then sends the key to // RESPONDER, which creates both sending and receiving contexts, using the same // key received from INITIATOR. // // The method of selection: // // In HSv4, it's always data sender (the party that sets SRTO_SENDER flag on the // socket) INITIATOR, and receiver - RESPONDER. The HSREQ and KMREQ are done // AFTER the UDT connection is done using UMSG_EXT extension messages. As this // is unidirectional, the INITIATOR prepares the sending context only, the // RESPONDER - receiving context only. // // In HSv5, for caller-listener configuration, it's simple: caller is INITIATOR, // listener is RESPONDER. In case of rendezvous the parties are equivalent, // so the role is resolved by "cookie contest". Rendezvous sockets both know // each other's cookie generated during the URQ_WAVEAHAND handshake phase. // The cookies are simply compared as integer numbers; the party which's cookie // is a greater number becomes an INITIATOR, and the other party becomes a // RESPONDER. // // The case of a draw - that both occasionally have baked identical cookies - // is treated as an extremely rare and virtually impossible case, so this // results in connection rejected. enum HandshakeSide { HSD_DRAW, HSD_INITIATOR, //< Side that initiates HSREQ/KMREQ. HSv4: data sender, HSv5: connecting socket or winner rendezvous socket HSD_RESPONDER //< Side that expects HSREQ/KMREQ from the peer. HSv4: data receiver, HSv5: accepted socket or loser rendezvous socket }; // For debug std::string MessageTypeStr(UDTMessageType mt, uint32_t extt = 0); //////////////////////////////////////////////////////////////////////////////// // Commonly used by various reading facilities enum EReadStatus { RST_OK = 0, //< A new portion of data has been received RST_AGAIN, //< Nothing has been received, try again RST_ERROR = -1 //< Irrecoverable error, please close descriptor and stop reading. }; enum EConnectStatus { CONN_ACCEPT = 0, //< Received final handshake that confirms connection established CONN_REJECT = -1, //< Error during processing handshake. CONN_CONTINUE = 1, //< induction->conclusion phase CONN_RENDEZVOUS = 2, //< pass to a separate rendezvous processing (HSv5 only) CONN_CONFUSED = 3, //< listener thinks it's connected, but caller missed conclusion CONN_RUNNING = 10, //< no connection in progress, already connected CONN_AGAIN = -2 //< No data was read, don't change any state. }; enum EConnectMethod { COM_ASYNCHRO, COM_SYNCHRO }; std::string ConnectStatusStr(EConnectStatus est); const int64_t BW_INFINITE = 1000000000/8; //Infinite=> 1 Gbps enum ETransmissionEvent { TEV_INIT, // --> After creation, and after any parameters were updated. TEV_ACK, // --> When handling UMSG_ACK - older CCC:onAck() TEV_ACKACK, // --> UDT does only RTT sync, can be read from CUDT::SRTT(). TEV_LOSSREPORT, // --> When handling UMSG_LOSSREPORT - older CCC::onLoss() TEV_CHECKTIMER, // --> See TEV_CHT_REXMIT TEV_SEND, // --> When the packet is scheduled for sending - older CCC::onPktSent TEV_RECEIVE, // --> When a data packet was received - older CCC::onPktReceived TEV_CUSTOM, // --> probably dead call - older CCC::processCustomMsg TEV_E_SIZE }; std::string TransmissionEventStr(ETransmissionEvent ev); // Special parameter for TEV_CHECKTIMER enum ECheckTimerStage { TEV_CHT_INIT, // --> UDT: just update parameters, don't call any CCC::* TEV_CHT_FASTREXMIT, // --> not available on UDT TEV_CHT_REXMIT // --> CCC::onTimeout() in UDT }; enum EInitEvent { TEV_INIT_RESET = 0, TEV_INIT_INPUTBW, TEV_INIT_OHEADBW }; namespace srt { class CPacket; } // XXX Use some more standard less hand-crafted solution, if possible // XXX Consider creating a mapping between TEV_* values and associated types, // so that the type is compiler-enforced when calling updateCC() and when // connecting signals to slots. struct EventVariant { enum Type {UNDEFINED, PACKET, ARRAY, ACK, STAGE, INIT} type; union U { const srt::CPacket* packet; int32_t ack; struct { const int32_t* ptr; size_t len; } array; ECheckTimerStage stage; EInitEvent init; } u; template struct VariantFor; // Note: UNDEFINED and ARRAY don't have assignment operator. // For ARRAY you'll use 'set' function. For UNDEFINED there's nothing. explicit EventVariant(const srt::CPacket* arg) { type = PACKET; u.packet = arg; } explicit EventVariant(int32_t arg) { type = ACK; u.ack = arg; } explicit EventVariant(ECheckTimerStage arg) { type = STAGE; u.stage = arg; } explicit EventVariant(EInitEvent arg) { type = INIT; u.init = arg; } const int32_t* get_ptr() const { return u.array.ptr; } size_t get_len() const { return u.array.len; } void set(const int32_t* ptr, size_t len) { type = ARRAY; u.array.ptr = ptr; u.array.len = len; } EventVariant(const int32_t* ptr, size_t len) { set(ptr, len); } template typename VariantFor::type get() const { return u.*(VariantFor::field()); } }; /* Maybe later. This had to be a solution for automatic extraction of the type hidden in particular EventArg for particular event so that it's not runtime-mistaken. In order that this make sense there would be required an array indexed by event id (just like a slot array m_Slots in CUDT), where the "type distiller" function would be extracted and then combined with the user-connected slot function this would call it already with correct type. Note that also the ConnectSignal function would have to get the signal id by template parameter, not function parameter. For example: m_parent->ConnectSignal(SSLOT(updateOnSent)); in which updateOnSent would have to receive an appropriate type. This has a disadvantage that you can't connect multiple signals with different argument types to the same slot, you'd have to make slot wrappers to translate arguments. It seems that a better idea would be to create binders that would translate the argument from EventArg to the correct type according to the rules imposed by particular event id. But I'd not make it until there's a green light on C++11 for SRT, so maybe in a far future. template class EventArgType; #define MAP_EVENT_TYPE(tev, tp) template<> class EventArgType { typedef tp type; } */ // The 'type' field wouldn't be even necessary if we // use a full-templated version. TBD. template<> struct EventVariant::VariantFor { typedef const srt::CPacket* type; static type U::*field() {return &U::packet;} }; template<> struct EventVariant::VariantFor { typedef int32_t type; static type U::*field() { return &U::ack; } }; template<> struct EventVariant::VariantFor { typedef ECheckTimerStage type; static type U::*field() { return &U::stage; } }; template<> struct EventVariant::VariantFor { typedef EInitEvent type; static type U::*field() { return &U::init; } }; // Using a hand-crafted solution because there's a non-backward-compatible // change between C++03 and others on the way up to C++17 (and we want this // code to be compliant with all C++ standards): // // - there's std::mem_fun in C++03 - deprecated in C++11, removed in C++17 // - std::function in C++11 would be perfect, but not in C++03 // This can be changed in future to use C++11 way, but only after C++03 // compatibility is finally abaondoned. Until then, this stays with a custom // class. class EventSlotBase { public: virtual void emit(ETransmissionEvent tev, EventVariant var) = 0; typedef void dispatcher_t(void* opaque, ETransmissionEvent tev, EventVariant var); virtual ~EventSlotBase() {} }; class SimpleEventSlot: public EventSlotBase { public: void* opaque; dispatcher_t* dispatcher; SimpleEventSlot(void* op, dispatcher_t* disp): opaque(op), dispatcher(disp) {} void emit(ETransmissionEvent tev, EventVariant var) ATR_OVERRIDE { (*dispatcher)(opaque, tev, var); } }; template class ObjectEventSlot: public EventSlotBase { public: typedef void (Class::*method_ptr_t)(ETransmissionEvent tev, EventVariant var); method_ptr_t pm; Class* po; ObjectEventSlot(Class* o, method_ptr_t m): pm(m), po(o) {} void emit(ETransmissionEvent tev, EventVariant var) ATR_OVERRIDE { (po->*pm)(tev, var); } }; struct EventSlot { mutable EventSlotBase* slot; // Create empty slot. Calls are ignored. EventSlot(): slot(0) {} // "Stealing" copy constructor, following the auto_ptr method. // This isn't very nice, but no other way to do it in C++03 // without rvalue-reference and move. void moveFrom(const EventSlot& victim) { slot = victim.slot; // Should MOVE. victim.slot = 0; } EventSlot(const EventSlot& victim) { moveFrom(victim); } EventSlot& operator=(const EventSlot& victim) { moveFrom(victim); return *this; } EventSlot(void* op, EventSlotBase::dispatcher_t* disp) { slot = new SimpleEventSlot(op, disp); } template EventSlot(ObjectClass* obj, typename ObjectEventSlot::method_ptr_t method) { slot = new ObjectEventSlot(obj, method); } void emit(ETransmissionEvent tev, EventVariant var) { if (!slot) return; slot->emit(tev, var); } ~EventSlot() { delete slot; } }; // 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 { int32_t value; public: explicit CSeqNo(int32_t v): value(v) {} // Comparison bool operator == (const CSeqNo& other) const { return other.value == value; } bool operator < (const CSeqNo& other) const { return seqcmp(value, other.value) < 0; } // The std::rel_ops namespace cannot be "imported" // as a whole into the class - it can only be used // in the application code. bool operator != (const CSeqNo& other) const { return other.value != value; } bool operator > (const CSeqNo& other) const { return other < *this; } bool operator >= (const CSeqNo& other) const { return seqcmp(value, other.value) >= 0; } bool operator <=(const CSeqNo& other) const { return seqcmp(value, other.value) <= 0; } // circular arithmetics friend int operator-(const CSeqNo& c1, const CSeqNo& c2) { return seqoff(c2.value, c1.value); } friend CSeqNo operator-(const CSeqNo& c1, int off) { return CSeqNo(decseq(c1.value, off)); } friend CSeqNo operator+(const CSeqNo& c1, int off) { return CSeqNo(incseq(c1.value, off)); } friend CSeqNo operator+(int off, const CSeqNo& c1) { return CSeqNo(incseq(c1.value, off)); } CSeqNo& operator++() { value = incseq(value); return *this; } /// This behaves like seq1 - seq2, in comparison to numbers, /// and with the statement that only the sign of the result matters. /// That is, it returns a negative value if seq1 < seq2, /// positive if seq1 > seq2, and zero if they are equal. /// The only correct application of this function is when you /// compare two values and it works faster than seqoff. However /// the result's meaning is only in its sign. DO NOT USE THE /// VALUE for any other purpose. It is not meant to be the /// distance between two sequence numbers. /// /// Example: to check if (seq1 %> seq2): seqcmp(seq1, seq2) > 0. /// Note: %> stands for "later than". inline static int seqcmp(int32_t seq1, int32_t seq2) {return (abs(seq1 - seq2) < m_iSeqNoTH) ? (seq1 - seq2) : (seq2 - seq1);} /// This function measures a length of the range from seq1 to seq2, /// including endpoints (seqlen(a, a) = 1; seqlen(a, a + 1) = 2), /// WITH A PRECONDITION that certainly @a seq1 is earlier than @a seq2. /// This can also include an enormously large distance between them, /// that is, exceeding the m_iSeqNoTH value (can be also used to test /// if this distance is larger). /// Prior to calling this function the caller must be certain that /// @a seq2 is a sequence coming from a later time than @a seq1, /// and that the distance does not exceed m_iMaxSeqNo. inline static int seqlen(int32_t seq1, int32_t seq2) { SRT_ASSERT(seq1 >= 0 && seq1 <= m_iMaxSeqNo); SRT_ASSERT(seq2 >= 0 && seq2 <= m_iMaxSeqNo); return (seq1 <= seq2) ? (seq2 - seq1 + 1) : (seq2 - seq1 + m_iMaxSeqNo + 2); } /// This behaves like seq2 - seq1, with the precondition that the true /// distance between two sequence numbers never exceeds m_iSeqNoTH. /// That is, if the difference in numeric values of these two arguments /// exceeds m_iSeqNoTH, it is treated as if the later of these two /// sequence numbers has overflown and actually a segment of the /// MAX+1 value should be added to it to get the proper result. /// /// Note: this function does more calculations than seqcmp, so it should /// be used if you need the exact distance between two sequences. If /// you are only interested with their relationship, use seqcmp. 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; } static int32_t maxseq(int32_t seq1, int32_t seq2) { if (seqcmp(seq1, seq2) < 0) return seq2; return seq1; } public: static const int32_t m_iSeqNoTH = 0x3FFFFFFF; // threshold for comparing seq. no. static const int32_t m_iMaxSeqNo = 0x7FFFFFFF; // maximum sequence number used in UDT }; //////////////////////////////////////////////////////////////////////////////// // 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 = 0x7FFFFFFF; // maximum ACK sub-sequence number used in UDT }; template class RollNumber { typedef RollNumber this_t; typedef Bits number_t; uint32_t number; public: static const size_t OVER = number_t::mask+1; static const size_t HALF = (OVER-MIN)/2; private: static int Diff(uint32_t left, uint32_t right) { // UNExpected order, diff is negative if ( left < right ) { int32_t diff = right - left; if ( diff >= int32_t(HALF) ) // over barrier { // It means that left is less than right because it was overflown // For example: left = 0x0005, right = 0xFFF0; diff = 0xFFEB > HALF left += OVER - MIN; // left was really 0x00010005, just narrowed. // Now the difference is 0x0015, not 0xFFFF0015 } } else { int32_t diff = left - right; if ( diff >= int32_t(HALF) ) { right += OVER - MIN; } } return left - right; } public: explicit RollNumber(uint32_t val): number(val) { } bool operator<(const this_t& right) const { int32_t ndiff = number - right.number; if (ndiff < -int32_t(HALF)) { // it' like ndiff > 0 return false; } if (ndiff > int32_t(HALF)) { // it's like ndiff < 0 return true; } return ndiff < 0; } bool operator>(const this_t& right) const { return right < *this; } bool operator==(const this_t& right) const { return number == right.number; } bool operator<=(const this_t& right) const { return !(*this > right); } bool operator>=(const this_t& right) const { return !(*this < right); } void operator++(int) { ++number; if (number > number_t::mask) number = MIN; } this_t& operator++() { (*this)++; return *this; } void operator--(int) { if (number == MIN) number = number_t::mask; else --number; } this_t& operator--() { (*this)--; return *this; } int32_t operator-(this_t right) { return Diff(this->number, right.number); } void operator+=(int32_t delta) { // NOTE: this condition in practice tests if delta is negative. // That's because `number` is always positive, so negated delta // can't be ever greater than this, unless it's negative. if (-delta > int64_t(number)) { number = OVER - MIN + number + delta; // NOTE: delta is negative } else { number += delta; if (number >= OVER) number -= OVER - MIN; } } operator uint32_t() const { return number; } }; //////////////////////////////////////////////////////////////////////////////// struct CIPAddress { static bool ipcmp(const struct sockaddr* addr1, const struct sockaddr* addr2, int ver = AF_INET); static void ntop(const struct sockaddr_any& addr, uint32_t ip[4]); static void pton(sockaddr_any& addr, const uint32_t ip[4], const sockaddr_any& peer); static std::string show(const struct 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(SRT_SEQNO_NONE) {} // 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 != SRT_SEQNO_NONE && CSeqNo::seqcmp(seq, initseq) >= 0; } int32_t base() { return initseq; } void clear() { initseq = SRT_SEQNO_NONE; array.reset(); } void add(int32_t lo, int32_t hi) { int32_t end = CSeqNo::incseq(hi); 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::seqoff(initseq, seq); 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::seqoff(initseq, seq); 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::seqoff(initseq, seq); 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 }; // There are some better or worse things you can find outside, // there's also boost::circular_buffer, but it's too overspoken // to be included here. We also can't rely on boost. Maybe in future // when it's added to the standard and SRT can heighten C++ standard // requirements; until then it needs this replacement. template class CircularBuffer { #ifdef SRT_TEST_CIRCULAR_BUFFER public: #endif int m_iSize; Value* m_aStorage; int m_xBegin; int m_xEnd; static void destr(Value& v) { v.~Value(); } static void constr(Value& v) { new ((void*)&v) Value(); } template static void constr(Value& v, const V& source) { new ((void*)&v) Value(source); } // Wipe the copy constructor CircularBuffer(const CircularBuffer&); public: typedef Value value_type; CircularBuffer(int size) :m_iSize(size+1), m_xBegin(0), m_xEnd(0) { // We reserve one spare element just for a case. if (size == 0) m_aStorage = 0; else m_aStorage = (Value*)::operator new (sizeof(Value) * m_iSize); } void set_capacity(int size) { reset(); // This isn't called resize (the size is 0 after the operation) // nor reserve (the existing elements are removed). if (size != m_iSize) { if (m_aStorage) ::operator delete (m_aStorage); m_iSize = size+1; m_aStorage = (Value*)::operator new (sizeof(Value) * m_iSize); } } void reset() { if (m_xEnd < m_xBegin) { for (int i = m_xBegin; i < m_iSize; ++i) destr(m_aStorage[i]); for (int i = 0; i < m_xEnd; ++i) destr(m_aStorage[i]); } else { for (int i = m_xBegin; i < m_xEnd; ++i) destr(m_aStorage[i]); } m_xBegin = 0; m_xEnd = 0; } ~CircularBuffer() { reset(); ::operator delete (m_aStorage); } // In the beginning, m_xBegin == m_xEnd, which // means that the container is empty. Adding can // be done exactly at the place pointed to by m_xEnd, // and m_xEnd must be then shifted to the next unused one. // When (m_xEnd + 1) % m_zSize == m_xBegin, the container // is considered full and the element adding is rejected. // // This container is not designed to be STL-compatible // because it doesn't make much sense. It's not a typical // container, even treated as random-access container. int shift(int basepos, int shift) const { return (basepos + shift) % m_iSize; } // Simplified versions with ++ and --; avoid using division instruction int shift_forward(int basepos) const { if (++basepos == m_iSize) return 0; return basepos; } int shift_backward(int basepos) const { if (basepos == 0) return m_iSize-1; return --basepos; } int size() const { // Count the distance between begin and end if (m_xEnd < m_xBegin) { // Use "merge two slices" method. // (BEGIN - END) is the distance of the unused // space in the middle. Used space is left to END // and right to BEGIN, the sum of the left and right // slice and the free space is the size. // This includes also a case when begin and end // are equal, which means that it's empty, so // spaceleft() should simply return m_iSize. return m_iSize - (m_xBegin - m_xEnd); } return m_xEnd - m_xBegin; } bool empty() const { return m_xEnd == m_xBegin; } size_t capacity() const { return m_iSize-1; } int spaceleft() const { // It's kinda tautology, but this will be more efficient. if (m_xEnd < m_xBegin) { return m_xBegin - m_xEnd; } return m_iSize - (m_xEnd - m_xBegin); } // This is rather written for testing and rather won't // be used in the real code. template int push(const V& v) { // Check if you can add int nend = shift_forward(m_xEnd); if ( nend == m_xBegin) return -1; constr(m_aStorage[m_xEnd], v); m_xEnd = nend; return size() - 1; } Value* push() { int nend = shift_forward(m_xEnd); if ( nend == m_xBegin) return NULL; Value* pos = &m_aStorage[m_xEnd]; constr(*pos); m_xEnd = nend; return pos; } bool access(int position, Value*& w_v) { // This version doesn't require the boolean value to report // whether the element is newly added because it never adds // a new element. int ipos, vend; if (!INT_checkAccess(position, ipos, vend)) return false; if (ipos >= vend) // exceeds return false; INT_access(ipos, false, (w_v)); // never exceeds return true; } // Ok, now it's the real deal. bool access(int position, Value*& w_v, bool& w_isnew) { int ipos, vend; if (!INT_checkAccess(position, ipos, vend)) return false; bool exceeds = (ipos >= vend); w_isnew = exceeds; INT_access(ipos, exceeds, (w_v)); return true; } private: bool INT_checkAccess(int position, int& ipos, int& vend) { // Reject if no space left. // Also INVAL if negative position. if (position >= (m_iSize-1) || position < 0) return false; // That's way to far, we can't even calculate ipos = m_xBegin + position; vend = m_xEnd; if (m_xEnd < m_xBegin) vend += m_iSize; return true; } void INT_access(int ipos, bool exceeds, Value*& w_v) { if (ipos >= m_iSize) ipos -= m_iSize; // wrap around // Update the end position. if (exceeds) { int nend = ipos+1; if (m_xEnd > nend) { // Here we know that the current index exceeds the size. // So, if this happens, it's m_xEnd wrapped around. // Clear out elements in two slices: // - from m_xEnd to m_iSize-1 // - from 0 to nend for (int i = m_xEnd; i < m_iSize; ++i) constr(m_aStorage[i]); for (int i = 0; i < nend; ++i) constr(m_aStorage[i]); } else { for (int i = m_xEnd; i < nend; ++i) constr(m_aStorage[i]); } if (nend == m_iSize) nend = 0; m_xEnd = nend; } w_v = &m_aStorage[ipos]; } public: bool set(int position, const Value& newval, bool overwrite = true) { Value* pval = 0; bool isnew = false; if (!access(position, (pval), (isnew))) return false; if (isnew || overwrite) *pval = newval; return true; } template bool update(int position, Updater updater) { Value* pval = 0; bool isnew = false; if (!access(position, (pval), (isnew))) return false; updater(*pval, isnew); return true; } int getIndexFor(int position) const { int ipos = m_xBegin + position; int vend = m_xEnd; if (vend < m_xBegin) vend += m_iSize; if (ipos >= vend) return -1; if (ipos >= m_iSize) ipos -= m_iSize; return ipos; } bool get(int position, Value& w_out) const { // Check if that position is occupied if (position > m_iSize || position < 0) return false; int ipos = getIndexFor(position); if (ipos == -1) return false; w_out = m_aStorage[ipos]; return true; } bool drop(int position) { // This function "deletes" items by shifting the // given position to position 0. That is, // elements from the beginning are being deleted // up to (including) the given position. if (position > m_iSize || position < 1) return false; int ipos = m_xBegin + position; int vend = m_xEnd; if (vend < m_xBegin) vend += m_iSize; // Destroy the elements in the removed range if (ipos >= vend) { // There was a request to drop; the position // is higher than the number of items. Allow this // and simply make the container empty. reset(); return true; } // Otherwise we have a new beginning. int nbegin = ipos; // Destroy the old elements if (nbegin >= m_iSize) { nbegin -= m_iSize; for (int i = m_xBegin; i < m_iSize; ++i) destr(m_aStorage[i]); for (int i = 0; i < nbegin; ++i) destr(m_aStorage[i]); } else { for (int i = m_xBegin; i < nbegin; ++i) destr(m_aStorage[i]); } m_xBegin = nbegin; return true; } // This function searches for an element that satisfies // the given predicate. If none found, returns -1. template int find_if(Predicate pred) { if (m_xEnd < m_xBegin) { // Loop in two slices for (int i = m_xBegin; i < m_iSize; ++i) if (pred(m_aStorage[i])) return i - m_xBegin; for (int i = 0; i < m_xEnd; ++i) if (pred(m_aStorage[i])) return i + m_iSize - m_xBegin; } else { for (int i = m_xBegin; i < m_xEnd; ++i) if (pred(m_aStorage[i])) return i - m_xBegin; } return -1; } }; namespace srt_logging { std::string SockStatusStr(SRT_SOCKSTATUS s); #if ENABLE_EXPERIMENTAL_BONDING std::string MemberStatusStr(SRT_MEMBERSTATUS s); #endif } // Version parsing inline ATR_CONSTEXPR uint32_t SrtVersion(int major, int minor, int patch) { return patch + minor*0x100 + major*0x10000; } inline int32_t SrtParseVersion(const char* v) { int major, minor, patch; int result = sscanf(v, "%d.%d.%d", &major, &minor, &patch); if (result != 3) { return 0; } return SrtVersion(major, minor, patch); } inline std::string SrtVersionString(int version) { int patch = version % 0x100; int minor = (version/0x100)%0x100; int major = version/0x10000; char buf[20]; sprintf(buf, "%d.%d.%d", major, minor, patch); return buf; } bool SrtParseConfig(std::string s, srt::SrtConfig& w_config); struct PacketMetric { uint32_t pkts; uint64_t bytes; void update(uint64_t size) { ++pkts; bytes += size; } void update(size_t mult, uint64_t value) { pkts += (uint32_t) mult; bytes += mult * value; } uint64_t fullBytes(); }; template struct MetricOp; template struct MetricUsage { METRIC_TYPE local; METRIC_TYPE total; void Clear() { MetricOp::Clear(local); } void Init() { MetricOp::Clear(total); Clear(); } void Update(uint64_t value) { local += value; total += value; } void UpdateTimes(size_t mult, uint64_t value) { local += mult * value; total += mult * value; } }; template <> inline void MetricUsage::Update(uint64_t value) { local.update(value); total.update(value); } template <> inline void MetricUsage::UpdateTimes(size_t mult, uint64_t value) { local.update(mult, value); total.update(mult, value); } template struct MetricOp { static void Clear(METRIC_TYPE& m) { m = 0; } }; template <> struct MetricOp { static void Clear(PacketMetric& p) { p.pkts = 0; p.bytes = 0; } }; #endif srt-1.4.4/srtcore/congctl.cpp000066400000000000000000000601701412557703600161640ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ // This is a controversial thing, so temporarily blocking //#define SRT_ENABLE_SYSTEMBUFFER_TRACE #include "platform_sys.h" #ifdef SRT_ENABLE_SYSTEMBUFFER_TRACE #if defined(unix) // XXX will be nonportable #include #endif #endif #include #include #include "common.h" #include "core.h" #include "queue.h" #include "packet.h" #include "congctl.h" #include "logging.h" using namespace std; using namespace srt::sync; using namespace srt_logging; namespace srt { SrtCongestionControlBase::SrtCongestionControlBase(CUDT* parent) { m_parent = parent; m_dMaxCWndSize = m_parent->flowWindowSize(); // RcvRate (deliveryRate()), RTT and Bandwidth can be read directly from CUDT when needed. m_dCWndSize = 1000; m_dPktSndPeriod = 1; } void SrtCongestion::Check() { if (!congctl) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); } // Useful macro to shorthand passing a method as argument // Requires "Me" name by which a class refers to itself #define SSLOT(method) EventSlot(this, &Me:: method) class LiveCC: public SrtCongestionControlBase { int64_t m_llSndMaxBW; //Max bandwidth (bytes/sec) srt::sync::atomic m_zSndAvgPayloadSize; //Average Payload Size of packets to xmit size_t m_zMaxPayloadSize; // NAKREPORT stuff. int m_iMinNakInterval_us; // Minimum NAK Report Period (usec) int m_iNakReportAccel; // NAK Report Period (RTT) accelerator typedef LiveCC Me; // required for SSLOT macro public: LiveCC(CUDT* parent) : SrtCongestionControlBase(parent) { m_llSndMaxBW = BW_INFINITE; // 1 Gbbps in Bytes/sec BW_INFINITE m_zMaxPayloadSize = parent->OPT_PayloadSize(); if ( m_zMaxPayloadSize == 0 ) m_zMaxPayloadSize = parent->maxPayloadSize(); m_zSndAvgPayloadSize = m_zMaxPayloadSize; m_iMinNakInterval_us = 20000; //Minimum NAK Report Period (usec) m_iNakReportAccel = 2; //Default NAK Report Period (RTT) accelerator (send periodic NAK every RTT/2) HLOGC(cclog.Debug, log << "Creating LiveCC: bw=" << m_llSndMaxBW << " avgplsize=" << m_zSndAvgPayloadSize); updatePktSndPeriod(); // NOTE: TEV_SEND gets dispatched from Sending thread, all others // from receiving thread. parent->ConnectSignal(TEV_SEND, SSLOT(updatePayloadSize)); // // Adjust the max SndPeriod onACK and onTimeout. // parent->ConnectSignal(TEV_CHECKTIMER, SSLOT(onRTO)); parent->ConnectSignal(TEV_ACK, SSLOT(onAck)); } bool checkTransArgs(SrtCongestion::TransAPI api, SrtCongestion::TransDir dir, const char* , size_t size, int , bool ) ATR_OVERRIDE { if (api != SrtCongestion::STA_MESSAGE) { LOGC(cclog.Error, log << "LiveCC: invalid API use. Only sendmsg/recvmsg allowed."); return false; } if (dir == SrtCongestion::STAD_SEND) { // For sending, check if the size of data doesn't exceed the maximum live packet size. if (size > m_zMaxPayloadSize) { LOGC(cclog.Error, log << "LiveCC: payload size: " << size << " exceeds maximum allowed " << m_zMaxPayloadSize); return false; } } else { // For receiving, check if the buffer has enough space to keep the payload. if (size < m_zMaxPayloadSize) { LOGC(cclog.Error, log << "LiveCC: buffer size: " << size << " is too small for the maximum possible " << m_zMaxPayloadSize); return false; } } return true; } // XXX You can decide here if the not-fully-packed packet should require immediate ACK or not. // bool needsQuickACK(const CPacket& pkt) ATR_OVERRIDE virtual int64_t sndBandwidth() ATR_OVERRIDE { return m_llSndMaxBW; } private: // SLOTS: // TEV_SEND -> CPacket*. void updatePayloadSize(ETransmissionEvent, EventVariant var) { const CPacket& packet = *var.get(); // XXX NOTE: TEV_SEND is sent from CSndQueue::worker thread, which is // different to threads running any other events (TEV_CHECKTIMER and TEV_ACK). // The m_zSndAvgPayloadSize field is however left unguarded because // there's no other modifier of this field. // Worst case scenario, the procedure running in CRcvQueue::worker // thread will pick up a "slightly outdated" average value from this // field - this is insignificant. m_zSndAvgPayloadSize = avg_iir<128, size_t>(m_zSndAvgPayloadSize, packet.getLength()); HLOGC(cclog.Debug, log << "LiveCC: avg payload size updated: " << m_zSndAvgPayloadSize); } /// @brief On RTO event update an inter-packet send interval. /// @param arg EventVariant::STAGE to distinguish between INIT and actual RTO. void onRTO(ETransmissionEvent , EventVariant var) { if (var.get() != TEV_CHT_INIT ) updatePktSndPeriod(); } /// @brief Handle an incoming ACK event. /// Mainly updates a send interval between packets relying on the maximum BW limit. void onAck(ETransmissionEvent, EventVariant ) { updatePktSndPeriod(); } /// @brief Updates a send interval between packets relying on the maximum BW limit. void updatePktSndPeriod() { // packet = payload + header const double pktsize = (double) m_zSndAvgPayloadSize.load() + CPacket::SRT_DATA_HDR_SIZE; m_dPktSndPeriod = 1000 * 1000.0 * (pktsize / m_llSndMaxBW); HLOGC(cclog.Debug, log << "LiveCC: sending period updated: " << m_dPktSndPeriod << " by avg pktsize=" << m_zSndAvgPayloadSize << ", bw=" << m_llSndMaxBW); } void setMaxBW(int64_t maxbw) { m_llSndMaxBW = maxbw > 0 ? maxbw : BW_INFINITE; updatePktSndPeriod(); /* * UDT default flow control should not trigger under normal SRT operation * UDT stops sending if the number of packets in transit (not acknowledged) * is larger than the congestion window. * Up to SRT 1.0.6, this value was set at 1000 pkts, which may be insufficient * for satellite links with ~1000 msec RTT and high bit rate. */ // XXX Consider making this a socket option. m_dCWndSize = m_dMaxCWndSize; } void updateBandwidth(int64_t maxbw, int64_t bw) ATR_OVERRIDE { // bw is the bandwidth calculated with regard to the // SRTO_INPUTBW and SRTO_OHEADBW parameters. The maxbw // value simply represents the SRTO_MAXBW setting. if (maxbw) { setMaxBW(maxbw); return; } if (bw == 0) { return; } setMaxBW(bw); } SrtCongestion::RexmitMethod rexmitMethod() ATR_OVERRIDE { return SrtCongestion::SRM_FASTREXMIT; } int64_t updateNAKInterval(int64_t nakint_us, int /*rcv_speed*/, size_t /*loss_length*/) ATR_OVERRIDE { /* * 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_tk) is 300 ms which is too high * (~10 i30 video frames) to maintain low latency. */ // Note: this value will still be reshaped to defined minimum, // as per minNAKInterval. return nakint_us / m_iNakReportAccel; } int64_t minNAKInterval() ATR_OVERRIDE { return m_iMinNakInterval_us; } }; class FileCC : public SrtCongestionControlBase { typedef FileCC Me; // Required by SSLOT macro // Fields from CUDTCC int m_iRCInterval; // UDT Rate control interval steady_clock::time_point 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 int64_t m_maxSR; public: FileCC(CUDT* parent) : SrtCongestionControlBase(parent) , m_iRCInterval(CUDT::COMM_SYN_INTERVAL_US) , m_LastRCTime(steady_clock::now()) , m_bSlowStart(true) , m_iLastAck(parent->sndSeqNo()) , m_bLoss(false) , m_iLastDecSeq(CSeqNo::decseq(m_iLastAck)) , m_dLastDecPeriod(1) , m_iNAKCount(0) , m_iDecRandom(1) , m_iAvgNAKNum(0) , m_iDecCount(0) , m_maxSR(0) { // Note that this function is called at the moment of // calling m_Smoother.configure(this). It is placed more less // at the same position as the series-of-parameter-setting-then-init // in the original UDT code. So, old CUDTCC::init() can be moved // to constructor. // SmotherBase m_dCWndSize = 16; m_dPktSndPeriod = 1; parent->ConnectSignal(TEV_ACK, SSLOT(onACK)); parent->ConnectSignal(TEV_LOSSREPORT, SSLOT(onLossReport)); parent->ConnectSignal(TEV_CHECKTIMER, SSLOT(onRTO)); HLOGC(cclog.Debug, log << "Creating FileCC"); } bool checkTransArgs(SrtCongestion::TransAPI, SrtCongestion::TransDir, const char*, size_t, int, bool) ATR_OVERRIDE { // XXX // The FileCC has currently no restrictions, although it should be // rather required that the "message" mode or "buffer" mode be used on both sides the same. // This must be somehow checked separately. return true; } /// Tells if an early ACK is needed (before the next Full ACK happening every 10ms). /// In FileCC, treat non-full-payload as an end-of-message (stream) /// and request ACK to be sent immediately. bool needsQuickACK(const CPacket& pkt) ATR_OVERRIDE { if (pkt.getLength() < m_parent->maxPayloadSize()) { // This is not a regular fixed size packet... // an irregular sized packet usually indicates the end of a message, so send an ACK immediately return true; } return false; } void updateBandwidth(int64_t maxbw, int64_t) ATR_OVERRIDE { if (maxbw != 0) { m_maxSR = maxbw; HLOGC(cclog.Debug, log << "FileCC: updated BW: " << m_maxSR); } } private: /// Handle icoming ACK event. /// In slow start stage increase CWND. Leave slow start once maximum CWND is reached. /// In congestion avoidance stage adjust inter packet send interval value to achieve maximum rate. void onACK(ETransmissionEvent, EventVariant arg) { const int ack = arg.get(); const steady_clock::time_point currtime = steady_clock::now(); if (count_microseconds(currtime - m_LastRCTime) < m_iRCInterval) return; m_LastRCTime = currtime; if (m_bSlowStart) { m_dCWndSize += CSeqNo::seqlen(m_iLastAck, ack); m_iLastAck = ack; if (m_dCWndSize > m_dMaxCWndSize) { m_bSlowStart = false; if (m_parent->deliveryRate() > 0) { m_dPktSndPeriod = 1000000.0 / m_parent->deliveryRate(); HLOGC(cclog.Debug, log << "FileCC: UPD (slowstart:ENDED) wndsize=" << m_dCWndSize << "/" << m_dMaxCWndSize << " sndperiod=" << m_dPktSndPeriod << "us = 1M/(" << m_parent->deliveryRate() << " pkts/s)"); } else { m_dPktSndPeriod = m_dCWndSize / (m_parent->SRTT() + m_iRCInterval); HLOGC(cclog.Debug, log << "FileCC: UPD (slowstart:ENDED) wndsize=" << m_dCWndSize << "/" << m_dMaxCWndSize << " sndperiod=" << m_dPktSndPeriod << "us = wndsize/(RTT+RCIV) RTT=" << m_parent->SRTT() << " RCIV=" << m_iRCInterval); } } else { HLOGC(cclog.Debug, log << "FileCC: UPD (slowstart:KEPT) wndsize=" << m_dCWndSize << "/" << m_dMaxCWndSize << " sndperiod=" << m_dPktSndPeriod << "us"); } } else { m_dCWndSize = m_parent->deliveryRate() / 1000000.0 * (m_parent->SRTT() + m_iRCInterval) + 16; HLOGC(cclog.Debug, log << "FileCC: UPD (speed mode) wndsize=" << m_dCWndSize << "/" << m_dMaxCWndSize << " RTT = " << m_parent->SRTT() << " sndperiod=" << m_dPktSndPeriod << "us. deliverRate = " << m_parent->deliveryRate() << " pkts/s)"); } if (!m_bSlowStart) { if (m_bLoss) { m_bLoss = false; } // During Slow Start, no rate increase else { double inc = 0; const int loss_bw = static_cast(2 * (1000000 / m_dLastDecPeriod)); // 2 times last loss point const int bw_pktps = min(loss_bw, m_parent->bandwidth()); int64_t B = (int64_t)(bw_pktps - 1000000.0 / m_dPktSndPeriod); if ((m_dPktSndPeriod > m_dLastDecPeriod) && ((bw_pktps / 9) < B)) B = bw_pktps / 9; if (B <= 0) inc = 1.0 / m_parent->MSS(); 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_parent->MSS() * 8.0))) * 0.0000015 / m_parent->MSS(); inc = max(inc, 1.0 / m_parent->MSS()); } HLOGC(cclog.Debug, log << "FileCC: UPD (slowstart:OFF) loss_bw=" << loss_bw << " bandwidth=" << m_parent->bandwidth() << " inc=" << inc << " m_dPktSndPeriod=" << m_dPktSndPeriod << "->" << (m_dPktSndPeriod * m_iRCInterval) / (m_dPktSndPeriod * inc + m_iRCInterval)); m_dPktSndPeriod = (m_dPktSndPeriod * m_iRCInterval) / (m_dPktSndPeriod * inc + m_iRCInterval); } } #if ENABLE_HEAVY_LOGGING // Try to do reverse-calculation for m_dPktSndPeriod, as per minSP below // sndperiod = mega / (maxbw / MSS) // 1/sndperiod = (maxbw/MSS) / mega // mega/sndperiod = maxbw/MSS // maxbw = (MSS*mega)/sndperiod uint64_t usedbw = (m_parent->MSS() * 1000000.0) / m_dPktSndPeriod; #if defined(unix) && defined (SRT_ENABLE_SYSTEMBUFFER_TRACE) // Check the outgoing system queue level int udp_buffer_size = m_parent->sndQueue()->sockoptQuery(SOL_SOCKET, SO_SNDBUF); int udp_buffer_level = m_parent->sndQueue()->ioctlQuery(TIOCOUTQ); int udp_buffer_free = udp_buffer_size - udp_buffer_level; #else int udp_buffer_free = -1; #endif HLOGC(cclog.Debug, log << "FileCC: UPD (slowstart:" << (m_bSlowStart ? "ON" : "OFF") << ") wndsize=" << m_dCWndSize << " sndperiod=" << m_dPktSndPeriod << "us BANDWIDTH USED:" << usedbw << " (limit: " << m_maxSR << ")" " SYSTEM BUFFER LEFT: " << udp_buffer_free); #endif //set maximum transfer rate if (m_maxSR) { double minSP = 1000000.0 / (double(m_maxSR) / m_parent->MSS()); if (m_dPktSndPeriod < minSP) { m_dPktSndPeriod = minSP; HLOGC(cclog.Debug, log << "FileCC: BW limited to " << m_maxSR << " - SLOWDOWN sndperiod=" << m_dPktSndPeriod << "us"); } } } /// When a lossreport has been received, it might be due to having /// reached the available bandwidth limit. Slowdown to avoid further losses. /// Leave the slow start stage if it was active. void onLossReport(ETransmissionEvent, EventVariant arg) { const int32_t* losslist = arg.get_ptr(); size_t losslist_size = arg.get_len(); // Sanity check. Should be impossible that TEV_LOSSREPORT event // is called with a nonempty loss list. if (losslist_size == 0) { LOGC(cclog.Error, log << "IPE: FileCC: empty loss list!"); return; } //Slow Start stopped, if it hasn't yet if (m_bSlowStart) { m_bSlowStart = false; if (m_parent->deliveryRate() > 0) { m_dPktSndPeriod = 1000000.0 / m_parent->deliveryRate(); HLOGC(cclog.Debug, log << "FileCC: LOSS, SLOWSTART:OFF, sndperiod=" << m_dPktSndPeriod << "us AS mega/rate (rate=" << m_parent->deliveryRate() << ")"); } else { m_dPktSndPeriod = m_dCWndSize / (m_parent->SRTT() + m_iRCInterval); HLOGC(cclog.Debug, log << "FileCC: LOSS, SLOWSTART:OFF, sndperiod=" << m_dPktSndPeriod << "us AS wndsize/(RTT+RCIV) (RTT=" << m_parent->SRTT() << " RCIV=" << m_iRCInterval << ")"); } } m_bLoss = true; // TODO: const int pktsInFlight = CSeqNo::seqoff(m_iLastAck, m_parent->sndSeqNo()); const int pktsInFlight = static_cast(m_parent->SRTT() / m_dPktSndPeriod); const int numPktsLost = m_parent->sndLossLength(); const int lost_pcent_x10 = pktsInFlight > 0 ? (numPktsLost * 1000) / pktsInFlight : 0; HLOGC(cclog.Debug, log << "FileCC: LOSS: " << "sent=" << CSeqNo::seqlen(m_iLastAck, m_parent->sndSeqNo()) << ", inFlight=" << pktsInFlight << ", lost=" << numPktsLost << " (" << lost_pcent_x10 / 10 << "." << lost_pcent_x10 % 10 << "%)"); if (lost_pcent_x10 < 20) // 2.0% { HLOGC(cclog.Debug, log << "FileCC: LOSS: m_dLastDecPeriod=" << m_dLastDecPeriod << "->" << m_dPktSndPeriod); m_dLastDecPeriod = m_dPktSndPeriod; return; } // In contradiction to UDT, TEV_LOSSREPORT will be reported also when // the lossreport is being sent again, periodically, as a result of // NAKREPORT feature. You should make sure that NAKREPORT is off when // using FileCC, so relying on SRTO_TRANSTYPE rather than // just SRTO_CONGESTION is recommended. int32_t lossbegin = SEQNO_VALUE::unwrap(losslist[0]); if (CSeqNo::seqcmp(lossbegin, m_iLastDecSeq) > 0) { m_dLastDecPeriod = m_dPktSndPeriod; m_dPktSndPeriod = ceil(m_dPktSndPeriod * 1.03); const double loss_share_factor = 0.03; m_iAvgNAKNum = (int)ceil(m_iAvgNAKNum * (1 - loss_share_factor) + m_iNAKCount * loss_share_factor); m_iNAKCount = 1; m_iDecCount = 1; m_iLastDecSeq = m_parent->sndSeqNo(); m_iDecRandom = m_iAvgNAKNum > 1 ? genRandomInt(1, m_iAvgNAKNum) : 1; SRT_ASSERT(m_iDecRandom >= 1); HLOGC(cclog.Debug, log << "FileCC: LOSS:NEW lseqno=" << lossbegin << ", lastsentseqno=" << m_iLastDecSeq << ", seqdiff=" << CSeqNo::seqoff(m_iLastDecSeq, lossbegin) << ", rand=" << m_iDecRandom << " avg NAK:" << m_iAvgNAKNum << ", sndperiod=" << m_dPktSndPeriod << "us"); } 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.03); m_iLastDecSeq = m_parent->sndSeqNo(); HLOGC(cclog.Debug, log << "FileCC: LOSS:PERIOD lseqno=" << lossbegin << ", lastsentseqno=" << m_iLastDecSeq << ", seqdiff=" << CSeqNo::seqoff(m_iLastDecSeq, lossbegin) << ", deccnt=" << m_iDecCount << ", decrnd=" << m_iDecRandom << ", sndperiod=" << m_dPktSndPeriod << "us"); } else { HLOGC(cclog.Debug, log << "FileCC: LOSS:STILL lseqno=" << lossbegin << ", lastsentseqno=" << m_iLastDecSeq << ", seqdiff=" << CSeqNo::seqoff(m_iLastDecSeq, lossbegin) << ", deccnt=" << m_iDecCount << ", decrnd=" << m_iDecRandom << ", sndperiod=" << m_dPktSndPeriod << "us"); } } /// @brief On retransmission timeout leave slow start stage if it was active. /// @param arg EventVariant::STAGE to distinguish between INIT and actual RTO. void onRTO(ETransmissionEvent, EventVariant arg) { ECheckTimerStage stg = arg.get(); // TEV_INIT is in the beginning of checkTimers(), used // only to synchronize back the values (which is done in updateCC // after emitting the signal). if (stg == TEV_CHT_INIT) return; if (m_bSlowStart) { m_bSlowStart = false; if (m_parent->deliveryRate() > 0) { m_dPktSndPeriod = 1000000.0 / m_parent->deliveryRate(); HLOGC(cclog.Debug, log << "FileCC: CHKTIMER, SLOWSTART:OFF, sndperiod=" << m_dPktSndPeriod << "us AS mega/rate (rate=" << m_parent->deliveryRate() << ")"); } else { m_dPktSndPeriod = m_dCWndSize / (m_parent->SRTT() + m_iRCInterval); HLOGC(cclog.Debug, log << "FileCC: CHKTIMER, SLOWSTART:OFF, sndperiod=" << m_dPktSndPeriod << "us AS wndsize/(RTT+RCIV) (wndsize=" << setprecision(6) << m_dCWndSize << " RTT=" << m_parent->SRTT() << " RCIV=" << m_iRCInterval << ")"); } } else { // XXX This code is a copy of legacy CUDTCC::onTimeout() body. // This part was commented out there already. /* m_dLastDecPeriod = m_dPktSndPeriod; m_dPktSndPeriod = ceil(m_dPktSndPeriod * 2); m_iLastDecSeq = m_iLastAck; */ } } SrtCongestion::RexmitMethod rexmitMethod() ATR_OVERRIDE { return SrtCongestion::SRM_LATEREXMIT; } }; #undef SSLOT template struct Creator { static SrtCongestionControlBase* Create(CUDT* parent) { return new Target(parent); } }; SrtCongestion::NamePtr SrtCongestion::congctls[N_CONTROLLERS] = { {"live", Creator::Create }, {"file", Creator::Create } }; bool SrtCongestion::configure(CUDT* parent) { if (selector == N_CONTROLLERS) return false; // Found a congctl, so call the creation function congctl = (*congctls[selector].second)(parent); // The congctl should have pinned in all events // that are of its interest. It's stated that // it's ready after creation. return !!congctl; } void SrtCongestion::dispose() { if (congctl) { delete congctl; congctl = 0; } } SrtCongestion::~SrtCongestion() { dispose(); } } // namespace srt srt-1.4.4/srtcore/congctl.h000066400000000000000000000164111412557703600156300ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_CONGCTL_H #define INC_SRT_CONGCTL_H #include #include #include #include namespace srt { class CUDT; class SrtCongestionControlBase; typedef SrtCongestionControlBase* srtcc_create_t(srt::CUDT* parent); class SrtCongestion { // Temporarily changed to linear searching, until this is exposed // for a user-defined controller. // Note that this is a pointer to function :) static const size_t N_CONTROLLERS = 2; // The first/second is to mimic the map. typedef struct { const char* first; srtcc_create_t* second; } NamePtr; static NamePtr congctls[N_CONTROLLERS]; // This is a congctl container. SrtCongestionControlBase* congctl; size_t selector; void Check(); public: // If you predict to allow something to be done on controller also // before it is configured, call this first. If you need it configured, // you can rely on Check(). bool ready() { return congctl; } SrtCongestionControlBase* operator->() { Check(); return congctl; } // In the beginning it's uninitialized SrtCongestion(): congctl(), selector(N_CONTROLLERS) {} struct IsName { std::string n; IsName(std::string nn): n(nn) {} bool operator()(NamePtr np) { return n == np.first; } }; static NamePtr* find(const std::string& name) { NamePtr* end = congctls+N_CONTROLLERS; NamePtr* try_selector = std::find_if(congctls, end, IsName(name)); return try_selector != end ? try_selector : NULL; } static bool exists(const std::string& name) { return find(name); } // You can call select() multiple times, until finally // the 'configure' method is called. bool select(const std::string& name) { NamePtr* try_selector = find(name); if (!try_selector) return false; selector = try_selector - congctls; return true; } std::string selected_name() { if (selector == N_CONTROLLERS) return ""; return congctls[selector].first; } // Copy constructor - important when listener-spawning // Things being done: // 1. The congctl is individual, so don't copy it. Set NULL. // 2. The selected name is copied so that it's configured correctly. SrtCongestion(const SrtCongestion& source): congctl(), selector(source.selector) {} void operator=(const SrtCongestion& source) { congctl = 0; selector = source.selector; } // This function will be called by the parent CUDT // in appropriate time. It should select appropriate // congctl basing on the value in selector, then // pin oneself in into CUDT for receiving event signals. bool configure(srt::CUDT* parent); // This function will intentionally delete the contained object. // This makes future calls to ready() return false. Calling // configure on it again will create it again. void dispose(); // Will delete the pinned in congctl object. // This must be defined in *.cpp file due to virtual // destruction. ~SrtCongestion(); enum RexmitMethod { SRM_LATEREXMIT, SRM_FASTREXMIT }; enum TransAPI { STA_MESSAGE = 0x1, // sendmsg/recvmsg functions STA_BUFFER = 0x2, // send/recv functions STA_FILE = 0x3, // sendfile/recvfile functions }; enum TransDir { STAD_RECV = 0, STAD_SEND = 1 }; }; class CPacket; class SrtCongestionControlBase { protected: // Here can be some common fields srt::CUDT* m_parent; double m_dPktSndPeriod; double m_dCWndSize; //int m_iBandwidth; // NOT REQUIRED. Use m_parent->bandwidth() instead. double m_dMaxCWndSize; //int m_iMSS; // NOT REQUIRED. Use m_parent->MSS() instead. //int32_t m_iSndCurrSeqNo; // NOT REQUIRED. Use m_parent->sndSeqNo(). //int m_iRcvRate; // NOT REQUIRED. Use m_parent->deliveryRate() instead. //int m_RTT; // NOT REQUIRED. Use m_parent->SRTT() instead. //char* m_pcParam; // Used to access m_llMaxBw. Use m_parent->maxBandwidth() instead. // Constructor in protected section so that this class is semi-abstract. SrtCongestionControlBase(srt::CUDT* parent); public: // This could be also made abstract, but this causes a linkage // problem in C++: this would constitute the first virtual method, // and C++ compiler uses the location of the first virtual method as the // file to which it also emits the virtual call table. When this is // abstract, there would have to be simultaneously either defined // an empty method in congctl.cpp file (obviously never called), // or simply left empty body here. virtual ~SrtCongestionControlBase() { } // All these functions that return values interesting for processing // by CUDT can be overridden. Normally they should refer to the fields // and these fields should keep the values as a state. virtual double pktSndPeriod_us() { return m_dPktSndPeriod; } virtual double cgWindowSize() { return m_dCWndSize; } virtual double cgWindowMaxSize() { return m_dMaxCWndSize; } virtual int64_t sndBandwidth() { return 0; } // If user-defined, will return nonzero value. // If not, it will be internally calculated. virtual int RTO() { return 0; } // Maximum number of packets to trigger ACK sending. // Specifies the number of packets to receive before sending the ACK. // Used by CUDT together with ACKTimeout_us() to trigger ACK packet sending. virtual int ACKMaxPackets() const { return 0; } // Periodical interval to send an ACK, in microseconds. // If user-defined, this value will be used to calculate // the next ACK time every time ACK is considered to be sent (see CUDT::checkTimers). // Otherwise this will be calculated internally in CUDT, normally taken // from CUDT::COMM_SYN_INTERVAL_US. virtual int ACKTimeout_us() const { return 0; } // Called when the settings concerning m_llMaxBW were changed. // Arg 1: value of CUDT's m_config.m_llMaxBW // Arg 2: value calculated out of CUDT's m_config.llInputBW and m_config.iOverheadBW. virtual void updateBandwidth(int64_t, int64_t) {} virtual bool needsQuickACK(const srt::CPacket&) { return false; } // Particular controller is allowed to agree or disagree on the use of particular API. virtual bool checkTransArgs(SrtCongestion::TransAPI , SrtCongestion::TransDir , const char* /*buffer*/, size_t /*size*/, int /*ttl*/, bool /*inorder*/) { return true; } virtual SrtCongestion::RexmitMethod rexmitMethod() = 0; // Implementation enforced. virtual int64_t updateNAKInterval(int64_t nakint_us, int rcv_speed, size_t loss_length) { if (rcv_speed > 0) nakint_us += (loss_length * int64_t(1000000) / rcv_speed); return nakint_us; } virtual int64_t minNAKInterval() { return 0; // Leave default } }; } // namespace srt #endif srt-1.4.4/srtcore/core.cpp000066400000000000000000015615051412557703600154740ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 02/28/2012 modified by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" // Linux specific #ifdef SRT_ENABLE_BINDTODEVICE #include #endif #include #include #include #include #include "srt.h" #include "queue.h" #include "api.h" #include "core.h" #include "logging.h" #include "crypto.h" #include "logging_api.h" // Required due to containing extern srt_logger_config #include "logger_defs.h" // Again, just in case when some "smart guy" provided such a global macro #ifdef min #undef min #endif #ifdef max #undef max #endif using namespace std; using namespace srt; using namespace srt::sync; using namespace srt_logging; namespace srt { CUDTUnited CUDT::s_UDTUnited; } const SRTSOCKET UDT::INVALID_SOCK = srt::CUDT::INVALID_SOCK; const int UDT::ERROR = srt::CUDT::ERROR; //#define SRT_CMD_HSREQ 1 /* SRT Handshake Request (sender) */ #define SRT_CMD_HSREQ_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */ #define SRT_CMD_HSREQ_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 */ extern const SRT_SOCKOPT srt_post_opt_list [SRT_SOCKOPT_NPOST] = { SRTO_SNDSYN, SRTO_RCVSYN, SRTO_LINGER, SRTO_SNDTIMEO, SRTO_RCVTIMEO, SRTO_MAXBW, SRTO_INPUTBW, SRTO_MININPUTBW, SRTO_OHEADBW, SRTO_SNDDROPDELAY, SRTO_DRIFTTRACER, SRTO_LOSSMAXTTL }; const int32_t SRTO_R_PREBIND = BIT(0), //< cannot be modified after srt_bind() SRTO_R_PRE = BIT(1), //< cannot be modified after connection is established SRTO_POST_SPEC = BIT(2); //< executes some action after setting the option namespace srt { struct SrtOptionAction { int flags[SRTO_E_SIZE]; std::map private_default; SrtOptionAction() { // Set everything to 0 to clear all flags // When an option isn't present here, it means that: // * it is not settable, or // * the option is POST (non-restricted) // * it has no post-actions // The post-action may be defined independently on restrictions. memset(flags, 0, sizeof flags); flags[SRTO_MSS] = SRTO_R_PREBIND; flags[SRTO_FC] = SRTO_R_PRE; flags[SRTO_SNDBUF] = SRTO_R_PREBIND; flags[SRTO_RCVBUF] = SRTO_R_PREBIND; flags[SRTO_UDP_SNDBUF] = SRTO_R_PREBIND; flags[SRTO_UDP_RCVBUF] = SRTO_R_PREBIND; flags[SRTO_RENDEZVOUS] = SRTO_R_PRE; flags[SRTO_REUSEADDR] = SRTO_R_PREBIND; flags[SRTO_MAXBW] = SRTO_POST_SPEC; flags[SRTO_SENDER] = SRTO_R_PRE; flags[SRTO_TSBPDMODE] = SRTO_R_PRE; flags[SRTO_LATENCY] = SRTO_R_PRE; flags[SRTO_INPUTBW] = SRTO_POST_SPEC; flags[SRTO_MININPUTBW] = SRTO_POST_SPEC; flags[SRTO_OHEADBW] = SRTO_POST_SPEC; flags[SRTO_PASSPHRASE] = SRTO_R_PRE; flags[SRTO_PBKEYLEN] = SRTO_R_PRE; flags[SRTO_IPTTL] = SRTO_R_PREBIND; flags[SRTO_IPTOS] = SRTO_R_PREBIND; flags[SRTO_TLPKTDROP] = SRTO_R_PRE; flags[SRTO_SNDDROPDELAY] = SRTO_POST_SPEC; flags[SRTO_NAKREPORT] = SRTO_R_PRE; flags[SRTO_VERSION] = SRTO_R_PRE; flags[SRTO_CONNTIMEO] = SRTO_R_PRE; flags[SRTO_LOSSMAXTTL] = SRTO_POST_SPEC; flags[SRTO_RCVLATENCY] = SRTO_R_PRE; flags[SRTO_PEERLATENCY] = SRTO_R_PRE; flags[SRTO_MINVERSION] = SRTO_R_PRE; flags[SRTO_STREAMID] = SRTO_R_PRE; flags[SRTO_CONGESTION] = SRTO_R_PRE; flags[SRTO_MESSAGEAPI] = SRTO_R_PRE; flags[SRTO_PAYLOADSIZE] = SRTO_R_PRE; flags[SRTO_TRANSTYPE] = SRTO_R_PREBIND; flags[SRTO_KMREFRESHRATE] = SRTO_R_PRE; flags[SRTO_KMPREANNOUNCE] = SRTO_R_PRE; flags[SRTO_ENFORCEDENCRYPTION] = SRTO_R_PRE; flags[SRTO_IPV6ONLY] = SRTO_R_PREBIND; flags[SRTO_PEERIDLETIMEO] = SRTO_R_PRE; #ifdef SRT_ENABLE_BINDTODEVICE flags[SRTO_BINDTODEVICE] = SRTO_R_PREBIND; #endif #if ENABLE_EXPERIMENTAL_BONDING flags[SRTO_GROUPCONNECT] = SRTO_R_PRE; #endif flags[SRTO_PACKETFILTER] = SRTO_R_PRE; flags[SRTO_RETRANSMITALGO] = SRTO_R_PRE; // For "private" options (not derived from the listener // socket by an accepted socket) provide below private_default // to which these options will be reset after blindly // copying the option object from the listener socket. // Note that this option cannot have runtime-dependent // default value, like options affected by SRTO_TRANSTYPE. // Options may be of different types, but this value should be only // used as a source of the value. For example, in case of int64_t you'd // have to place here a string of 8 characters. It should be copied // always in the hardware order, as this is what will be directly // passed to a setting function. private_default[SRTO_STREAMID] = string(); } }; const SrtOptionAction s_sockopt_action; } // namespace srt void srt::CUDT::construct() { m_pSndBuffer = NULL; m_pRcvBuffer = NULL; m_pSndLossList = NULL; m_pRcvLossList = NULL; m_iReorderTolerance = 0; // How many times so far the packet considered lost has been received // before TTL expires. m_iConsecEarlyDelivery = 0; m_iConsecOrderedDelivery = 0; m_pSndQueue = NULL; m_pRcvQueue = NULL; m_pSNode = NULL; m_pRNode = NULL; // Will be reset to 0 for HSv5, this value is important for HSv4. m_iSndHsRetryCnt = SRT_MAX_HSRETRY + 1; m_PeerID = 0; m_bOpened = false; m_bListening = false; m_bConnecting = false; m_bConnected = false; m_bClosing = false; m_bShutdown = false; m_bBroken = false; m_bBreakAsUnstable = false; // TODO: m_iBrokenCounter should be still set to some default. m_bPeerHealth = true; m_RejectReason = SRT_REJ_UNKNOWN; m_tsLastReqTime.store(steady_clock::time_point()); m_SrtHsSide = HSD_DRAW; m_uPeerSrtVersion = 0; // Not defined until connected. m_iTsbPdDelay_ms = 0; m_iPeerTsbPdDelay_ms = 0; m_bPeerTsbPd = false; m_iPeerTsbPdDelay_ms = 0; m_bTsbPd = false; m_bTsbPdAckWakeup = false; m_bGroupTsbPd = false; m_bPeerTLPktDrop = false; // Initilize mutex and condition variables. initSynch(); // TODO: Uncomment when the callback is implemented. // m_cbPacketArrival.set(this, &CUDT::defaultPacketArrival); } srt::CUDT::CUDT(CUDTSocket* parent): m_parent(parent) { construct(); (void)SRT_DEF_VERSION; // Runtime fields #if ENABLE_EXPERIMENTAL_BONDING m_HSGroupType = SRT_GTYPE_UNDEFINED; #endif m_bTLPktDrop = true; // Too-late Packet Drop m_pCache = NULL; // This is in order to set it ANY kind of initial value, however // this value should not be used when not connected and should be // updated in the handshake. When this value is 0, it means that // packets shall not be sent, as the other party doesn't have a // room to receive and store it. Therefore this value should be // overridden before any sending happens. m_iFlowWindowSize = 0; } srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor): m_parent(parent) { construct(); // XXX Consider all below fields (except m_bReuseAddr) to be put // into a separate class for easier copying. m_config = ancestor.m_config; // Reset values that shall not be derived to default ones. // These declarations should be consistent with SRTO_R_PRIVATE flag. for (size_t i = 0; i < Size(s_sockopt_action.flags); ++i) { const string* pdef = map_getp(s_sockopt_action.private_default, SRT_SOCKOPT(i)); if (pdef) { try { // Ignore errors here - this is a development-time granted // value, not user-provided value. m_config.set(SRT_SOCKOPT(i), pdef->data(), (int) pdef->size()); } catch (...) { LOGC(gglog.Error, log << "IPE: failed to set a declared default option!"); } } } m_SrtHsSide = ancestor.m_SrtHsSide; // actually it sets it to HSD_RESPONDER m_bTLPktDrop = ancestor.m_bTLPktDrop; m_iReorderTolerance = m_config.iMaxReorderTolerance; // Initialize with maximum value // Runtime m_pCache = ancestor.m_pCache; } srt::CUDT::~CUDT() { // release mutex/condtion variables destroySynch(); // destroy the data structures delete m_pSndBuffer; delete m_pRcvBuffer; delete m_pSndLossList; delete m_pRcvLossList; delete m_pSNode; delete m_pRNode; } void srt::CUDT::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) { if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); // Match check (confirm optName as index for s_sockopt_action) if (int(optName) < 0 || int(optName) >= int(SRTO_E_SIZE)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); // Restriction check const int oflags = s_sockopt_action.flags[optName]; ScopedLock cg (m_ConnectionLock); ScopedLock sendguard (m_SendLock); ScopedLock recvguard (m_RecvLock); HLOGC(aclog.Debug, log << CONID() << "OPTION: #" << optName << " value:" << FormatBinaryString((uint8_t*)optval, optlen)); if (IsSet(oflags, SRTO_R_PREBIND) && m_bOpened) throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); if (IsSet(oflags, SRTO_R_PRE) && (m_bConnected || m_bConnecting || m_bListening)) throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); // Option execution. If this returns -1, there's no such option. const int status = m_config.set(optName, optval, optlen); if (status == -1) { LOGC(aclog.Error, log << CONID() << "OPTION: #" << optName << " UNKNOWN"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } // Post-action, if applicable if (IsSet(oflags, SRTO_POST_SPEC) && m_bConnected) { switch (optName) { case SRTO_MAXBW: updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET)); break; case SRTO_INPUTBW: case SRTO_MININPUTBW: updateCC(TEV_INIT, EventVariant(TEV_INIT_INPUTBW)); break; case SRTO_OHEADBW: updateCC(TEV_INIT, EventVariant(TEV_INIT_OHEADBW)); break; case SRTO_LOSSMAXTTL: m_iReorderTolerance = m_config.iMaxReorderTolerance; default: break; } } } void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) { ScopedLock cg(m_ConnectionLock); switch (optName) { case SRTO_MSS: *(int *)optval = m_config.iMSS; optlen = sizeof(int); break; case SRTO_SNDSYN: *(bool *)optval = m_config.bSynSending; optlen = sizeof(bool); break; case SRTO_RCVSYN: *(bool *)optval = m_config.bSynRecving; optlen = sizeof(bool); break; case SRTO_ISN: *(int *)optval = m_iISN; optlen = sizeof(int); break; case SRTO_FC: *(int *)optval = m_config.iFlightFlagSize; optlen = sizeof(int); break; case SRTO_SNDBUF: *(int *)optval = m_config.iSndBufSize * (m_config.iMSS - CPacket::UDP_HDR_SIZE); optlen = sizeof(int); break; case SRTO_RCVBUF: *(int *)optval = m_config.iRcvBufSize * (m_config.iMSS - CPacket::UDP_HDR_SIZE); optlen = sizeof(int); break; case SRTO_LINGER: if (optlen < (int)(sizeof(linger))) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); *(linger *)optval = m_config.Linger; optlen = sizeof(linger); break; case SRTO_UDP_SNDBUF: *(int *)optval = m_config.iUDPSndBufSize; optlen = sizeof(int); break; case SRTO_UDP_RCVBUF: *(int *)optval = m_config.iUDPRcvBufSize; optlen = sizeof(int); break; case SRTO_RENDEZVOUS: *(bool *)optval = m_config.bRendezvous; optlen = sizeof(bool); break; case SRTO_SNDTIMEO: *(int *)optval = m_config.iSndTimeOut; optlen = sizeof(int); break; case SRTO_RCVTIMEO: *(int *)optval = m_config.iRcvTimeOut; optlen = sizeof(int); break; case SRTO_REUSEADDR: *(bool *)optval = m_config.bReuseAddr; optlen = sizeof(bool); break; case SRTO_MAXBW: if (size_t(optlen) < sizeof(m_config.llMaxBW)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); *(int64_t *)optval = m_config.llMaxBW; optlen = sizeof(int64_t); break; case SRTO_INPUTBW: if (size_t(optlen) < sizeof(m_config.llInputBW)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); *(int64_t*)optval = m_config.llInputBW; optlen = sizeof(int64_t); break; case SRTO_MININPUTBW: if (size_t(optlen) < sizeof (m_config.llMinInputBW)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); *(int64_t*)optval = m_config.llMinInputBW; optlen = sizeof(int64_t); break; case SRTO_OHEADBW: *(int32_t *)optval = m_config.iOverheadBW; optlen = sizeof(int32_t); break; case SRTO_STATE: *(int32_t *)optval = s_UDTUnited.getStatus(m_SocketID); optlen = sizeof(int32_t); break; case SRTO_EVENT: { int32_t event = 0; if (m_bBroken) event |= SRT_EPOLL_ERR; else { enterCS(m_RecvLock); if (m_pRcvBuffer && m_pRcvBuffer->isRcvDataReady()) event |= SRT_EPOLL_IN; leaveCS(m_RecvLock); if (m_pSndBuffer && (m_config.iSndBufSize > m_pSndBuffer->getCurrBufSize())) event |= SRT_EPOLL_OUT; } *(int32_t *)optval = event; optlen = sizeof(int32_t); break; } case SRTO_SNDDATA: if (m_pSndBuffer) *(int32_t *)optval = m_pSndBuffer->getCurrBufSize(); else *(int32_t *)optval = 0; optlen = sizeof(int32_t); break; case SRTO_RCVDATA: if (m_pRcvBuffer) { enterCS(m_RecvLock); *(int32_t *)optval = m_pRcvBuffer->getRcvDataSize(); leaveCS(m_RecvLock); } else *(int32_t *)optval = 0; optlen = sizeof(int32_t); break; case SRTO_IPTTL: if (m_bOpened) *(int32_t *)optval = m_pSndQueue->getIpTTL(); else *(int32_t *)optval = m_config.iIpTTL; optlen = sizeof(int32_t); break; case SRTO_IPTOS: if (m_bOpened) *(int32_t *)optval = m_pSndQueue->getIpToS(); else *(int32_t *)optval = m_config.iIpToS; optlen = sizeof(int32_t); break; case SRTO_BINDTODEVICE: #ifdef SRT_ENABLE_BINDTODEVICE if (optlen < IFNAMSIZ) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); if (m_bOpened && m_pSndQueue->getBind(((char*)optval), optlen)) { optlen = strlen((char*)optval); break; } // Fallback: return from internal data strcpy(((char*)optval), m_config.sBindToDevice.c_str()); optlen = m_config.sBindToDevice.size(); #else LOGC(smlog.Error, log << "SRTO_BINDTODEVICE is not supported on that platform"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #endif break; case SRTO_SENDER: *(bool *)optval = m_config.bDataSender; optlen = sizeof(bool); break; case SRTO_TSBPDMODE: *(bool *)optval = m_config.bTSBPD; optlen = sizeof(bool); break; case SRTO_LATENCY: case SRTO_RCVLATENCY: if (m_bConnected) *(int32_t *)optval = m_iTsbPdDelay_ms; else *(int32_t *)optval = m_config.iRcvLatency; optlen = sizeof(int32_t); break; case SRTO_PEERLATENCY: if (m_bConnected) *(int32_t *)optval = m_iPeerTsbPdDelay_ms; else *(int32_t *)optval = m_config.iPeerLatency; optlen = sizeof(int32_t); break; case SRTO_TLPKTDROP: if (m_bConnected) *(bool *)optval = m_bTLPktDrop; else *(bool *)optval = m_config.bTLPktDrop; optlen = sizeof(bool); break; case SRTO_SNDDROPDELAY: *(int32_t *)optval = m_config.iSndDropDelay; optlen = sizeof(int32_t); break; case SRTO_PBKEYLEN: if (m_pCryptoControl) *(int32_t *)optval = (int32_t) m_pCryptoControl->KeyLen(); // Running Key length. else *(int32_t *)optval = m_config.iSndCryptoKeyLen; // May be 0. optlen = sizeof(int32_t); break; case SRTO_KMSTATE: if (!m_pCryptoControl) *(int32_t *)optval = SRT_KM_S_UNSECURED; else if (m_config.bDataSender) *(int32_t *)optval = m_pCryptoControl->m_SndKmState; else *(int32_t *)optval = m_pCryptoControl->m_RcvKmState; optlen = sizeof(int32_t); break; case SRTO_SNDKMSTATE: // State imposed by Agent depending on PW and KMX if (m_pCryptoControl) *(int32_t *)optval = m_pCryptoControl->m_SndKmState; else *(int32_t *)optval = SRT_KM_S_UNSECURED; optlen = sizeof(int32_t); break; case SRTO_RCVKMSTATE: // State returned by Peer as informed during KMX if (m_pCryptoControl) *(int32_t *)optval = m_pCryptoControl->m_RcvKmState; else *(int32_t *)optval = SRT_KM_S_UNSECURED; optlen = sizeof(int32_t); break; case SRTO_LOSSMAXTTL: *(int32_t*)optval = m_config.iMaxReorderTolerance; optlen = sizeof(int32_t); break; case SRTO_NAKREPORT: *(bool *)optval = m_config.bRcvNakReport; optlen = sizeof(bool); break; case SRTO_VERSION: *(int32_t *)optval = m_config.uSrtVersion; optlen = sizeof(int32_t); break; case SRTO_PEERVERSION: *(int32_t *)optval = m_uPeerSrtVersion; optlen = sizeof(int32_t); break; case SRTO_CONNTIMEO: *(int*)optval = (int) count_milliseconds(m_config.tdConnTimeOut); optlen = sizeof(int); break; case SRTO_DRIFTTRACER: *(bool*)optval = m_config.bDriftTracer; optlen = sizeof(bool); break; case SRTO_MINVERSION: *(uint32_t *)optval = m_config.uMinimumPeerSrtVersion; optlen = sizeof(uint32_t); break; case SRTO_STREAMID: if (size_t(optlen) < m_config.sStreamName.size() + 1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); strcpy((char *)optval, m_config.sStreamName.c_str()); optlen = (int) m_config.sStreamName.size(); break; case SRTO_CONGESTION: if (size_t(optlen) < m_config.sCongestion.size() + 1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); strcpy((char *)optval, m_config.sCongestion.c_str()); optlen = (int) m_config.sCongestion.size(); break; case SRTO_MESSAGEAPI: optlen = sizeof(bool); *(bool *)optval = m_config.bMessageAPI; break; case SRTO_PAYLOADSIZE: optlen = sizeof(int); *(int *)optval = (int) m_config.zExpPayloadSize; break; case SRTO_KMREFRESHRATE: optlen = sizeof(int); *(int*)optval = (int)m_config.uKmRefreshRatePkt; break; case SRTO_KMPREANNOUNCE: optlen = sizeof(int); *(int*)optval = (int)m_config.uKmPreAnnouncePkt; break; #if ENABLE_EXPERIMENTAL_BONDING case SRTO_GROUPCONNECT: optlen = sizeof (int); *(int*)optval = m_config.iGroupConnect; break; case SRTO_GROUPTYPE: optlen = sizeof (int); *(int*)optval = m_HSGroupType; break; #endif case SRTO_ENFORCEDENCRYPTION: optlen = sizeof(bool); *(bool *)optval = m_config.bEnforcedEnc; break; case SRTO_IPV6ONLY: optlen = sizeof(int); *(int *)optval = m_config.iIpV6Only; break; case SRTO_PEERIDLETIMEO: *(int *)optval = m_config.iPeerIdleTimeout; optlen = sizeof(int); break; case SRTO_PACKETFILTER: if (size_t(optlen) < m_config.sPacketFilterConfig.size() + 1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); strcpy((char *)optval, m_config.sPacketFilterConfig.c_str()); optlen = (int) m_config.sPacketFilterConfig.size(); break; case SRTO_RETRANSMITALGO: *(int32_t *)optval = m_config.iRetransmitAlgo; optlen = sizeof(int32_t); break; default: throw CUDTException(MJ_NOTSUP, MN_NONE, 0); } } #if ENABLE_EXPERIMENTAL_BONDING SRT_ERRNO srt::CUDT::applyMemberConfigObject(const SRT_SocketOptionObject& opt) { SRT_SOCKOPT this_opt = SRTO_VERSION; for (size_t i = 0; i < opt.options.size(); ++i) { SRT_SocketOptionObject::SingleOption* o = opt.options[i]; HLOGC(smlog.Debug, log << "applyMemberConfigObject: OPTION @" << m_SocketID << " #" << o->option); this_opt = SRT_SOCKOPT(o->option); setOpt(this_opt, o->storage, o->length); } return SRT_SUCCESS; } #endif bool srt::CUDT::setstreamid(SRTSOCKET u, const std::string &sid) { CUDT *that = getUDTHandle(u); if (!that) return false; if (sid.size() > CSrtConfig::MAX_SID_LENGTH) return false; if (that->m_bConnected) return false; that->m_config.sStreamName.set(sid); return true; } string srt::CUDT::getstreamid(SRTSOCKET u) { CUDT *that = getUDTHandle(u); if (!that) return ""; return that->m_config.sStreamName.str(); } // XXX REFACTOR: Make common code for CUDT constructor and clearData, // possibly using CUDT::construct. void srt::CUDT::clearData() { // Initial sequence number, loss, acknowledgement, etc. int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE; m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; HLOGC(cnlog.Debug, log << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); m_iEXPCount = 1; m_iBandwidth = 1; // pkts/sec // XXX use some constant for this 16 m_iDeliveryRate = 16; m_iByteDeliveryRate = 16 * m_iMaxSRTPayloadSize; m_iAckSeqNo = 0; m_tsLastAckTime = steady_clock::now(); // trace information { ScopedLock stat_lock(m_StatsLock); m_stats.tsStartTime = steady_clock::now(); m_stats.sentTotal = m_stats.sentUniqTotal = m_stats.recvTotal = m_stats.recvUniqTotal = m_stats.sndLossTotal = m_stats.rcvLossTotal = m_stats.retransTotal = m_stats.sentACKTotal = m_stats.recvACKTotal = m_stats.sentNAKTotal = m_stats.recvNAKTotal = 0; m_stats.tsLastSampleTime = steady_clock::now(); m_stats.traceSent = m_stats.traceSentUniq = m_stats.traceRecv = m_stats.traceRecvUniq = m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans = m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0; m_stats.traceRcvRetrans = 0; m_stats.traceReorderDistance = 0; m_stats.traceBelatedTime = 0.0; m_stats.traceRcvBelated = 0; m_stats.sndDropTotal = 0; m_stats.traceSndDrop = 0; m_stats.rcvDropTotal = 0; m_stats.traceRcvDrop = 0; m_stats.m_rcvUndecryptTotal = 0; m_stats.traceRcvUndecrypt = 0; m_stats.bytesSentTotal = 0; m_stats.bytesSentUniqTotal = 0; m_stats.bytesRecvTotal = 0; m_stats.bytesRecvUniqTotal = 0; m_stats.bytesRetransTotal = 0; m_stats.traceBytesSent = 0; m_stats.traceBytesSentUniq = 0; m_stats.traceBytesRecv = 0; m_stats.traceBytesRecvUniq = 0; m_stats.sndFilterExtra = 0; m_stats.rcvFilterExtra = 0; m_stats.rcvFilterSupply = 0; m_stats.rcvFilterLoss = 0; m_stats.traceBytesRetrans = 0; m_stats.traceRcvBytesLoss = 0; m_stats.sndBytesDropTotal = 0; m_stats.rcvBytesDropTotal = 0; m_stats.traceSndBytesDrop = 0; m_stats.traceRcvBytesDrop = 0; m_stats.m_rcvBytesUndecryptTotal = 0; m_stats.traceRcvBytesUndecrypt = 0; m_stats.sndDuration = m_stats.m_sndDurationTotal = 0; } // Resetting these data because this happens when agent isn't connected. m_bPeerTsbPd = false; m_iPeerTsbPdDelay_ms = 0; // TSBPD as state should be set to FALSE here. // Only when the HSREQ handshake is exchanged, // should they be set to possibly true. m_bTsbPd = false; m_bGroupTsbPd = false; m_iTsbPdDelay_ms = m_config.iRcvLatency; m_bTLPktDrop = m_config.bTLPktDrop; m_bPeerTLPktDrop = false; m_bPeerNakReport = false; m_bPeerRexmitFlag = false; m_RdvState = CHandShake::RDV_INVALID; m_tsRcvPeerStartTime = steady_clock::time_point(); } void srt::CUDT::open() { ScopedLock cg(m_ConnectionLock); clearData(); // structures for queue if (m_pSNode == NULL) m_pSNode = new CSNode; m_pSNode->m_pUDT = this; m_pSNode->m_tsTimeStamp = steady_clock::now(); m_pSNode->m_iHeapLoc = -1; if (m_pRNode == NULL) m_pRNode = new CRNode; m_pRNode->m_pUDT = this; m_pRNode->m_tsTimeStamp = steady_clock::now(); m_pRNode->m_pPrev = m_pRNode->m_pNext = NULL; m_pRNode->m_bOnList = false; // Set initial values of smoothed RTT and RTT variance. m_iSRTT = INITIAL_RTT; m_iRTTVar = INITIAL_RTTVAR; m_bIsFirstRTTReceived = false; // set minimum NAK and EXP timeout to 300ms m_tdMinNakInterval = milliseconds_from(300); m_tdMinExpInterval = milliseconds_from(300); m_tdACKInterval = microseconds_from(COMM_SYN_INTERVAL_US); m_tdNAKInterval = m_tdMinNakInterval; const steady_clock::time_point currtime = steady_clock::now(); m_tsLastRspTime.store(currtime); m_tsNextACKTime.store(currtime + m_tdACKInterval); m_tsNextNAKTime.store(currtime + m_tdNAKInterval); m_tsLastRspAckTime = currtime; m_tsLastSndTime.store(currtime); m_tsUnstableSince = steady_clock::time_point(); m_tsFreshActivation = steady_clock::time_point(); m_tsWarySince = steady_clock::time_point(); m_iReXmitCount = 1; m_iPktCount = 0; m_iLightACKCount = 1; m_tsNextSendTime = steady_clock::time_point(); m_tdSendTimeDiff = microseconds_from(0); // Now UDT is opened. m_bOpened = true; } void srt::CUDT::setListenState() { ScopedLock cg(m_ConnectionLock); if (!m_bOpened) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); if (m_bConnecting || m_bConnected) throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); // listen can be called more than once if (m_bListening) return; // if there is already another socket listening on the same port if (m_pRcvQueue->setListener(this) < 0) throw CUDTException(MJ_NOTSUP, MN_BUSY, 0); m_bListening = true; } size_t srt::CUDT::fillSrtHandshake(uint32_t *aw_srtdata, size_t srtlen, int msgtype, int hs_version) { if (srtlen < SRT_HS_E_SIZE) { LOGC(cnlog.Fatal, log << "IPE: fillSrtHandshake: buffer too small: " << srtlen << " (expected: " << SRT_HS_E_SIZE << ")"); return 0; } srtlen = SRT_HS_E_SIZE; // We use only that much space. memset((aw_srtdata), 0, sizeof(uint32_t) * srtlen); /* Current version (1.x.x) SRT handshake */ aw_srtdata[SRT_HS_VERSION] = m_config.uSrtVersion; /* Required version */ aw_srtdata[SRT_HS_FLAGS] |= SrtVersionCapabilities(); switch (msgtype) { case SRT_CMD_HSREQ: return fillSrtHandshake_HSREQ((aw_srtdata), srtlen, hs_version); case SRT_CMD_HSRSP: return fillSrtHandshake_HSRSP((aw_srtdata), srtlen, hs_version); default: LOGC(cnlog.Fatal, log << "IPE: fillSrtHandshake/sendSrtMsg called with value " << msgtype); return 0; } } size_t srt::CUDT::fillSrtHandshake_HSREQ(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) { // INITIATOR sends HSREQ. // The TSBPD(SND|RCV) options are being set only if the TSBPD is set in the current agent. // The agent has a decisive power only in the range of RECEIVING the data, however it can // also influence the peer's latency. If agent doesn't set TSBPD mode, it doesn't send any // latency flags, although the peer might still want to do Rx with TSBPD. When agent sets // TsbPd mode, it defines latency values for Rx (itself) and Tx (peer's Rx). If peer does // not set TsbPd mode, it will simply ignore the proposed latency (PeerTsbPdDelay), although // if it has received the Rx latency as well, it must honor it and respond accordingly // (the latter is only in case of HSv5 and bidirectional connection). if (m_config.bTSBPD) { m_iTsbPdDelay_ms = m_config.iRcvLatency; m_iPeerTsbPdDelay_ms = m_config.iPeerLatency; /* * Sent data is real-time, use Time-based Packet Delivery, * set option bit and configured delay */ aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND; if (hs_version < CUDT::HS_VERSION_SRT1) { // HSv4 - this uses only one value. aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iPeerTsbPdDelay_ms); } else { // HSv5 - this will be understood only since this version when this exists. aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms); // And in the reverse direction. aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV; aw_srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms); // This wasn't there for HSv4, this setting is only for the receiver. // HSv5 is bidirectional, so every party is a receiver. if (m_bTLPktDrop) aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; } } if (m_config.bRcvNakReport) aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT; // I support SRT_OPT_REXMITFLG. Do you? aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; // Declare the API used. The flag is set for "stream" API because // the older versions will never set this flag, but all old SRT versions use message API. if (!m_config.bMessageAPI) aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_STREAM; HLOGC(cnlog.Debug, log << "HSREQ/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(aw_srtdata[SRT_HS_LATENCY]) << " RCV:" << SRT_HS_LATENCY_RCV::unwrap(aw_srtdata[SRT_HS_LATENCY]) << "] FLAGS[" << SrtFlagString(aw_srtdata[SRT_HS_FLAGS]) << "]"); return 3; } size_t srt::CUDT::fillSrtHandshake_HSRSP(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) { // Setting m_tsRcvPeerStartTime is done in processSrtMsg_HSREQ(), so // this condition will be skipped only if this function is called without // getting first received HSREQ. Doesn't look possible in both HSv4 and HSv5. if (is_zero(m_tsRcvPeerStartTime)) { LOGC(cnlog.Fatal, log << "IPE: fillSrtHandshake_HSRSP: m_tsRcvPeerStartTime NOT SET!"); return 0; } // If Agent doesn't set TSBPD, it will not set the TSBPD flag back to the Peer. // The peer doesn't have be disturbed by it anyway. if (isOPT_TsbPd()) { /* * We got and transposed peer start time (HandShake request timestamp), * we can support Timestamp-based Packet Delivery */ aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV; if (hs_version < HS_VERSION_SRT1) { // HSv4 - this uses only one value aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iTsbPdDelay_ms); } else { // HSv5 - this puts "agent's" latency into RCV field and "peer's" - // into SND field. aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms); } } else { HLOGC(cnlog.Debug, log << "HSRSP/snd: TSBPD off, NOT responding TSBPDRCV flag."); } // Hsv5, only when peer has declared TSBPD mode. // The flag was already set, and the value already "maximized" in processSrtMsg_HSREQ(). if (m_bPeerTsbPd && hs_version >= HS_VERSION_SRT1) { // HSv5 is bidirectional - so send the TSBPDSND flag, and place also the // peer's latency into SND field. aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND; aw_srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms); HLOGC(cnlog.Debug, log << "HSRSP/snd: HSv5 peer uses TSBPD, responding TSBPDSND latency=" << m_iPeerTsbPdDelay_ms); } else { HLOGC(cnlog.Debug, log << "HSRSP/snd: HSv" << (hs_version == CUDT::HS_VERSION_UDT4 ? 4 : 5) << " with peer TSBPD=" << (m_bPeerTsbPd ? "on" : "off") << " - NOT responding TSBPDSND"); } if (m_bTLPktDrop) aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; if (m_config.bRcvNakReport) { // HSv5: Note that this setting is independent on the value of // m_bPeerNakReport, which represent this setting in the peer. aw_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_uPeerSrtVersion <= SrtVersion(1, 0, 7)) aw_srtdata[SRT_HS_FLAGS] &= ~SRT_OPT_TLPKTDROP; } if (m_config.uSrtVersion >= SrtVersion(1, 2, 0)) { if (!m_bPeerRexmitFlag) { // Peer does not request to use rexmit flag, if so, // we won't use as well. HLOGC(cnlog.Debug, log << "HSRSP/snd: AGENT understands REXMIT flag, but PEER DOES NOT. NOT setting."); } else { // Request that the rexmit bit be used as a part of msgno. aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; HLOGF(cnlog.Debug, "HSRSP/snd: AGENT UNDERSTANDS REXMIT flag and PEER reported that it does, too."); } } else { // Since this is now in the code, it can occur only in case when you change the // version specification in the build configuration. HLOGF(cnlog.Debug, "HSRSP/snd: AGENT DOES NOT UNDERSTAND REXMIT flag"); } HLOGC(cnlog.Debug, log << "HSRSP/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(aw_srtdata[SRT_HS_LATENCY]) << " RCV:" << SRT_HS_LATENCY_RCV::unwrap(aw_srtdata[SRT_HS_LATENCY]) << "] FLAGS[" << SrtFlagString(aw_srtdata[SRT_HS_FLAGS]) << "]"); return 3; } size_t srt::CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size) { size_t srtlen = fillSrtHandshake(srtdata, size, cmd, handshakeVersion()); HLOGF(cnlog.Debug, "CMD:%s(%d) Len:%d Version: %s Flags: %08X (%s) sdelay:%d", MessageTypeStr(UMSG_EXT, cmd).c_str(), cmd, (int)(srtlen * sizeof(int32_t)), SrtVersionString(srtdata[SRT_HS_VERSION]).c_str(), srtdata[SRT_HS_FLAGS], SrtFlagString(srtdata[SRT_HS_FLAGS]).c_str(), srtdata[SRT_HS_LATENCY]); return srtlen; } void srt::CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, size_t srtlen_in) { CPacket srtpkt; int32_t srtcmd = (int32_t)cmd; SRT_STATIC_ASSERT(SRTDATA_MAXSIZE >= SRT_HS_E_SIZE, "SRT_CMD_MAXSZ is too small to hold all the data"); // This will be effectively larger than SRT_HS_E_SIZE, but it will be also used for incoming data. uint32_t srtdata[SRTDATA_MAXSIZE]; size_t srtlen = 0; if (cmd == SRT_CMD_REJECT) { // This is a value returned by processSrtMsg underlying layer, potentially // to be reported here. Should this happen, just send a rejection message. cmd = SRT_CMD_HSRSP; srtdata[SRT_HS_VERSION] = 0; } switch (cmd) { case SRT_CMD_HSREQ: case SRT_CMD_HSRSP: srtlen = prepareSrtHsMsg(cmd, srtdata, SRTDATA_MAXSIZE); break; case SRT_CMD_KMREQ: // Sender case SRT_CMD_KMRSP: // Receiver srtlen = srtlen_in; /* Msg already in network order * But CChannel:sendto will swap again (assuming 32-bit fields) * Pre-swap to cancel it. */ HtoNLA(srtdata, srtdata_in, srtlen); m_pCryptoControl->updateKmState(cmd, srtlen); // <-- THIS function can't be moved to CUDT break; default: LOGF(cnlog.Error, "sndSrtMsg: IPE: cmd=%d unsupported", cmd); break; } if (srtlen > 0) { /* srtpkt.pack will set message data in network order */ srtpkt.pack(UMSG_EXT, &srtcmd, srtdata, srtlen * sizeof(int32_t)); addressAndSend(srtpkt); } } size_t srt::CUDT::fillHsExtConfigString(uint32_t* pcmdspec, int cmd, const string& str) { uint32_t* space = pcmdspec + 1; size_t wordsize = (str.size() + 3) / 4; size_t aligned_bytesize = wordsize * 4; memset((space), 0, aligned_bytesize); memcpy((space), str.data(), str.size()); // Preswap to little endian (in place due to possible padding zeros) HtoILA((space), space, wordsize); *pcmdspec = HS_CMDSPEC_CMD::wrap(cmd) | HS_CMDSPEC_SIZE::wrap((uint32_t) wordsize); return wordsize; } #if ENABLE_EXPERIMENTAL_BONDING // [[using locked(m_parent->m_ControlLock)]] // [[using locked(s_UDTUnited.m_GlobControlLock)]] size_t srt::CUDT::fillHsExtGroup(uint32_t* pcmdspec) { SRT_ASSERT(m_parent->m_GroupOf != NULL); uint32_t* space = pcmdspec + 1; SRTSOCKET id = m_parent->m_GroupOf->id(); SRT_GROUP_TYPE tp = m_parent->m_GroupOf->type(); uint32_t flags = 0; // Note: if agent is a listener, and the current version supports // both sync methods, this flag might have been changed according to // the wish of the caller. if (m_parent->m_GroupOf->synconmsgno()) flags |= SRT_GFLAG_SYNCONMSG; // NOTE: this code remains as is for historical reasons. // The initial implementation stated that the peer id be // extracted so that it can be reported and possibly the // start time somehow encoded and written into the group // extension, but it was later seen not necessary. Therefore // this code remains, but now it's informational only. #if ENABLE_HEAVY_LOGGING m_parent->m_GroupOf->debugMasterData(m_SocketID); #endif // See CUDT::interpretGroup() uint32_t dataword = 0 | SrtHSRequest::HS_GROUP_TYPE::wrap(tp) | SrtHSRequest::HS_GROUP_FLAGS::wrap(flags) | SrtHSRequest::HS_GROUP_WEIGHT::wrap(m_parent->m_GroupMemberData->weight); const uint32_t storedata [GRPD_E_SIZE] = { uint32_t(id), dataword }; memcpy((space), storedata, sizeof storedata); const size_t ra_size = Size(storedata); *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_GROUP) | HS_CMDSPEC_SIZE::wrap(ra_size); return ra_size; } #endif size_t srt::CUDT::fillHsExtKMREQ(uint32_t* pcmdspec, size_t ki) { uint32_t* space = pcmdspec + 1; size_t msglen = m_pCryptoControl->getKmMsg_size(ki); // Make ra_size back in element unit // Add one extra word if the size isn't aligned to 32-bit. size_t ra_size = (msglen / sizeof(uint32_t)) + (msglen % sizeof(uint32_t) ? 1 : 0); // Store the CMD + SIZE in the next field *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_KMREQ) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); // Copy the key - do the endian inversion because another endian inversion // will be done for every control message before sending, and this KM message // is ALREADY in network order. const uint32_t* keydata = reinterpret_cast(m_pCryptoControl->getKmMsg_data(ki)); HLOGC(cnlog.Debug, log << "createSrtHandshake: KMREQ: adding key #" << ki << " length=" << ra_size << " words (KmMsg_size=" << msglen << ")"); // XXX INSECURE ": [" << FormatBinaryString((uint8_t*)keydata, msglen) << "]"; // Yes, I know HtoNLA and NtoHLA do exactly the same operation, but I want // to be clear about the true intention. NtoHLA((space), keydata, ra_size); return ra_size; } size_t srt::CUDT::fillHsExtKMRSP(uint32_t* pcmdspec, const uint32_t* kmdata, size_t kmdata_wordsize) { uint32_t* space = pcmdspec + 1; const uint32_t failure_kmrsp[] = {SRT_KM_S_UNSECURED}; const uint32_t* keydata = 0; // Shift the starting point with the value of previously added block, // to start with the new one. size_t ra_size; if (kmdata_wordsize == 0) { LOGC(cnlog.Warn, log << "createSrtHandshake: Agent has PW, but Peer sent no KMREQ. Sending error KMRSP response"); ra_size = 1; keydata = failure_kmrsp; // Update the KM state as well m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Agent has PW, but Peer won't decrypt m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Peer won't encrypt as well. } else { if (!kmdata) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << "createSrtHandshake: IPE: srtkm_cmd=SRT_CMD_KMRSP and no kmdata!"); return false; } ra_size = kmdata_wordsize; keydata = reinterpret_cast(kmdata); } *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_KMRSP) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); HLOGC(cnlog.Debug, log << "createSrtHandshake: KMRSP: applying returned key length=" << ra_size); // XXX INSECURE << " words: [" << FormatBinaryString((uint8_t*)kmdata, // kmdata_wordsize*sizeof(uint32_t)) << "]"; NtoHLA((space), keydata, ra_size); return ra_size; } // PREREQUISITE: // pkt must be set the buffer and configured for UMSG_HANDSHAKE. // Note that this function replaces also serialization for the HSv4. bool srt::CUDT::createSrtHandshake( int srths_cmd, int srtkm_cmd, const uint32_t* kmdata, size_t kmdata_wordsize, // IN WORDS, NOT BYTES!!! CPacket& w_pkt, CHandShake& w_hs) { // This function might be called before the opposite version was recognized. // Check if the version is exactly 4 because this means that the peer has already // sent something - asynchronously, and usually in rendezvous - and we already know // that the peer is version 4. In this case, agent must behave as HSv4, til the end. if (m_ConnRes.m_iVersion == HS_VERSION_UDT4) { w_hs.m_iVersion = HS_VERSION_UDT4; w_hs.m_iType = UDT_DGRAM; if (w_hs.m_extension) { // Should be impossible LOGC(cnlog.Error, log << "createSrtHandshake: IPE: EXTENSION SET WHEN peer reports version 4 - fixing..."); w_hs.m_extension = false; } } else { w_hs.m_iType = 0; // Prepare it for flags } HLOGC(cnlog.Debug, log << "createSrtHandshake: buf size=" << w_pkt.getLength() << " hsx=" << MessageTypeStr(UMSG_EXT, srths_cmd) << " kmx=" << MessageTypeStr(UMSG_EXT, srtkm_cmd) << " kmdata_wordsize=" << kmdata_wordsize << " version=" << w_hs.m_iVersion); // Once you are certain that the version is HSv5, set the enc type flags // to advertise pbkeylen. Otherwise make sure that the old interpretation // will correctly pick up the type field. PBKEYLEN should be advertized // regardless of what URQ stage the handshake is (note that in case of rendezvous // CONCLUSION might be the FIRST MESSAGE EVER RECEIVED by a party). if (w_hs.m_iVersion > HS_VERSION_UDT4) { // Check if there was a failure to receie HSREQ before trying to craft HSRSP. // If fillSrtHandshake_HSRSP catches the condition of m_tsRcvPeerStartTime == steady_clock::zero(), // it will return size 0, which will mess up with further extension procedures; // PREVENT THIS HERE. if (w_hs.m_iReqType == URQ_CONCLUSION && srths_cmd == SRT_CMD_HSRSP && is_zero(m_tsRcvPeerStartTime)) { LOGC(cnlog.Error, log << "createSrtHandshake: IPE (non-fatal): Attempting to craft HSRSP without received HSREQ. " "BLOCKING extensions."); w_hs.m_extension = false; } // The situation when this function is called without requested extensions // is URQ_CONCLUSION in rendezvous mode in some of the transitions. // In this case for version 5 just clear the m_iType field, as it has // different meaning in HSv5 and contains extension flags. // // Keep 0 in the SRT_HSTYPE_HSFLAGS field, but still advertise PBKEYLEN // in the SRT_HSTYPE_ENCFLAGS field. w_hs.m_iType = SrtHSRequest::wrapFlags(false /*no magic in HSFLAGS*/, m_config.iSndCryptoKeyLen); IF_HEAVY_LOGGING(bool whether = m_config.iSndCryptoKeyLen != 0); HLOGC(cnlog.Debug, log << "createSrtHandshake: " << (whether ? "" : "NOT ") << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); // Note: This is required only when sending a HS message without SRT extensions. // When this is to be sent with SRT extensions, then KMREQ will be attached here // and the PBKEYLEN will be extracted from it. If this is going to attach KMRSP // here, it's already too late (it should've been advertised before getting the first // handshake message with KMREQ). } else { w_hs.m_iType = UDT_DGRAM; } // values > URQ_CONCLUSION include also error types // if (w_hs.m_iVersion == HS_VERSION_UDT4 || w_hs.m_iReqType > URQ_CONCLUSION) <--- This condition was checked b4 and // it's only valid for caller-listener mode if (!w_hs.m_extension) { // Serialize only the basic handshake, if this is predicted for // Hsv4 peer or this is URQ_INDUCTION or URQ_WAVEAHAND. size_t hs_size = w_pkt.getLength(); w_hs.store_to((w_pkt.m_pcData), (hs_size)); w_pkt.setLength(hs_size); HLOGC(cnlog.Debug, log << "createSrtHandshake: (no ext) size=" << hs_size << " data: " << w_hs.show()); return true; } // Sanity check, applies to HSv5 only cases. if (srths_cmd == SRT_CMD_HSREQ && m_SrtHsSide == HSD_RESPONDER) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << "IPE: SRT_CMD_HSREQ was requested to be sent in HSv5 by an INITIATOR side!"); return false; // should cause rejection } ostringstream logext; logext << "HSX"; // Install the SRT extensions w_hs.m_iType |= CHandShake::HS_EXT_HSREQ; bool have_sid = false; if (srths_cmd == SRT_CMD_HSREQ && !m_config.sStreamName.empty()) { have_sid = true; w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; logext << ",SID"; } // If this is a response, we have also information // on the peer. If Peer is NOT filter capable, don't // put filter config, even if agent is capable. bool peer_filter_capable = true; if (srths_cmd == SRT_CMD_HSRSP) { if (m_sPeerPktFilterConfigString != "") { peer_filter_capable = true; } else if (IsSet(m_uPeerSrtFlags, SRT_OPT_FILTERCAP)) { peer_filter_capable = true; } else { peer_filter_capable = false; } } // Now, if this is INITIATOR, then it has its // filter config already set, if configured, otherwise // it should not attach the filter config extension. // If this is a RESPONDER, then it has already received // the filter config string from the peer and therefore // possibly confronted with the contents of m_OPT_FECConfigString, // and if it decided to go with filter, it will be nonempty. bool have_filter = false; if (peer_filter_capable && !m_config.sPacketFilterConfig.empty()) { have_filter = true; w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; logext << ",filter"; } bool have_congctl = false; const string sm = m_config.sCongestion.str(); if (sm != "" && sm != "live") { have_congctl = true; w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; logext << ",CONGCTL"; } bool have_kmreq = false; // Prevent adding KMRSP only in case when BOTH: // - Agent has set no password // - no KMREQ has arrived from Peer // KMRSP must be always sent when: // - Agent set a password, Peer did not send KMREQ: Agent sets snd=NOSECRET. // - Agent set no password, but Peer sent KMREQ: Ageng sets rcv=NOSECRET. if (m_config.CryptoSecret.len > 0 || kmdata_wordsize > 0) { have_kmreq = true; w_hs.m_iType |= CHandShake::HS_EXT_KMREQ; logext << ",KMX"; } #if ENABLE_EXPERIMENTAL_BONDING bool have_group = false; // Note: this is done without locking because we have the following possibilities: // // 1. Most positive: the group will be the same all the time up to the moment when we use it. // 2. The group will disappear when next time we try to use it having now have_group set true. // // Not possible that a group is NULL now but would appear later: the group must be either empty // or already set as valid at this time. // // If the 2nd possibility happens, then simply it means that the group has been closed during // the operation and the socket got this information updated in the meantime. This means that // it was an abnormal interrupt during the processing so the handshake process should be aborted // anyway, and that's what will be done. // LOCKING INFORMATION: accesing this field just for NULL check doesn't // hurt, even if this field could be dangling in the moment. This will be // followed by an additional check, done this time under lock, and there will // be no dangling pointers at this time. if (m_parent->m_GroupOf) { // Whatever group this socket belongs to, the information about // the group is always sent the same way with the handshake. have_group = true; w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; logext << ",GROUP"; } #endif HLOGC(cnlog.Debug, log << "createSrtHandshake: (ext: " << logext.str() << ") data: " << w_hs.show()); // NOTE: The HSREQ is practically always required, although may happen // in future that CONCLUSION can be sent multiple times for a separate // stream encryption support, and this way it won't enclose HSREQ. // Also, KMREQ may occur multiple times. // So, initially store the UDT legacy handshake. size_t hs_size = w_pkt.getLength(), total_ra_size = (hs_size / sizeof(uint32_t)); // Maximum size of data w_hs.store_to((w_pkt.m_pcData), (hs_size)); // hs_size is updated size_t ra_size = hs_size / sizeof(int32_t); // Now attach the SRT handshake for HSREQ size_t offset = ra_size; uint32_t *p = reinterpret_cast(w_pkt.m_pcData); // NOTE: since this point, ra_size has a size in int32_t elements, NOT BYTES. // The first 4-byte item is the CMD/LENGTH spec. uint32_t *pcmdspec = p + offset; // Remember the location to be filled later, when we know the length ++offset; // Now use the original function to store the actual SRT_HS data // ra_size after that // NOTE: so far, ra_size is m_iMaxSRTPayloadSize expressed in number of elements. // WILL BE CHANGED HERE. ra_size = fillSrtHandshake((p + offset), total_ra_size - offset, srths_cmd, HS_VERSION_SRT1); *pcmdspec = HS_CMDSPEC_CMD::wrap(srths_cmd) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); HLOGC(cnlog.Debug, log << "createSrtHandshake: after HSREQ: offset=" << offset << " HSREQ size=" << ra_size << " space left: " << (total_ra_size - offset)); // Use only in REQ phase and only if stream name is set if (have_sid) { // Now prepare the string with 4-byte alignment. The string size is limited // to half the payload size. Just a sanity check to not pack too much into // the conclusion packet. size_t size_limit = m_iMaxSRTPayloadSize / 2; if (m_config.sStreamName.size() >= size_limit) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Warn, log << "createSrtHandshake: stream id too long, limited to " << (size_limit - 1) << " bytes"); return false; } offset += ra_size + 1; ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_SID, m_config.sStreamName.str()); HLOGC(cnlog.Debug, log << "createSrtHandshake: after SID [" << m_config.sStreamName.c_str() << "] length=" << m_config.sStreamName.size() << " alignedln=" << (4 * ra_size) << ": offset=" << offset << " SID size=" << ra_size << " space left: " << (total_ra_size - offset)); } if (have_congctl) { // Pass the congctl to the other side as informational. // The other side should reject connection if it uses a different congctl. // The other side should also respond with the congctl it uses, if its non-default (for backward compatibility). offset += ra_size + 1; ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_CONGESTION, sm); HLOGC(cnlog.Debug, log << "createSrtHandshake: after CONGCTL [" << sm << "] length=" << sm.size() << " alignedln=" << (4 * ra_size) << ": offset=" << offset << " CONGCTL size=" << ra_size << " space left: " << (total_ra_size - offset)); } if (have_filter) { offset += ra_size + 1; ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_FILTER, m_config.sPacketFilterConfig.str()); HLOGC(cnlog.Debug, log << "createSrtHandshake: after filter [" << m_config.sPacketFilterConfig.c_str() << "] length=" << m_config.sPacketFilterConfig.size() << " alignedln=" << (4 * ra_size) << ": offset=" << offset << " filter size=" << ra_size << " space left: " << (total_ra_size - offset)); } #if ENABLE_EXPERIMENTAL_BONDING // Note that this will fire in both cases: // - When the group has been set by the user on a socket (or socket was created as a part of the group), // and the handshake request is to be sent with informing the peer that this conenction belongs to a group // - When the agent received a HS request with a group, has created its mirror group on its side, and // now sends the HS response to the peer, with ITS OWN group id (the mirror one). // // XXX Probably a condition should be checked here around the group type. // The time synchronization should be done only on any kind of parallel sending group. // Currently all groups are such groups (broadcast, backup, balancing), but it may // need to be changed for some other types. if (have_group) { // NOTE: See information about mutex ordering in api.h ScopedLock gdrg (s_UDTUnited.m_GlobControlLock); if (!m_parent->m_GroupOf) { // This may only happen if since last check of m_GroupOf pointer the socket was removed // from the group in the meantime, which can only happen due to that the group was closed. // In such a case it simply means that the handshake process was requested to be interrupted. LOGC(cnlog.Fatal, log << "GROUP DISAPPEARED. Socket not capable of continuing HS"); return false; } else { if (m_parent->m_GroupOf->closing()) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << "createSrtHandshake: group is closing during the process, rejecting."); return false; } offset += ra_size + 1; ra_size = fillHsExtGroup(p + offset - 1); HLOGC(cnlog.Debug, log << "createSrtHandshake: after GROUP [" << sm << "] length=" << sm.size() << ": offset=" << offset << " GROUP size=" << ra_size << " space left: " << (total_ra_size - offset)); } } #endif // When encryption turned on if (have_kmreq) { HLOGC(cnlog.Debug, log << "createSrtHandshake: " << (m_config.CryptoSecret.len > 0 ? "Agent uses ENCRYPTION" : "Peer requires ENCRYPTION")); if (!m_pCryptoControl && (srtkm_cmd == SRT_CMD_KMREQ || srtkm_cmd == SRT_CMD_KMRSP)) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << "createSrtHandshake: IPE: need to send KM, but CryptoControl does not exist." << " Socket state: connected=" << boolalpha << m_bConnected << ", connecting=" << m_bConnecting << ", broken=" << m_bBroken << ", closing=" << m_bClosing << "."); return false; } if (srtkm_cmd == SRT_CMD_KMREQ) { bool have_any_keys = false; for (size_t ki = 0; ki < 2; ++ki) { // Skip those that have expired if (!m_pCryptoControl->getKmMsg_needSend(ki, false)) continue; m_pCryptoControl->getKmMsg_markSent(ki, false); offset += ra_size + 1; ra_size = fillHsExtKMREQ(p + offset - 1, ki); have_any_keys = true; } if (!have_any_keys) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << "createSrtHandshake: IPE: all keys have expired, no KM to send."); return false; } } else if (srtkm_cmd == SRT_CMD_KMRSP) { offset += ra_size + 1; ra_size = fillHsExtKMRSP(p + offset - 1, kmdata, kmdata_wordsize); } else { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << "createSrtHandshake: IPE: wrong value of srtkm_cmd: " << srtkm_cmd); return false; } } // ra_size + offset has a value in element unit. // Switch it again to byte unit. w_pkt.setLength((ra_size + offset) * sizeof(int32_t)); HLOGC(cnlog.Debug, log << "createSrtHandshake: filled HSv5 handshake flags: " << CHandShake::ExtensionFlagStr(w_hs.m_iType) << " length: " << w_pkt.getLength() << " bytes"); return true; } template static inline int FindExtensionBlock(Integer* begin, size_t total_length, size_t& w_out_len, Integer*& w_next_block) { // Check if there's anything to process if (total_length == 0) { w_next_block = NULL; w_out_len = 0; return SRT_CMD_NONE; } // This function extracts the block command from the block and its length. // The command value is returned as a function result. // The size of that command block is stored into w_out_len. // The beginning of the prospective next block is stored in w_next_block. // The caller must be aware that: // - exactly one element holds the block header (cmd+size), so the actual data are after this one. // - the returned size is the number of uint32_t elements since that first data element // - the remaining size should be manually calculated as total_length - 1 - w_out_len, or // simply, as w_next_block - begin. // Note that if the total_length is too short to extract the whole block, it will return // SRT_CMD_NONE. Note that total_length includes this first CMDSPEC word. // // When SRT_CMD_NONE is returned, it means that nothing has been extracted and nothing else // can be further extracted from this block. int cmd = HS_CMDSPEC_CMD::unwrap(*begin); size_t size = HS_CMDSPEC_SIZE::unwrap(*begin); if (size + 1 > total_length) return SRT_CMD_NONE; w_out_len = size; if (total_length == size + 1) w_next_block = NULL; else w_next_block = begin + 1 + size; return cmd; } // NOTE: the rule of order of arguments is broken here because this order // serves better the logics and readability. template static inline bool NextExtensionBlock(Integer*& w_begin, Integer* next, size_t& w_length) { if (!next) return false; w_length = w_length - (next - w_begin); w_begin = next; return true; } void SrtExtractHandshakeExtensions(const char* bufbegin, size_t buflength, vector& w_output) { const uint32_t *begin = reinterpret_cast(bufbegin + CHandShake::m_iContentSize); size_t size = buflength - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0 const uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // ONE SHOT, but continuable loop { const int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); if (cmd == SRT_CMD_NONE) { // End of blocks break; } w_output.push_back(SrtHandshakeExtension(cmd)); SrtHandshakeExtension& ext = w_output.back(); std::copy(begin+1, begin+blocklen+1, back_inserter(ext.contents)); // Any other kind of message extracted. Search on. if (!NextExtensionBlock((begin), next, (length))) break; } } #if SRT_DEBUG_RTT class RttTracer { public: RttTracer() { } ~RttTracer() { srt::sync::ScopedLock lck(m_mtx); m_fout.close(); } void trace(const srt::sync::steady_clock::time_point& currtime, const std::string& event, int rtt_sample, int rttvar_sample, bool is_smoothed_rtt_reset, int64_t recvTotal, int smoothed_rtt, int rttvar) { srt::sync::ScopedLock lck(m_mtx); create_file(); m_fout << srt::sync::FormatTimeSys(currtime) << ","; m_fout << srt::sync::FormatTime(currtime) << ","; m_fout << event << ","; m_fout << rtt_sample << ","; m_fout << rttvar_sample << ","; m_fout << is_smoothed_rtt_reset << ","; m_fout << recvTotal << ","; m_fout << smoothed_rtt << ","; m_fout << rttvar << "\n"; m_fout.flush(); } private: void print_header() { m_fout << "Timepoint_SYST,Timepoint_STDY,Event,usRTTSample," "usRTTVarSample,IsSmoothedRTTReset,pktsRecvTotal," "usSmoothedRTT,usRTTVar\n"; } void create_file() { if (m_fout.is_open()) return; std::string str_tnow = srt::sync::FormatTimeSys(srt::sync::steady_clock::now()); str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part while (str_tnow.find(':') != std::string::npos) { str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); } const std::string fname = "rtt_trace_" + str_tnow + "_" + SRT_SYNC_CLOCK_STR + ".csv"; m_fout.open(fname, std::ofstream::out); if (!m_fout) std::cerr << "IPE: Failed to open " << fname << "!!!\n"; print_header(); } private: srt::sync::Mutex m_mtx; std::ofstream m_fout; }; RttTracer s_rtt_trace; #endif bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) { uint32_t *srtdata = (uint32_t *)ctrlpkt->m_pcData; size_t len = ctrlpkt->getLength(); int etype = ctrlpkt->getExtendedType(); uint32_t ts = ctrlpkt->m_iTimeStamp; int res = SRT_CMD_NONE; HLOGC(cnlog.Debug, log << "Dispatching message type=" << etype << " data length=" << (len / sizeof(int32_t))); switch (etype) { case SRT_CMD_HSREQ: { res = processSrtMsg_HSREQ(srtdata, len, ts, CUDT::HS_VERSION_UDT4); break; } case SRT_CMD_HSRSP: { res = processSrtMsg_HSRSP(srtdata, len, ts, CUDT::HS_VERSION_UDT4); break; } case SRT_CMD_KMREQ: // Special case when the data need to be processed here // and the appropriate message must be constructed for sending. // No further processing required { uint32_t srtdata_out[SRTDATA_MAXSIZE]; size_t len_out = 0; res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, (srtdata_out), (len_out)); if (res == SRT_CMD_KMRSP) { if (len_out == 1) { if (m_config.bEnforcedEnc) { LOGC(cnlog.Warn, log << "KMREQ FAILURE: " << KmStateStr(SRT_KM_STATE(srtdata_out[0])) << " - rejecting per enforced encryption"); res = SRT_CMD_NONE; break; } HLOGC(cnlog.Debug, log << "MKREQ -> KMRSP FAILURE state: " << KmStateStr(SRT_KM_STATE(srtdata_out[0]))); } else { HLOGC(cnlog.Debug, log << "KMREQ -> requested to send KMRSP length=" << len_out); } sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out); } // XXX Dead code. processSrtMsg_KMREQ now doesn't return any other value now. // Please review later. else { LOGC(cnlog.Warn, log << "KMREQ failed to process the request - ignoring"); } return true; // already done what's necessary } case SRT_CMD_KMRSP: { // KMRSP doesn't expect any following action m_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, CUDT::HS_VERSION_UDT4); return true; // nothing to do } default: return false; } if (res == SRT_CMD_NONE) return true; // Send the message that the message handler requested. sendSrtMsg(res); return true; } int srt::CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv) { // Set this start time in the beginning, regardless as to whether TSBPD is being // used or not. This must be done in the Initiator as well as Responder. /* * Compute peer StartTime in our time reference * This takes time zone, time drift into account. * Also includes current packet transit time (rtt/2) */ m_tsRcvPeerStartTime = steady_clock::now() - microseconds_from(ts); // (in case of bonding group, this value will be OVERWRITTEN // later in CUDT::interpretGroup). // Prepare the initial runtime values of latency basing on the option values. // They are going to get the value fixed HERE. m_iTsbPdDelay_ms = m_config.iRcvLatency; m_iPeerTsbPdDelay_ms = m_config.iPeerLatency; if (bytelen < SRT_CMD_HSREQ_MINSZ) { m_RejectReason = SRT_REJ_ROGUE; /* Packet smaller than minimum compatible packet size */ LOGF(cnlog.Error, "HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " invalid", SRT_CMD_HSREQ, bytelen); return SRT_CMD_NONE; } LOGF(cnlog.Note, "HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " vers=0x%x opts=0x%x delay=%d", SRT_CMD_HSREQ, bytelen, srtdata[SRT_HS_VERSION], srtdata[SRT_HS_FLAGS], SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY])); m_uPeerSrtVersion = srtdata[SRT_HS_VERSION]; m_uPeerSrtFlags = srtdata[SRT_HS_FLAGS]; if (hsv == CUDT::HS_VERSION_UDT4) { if (m_uPeerSrtVersion >= SRT_VERSION_FEAT_HSv5) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "HSREQ/rcv: With HSv4 version >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " is not acceptable."); return SRT_CMD_REJECT; } } else { if (m_uPeerSrtVersion < SRT_VERSION_FEAT_HSv5) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "HSREQ/rcv: With HSv5 version must be >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " ."); return SRT_CMD_REJECT; } } // Check also if the version satisfies the minimum required version if (m_uPeerSrtVersion < m_config.uMinimumPeerSrtVersion) { m_RejectReason = SRT_REJ_VERSION; LOGC(cnlog.Error, log << "HSREQ/rcv: Peer version: " << SrtVersionString(m_uPeerSrtVersion) << " is too old for requested: " << SrtVersionString(m_config.uMinimumPeerSrtVersion) << " - REJECTING"); return SRT_CMD_REJECT; } HLOGC(cnlog.Debug, log << "HSREQ/rcv: PEER Version: " << SrtVersionString(m_uPeerSrtVersion) << " Flags: " << m_uPeerSrtFlags << "(" << SrtFlagString(m_uPeerSrtFlags) << ") Min req version:" << SrtVersionString(m_config.uMinimumPeerSrtVersion)); m_bPeerRexmitFlag = IsSet(m_uPeerSrtFlags, SRT_OPT_REXMITFLG); HLOGF(cnlog.Debug, "HSREQ/rcv: peer %s REXMIT flag", m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND"); // Check if both use the same API type. Reject if not. bool peer_message_api = !IsSet(m_uPeerSrtFlags, SRT_OPT_STREAM); if (peer_message_api != m_config.bMessageAPI) { m_RejectReason = SRT_REJ_MESSAGEAPI; LOGC(cnlog.Error, log << "HSREQ/rcv: Agent uses " << (m_config.bMessageAPI ? "MESSAGE" : "STREAM") << " API, but the Peer declares " << (peer_message_api ? "MESSAGE" : "STREAM") << " API. Not compatible transmission type, rejecting."); return SRT_CMD_REJECT; } SRT_STATIC_ASSERT(SRT_HS_E_SIZE == SRT_HS_LATENCY + 1, "Assuming latency is the last field"); if (bytelen < (SRT_HS_E_SIZE * sizeof(uint32_t))) { // Handshake extension message includes VERSION, FLAGS and LATENCY // (3 x 32 bits). SRT v1.2.0 and earlier might supply shorter extension message, // without LATENCY fields. // It is acceptable, as long as the latency flags are not set on our side. // // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | SRT Version | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | SRT Flags | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Receiver TSBPD Delay | Sender TSBPD Delay | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDSND) || IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, but TSBPD flags are set. Rejecting."); return SRT_CMD_REJECT; } LOGC(cnlog.Warn, log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, not getting any TSBPD settings."); // Don't process any further settings in this case. Turn off TSBPD, just for a case. m_bTsbPd = false; m_bPeerTsbPd = false; return SRT_CMD_HSRSP; } const uint32_t latencystr = srtdata[SRT_HS_LATENCY]; if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDSND)) { // TimeStamp-based Packet Delivery feature enabled if (!isOPT_TsbPd()) { LOGC(cnlog.Warn, log << "HSREQ/rcv: Agent did not set rcv-TSBPD - ignoring proposed latency from peer"); // Note: also don't set the peer TSBPD flag HERE because // - in HSv4 it will be a sender, so it doesn't matter anyway // - in HSv5 if it's going to receive, the TSBPDRCV flag will define it. } else { int peer_decl_latency; if (hsv < CUDT::HS_VERSION_SRT1) { // In HSv4 there is only one value and this is the latency // that the sender peer proposes for the agent. peer_decl_latency = SRT_HS_LATENCY_LEG::unwrap(latencystr); } else { // In HSv5 there are latency declared for sending and receiving separately. // SRT_HS_LATENCY_SND is the value that the peer proposes to be the // value used by agent when receiving data. We take this as a local latency value. peer_decl_latency = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]); } // Use the maximum latency out of latency from our settings and the latency // "proposed" by the peer. int maxdelay = std::max(m_iTsbPdDelay_ms, peer_decl_latency); HLOGC(cnlog.Debug, log << "HSREQ/rcv: LOCAL/RCV LATENCY: Agent:" << m_iTsbPdDelay_ms << " Peer:" << peer_decl_latency << " Selecting:" << maxdelay); m_iTsbPdDelay_ms = maxdelay; m_bTsbPd = true; } } else { std::string how_about_agent = isOPT_TsbPd() ? "BUT AGENT DOES" : "and nor does Agent"; HLOGC(cnlog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for sending - " << how_about_agent); } // This happens when the HSv5 RESPONDER receives the HSREQ message; it declares // that the peer INITIATOR will receive the data and informs about its predefined // latency. We need to maximize this with our setting of the peer's latency and // record as peer's latency, which will be then sent back with HSRSP. if (hsv > CUDT::HS_VERSION_UDT4 && IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { // So, PEER uses TSBPD, set the flag. // NOTE: it doesn't matter, if AGENT uses TSBPD. m_bPeerTsbPd = true; // SRT_HS_LATENCY_RCV is the value that the peer declares as to be // used by it when receiving data. We take this as a peer's value, // and select the maximum of this one and our proposed latency for the peer. int peer_decl_latency = SRT_HS_LATENCY_RCV::unwrap(latencystr); int maxdelay = std::max(m_iPeerTsbPdDelay_ms, peer_decl_latency); HLOGC(cnlog.Debug, log << "HSREQ/rcv: PEER/RCV LATENCY: Agent:" << m_iPeerTsbPdDelay_ms << " Peer:" << peer_decl_latency << " Selecting:" << maxdelay); m_iPeerTsbPdDelay_ms = maxdelay; } else { std::string how_about_agent = isOPT_TsbPd() ? "BUT AGENT DOES" : "and nor does Agent"; HLOGC(cnlog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for receiving - " << how_about_agent); } if (hsv > CUDT::HS_VERSION_UDT4) { // This is HSv5, do the same things as required for the sending party in HSv4, // as in HSv5 this can also be a sender. if (IsSet(m_uPeerSrtFlags, SRT_OPT_TLPKTDROP)) { // Too late packets dropping feature supported m_bPeerTLPktDrop = true; } if (IsSet(m_uPeerSrtFlags, SRT_OPT_NAKREPORT)) { // Peer will send Periodic NAK Reports m_bPeerNakReport = true; } } return SRT_CMD_HSRSP; } int srt::CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv) { // XXX Check for mis-version // With HSv4 we accept only version less than 1.3.0 if (hsv == CUDT::HS_VERSION_UDT4 && srtdata[SRT_HS_VERSION] >= SRT_VERSION_FEAT_HSv5) { LOGC(cnlog.Error, log << "HSRSP/rcv: With HSv4 version >= 1.2.0 is not acceptable."); return SRT_CMD_NONE; } if (bytelen < SRT_CMD_HSRSP_MINSZ) { /* Packet smaller than minimum compatible packet size */ LOGF(cnlog.Error, "HSRSP/rcv: cmd=%d(HSRSP) len=%" PRIzu " invalid", SRT_CMD_HSRSP, bytelen); return SRT_CMD_NONE; } // Set this start time in the beginning, regardless as to whether TSBPD is being // used or not. This must be done in the Initiator as well as Responder. In case when // agent is sender only (HSv4) this value simply won't be used. /* * Compute peer StartTime in our time reference * This takes time zone, time drift into account. * Also includes current packet transit time (rtt/2) */ if (is_zero(m_tsRcvPeerStartTime)) { // Do not set this time when it's already set, which may be the case // if the agent has this value already "borrowed" from a master socket // that was in the group at the time when it was added. m_tsRcvPeerStartTime = steady_clock::now() - microseconds_from(ts); HLOGC(cnlog.Debug, log << "HSRSP/rcv: PEER START TIME not yet defined, setting: " << FormatTime(m_tsRcvPeerStartTime)); } else { HLOGC(cnlog.Debug, log << "HSRSP/rcv: PEER START TIME already set (derived): " << FormatTime(m_tsRcvPeerStartTime)); } m_uPeerSrtVersion = srtdata[SRT_HS_VERSION]; m_uPeerSrtFlags = srtdata[SRT_HS_FLAGS]; HLOGF(cnlog.Debug, "HSRSP/rcv: Version: %s Flags: SND:%08X (%s)", SrtVersionString(m_uPeerSrtVersion).c_str(), m_uPeerSrtFlags, SrtFlagString(m_uPeerSrtFlags).c_str()); // Basic version check if (m_uPeerSrtVersion < m_config.uMinimumPeerSrtVersion) { m_RejectReason = SRT_REJ_VERSION; LOGC(cnlog.Error, log << "HSRSP/rcv: Peer version: " << SrtVersionString(m_uPeerSrtVersion) << " is too old for requested: " << SrtVersionString(m_config.uMinimumPeerSrtVersion) << " - REJECTING"); return SRT_CMD_REJECT; } if (hsv == CUDT::HS_VERSION_UDT4) { // The old HSv4 way: extract just one value and put it under peer. if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { // TsbPd feature enabled m_bPeerTsbPd = true; m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_LEG::unwrap(srtdata[SRT_HS_LATENCY]); HLOGC(cnlog.Debug, log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << " (Agent: declared:" << m_iTsbPdDelay_ms << " rcv:" << m_iTsbPdDelay_ms << ")"); } // TSBPDSND isn't set in HSv4 by the RESPONDER, because HSv4 RESPONDER is always RECEIVER. } else { // HSv5 way: extract the receiver latency and sender latency, if used. // PEER WILL RECEIVE TSBPD == AGENT SHALL SEND TSBPD. if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { // TsbPd feature enabled m_bPeerTsbPd = true; m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]); HLOGC(cnlog.Debug, log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << "ms"); } else { HLOGC(cnlog.Debug, log << "HSRSP/rcv: Peer (responder) DOES NOT USE latency"); } // PEER WILL SEND TSBPD == AGENT SHALL RECEIVE TSBPD. if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDSND)) { if (!isOPT_TsbPd()) { LOGC(cnlog.Warn, log << "HSRSP/rcv: BUG? Peer (responder) declares sending latency, but Agent turned off TSBPD."); } else { m_bTsbPd = true; // NOTE: in case of Group TSBPD receiving, this field will be SWITCHED TO m_bGroupTsbPd. // Take this value as a good deal. In case when the Peer did not "correct" the latency // because it has TSBPD turned off, just stay with the present value defined in options. m_iTsbPdDelay_ms = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]); HLOGC(cnlog.Debug, log << "HSRSP/rcv: LATENCY Agent/rcv: " << m_iTsbPdDelay_ms << "ms"); } } } if ((m_config.uSrtVersion >= SrtVersion(1, 0, 5)) && IsSet(m_uPeerSrtFlags, SRT_OPT_TLPKTDROP)) { // Too late packets dropping feature supported m_bPeerTLPktDrop = true; } if ((m_config.uSrtVersion >= SrtVersion(1, 1, 0)) && IsSet(m_uPeerSrtFlags, SRT_OPT_NAKREPORT)) { // Peer will send Periodic NAK Reports m_bPeerNakReport = true; } if (m_config.uSrtVersion >= SrtVersion(1, 2, 0)) { if (IsSet(m_uPeerSrtFlags, SRT_OPT_REXMITFLG)) { // Peer will use REXMIT flag in packet retransmission. m_bPeerRexmitFlag = true; HLOGP(cnlog.Debug, "HSRSP/rcv: 1.2.0+ Agent understands REXMIT flag and so does peer."); } else { HLOGP(cnlog.Debug, "HSRSP/rcv: Agent understands REXMIT flag, but PEER DOES NOT"); } } else { HLOGF(cnlog.Debug, "HSRSP/rcv: <1.2.0 Agent DOESN'T understand REXMIT flag"); } handshakeDone(); return SRT_CMD_NONE; } // This function is called only when the URQ_CONCLUSION handshake has been received from the peer. bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, const CPacket& hspkt, uint32_t* out_data SRT_ATR_UNUSED, size_t* pw_len) { // Initialize pw_len to 0 to handle the unencrypted case if (pw_len) *pw_len = 0; // The version=0 statement as rejection is used only since HSv5. // The HSv4 sends the AGREEMENT handshake message with version=0, do not misinterpret it. if (m_ConnRes.m_iVersion > HS_VERSION_UDT4 && hs.m_iVersion == 0) { m_RejectReason = SRT_REJ_PEER; LOGC(cnlog.Error, log << "HS VERSION = 0, meaning the handshake has been rejected."); return false; } if (hs.m_iVersion < HS_VERSION_SRT1) { if (m_config.uMinimumPeerSrtVersion && m_config.uMinimumPeerSrtVersion >= SRT_VERSION_FEAT_HSv5) { m_RejectReason = SRT_REJ_VERSION; // This means that a version with minimum 1.3.0 that features HSv5 is required, // hence all HSv4 clients should be rejected. LOGP(cnlog.Error, "interpretSrtHandshake: minimum peer version 1.3.0 (HSv5 only), rejecting HSv4 client"); return false; } return true; // do nothing } // Anyway, check if the handshake contains any extra data. if (hspkt.getLength() <= CHandShake::m_iContentSize) { m_RejectReason = SRT_REJ_ROGUE; // This would mean that the handshake was at least HSv5, but somehow no extras were added. // Dismiss it then, however this has to be logged. LOGC(cnlog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension found!"); return false; } // We still believe it should work, let's check the flags. int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType); if (ext_flags == 0) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension flags are set!"); return false; } HLOGC(cnlog.Debug, log << "HS VERSION=" << hs.m_iVersion << " EXTENSIONS: " << CHandShake::ExtensionFlagStr(ext_flags)); // Ok, now find the beginning of an int32_t array that follows the UDT handshake. uint32_t* p = reinterpret_cast(hspkt.m_pcData + CHandShake::m_iContentSize); size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0 int hsreq_type_cmd SRT_ATR_UNUSED = SRT_CMD_NONE; if (IsSet(ext_flags, CHandShake::HS_EXT_HSREQ)) { HLOGC(cnlog.Debug, log << "interpretSrtHandshake: extracting HSREQ/RSP type extension"); uint32_t *begin = p; uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // this is ONE SHOT LOOP { int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_HSREQ) { hsreq_type_cmd = cmd; // Set is the size as it should, then give it for interpretation for // the proper function. if (blocklen < SRT_HS_E_SIZE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "HS-ext HSREQ found but invalid size: " << bytelen << " (expected: " << SRT_HS_E_SIZE << ")"); return false; // don't interpret } int rescmd = processSrtMsg_HSREQ(begin + 1, bytelen, hspkt.m_iTimeStamp, HS_VERSION_SRT1); // Interpreted? Then it should be responded with SRT_CMD_HSRSP. if (rescmd != SRT_CMD_HSRSP) { // m_RejectReason already set LOGC(cnlog.Error, log << "interpretSrtHandshake: process HSREQ returned unexpected value " << rescmd); return false; } handshakeDone(); // updateAfterSrtHandshake -> moved to postConnect and processRendezvous } else if (cmd == SRT_CMD_HSRSP) { hsreq_type_cmd = cmd; // Set is the size as it should, then give it for interpretation for // the proper function. if (blocklen < SRT_HS_E_SIZE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "HS-ext HSRSP found but invalid size: " << bytelen << " (expected: " << SRT_HS_E_SIZE << ")"); return false; // don't interpret } int rescmd = processSrtMsg_HSRSP(begin + 1, bytelen, hspkt.m_iTimeStamp, HS_VERSION_SRT1); // Interpreted? Then it should be responded with SRT_CMD_NONE. // (nothing to be responded for HSRSP, unless there was some kinda problem) if (rescmd != SRT_CMD_NONE) { // Just formally; the current code doesn't seem to return anything else // (unless it's already set) if (m_RejectReason == SRT_REJ_UNKNOWN) m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd); return false; } handshakeDone(); // updateAfterSrtHandshake -> moved to postConnect and processRendezvous } else if (cmd == SRT_CMD_NONE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Warn, log << "interpretSrtHandshake: no HSREQ/HSRSP block found in the handshake msg!"); // This means that there can be no more processing done by FindExtensionBlock(). // And we haven't found what we need - otherwise one of the above cases would pass // and lead to exit this loop immediately. return false; } else { // Any other kind of message extracted. Search on. length -= (next - begin); begin = next; if (begin) continue; } break; } } HLOGC(cnlog.Debug, log << "interpretSrtHandshake: HSREQ done, checking KMREQ"); // Now check the encrypted bool encrypted = false; if (IsSet(ext_flags, CHandShake::HS_EXT_KMREQ)) { HLOGC(cnlog.Debug, log << "interpretSrtHandshake: extracting KMREQ/RSP type extension"); #ifdef SRT_ENABLE_ENCRYPTION if (!m_pCryptoControl->hasPassphrase()) { if (m_config.bEnforcedEnc) { m_RejectReason = SRT_REJ_UNSECURE; LOGC(cnlog.Error, log << "HS KMREQ: Peer declares encryption, but agent does not - rejecting per enforced encryption"); return false; } LOGC(cnlog.Warn, log << "HS KMREQ: Peer declares encryption, but agent does not - still allowing connection."); // Still allow for connection, and allow Agent to send unencrypted stream to the peer. // Also normally allow the key to be processed; worst case it will send the failure response. } uint32_t *begin = p; uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // This is one shot loop, unless REPEATED by 'continue'. { int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); HLOGC(cnlog.Debug, log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_KMREQ) { if (!out_data || !pw_len) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << "IPE: HS/KMREQ extracted without passing target buffer!"); return false; } int res = m_pCryptoControl->processSrtMsg_KMREQ(begin + 1, bytelen, HS_VERSION_SRT1, (out_data), (*pw_len)); if (res != SRT_CMD_KMRSP) { m_RejectReason = SRT_REJ_IPE; // Something went wrong. HLOGC(cnlog.Debug, log << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned " << res); return false; } if (*pw_len == 1) { // This means that there was an abnormal encryption situation occurred. // This is inacceptable in case of strict encryption. if (m_config.bEnforcedEnc) { if (m_pCryptoControl->m_RcvKmState == SRT_KM_S_BADSECRET) { m_RejectReason = SRT_REJ_BADSECRET; } else { m_RejectReason = SRT_REJ_UNSECURE; } LOGC(cnlog.Error, log << "interpretSrtHandshake: KMREQ result abnornal - rejecting per enforced encryption"); return false; } } encrypted = true; } else if (cmd == SRT_CMD_KMRSP) { int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, HS_VERSION_SRT1); if (m_config.bEnforcedEnc && res == -1) { m_RejectReason = SRT_REJ_UNSECURE; LOGC(cnlog.Error, log << "KMRSP failed - rejecting connection as per enforced encryption."); return false; } encrypted = true; } else if (cmd == SRT_CMD_NONE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "HS KMREQ expected - none found!"); return false; } else { HLOGC(cnlog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); if (NextExtensionBlock((begin), next, (length))) continue; } break; } #else // When encryption is not enabled at compile time, behave as if encryption wasn't set, // so accordingly to StrictEncryption flag. if (m_config.bEnforcedEnc) { m_RejectReason = SRT_REJ_UNSECURE; LOGC(cnlog.Error, log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - rejecting " "per enforced encryption"); return false; } LOGC(cnlog.Warn, log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - still allowing " "connection."); encrypted = true; #endif } bool have_congctl = false; bool have_filter = false; string agsm = m_config.sCongestion.str(); if (agsm == "") { agsm = "live"; m_config.sCongestion.set("live", 4); } bool have_group SRT_ATR_UNUSED = false; if (IsSet(ext_flags, CHandShake::HS_EXT_CONFIG)) { HLOGC(cnlog.Debug, log << "interpretSrtHandshake: extracting various CONFIG extensions"); uint32_t *begin = p; uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // This is one shot loop, unless REPEATED by 'continue'. { int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); HLOGC(cnlog.Debug, log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); const size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_SID) { if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH) { LOGC(cnlog.Error, log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // Copied through a cleared array. This is because the length is aligned to 4 // where the padding is filled by zero bytes. For the case when the string is // exactly of a 4-divisible length, we make a big array with maximum allowed size // filled with zeros. Copying to this array should then copy either only the valid // characters of the string (if the lenght is divisible by 4), or the string with // padding zeros. In all these cases in the resulting array we should have all // subsequent characters of the string plus at least one '\0' at the end. This will // make it a perfect NUL-terminated string, to be used to initialize a string. char target[CSrtConfig::MAX_SID_LENGTH + 1]; memset((target), 0, CSrtConfig::MAX_SID_LENGTH + 1); memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen); m_config.sStreamName.set(target, strlen(target)); HLOGC(cnlog.Debug, log << "CONNECTOR'S REQUESTED SID [" << m_config.sStreamName.c_str() << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } else if (cmd == SRT_CMD_CONGESTION) { if (have_congctl) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "CONGCTL BLOCK REPEATED!"); return false; } if (!bytelen || bytelen > CSrtConfig::MAX_CONG_LENGTH) { LOGC(cnlog.Error, log << "interpretSrtHandshake: CONGESTION-control type length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_CONG_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // Declare that congctl has been received have_congctl = true; char target[CSrtConfig::MAX_CONG_LENGTH + 1]; memset((target), 0, CSrtConfig::MAX_CONG_LENGTH + 1); memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen); string sm = target; // As the congctl has been declared by the peer, // check if your congctl is compatible. // sm cannot be empty, but the agent's sm can be empty meaning live. if (sm != agsm) { m_RejectReason = SRT_REJ_CONGESTION; LOGC(cnlog.Error, log << "PEER'S CONGCTL '" << sm << "' does not match AGENT'S CONGCTL '" << agsm << "'"); return false; } HLOGC(cnlog.Debug, log << "CONNECTOR'S CONGCTL [" << sm << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } else if (cmd == SRT_CMD_FILTER) { if (have_filter) { m_RejectReason = SRT_REJ_FILTER; LOGC(cnlog.Error, log << "FILTER BLOCK REPEATED!"); return false; } if (!bytelen || bytelen > CSrtConfig::MAX_PFILTER_LENGTH) { LOGC(cnlog.Error, log << "interpretSrtHandshake: packet-filter type length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_PFILTER_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // Declare that filter has been received have_filter = true; char target[CSrtConfig::MAX_PFILTER_LENGTH + 1]; memset((target), 0, CSrtConfig::MAX_PFILTER_LENGTH + 1); memcpy((target), begin + 1, bytelen); string fltcfg = target; HLOGC(cnlog.Debug, log << "PEER'S FILTER CONFIG [" << fltcfg << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); if (!checkApplyFilterConfig(fltcfg)) { m_RejectReason = SRT_REJ_FILTER; LOGC(cnlog.Error, log << "PEER'S FILTER CONFIG [" << fltcfg << "] has been rejected"); return false; } } #if ENABLE_EXPERIMENTAL_BONDING else if ( cmd == SRT_CMD_GROUP ) { // Note that this will fire in both cases: // - When receiving HS request from the Initiator, which belongs to a group, and agent must // create the mirror group on his side (or join the existing one, if there's already // a mirror group for that group ID). // - When receiving HS response from the Responder, with its mirror group ID, so the agent // must put the group into his peer group data int32_t groupdata[GRPD_E_SIZE] = {}; if (bytelen < GRPD_MIN_SIZE * GRPD_FIELD_SIZE || bytelen % GRPD_FIELD_SIZE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "PEER'S GROUP wrong size: " << (bytelen/GRPD_FIELD_SIZE)); return false; } size_t groupdata_size = bytelen / GRPD_FIELD_SIZE; memcpy(groupdata, begin+1, bytelen); if (!interpretGroup(groupdata, groupdata_size, hsreq_type_cmd) ) { // m_RejectReason handled inside interpretGroup(). return false; } have_group = true; HLOGC(cnlog.Debug, log << "CONNECTOR'S PEER GROUP [" << groupdata[0] << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } #endif else if (cmd == SRT_CMD_NONE) { break; } else { // Found some block that is not interesting here. Skip this and get the next one. HLOGC(cnlog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); } if (!NextExtensionBlock((begin), next, (length))) break; } } // Post-checks // Check if peer declared encryption if (!encrypted && m_config.CryptoSecret.len > 0) { if (m_config.bEnforcedEnc) { m_RejectReason = SRT_REJ_UNSECURE; LOGC(cnlog.Error, log << "HS EXT: Agent declares encryption, but Peer does not - rejecting connection per " "enforced encryption."); return false; } LOGC(cnlog.Warn, log << "HS EXT: Agent declares encryption, but Peer does not (Agent can still receive unencrypted packets " "from Peer)."); // This is required so that the sender is still allowed to send data, when encryption is required, // just this will be for waste because the receiver won't decrypt them anyway. m_pCryptoControl->createFakeSndContext(); m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Because Peer did not send KMX, though Agent has pw m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Because Peer has no PW, as has sent no KMREQ. return true; } // If agent has set some nondefault congctl, then congctl is expected from the peer. if (agsm != "live" && !have_congctl) { m_RejectReason = SRT_REJ_CONGESTION; LOGC(cnlog.Error, log << "HS EXT: Agent uses '" << agsm << "' congctl, but peer DID NOT DECLARE congctl (assuming 'live')."); return false; } #if ENABLE_EXPERIMENTAL_BONDING // m_GroupOf and locking info: NULL check won't hurt here. If the group // was deleted in the meantime, it will be found out later anyway and result with error. if (m_SrtHsSide == HSD_INITIATOR && m_parent->m_GroupOf) { // XXX Later probably needs to check if this group REQUIRES the group // response. Currently this implements the bonding-category group, and this // always requires that the listener respond with the group id, otherwise // it probably DID NOT UNDERSTAND THE GROUP, so the connection should be rejected. if (!have_group) { m_RejectReason = SRT_REJ_GROUP; LOGC(cnlog.Error, log << "HS EXT: agent is a group member, but the listener did not respond with group ID. Rejecting."); return false; } } #endif // Ok, finished, for now. return true; } bool srt::CUDT::checkApplyFilterConfig(const std::string &confstr) { SrtFilterConfig cfg; if (!ParseFilterConfig(confstr, (cfg))) return false; // Now extract the type, if present, and // check if you have this type of corrector available. if (!PacketFilter::correctConfig(cfg)) return false; string thisconf = m_config.sPacketFilterConfig.str(); // Now parse your own string, if you have it. if (thisconf != "") { // - for rendezvous, both must be exactly the same (it's unspecified, which will be the first one) if (m_config.bRendezvous && thisconf != confstr) { return false; } SrtFilterConfig mycfg; if (!ParseFilterConfig(thisconf, (mycfg))) return false; // Check only if both have set a filter of the same type. if (mycfg.type != cfg.type) return false; // If so, then: // - for caller-listener configuration, accept the listener version. if (m_SrtHsSide == HSD_INITIATOR) { // This is a caller, this should apply all parameters received // from the listener, forcefully. for (map::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x) { mycfg.parameters[x->first] = x->second; } } else { if (!CheckFilterCompat((mycfg), cfg)) return false; } HLOGC(cnlog.Debug, log << "checkApplyFilterConfig: param: LOCAL: " << Printable(mycfg.parameters) << " FORGN: " << Printable(cfg.parameters)); ostringstream myos; myos << mycfg.type; for (map::iterator x = mycfg.parameters.begin(); x != mycfg.parameters.end(); ++x) { myos << "," << x->first << ":" << x->second; } m_config.sPacketFilterConfig.set(myos.str()); HLOGC(cnlog.Debug, log << "checkApplyFilterConfig: Effective config: " << thisconf); } else { // Take the foreign configuration as a good deal. HLOGC(cnlog.Debug, log << "checkApplyFilterConfig: Good deal config: " << thisconf); m_config.sPacketFilterConfig.set(confstr); } size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - cfg.extra_size; if (m_config.zExpPayloadSize > efc_max_payload_size) { LOGC(cnlog.Warn, log << "Due to filter-required extra " << cfg.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to " << efc_max_payload_size << " bytes"); m_config.zExpPayloadSize = efc_max_payload_size; } return true; } #if ENABLE_EXPERIMENTAL_BONDING bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_ATR_UNUSED, int hsreq_type_cmd SRT_ATR_UNUSED) { // `data_size` isn't checked because we believe it's checked earlier. // Also this code doesn't predict to get any other format than the official one, // so there are only data in two fields. Passing this argument is only left // for consistency and possibly changes in future. // We are granted these two fields do exist SRTSOCKET grpid = groupdata[GRPD_GROUPID]; uint32_t gd = groupdata[GRPD_GROUPDATA]; SRT_GROUP_TYPE gtp = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd)); int link_weight = SrtHSRequest::HS_GROUP_WEIGHT::unwrap(gd); uint32_t link_flags = SrtHSRequest::HS_GROUP_FLAGS::unwrap(gd); if (m_config.iGroupConnect == 0) { m_RejectReason = SRT_REJ_GROUP; LOGC(cnlog.Error, log << "HS/GROUP: this socket is not allowed for group connect."); return false; } // This is called when the group type has come in the handshake is invalid. if (gtp >= SRT_GTYPE_E_END) { m_RejectReason = SRT_REJ_GROUP; LOGC(cnlog.Error, log << "HS/GROUP: incorrect group type value " << gtp << " (max is " << SRT_GTYPE_E_END << ")"); return false; } if ((grpid & SRTGROUP_MASK) == 0) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "HS/GROUP: socket ID passed as a group ID is not a group ID"); return false; } // We have the group, now take appropriate action. // The redundancy group requires to make a mirror group // on this side, and the newly created socket should // be made belong to it. #if ENABLE_HEAVY_LOGGING static const char* hs_side_name[] = {"draw", "initiator", "responder"}; HLOGC(cnlog.Debug, log << "interpretGroup: STATE: HsSide=" << hs_side_name[m_SrtHsSide] << " HS MSG: " << MessageTypeStr(UMSG_EXT, hsreq_type_cmd) << " $" << grpid << " type=" << gtp << " weight=" << link_weight << " flags=0x" << std::hex << link_flags); #endif // XXX Here are two separate possibilities: // // 1. This is a HS request and this is a newly created socket not yet part of any group. // 2. This is a HS response and the group is the mirror group for the group to which the agent belongs; we need to pin the mirror group as peer group // // These two situations can be only distinguished by the HS side. if (m_SrtHsSide == HSD_DRAW) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << "IPE: interpretGroup: The HS side should have been already decided; it's still DRAW. Grouping rejected."); return false; } ScopedLock guard_group_existence (s_UDTUnited.m_GlobControlLock); if (m_SrtHsSide == HSD_INITIATOR) { // This is a connection initiator that has requested the peer to make a // mirror group and join it, then respond its mirror group id. The // `grpid` variable contains this group ID; map this as your peer // group. If your group already has a peer group set, check if this is // the same id, otherwise the connection should be rejected. // So, first check the group of the current socket and see if a peer is set. CUDTGroup* pg = m_parent->m_GroupOf; if (!pg) { // This means that the responder has responded with a group membership, // but the initiator did not request any group membership presence. // Currently impossible situation. m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << "IPE: HS/RSP: group membership responded, while not requested."); return false; } // Group existence is guarded, so we can now lock the group as well. ScopedLock gl(*pg->exp_groupLock()); // Now we know the group exists, but it might still be closed if (pg->closing()) { LOGC(cnlog.Error, log << "HS/RSP: group was closed in the process, can't continue connecting"); m_RejectReason = SRT_REJ_IPE; return false; } SRTSOCKET peer = pg->peerid(); if (peer == -1) { // This is the first connection within this group, so this group // has just been informed about the peer membership. Accept it. pg->set_peerid(grpid); HLOGC(cnlog.Debug, log << "HS/RSP: group $" << pg->id() << " -> peer $" << pg->peerid() << ", copying characteristic data"); // The call to syncWithSocket is copying // some interesting data from the first connected // socket. This should be only done for the first successful connection. pg->syncWithSocket(*this, HSD_INITIATOR); } // Otherwise the peer id must be the same as existing, otherwise // this group is considered already bound to another peer group. // (Note that the peer group is peer-specific, and peer id numbers // may repeat among sockets connected to groups established on // different peers). else if (peer != grpid) { LOGC(cnlog.Error, log << "IPE: HS/RSP: group membership responded for peer $" << grpid << " but the current socket's group $" << pg->id() << " has already a peer $" << peer); m_RejectReason = SRT_REJ_GROUP; return false; } else { HLOGC(cnlog.Debug, log << "HS/RSP: group $" << pg->id() << " ALREADY MAPPED to peer mirror $" << pg->peerid()); } } else { // This is a connection responder that has been requested to make a // mirror group and join it. Later on, the HS response will be sent // and its group ID will be added to the HS extensions as mirror group // ID to the peer. SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags); if (!lgid) return true; // already done if (lgid == -1) { // NOTE: This error currently isn't reported by makeMePeerOf, // so this is left to handle a possible error introduced in future. m_RejectReason = SRT_REJ_GROUP; return false; // error occurred } if (!m_parent->m_GroupOf) { // Strange, we just added it... m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << "IPE: socket not in group after adding to it"); return false; } groups::SocketData* f = m_parent->m_GroupMemberData; f->weight = link_weight; f->agent = m_parent->m_SelfAddr; f->peer = m_PeerAddr; } m_parent->m_GroupOf->debugGroup(); // That's all. For specific things concerning group // types, this will be later. return true; } #endif #if ENABLE_EXPERIMENTAL_BONDING // NOTE: This function is called only in one place and it's done // exclusively on the listener side (HSD_RESPONDER, HSv5+). // [[using locked(s_UDTUnited.m_GlobControlLock)]] SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t link_flags) { // Note: This function will lock pg->m_GroupLock! CUDTSocket* s = m_parent; // Note that the socket being worked out here is about to be returned // from `srt_accept` call, and until this moment it will be inaccessible // for any other thread. It is then assumed that no other thread is accessing // it right now so there's no need to lock s->m_ControlLock. // Check if there exists a group that this one is a peer of. CUDTGroup* gp = s_UDTUnited.findPeerGroup_LOCKED(peergroup); bool was_empty = true; if (gp) { if (gp->type() != gtp) { LOGC(gmlog.Error, log << "HS: GROUP TYPE COLLISION: peer group=$" << peergroup << " type " << gtp << " agent group=$" << gp->id() << " type" << gp->type()); return -1; } HLOGC(gmlog.Debug, log << "makeMePeerOf: group for peer=$" << peergroup << " found: $" << gp->id()); if (!gp->groupEmpty()) was_empty = false; } else { try { gp = &newGroup(gtp); } catch (...) { // Expected exceptions are only those referring to system resources return -1; } if (!gp->applyFlags(link_flags, m_SrtHsSide)) { // Wrong settings. Must reject. Delete group. s_UDTUnited.deleteGroup_LOCKED(gp); return -1; } gp->set_peerid(peergroup); gp->deriveSettings(this); // This can only happen on a listener (it's only called on a site that is // HSD_RESPONDER), so it was a response for a groupwise connection. // Therefore such a group shall always be considered opened. gp->setOpen(); HLOGC(gmlog.Debug, log << "makeMePeerOf: no group has peer=$" << peergroup << " - creating new mirror group $" << gp->id()); } { ScopedLock glock (*gp->exp_groupLock()); if (gp->closing()) { HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: group $" << gp->id() << " is being closed, can't process"); } if (was_empty) { gp->syncWithSocket(s->core(), HSD_RESPONDER); } } // Setting non-blocking reading for group socket. s->core().m_config.bSynRecving = false; s->core().m_config.bSynSending = false; // Copy of addSocketToGroup. No idea how many parts could be common, not much. // Check if the socket already is in the group groups::SocketData* f; if (gp->contains(m_SocketID, (f))) { // XXX This is internal error. Report it, but continue // (A newly created socket from acceptAndRespond should not have any group membership yet) LOGC(gmlog.Error, log << "IPE (non-fatal): the socket is in the group, but has no clue about it!"); s->m_GroupOf = gp; s->m_GroupMemberData = f; return 0; } s->m_GroupMemberData = gp->add(groups::prepareSocketData(s)); s->m_GroupOf = gp; // Record the remote address in the group data. return gp->id(); } void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) { ScopedLock gl (*gp->exp_groupLock()); // We have blocked here the process of connecting a new // socket and adding anything new to the group, so no such // thing may happen in the meantime. steady_clock::time_point start_time, peer_start_time; start_time = m_stats.tsStartTime; peer_start_time = m_tsRcvPeerStartTime; if (!gp->applyGroupTime((start_time), (peer_start_time))) { HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << " DERIVED: ST=" << FormatTime(m_stats.tsStartTime) << " -> " << FormatTime(start_time) << " PST=" << FormatTime(m_tsRcvPeerStartTime) << " -> " << FormatTime(peer_start_time)); m_stats.tsStartTime = start_time; m_tsRcvPeerStartTime = peer_start_time; } else { // This was the first connected socket and it defined start time. HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << " DEFINED: ST=" << FormatTime(m_stats.tsStartTime) << " PST=" << FormatTime(m_tsRcvPeerStartTime)); } steady_clock::time_point rcv_buffer_time_base; bool rcv_buffer_wrap_period = false; steady_clock::duration rcv_buffer_udrift(0); if (m_bTsbPd && gp->getBufferTimeBase(this, (rcv_buffer_time_base), (rcv_buffer_wrap_period), (rcv_buffer_udrift))) { // We have at least one socket in the group, each socket should have // the value of the timebase set exactly THE SAME. // In case when we have the following situation: // - the existing link is before [LAST30] (so wrap period is off) // - the new link gets the timestamp from [LAST30] range // --> this will be recognized as entering the wrap period, next // timebase will get added a segment to this value // // The only dangerous situations could be when one link gets // timestamps from the [FOLLOWING30] and the other in [FIRST30], // but between them there's a 30s distance, considered large enough // time to not fill a network window. enterCS(m_RecvLock); m_pRcvBuffer->applyGroupTime(rcv_buffer_time_base, rcv_buffer_wrap_period, m_iTsbPdDelay_ms * 1000, rcv_buffer_udrift); leaveCS(m_RecvLock); HLOGF(gmlog.Debug, "AFTER HS: Set Rcv TsbPd mode: delay=%u.%03us GROUP TIME BASE: %s%s", m_iTsbPdDelay_ms/1000, m_iTsbPdDelay_ms%1000, FormatTime(rcv_buffer_time_base).c_str(), rcv_buffer_wrap_period ? " (WRAP PERIOD)" : " (NOT WRAP PERIOD)"); } else { HLOGC(gmlog.Debug, log << "AFTER HS: (GROUP, but " << (m_bTsbPd ? "FIRST SOCKET is initialized normally)" : "no TSBPD set)")); updateSrtRcvSettings(); } // This function currently does nothing, just left for consistency // with updateAfterSrtHandshake(). updateSrtSndSettings(); if (gp->synconmsgno()) { HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << ": NOT synchronizing sequence numbers."); } else { // These are the values that are normally set initially by setters. int32_t snd_isn = m_iSndLastAck, rcv_isn = m_iRcvLastAck; if (!gp->applyGroupSequences(m_SocketID, (snd_isn), (rcv_isn))) { HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << " DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn << " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck) << ") SND=%" << m_iSndLastAck << " -> %" << snd_isn << " (shift by " << CSeqNo::seqcmp(snd_isn, m_iSndLastAck) << ")"); setInitialRcvSeq(rcv_isn); setInitialSndSeq(snd_isn); } else { HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << " DEFINED ISN: RCV=%" << m_iRcvLastAck << " SND=%" << m_iSndLastAck); } } } #endif void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) { ScopedLock cg (m_ConnectionLock); HLOGC(aclog.Debug, log << CONID() << "startConnect: -> " << serv_addr.str() << (m_config.bSynRecving ? " (SYNCHRONOUS)" : " (ASYNCHRONOUS)") << "..."); 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 m_PeerAddr = serv_addr; // register this socket in the rendezvous queue // RendezevousQueue is used to temporarily store incoming handshake, non-rendezvous connections also require this // function steady_clock::duration ttl = m_config.tdConnTimeOut; if (m_config.bRendezvous) ttl *= 10; const steady_clock::time_point ttl_time = steady_clock::now() + ttl; m_pRcvQueue->registerConnector(m_SocketID, this, serv_addr, ttl_time); // The m_iType is used in the INDUCTION for nothing. This value is only regarded // in CONCLUSION handshake, however this must be created after the handshake version // is already known. UDT_DGRAM is the value that was the only valid in the old SRT // with HSv4 (it supported only live transmission), for HSv5 it will be changed to // handle handshake extension flags. m_ConnReq.m_iType = UDT_DGRAM; // This is my current configuration if (m_config.bRendezvous) { // For rendezvous, use version 5 in the waveahand and the cookie. // In case when you get the version 4 waveahand, simply switch to // the legacy HSv4 rendezvous and this time send version 4 CONCLUSION. // The HSv4 client simply won't check the version nor the cookie and it // will be sending its waveahands with version 4. Only when the party // has sent version 5 waveahand should the agent continue with HSv5 // rendezvous. m_ConnReq.m_iVersion = HS_VERSION_SRT1; // m_ConnReq.m_iVersion = HS_VERSION_UDT4; // <--- Change in order to do regression test. m_ConnReq.m_iReqType = URQ_WAVEAHAND; m_ConnReq.m_iCookie = bake(serv_addr); // This will be also passed to a HSv4 rendezvous, but fortunately the old // SRT didn't read this field from URQ_WAVEAHAND message, only URQ_CONCLUSION. m_ConnReq.m_iType = SrtHSRequest::wrapFlags(false /* no MAGIC here */, m_config.iSndCryptoKeyLen); bool whether SRT_ATR_UNUSED = m_config.iSndCryptoKeyLen != 0; HLOGC(aclog.Debug, log << "startConnect (rnd): " << (whether ? "" : "NOT ") << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); m_RdvState = CHandShake::RDV_WAVING; m_SrtHsSide = HSD_DRAW; // initially not resolved. } else { // For caller-listener configuration, set the version 4 for INDUCTION // due to a serious problem in UDT code being also in the older SRT versions: // the listener peer simply sents the EXACT COPY of the caller's induction // handshake, except the cookie, which means that when the caller sents version 5, // the listener will respond with version 5, which is a false information. Therefore // HSv5 clients MUST send HS_VERSION_UDT4 from the caller, regardless of currently // supported handshake version. // // The HSv5 listener should only respond with INDUCTION with m_iVersion == HS_VERSION_SRT1. m_ConnReq.m_iVersion = HS_VERSION_UDT4; m_ConnReq.m_iReqType = URQ_INDUCTION; m_ConnReq.m_iCookie = 0; m_RdvState = CHandShake::RDV_INVALID; } m_ConnReq.m_iMSS = m_config.iMSS; // Defined as the size of the receiver buffer in packets, unless // SRTO_FC has been set to a less value. m_ConnReq.m_iFlightFlagSize = m_config.flightCapacity(); m_ConnReq.m_iID = m_SocketID; CIPAddress::ntop(serv_addr, (m_ConnReq.m_piPeerIP)); if (forced_isn == SRT_SEQNO_NONE) { forced_isn = generateISN(); HLOGC(aclog.Debug, log << "startConnect: ISN generated = " << forced_isn); } else { HLOGC(aclog.Debug, log << "startConnect: ISN forced = " << forced_isn); } m_iISN = m_ConnReq.m_iISN = forced_isn; setInitialSndSeq(m_iISN); m_SndLastAck2Time = steady_clock::now(); // Inform the server my configurations. CPacket reqpkt; reqpkt.setControl(UMSG_HANDSHAKE); reqpkt.allocate(m_iMaxSRTPayloadSize); // XXX NOTE: Now the memory for the payload part is allocated automatically, // and such allocated memory is also automatically deallocated in the // destructor. If you use CPacket::allocate, remember that you must not: // - delete this memory // - assign to m_pcData. // If you use only manual assignment to m_pCData, this is then manual // allocation and so it won't be deallocated in the destructor. // // (Desired would be to disallow modification of m_pcData outside the // control of methods.) // ID = 0, connection request reqpkt.m_iID = 0; size_t hs_size = m_iMaxSRTPayloadSize; m_ConnReq.store_to((reqpkt.m_pcData), (hs_size)); // Note that CPacket::allocate() sets also the size // to the size of the allocated buffer, which not // necessarily is to be the size of the data. reqpkt.setLength(hs_size); steady_clock::time_point now = steady_clock::now(); setPacketTS(reqpkt, now); HLOGC(cnlog.Debug, log << CONID() << "CUDT::startConnect: REQ-TIME set HIGH (TimeStamp: " << reqpkt.m_iTimeStamp << "). SENDING HS: " << m_ConnReq.show()); /* * Race condition if non-block connect response thread scheduled before we set m_bConnecting to true? * Connect response will be ignored and connecting will wait until timeout. * Maybe m_ConnectionLock handling problem? Not used in CUDT::connect(const CPacket& response) */ m_tsLastReqTime = now; m_bConnecting = true; m_pSndQueue->sendto(serv_addr, reqpkt); // /// //// ---> CONTINUE TO: .CUDT::processConnectRequest() /// (Take the part under condition: hs.m_iReqType == URQ_INDUCTION) //// <--- RETURN WHEN: m_pSndQueue->sendto() is called. //// .... SKIP UNTIL m_pRcvQueue->recvfrom() HERE.... //// (the first "sendto" will not be called due to being too early) /// // ////////////////////////////////////////////////////// // SYNCHRO BAR ////////////////////////////////////////////////////// if (!m_config.bSynRecving) { HLOGC(cnlog.Debug, log << CONID() << "startConnect: ASYNC MODE DETECTED. Deferring the process to RcvQ:worker"); return; } // Below this bar, rest of function maintains only and exclusively // the SYNCHRONOUS (blocking) connection process. // Wait for the negotiated configurations from the peer side. // This packet only prepares the storage where we will read the // next incoming packet. CPacket response; response.setControl(UMSG_HANDSHAKE); response.allocate(m_iMaxSRTPayloadSize); CUDTException e; EConnectStatus cst = CONN_CONTINUE; while (!m_bClosing) { const steady_clock::duration tdiff = steady_clock::now() - m_tsLastReqTime.load(); // avoid sending too many requests, at most 1 request per 250ms // SHORT VERSION: // The immediate first run of this loop WILL SKIP THIS PART, so // the processing really begins AFTER THIS CONDITION. // // Note that some procedures inside may set m_tsLastReqTime to 0, // which will result of this condition to trigger immediately in // the next iteration. if (count_milliseconds(tdiff) > 250) { HLOGC(cnlog.Debug, log << "startConnect: LOOP: time to send (" << count_milliseconds(tdiff) << " > 250 ms). size=" << reqpkt.getLength()); if (m_config.bRendezvous) reqpkt.m_iID = m_ConnRes.m_iID; now = steady_clock::now(); #if ENABLE_HEAVY_LOGGING { CHandShake debughs; debughs.load_from(reqpkt.m_pcData, reqpkt.getLength()); HLOGC(cnlog.Debug, log << CONID() << "startConnect: REQ-TIME HIGH." << " cont/sending HS to peer: " << debughs.show()); } #endif m_tsLastReqTime = now; setPacketTS(reqpkt, now); m_pSndQueue->sendto(serv_addr, reqpkt); } else { HLOGC(cnlog.Debug, log << "startConnect: LOOP: too early to send - " << count_milliseconds(tdiff) << " < 250ms"); } cst = CONN_CONTINUE; response.setLength(m_iMaxSRTPayloadSize); if (m_pRcvQueue->recvfrom(m_SocketID, (response)) > 0) { HLOGC(cnlog.Debug, log << CONID() << "startConnect: got response for connect request"); cst = processConnectResponse(response, &e); HLOGC(cnlog.Debug, log << CONID() << "startConnect: response processing result: " << ConnectStatusStr(cst)); // Expected is that: // - the peer responded with URQ_INDUCTION + cookie. This above function // should check that and craft the URQ_CONCLUSION handshake, in which // case this function returns CONN_CONTINUE. As an extra action taken // for that case, we set the SECURING mode if encryption requested, // and serialize again the handshake, possibly together with HS extension // blocks, if HSv5 peer responded. The serialized handshake will be then // sent again, as the loop is repeated. // - the peer responded with URQ_CONCLUSION. This handshake was accepted // as a connection, and for >= HSv5 the HS extension blocks have been // also read and interpreted. In this case this function returns: // - CONN_ACCEPT, if everything was correct - break this loop and return normally // - CONN_REJECT in case of any problems with the delivered handshake // (incorrect data or data conflict) - throw error exception // - the peer responded with any of URQ_ERROR_*. - throw error exception // // The error exception should make the API connect() function fail, if blocking // or mark the failure for that socket in epoll, if non-blocking. if (cst == CONN_RENDEZVOUS) { // When this function returned CONN_RENDEZVOUS, this requires // very special processing for the Rendezvous-v5 algorithm. This MAY // involve also preparing a new handshake form, also interpreting the // SRT handshake extension and crafting SRT handshake extension for the // peer, which should be next sent. When this function returns CONN_CONTINUE, // it means that it has done all that was required, however none of the below // things has to be done (this function will do it by itself if needed). // Otherwise the handshake rolling can be interrupted and considered complete. cst = processRendezvous(&response, serv_addr, RST_OK, (reqpkt)); if (cst == CONN_CONTINUE) continue; break; } if (cst == CONN_REJECT) sendCtrl(UMSG_SHUTDOWN); if (cst != CONN_CONTINUE && cst != CONN_CONFUSED) break; // --> OUTSIDE-LOOP // IMPORTANT // [[using assert(m_pCryptoControl != nullptr)]]; // new request/response should be sent out immediately on receving a response HLOGC(cnlog.Debug, log << "startConnect: SYNC CONNECTION STATUS:" << ConnectStatusStr(cst) << ", REQ-TIME: LOW."); m_tsLastReqTime = steady_clock::time_point(); // Now serialize the handshake again to the existing buffer so that it's // then sent later in this loop. // First, set the size back to the original size, m_iMaxSRTPayloadSize because // this is the size of the originally allocated space. It might have been // shrunk by serializing the INDUCTION handshake (which was required before // sending this packet to the output queue) and therefore be too // small to store the CONCLUSION handshake (with HSv5 extensions). reqpkt.setLength(m_iMaxSRTPayloadSize); HLOGC(cnlog.Debug, log << "startConnect: creating HS CONCLUSION: buffer size=" << reqpkt.getLength()); // NOTE: BUGFIX: SERIALIZE AGAIN. // The original UDT code didn't do it, so it was theoretically // turned into conclusion, but was sending still the original // induction handshake challenge message. It was working only // thanks to that simultaneously there were being sent handshake // messages from a separate thread (CSndQueue::worker) from // RendezvousQueue, this time serialized properly, which caused // that with blocking mode there was a kinda initial "drunk // passenger with taxi driver talk" until the RendezvousQueue sends // (when "the time comes") the right CONCLUSION handshake // challenge message. // // Now that this is fixed, the handshake messages from RendezvousQueue // are sent only when there is a rendezvous mode or non-blocking mode. if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (reqpkt), (m_ConnReq))) { LOGC(cnlog.Warn, log << "createSrtHandshake failed - REJECTING."); cst = CONN_REJECT; break; } // These last 2 parameters designate the buffer, which is in use only for SRT_CMD_KMRSP. // If m_ConnReq.m_iVersion == HS_VERSION_UDT4, this function will do nothing, // except just serializing the UDT handshake. // The trick is that the HS challenge is with version HS_VERSION_UDT4, but the // listener should respond with HS_VERSION_SRT1, if it is HSv5 capable. } HLOGC(cnlog.Debug, log << "startConnect: timeout from Q:recvfrom, looping again; cst=" << ConnectStatusStr(cst)); #if ENABLE_HEAVY_LOGGING // Non-fatal assertion if (cst == CONN_REJECT) // Might be returned by processRendezvous { LOGC(cnlog.Error, log << "startConnect: IPE: cst=REJECT NOT EXPECTED HERE, the loop should've been interrupted!"); break; } #endif if (steady_clock::now() > ttl_time) { // timeout e = CUDTException(MJ_SETUP, MN_TIMEOUT, 0); m_RejectReason = SRT_REJ_TIMEOUT; HLOGC(cnlog.Debug, log << "startConnect: TTL time " << FormatTime(ttl_time) << " exceeded, TIMEOUT."); break; } } // <--- OUTSIDE-LOOP // Here will fall the break when not CONN_CONTINUE. // CONN_RENDEZVOUS is handled by processRendezvous. // CONN_ACCEPT will skip this and pass on. if (cst == CONN_REJECT) { e = CUDTException(MJ_SETUP, MN_REJECTED, 0); } if (e.getErrorCode() == 0) { if (m_bClosing) // if the socket is closed before connection... e = CUDTException(MJ_SETUP, MN_CLOSED, 0); else if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) // connection request rejected { m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType); e = CUDTException(MJ_SETUP, MN_REJECTED, 0); } else if ((!m_config.bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // secuity check e = CUDTException(MJ_SETUP, MN_SECURITY, 0); } if (e.getErrorCode() != 0) { m_bConnecting = false; // The process is to be abnormally terminated, remove the connector // now because most likely no other processing part has done anything with it. m_pRcvQueue->removeConnector(m_SocketID); throw e; } HLOGC(cnlog.Debug, log << CONID() << "startConnect: handshake exchange succeeded."); // Parameters at the end. HLOGC(cnlog.Debug, log << "startConnect: END. Parameters:" " mss=" << m_config.iMSS << " max-cwnd-size=" << m_CongCtl->cgWindowMaxSize() << " cwnd-size=" << m_CongCtl->cgWindowSize() << " rtt=" << m_iSRTT << " bw=" << m_iBandwidth); } // Asynchronous connection EConnectStatus srt::CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NOEXCEPT { EConnectStatus cst = CONN_CONTINUE; CUDTException e; ScopedLock cg(m_ConnectionLock); HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectResponse: got response for connect request, processing"); cst = processConnectResponse(pkt, &e); HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectResponse: response processing result: " << ConnectStatusStr(cst) << "; REQ-TIME LOW to enforce immediate response"); m_tsLastReqTime = steady_clock::time_point(); return cst; } bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, EConnectStatus cst, const CPacket* pResponse /*[[nullable]]*/, const sockaddr_any& serv_addr) { // IMPORTANT! // This function is called, still asynchronously, but in the order // of call just after the call to the above processAsyncConnectResponse. // This should have got the original value returned from // processConnectResponse through processAsyncConnectResponse. CPacket request; request.setControl(UMSG_HANDSHAKE); request.allocate(m_iMaxSRTPayloadSize); const steady_clock::time_point now = steady_clock::now(); setPacketTS(request, now); HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: REQ-TIME: HIGH. Should prevent too quick responses."); m_tsLastReqTime = now; // ID = 0, connection request request.m_iID = !m_config.bRendezvous ? 0 : m_ConnRes.m_iID; bool status = true; ScopedLock cg(m_ConnectionLock); if (!m_bOpened) // Check the socket has not been closed before already. return false; if (cst == CONN_RENDEZVOUS) { HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: passing to processRendezvous"); cst = processRendezvous(pResponse, serv_addr, rst, (request)); if (cst == CONN_ACCEPT) { HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. " "Done."); return true; } if (cst != CONN_CONTINUE) { // processRendezvous already set the reject reason LOGC(cnlog.Warn, log << "processAsyncConnectRequest: REJECT reported from processRendezvous, not processing further."); status = false; } } else if (cst == CONN_REJECT) { // m_RejectReason already set at worker_ProcessAddressedPacket. LOGC(cnlog.Warn, log << "processAsyncConnectRequest: REJECT reported from HS processing: " << srt_rejectreason_str(m_RejectReason) << " - not processing further"); // m_tsLastReqTime = steady_clock::time_point(); XXX ? return false; } else { // (this procedure will be also run for HSv4 rendezvous) HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: serializing HS: buffer size=" << request.getLength()); if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (request), (m_ConnReq))) { // All 'false' returns from here are IPE-type, mostly "invalid argument" plus "all keys expired". LOGC(cnlog.Error, log << "IPE: processAsyncConnectRequest: createSrtHandshake failed, dismissing."); status = false; } else { HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType) << " to socket " << request.m_iID << " size=" << request.getLength()); } } if (!status) { return false; /* XXX Shouldn't it send a single response packet for the rejection? // Set the version to 0 as "handshake rejection" status and serialize it CHandShake zhs; size_t size = request.getLength(); zhs.store_to((request.m_pcData), (size)); request.setLength(size); */ } HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: setting REQ-TIME HIGH, SENDING HS:" << m_ConnReq.show()); m_tsLastReqTime = steady_clock::now(); m_pSndQueue->sendto(serv_addr, request); return status; } void srt::CUDT::cookieContest() { if (m_SrtHsSide != HSD_DRAW) return; LOGC(cnlog.Error, log << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie); // Here m_ConnReq.m_iCookie is a local cookie value sent in connection request to the peer. // m_ConnRes.m_iCookie is a cookie value sent by the peer in its connection request. if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0) { // Note that it's virtually impossible that Agent's cookie is not ready, this // shall be considered IPE. // Not all cookies are ready, don't start the contest. return; } // INITIATOR/RESPONDER role is resolved by COOKIE CONTEST. // // The cookie contest must be repeated every time because it // may change the state at some point. // // In SRT v1.4.3 and prior the below subtraction was performed in 32-bit arithmetic. // The result of subtraction can overflow 32-bits. // Example // m_ConnReq.m_iCookie = -1480577720; // m_ConnRes.m_iCookie = 811599203; // int64_t llBetterCookie = -1480577720 - 811599203 = -2292176923 (FFFF FFFF 7760 27E5); // int32_t iBetterCookie = 2002790373 (7760 27E5); // // Now 64-bit arithmetic is used to calculate the actual result of subtraction. // The 31-st bit is then checked to check if the resulting is negative in 32-bit aritmetics. // This way the old contest behavior is preserved, and potential compiler optimisations are avoided. const int64_t contest = int64_t(m_ConnReq.m_iCookie) - int64_t(m_ConnRes.m_iCookie); if ((contest & 0xFFFFFFFF) == 0) { // DRAW! The only way to continue would be to force the // cookies to be regenerated and to start over. But it's // not worth a shot - this is an extremely rare case. // This can simply do reject so that it can be started again. // Pretend then that the cookie contest wasn't done so that // it's done again. Cookies are baked every time anew, however // the successful initial contest remains valid no matter how // cookies will change. m_SrtHsSide = HSD_DRAW; return; } if (contest & 0x80000000) { m_SrtHsSide = HSD_RESPONDER; return; } m_SrtHsSide = HSD_INITIATOR; } // This function should complete the data for KMX needed for an out-of-band // handshake response. Possibilities are: // - There's no KMX (including first responder's handshake in rendezvous). This writes 0 to w_kmdatasize. // - The encryption status is failure. Respond with fail code and w_kmdatasize = 1. // - The last KMX was successful. Respond with the original kmdata and their size in w_kmdatasize. EConnectStatus srt::CUDT::craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatasize) { // If the last CONCLUSION message didn't contain the KMX extension, there's // no key recorded yet, so it can't be extracted. Mark this w_kmdatasize empty though. int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); if (IsSet(hs_flags, CHandShake::HS_EXT_KMREQ)) { // This is a periodic handshake update, so you need to extract the KM data from the // first message, provided that it is there. size_t msgsize = m_pCryptoControl->getKmMsg_size(0); if (msgsize == 0) { switch (m_pCryptoControl->m_RcvKmState) { // If the KMX process ended up with a failure, the KMX is not recorded. // In this case as the KMRSP answer the "failure status" should be crafted. case SRT_KM_S_NOSECRET: case SRT_KM_S_BADSECRET: { HLOGC(cnlog.Debug, log << "craftKmResponse: No KMX recorded, status = " << KmStateStr(m_pCryptoControl->m_RcvKmState) << ". Respond it."); // Just do the same thing as in CCryptoControl::processSrtMsg_KMREQ for that case, // that is, copy the NOSECRET code into KMX message. memcpy((aw_kmdata), &m_pCryptoControl->m_RcvKmState, sizeof(int32_t)); w_kmdatasize = 1; } break; // Treat as ACCEPT in general; might change to REJECT on enforced-encryption default: // Remaining values: // UNSECURED: should not fall here at all // SECURING: should not happen in HSv5 // SECURED: should have received the recorded KMX correctly (getKmMsg_size(0) > 0) { m_RejectReason = SRT_REJ_IPE; // Remaining situations: // - password only on this site: shouldn't be considered to be sent to a no-password site LOGC(cnlog.Error, log << "craftKmResponse: IPE: PERIODIC HS: NO KMREQ RECORDED KMSTATE: RCV=" << KmStateStr(m_pCryptoControl->m_RcvKmState) << " SND=" << KmStateStr(m_pCryptoControl->m_SndKmState)); return CONN_REJECT; } break; } } else { w_kmdatasize = msgsize / 4; if (msgsize > w_kmdatasize * 4) { // Sanity check LOGC(cnlog.Error, log << "IPE: KMX data not aligned to 4 bytes! size=" << msgsize); memset((aw_kmdata + (w_kmdatasize * 4)), 0, msgsize - (w_kmdatasize * 4)); ++w_kmdatasize; } HLOGC(cnlog.Debug, log << "craftKmResponse: getting KM DATA from the fore-recorded KMX from KMREQ, size=" << w_kmdatasize); memcpy((aw_kmdata), m_pCryptoControl->getKmMsg_data(0), msgsize); } } else { HLOGC(cnlog.Debug, log << "craftKmResponse: no KMX flag - not extracting KM data for KMRSP"); w_kmdatasize = 0; } return CONN_ACCEPT; } EConnectStatus srt::CUDT::processRendezvous( const CPacket* pResponse /*[[nullable]]*/, const sockaddr_any& serv_addr, EReadStatus rst, CPacket& w_reqpkt) { if (m_RdvState == CHandShake::RDV_CONNECTED) { HLOGC(cnlog.Debug, log << "processRendezvous: already in CONNECTED state."); return CONN_ACCEPT; } uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; cookieContest(); // We know that the other side was contacted and the other side has sent // the handshake message - we know then both cookies. If it's a draw, it's // a very rare case of creating identical cookies. if (m_SrtHsSide == HSD_DRAW) { m_RejectReason = SRT_REJ_RDVCOOKIE; LOGC(cnlog.Error, log << "COOKIE CONTEST UNRESOLVED: can't assign connection roles, please wait another minute."); return CONN_REJECT; } UDTRequestType rsp_type = URQ_FAILURE_TYPES; // just to track uninitialized errors // We can assume that the Handshake packet received here as 'response' // is already serialized in m_ConnRes. Check extra flags that are meaningful // for further processing here. int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); bool needs_extension = ext_flags != 0; // Initial value: received HS has extensions. bool needs_hsrsp; rendezvousSwitchState((rsp_type), (needs_extension), (needs_hsrsp)); if (rsp_type > URQ_FAILURE_TYPES) { m_RejectReason = RejectReasonForURQ(rsp_type); HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to switch-state response: " << RequestTypeStr(rsp_type)); return CONN_REJECT; } checkUpdateCryptoKeyLen("processRendezvous", m_ConnRes.m_iType); // We have three possibilities here as it comes to HSREQ extensions: // 1. The agent is loser in attention state, it sends EMPTY conclusion (without extensions) // 2. The agent is loser in initiated state, it interprets incoming HSREQ and creates HSRSP // 3. The agent is winner in attention or fine state, it sends HSREQ extension m_ConnReq.m_iReqType = rsp_type; m_ConnReq.m_extension = needs_extension; // This must be done before prepareConnectionObjects(). if (!applyResponseSettings()) { LOGC(cnlog.Error, log << "processRendezvous: rogue peer"); return CONN_REJECT; } // This must be done before interpreting and creating HSv5 extensions. if (!prepareConnectionObjects(m_ConnRes, m_SrtHsSide, 0)) { // m_RejectReason already handled HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in prepareConnectionObjects."); return CONN_REJECT; } // Case 2. if (needs_hsrsp) { // This means that we have received HSREQ extension with the handshake, so we need to interpret // it and craft the response. if (rst == RST_OK) { // We have JUST RECEIVED packet in this session (not that this is called as periodic update). // Sanity check m_tsLastReqTime = steady_clock::time_point(); if (!pResponse || pResponse->getLength() == size_t(-1)) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << "IPE: rst=RST_OK, but the packet has set -1 length - REJECTING (REQ-TIME: LOW)"); return CONN_REJECT; } if (!interpretSrtHandshake(m_ConnRes, *pResponse, kmdata, &kmdatasize)) { HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in interpretSrtHandshake REQ-TIME: LOW."); return CONN_REJECT; } updateAfterSrtHandshake(HS_VERSION_SRT1); // Pass on, inform about the shortened response-waiting period. HLOGC(cnlog.Debug, log << "processRendezvous: setting REQ-TIME: LOW. Forced to respond immediately."); } else { // This is a repeated handshake, so you can't use the incoming data to // prepare data for createSrtHandshake. They have to be extracted from inside. EConnectStatus conn = craftKmResponse((kmdata), (kmdatasize)); if (conn != CONN_ACCEPT) return conn; } // No matter the value of needs_extension, the extension is always needed // when HSREQ was interpreted (to store HSRSP extension). m_ConnReq.m_extension = true; HLOGC(cnlog.Debug, log << "processRendezvous: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize); w_reqpkt.setLength(m_iMaxSRTPayloadSize); if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (w_reqpkt), (m_ConnReq))) { HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in createSrtHandshake. REQ-TIME: LOW"); m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } // This means that it has received URQ_CONCLUSION with HSREQ, agent is then in RDV_FINE // state, it sends here URQ_CONCLUSION with HSREQ/KMREQ extensions and it awaits URQ_AGREEMENT. return CONN_CONTINUE; } // Special case: if URQ_AGREEMENT is to be sent, when this side is INITIATOR, // then it must have received HSRSP, so it must interpret it. Otherwise it would // end up with URQ_DONE, which means that it is the other side to interpret HSRSP. if (m_SrtHsSide == HSD_INITIATOR && m_ConnReq.m_iReqType == URQ_AGREEMENT) { // The same is done in CUDT::postConnect(), however this section will // not be done in case of rendezvous. The section in postConnect() is // predicted to run only in regular CALLER handling. if (rst != RST_OK || !pResponse || pResponse->getLength() == size_t(-1)) { // Actually the -1 length would be an IPE, but it's likely that this was reported already. HLOGC( cnlog.Debug, log << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on exising data)"); } else { HLOGC(cnlog.Debug, log << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension"); if (!interpretSrtHandshake(m_ConnRes, *pResponse, 0, 0)) { // m_RejectReason is already set, so set the reqtype accordingly m_ConnReq.m_iReqType = URQFailure(m_RejectReason); } } // This should be false, make a kinda assert here. if (needs_extension) { LOGC(cnlog.Fatal, log << "IPE: INITIATOR responding AGREEMENT should declare no extensions to HS"); m_ConnReq.m_extension = false; } updateAfterSrtHandshake(HS_VERSION_SRT1); } HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: COOKIES Agent/Peer: " << m_ConnReq.m_iCookie << "/" << m_ConnRes.m_iCookie << " HSD:" << (m_SrtHsSide == HSD_INITIATOR ? "initiator" : "responder") << " STATE:" << CHandShake::RdvStateStr(m_RdvState) << " ..."); if (rsp_type == URQ_DONE) { HLOGC(cnlog.Debug, log << "... WON'T SEND any response, both sides considered connected"); } else { HLOGC(cnlog.Debug, log << "... WILL SEND " << RequestTypeStr(rsp_type) << " " << (m_ConnReq.m_extension ? "with" : "without") << " SRT HS extensions"); } // This marks the information for the serializer that // the SRT handshake extension is required. // Rest of the data will be filled together with // serialization. m_ConnReq.m_extension = needs_extension; w_reqpkt.setLength(m_iMaxSRTPayloadSize); if (m_RdvState == CHandShake::RDV_CONNECTED) { int cst = postConnect(pResponse, true, 0); if (cst == CONN_REJECT) { // m_RejectReason already set HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in postConnect."); return CONN_REJECT; } } // URQ_DONE or URQ_AGREEMENT can be the result if the state is RDV_CONNECTED. // If URQ_DONE, then there's nothing to be done, when URQ_AGREEMENT then return // CONN_CONTINUE to make the caller send again the contents if the packet buffer, // this time with URQ_AGREEMENT message, but still consider yourself connected. if (rsp_type == URQ_DONE) { HLOGC(cnlog.Debug, log << "processRendezvous: rsp=DONE, reporting ACCEPT (nothing to respond)"); return CONN_ACCEPT; } // createSrtHandshake moved here because if the above conditions are satisfied, // no response is going to be send, so nothing needs to be "created". // needs_extension here distinguishes between cases 1 and 3. // NOTE: in case when interpretSrtHandshake was run under the conditions above (to interpret HSRSP), // then createSrtHandshake below will create only empty AGREEMENT message. if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (w_reqpkt), (m_ConnReq))) { // m_RejectReason already set LOGC(cnlog.Warn, log << "createSrtHandshake failed (IPE?), connection rejected. REQ-TIME: LOW"); m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } if (rsp_type == URQ_AGREEMENT && m_RdvState == CHandShake::RDV_CONNECTED) { // We are using our own serialization method (not the one called after // processConnectResponse, this is skipped in case when this function // is called), so we can also send this immediately. Agreement must be // sent just once and the party must switch into CONNECTED state - in // contrast to CONCLUSION messages, which should be sent in loop repeatedly. // // Even though in theory the AGREEMENT message sent just once may miss // the target (as normal thing in UDP), this is little probable to happen, // and this doesn't matter much because even if the other party doesn't // get AGREEMENT, but will get payload or KEEPALIVE messages, it will // turn into connected state as well. The AGREEMENT is rather kinda // catalyzer here and may turn the entity on the right track faster. When // AGREEMENT is missed, it may have kinda initial tearing. const steady_clock::time_point now = steady_clock::now(); m_tsLastReqTime = now; setPacketTS(w_reqpkt, now); HLOGC(cnlog.Debug, log << "processRendezvous: rsp=AGREEMENT, reporting ACCEPT and sending just this one, REQ-TIME HIGH."); m_pSndQueue->sendto(serv_addr, w_reqpkt); return CONN_ACCEPT; } if (rst == RST_OK) { // the request time must be updated so that the next handshake can be sent out immediately HLOGC(cnlog.Debug, log << "processRendezvous: rsp=" << RequestTypeStr(m_ConnReq.m_iReqType) << " REQ-TIME: LOW to send immediately, consider yourself conencted"); m_tsLastReqTime = steady_clock::time_point(); } else { HLOGC(cnlog.Debug, log << "processRendezvous: REQ-TIME: remains previous value, consider yourself connected"); } return CONN_CONTINUE; } // [[using locked(m_ConnectionLock)]]; EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTException* eout) ATR_NOEXCEPT { // NOTE: ASSUMED LOCK ON: m_ConnectionLock. // this is the 2nd half of a connection request. If the connection is setup successfully this returns 0. // Returned values: // - CONN_REJECT: there was some error when processing the response, connection should be rejected // - CONN_ACCEPT: the handshake is done and finished correctly // - CONN_CONTINUE: the induction handshake has been processed correctly, and expects CONCLUSION handshake if (!m_bConnecting) return CONN_REJECT; // This is required in HSv5 rendezvous, in which it should send the URQ_AGREEMENT message to // the peer, however switch to connected state. HLOGC(cnlog.Debug, log << "processConnectResponse: TYPE:" << (response.isControl() ? MessageTypeStr(response.getType(), response.getExtendedType()) : string("DATA"))); // ConnectStatus res = CONN_REJECT; // used later for status - must be declared here due to goto POST_CONNECT. // For HSv4, the data sender is INITIATOR, and the data receiver is RESPONDER, // regardless of the connecting side affiliation. This will be changed for HSv5. bool bidirectional = false; HandshakeSide hsd = m_config.bDataSender ? HSD_INITIATOR : HSD_RESPONDER; // (defined here due to 'goto' below). // SRT peer may send the SRT handshake private message (type 0x7fff) before a keep-alive. // This condition is checked when the current agent is trying to do connect() in rendezvous mode, // but the peer was faster to send a handshake packet earlier. This makes it continue with connecting // process if the peer is already behaving as if the connection was already established. // This value will check either the initial value, which is less than SRT1, or // the value previously loaded to m_ConnReq during the previous handshake response. // For the initial form this value should not be checked. bool hsv5 = m_ConnRes.m_iVersion >= HS_VERSION_SRT1; if (m_config.bRendezvous && (m_RdvState == CHandShake::RDV_CONNECTED // somehow Rendezvous-v5 switched it to CONNECTED. || !response.isControl() // WAS A PAYLOAD PACKET. || (response.getType() == UMSG_KEEPALIVE) // OR WAS A UMSG_KEEPALIVE message. || (response.getType() == UMSG_EXT) // OR WAS a CONTROL packet of some extended type (i.e. any SRT specific) ) // This may happen if this is an initial state in which the socket type was not yet set. // If this is a field that holds the response handshake record from the peer, this means that it wasn't received // yet. HSv5: added version check because in HSv5 the m_iType field has different meaning and it may be 0 in // case when the handshake does not carry SRT extensions. && (hsv5 || m_ConnRes.m_iType != UDT_UNDEFINED)) { // a data packet or a keep-alive packet comes, which means the peer side is already connected // in this situation, the previously recorded response will be used // In HSv5 this situation is theoretically possible if this party has missed the URQ_AGREEMENT message. HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: already connected - pinning in"); if (hsv5) { m_RdvState = CHandShake::RDV_CONNECTED; } return postConnect(&response, hsv5, eout); } if (!response.isControl(UMSG_HANDSHAKE)) { m_RejectReason = SRT_REJ_ROGUE; if (!response.isControl()) { LOGC(cnlog.Warn, log << CONID() << "processConnectResponse: received DATA while HANDSHAKE expected"); } else { LOGC(cnlog.Error, log << CONID() << "processConnectResponse: CONFUSED: expected UMSG_HANDSHAKE as connection not yet established, " "got: " << MessageTypeStr(response.getType(), response.getExtendedType())); } return CONN_CONFUSED; } if (m_ConnRes.load_from(response.m_pcData, response.getLength()) == -1) { m_RejectReason = SRT_REJ_ROGUE; // Handshake data were too small to reach the Handshake structure. Reject. LOGC(cnlog.Error, log << CONID() << "processConnectResponse: HANDSHAKE data buffer too small - possible blueboxing. Rejecting."); return CONN_REJECT; } HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: HS RECEIVED: " << m_ConnRes.show()); if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) { m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType); return CONN_REJECT; } if (size_t(m_ConnRes.m_iMSS) > CPacket::ETH_MAX_MTU_SIZE) { // Yes, we do abort to prevent buffer overrun. Set your MSS correctly // and you'll avoid problems. m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Fatal, log << "MSS size " << m_config.iMSS << "exceeds MTU size!"); return CONN_REJECT; } // (see createCrypter() call below) // // The CCryptoControl attached object must be created early // because it will be required to create a conclusion handshake in HSv5 // if (m_config.bRendezvous) { // SANITY CHECK: A rendezvous socket should reject any caller requests (it's not a listener) if (m_ConnRes.m_iReqType == URQ_INDUCTION) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "processConnectResponse: Rendezvous-point received INDUCTION handshake (expected WAVEAHAND). " "Rejecting."); return CONN_REJECT; } // The procedure for version 5 is completely different and changes the states // differently, so the old code will still maintain HSv4 the old way. if (m_ConnRes.m_iVersion > HS_VERSION_UDT4) { HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv5 DETECTED."); return CONN_RENDEZVOUS; // --> will continue in CUDT::processRendezvous(). } HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendsezvous HSv4 DETECTED."); // So, here it has either received URQ_WAVEAHAND handshake message (while it should be in URQ_WAVEAHAND itself) // or it has received URQ_CONCLUSION/URQ_AGREEMENT message while this box has already sent URQ_WAVEAHAND to the // peer, and DID NOT send the URQ_CONCLUSION yet. if (m_ConnReq.m_iReqType == URQ_WAVEAHAND || m_ConnRes.m_iReqType == URQ_WAVEAHAND) { HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: REQ-TIME LOW. got HS RDV. Agent state:" << RequestTypeStr(m_ConnReq.m_iReqType) << " Peer HS:" << m_ConnRes.show()); // Here we could have received WAVEAHAND or CONCLUSION. // For HSv4 simply switch to CONCLUSION for the sake of further handshake rolling. // For HSv5, make the cookie contest and basing on this decide, which party // should provide the HSREQ/KMREQ attachment. if (!createCrypter(hsd, false /* unidirectional */)) { m_RejectReason = SRT_REJ_RESOURCE; m_ConnReq.m_iReqType = URQFailure(SRT_REJ_RESOURCE); // the request time must be updated so that the next handshake can be sent out immediately. m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } m_ConnReq.m_iReqType = URQ_CONCLUSION; // the request time must be updated so that the next handshake can be sent out immediately. m_tsLastReqTime = steady_clock::time_point(); return CONN_CONTINUE; } else { HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv4 PAST waveahand"); } } else { // set cookie if (m_ConnRes.m_iReqType == URQ_INDUCTION) { HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: REQ-TIME LOW; got INDUCTION HS response (cookie:" << hex << m_ConnRes.m_iCookie << " version:" << dec << m_ConnRes.m_iVersion << "), sending CONCLUSION HS with this cookie"); m_ConnReq.m_iCookie = m_ConnRes.m_iCookie; m_ConnReq.m_iReqType = URQ_CONCLUSION; // Here test if the LISTENER has responded with version HS_VERSION_SRT1, // it means that it is HSv5 capable. It can still accept the HSv4 handshake. if (m_ConnRes.m_iVersion > HS_VERSION_UDT4) { int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); if (hs_flags != SrtHSRequest::SRT_MAGIC_CODE) { LOGC(cnlog.Warn, log << "processConnectResponse: Listener HSv5 did not set the SRT_MAGIC_CODE"); } checkUpdateCryptoKeyLen("processConnectResponse", m_ConnRes.m_iType); // This will catch HS_VERSION_SRT1 and any newer. // Set your highest version. m_ConnReq.m_iVersion = HS_VERSION_SRT1; // CONTROVERSIAL: use 0 as m_iType according to the meaning in HSv5. // The HSv4 client might not understand it, which means that agent // must switch itself to HSv4 rendezvous, and this time iType sould // be set to UDT_DGRAM value. m_ConnReq.m_iType = 0; // This marks the information for the serializer that // the SRT handshake extension is required. // Rest of the data will be filled together with // serialization. m_ConnReq.m_extension = true; // For HSv5, the caller is INITIATOR and the listener is RESPONDER. // The m_config.bDataSender value should be completely ignored and the // connection is always bidirectional. bidirectional = true; hsd = HSD_INITIATOR; m_SrtHsSide = hsd; } m_tsLastReqTime = steady_clock::time_point(); if (!createCrypter(hsd, bidirectional)) { m_RejectReason = SRT_REJ_RESOURCE; return CONN_REJECT; } // NOTE: This setup sets URQ_CONCLUSION and appropriate data in the handshake structure. // The full handshake to be sent will be filled back in the caller function -- CUDT::startConnect(). return CONN_CONTINUE; } } return postConnect(&response, false, eout); } bool srt::CUDT::applyResponseSettings() ATR_NOEXCEPT { if (!m_ConnRes.valid()) { LOGC(cnlog.Error, log << "applyResponseSettings: ROGUE HANDSHAKE - rejecting"); m_RejectReason = SRT_REJ_ROGUE; return false; } // Re-configure according to the negotiated values. m_config.iMSS = m_ConnRes.m_iMSS; m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize; const int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE; m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; m_iPeerISN = m_ConnRes.m_iISN; setInitialRcvSeq(m_iPeerISN); m_iRcvCurrPhySeqNo = CSeqNo::decseq(m_ConnRes.m_iISN); m_PeerID = m_ConnRes.m_iID; memcpy((m_piSelfIP), m_ConnRes.m_piPeerIP, sizeof m_piSelfIP); HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: HANSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize << " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " isn=" << m_ConnRes.m_iISN << " peerID=" << m_ConnRes.m_iID); return true; } EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, CUDTException *eout) ATR_NOEXCEPT { if (m_ConnRes.m_iVersion < HS_VERSION_SRT1) m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly in SRT HS. // This procedure isn't being executed in rendezvous because // in rendezvous it's completed before calling this function. if (!rendezvous) { // The "local storage depleted" case shouldn't happen here, but // this is a theoretical path that needs prevention. bool ok = pResponse; if (!ok) { m_RejectReason = SRT_REJ_IPE; if (eout) { *eout = CUDTException(MJ_SETUP, MN_REJECTED, 0); } return CONN_REJECT; } // [[assert (pResponse != NULL)]]; // NOTE: THIS function must be called before calling prepareConnectionObjects. // The reason why it's not part of prepareConnectionObjects is that the activities // done there are done SIMILAR way in acceptAndRespond, which also calls this // function. In fact, prepareConnectionObjects() represents the code that was // done separately in processConnectResponse() and acceptAndRespond(), so this way // this code is now common. Now acceptAndRespond() does "manually" something similar // to applyResponseSettings(), just a little bit differently. This SHOULD be made // common as a part of refactoring job, just needs a bit more time. // // Currently just this function must be called always BEFORE prepareConnectionObjects // everywhere except acceptAndRespond(). ok = applyResponseSettings(); // This will actually be done also in rendezvous HSv4, // however in this case the HSREQ extension will not be attached, // so it will simply go the "old way". // (&&: skip if failed already) ok = ok && prepareConnectionObjects(m_ConnRes, m_SrtHsSide, eout); // May happen that 'response' contains a data packet that was sent in rendezvous mode. // In this situation the interpretation of handshake was already done earlier. ok = ok && pResponse->isControl(); ok = ok && interpretSrtHandshake(m_ConnRes, *pResponse, 0, 0); if (!ok) { if (eout) { *eout = CUDTException(MJ_SETUP, MN_REJECTED, 0); } // m_RejectReason already set return CONN_REJECT; } } bool have_group = false; { #if ENABLE_EXPERIMENTAL_BONDING ScopedLock cl (s_UDTUnited.m_GlobControlLock); CUDTGroup* g = m_parent->m_GroupOf; if (g) { // This is the last moment when this can be done. // The updateAfterSrtHandshake call will copy the receiver // start time to the receiver buffer data, so the correct // value must be set before this happens. synchronizeWithGroup(g); have_group = true; } #endif } if (!have_group) { // This function will be called internally inside // synchronizeWithGroup(). This is just more complicated. updateAfterSrtHandshake(m_ConnRes.m_iVersion); } CInfoBlock ib; ib.m_iIPversion = m_PeerAddr.family(); CInfoBlock::convert(m_PeerAddr, ib.m_piIP); if (m_pCache->lookup(&ib) >= 0) { m_iSRTT = ib.m_iSRTT; m_iRTTVar = ib.m_iSRTT / 2; m_iBandwidth = ib.m_iBandwidth; } #if SRT_DEBUG_RTT s_rtt_trace.trace(steady_clock::now(), "Connect", -1, -1, m_bIsFirstRTTReceived, -1, m_iSRTT, m_iRTTVar); #endif SRT_REJECT_REASON rr = setupCC(); if (rr != SRT_REJ_UNKNOWN) { m_RejectReason = rr; return CONN_REJECT; } // And, I am connected too. m_bConnecting = false; // The lock on m_ConnectionLock should still be applied, but // the socket could have been started removal before this function // has started. Do a sanity check before you continue with the // connection process. CUDTSocket* s = s_UDTUnited.locateSocket(m_SocketID); if (s) { // The socket could be closed at this very moment. // Continue with removing the socket from the pending structures, // but prevent it from setting it as connected. m_bConnected = true; // register this socket for receiving data packets m_pRNode->m_bOnList = true; m_pRcvQueue->setNewEntry(this); } // XXX Problem around CONN_CONFUSED! // If some too-eager packets were received from a listener // that thinks it's connected, but his last handshake was missed, // they are collected by CRcvQueue::storePkt. The removeConnector // function will want to delete them all, so it would be nice // if these packets can be re-delivered. Of course the listener // should be prepared to resend them (as every packet can be lost // on UDP), but it's kinda overkill when we have them already and // can dispatch them. // Remove from rendezvous queue (in this particular case it's // actually removing the socket that undergoes asynchronous HS processing). // Removing at THIS point because since when setNewEntry is called, // the next iteration in the CRcvQueue::worker loop will be dispatching // packets normally, as within-connection, so the "connector" won't // play any role since this time. // The connector, however, must stay alive until the setNewEntry is called // because otherwise the packets that are coming for this socket before the // connection process is complete will be rejected as "attack", instead of // being enqueued for later pickup from the queue. m_pRcvQueue->removeConnector(m_SocketID); // Ok, no more things to be done as per "clear connecting state" if (!s) { LOGC(cnlog.Error, log << "Connection broken in the process - socket @" << m_SocketID << " closed"); m_RejectReason = SRT_REJ_CLOSE; if (eout) { *eout = CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } return CONN_REJECT; } // copy address information of local node // the local port must be correctly assigned BEFORE CUDT::startConnect(), // otherwise if startConnect() fails, the multiplexer cannot be located // by garbage collection and will cause leak s->core().m_pSndQueue->m_pChannel->getSockAddr((s->m_SelfAddr)); CIPAddress::pton((s->m_SelfAddr), s->core().m_piSelfIP, m_PeerAddr); //int token = -1; #if ENABLE_EXPERIMENTAL_BONDING { ScopedLock cl (s_UDTUnited.m_GlobControlLock); CUDTGroup* g = m_parent->m_GroupOf; if (g) { // XXX this might require another check of group type. // For redundancy group, at least, update the status in the group. // LEAVING as comment for historical reasons. Locking is here most // likely not necessary because the socket cannot be removed from the // group until the socket isn't removed, and this requires locking of // m_GlobControlLock. This should ensure that when m_GroupOf is // not NULL, m_GroupMemberData is also valid. // ScopedLock glock(g->m_GroupLock); HLOGC(cnlog.Debug, log << "group: Socket @" << m_parent->m_SocketID << " fresh connected, setting IDLE"); groups::SocketData* gi = m_parent->m_GroupMemberData; gi->sndstate = SRT_GST_IDLE; gi->rcvstate = SRT_GST_IDLE; gi->laststatus = SRTS_CONNECTED; //token = gi->token; g->setGroupConnected(); } } #endif s->m_Status = SRTS_CONNECTED; // acknowledde any waiting epolls to write s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_CONNECT, true); CGlobEvent::triggerEvent(); /* XXX Likely it should NOT be called here for two reasons: - likely lots of mutexes are locked here so any API call from here might cause a deadlock - if called from an asynchronous connection process, it was already called from inside updateConnStatus - if called from startConnect (synchronous mode), it is even wrong. if (m_cbConnectHook) { CALLBACK_CALL(m_cbConnectHook, m_SocketID, SRT_SUCCESS, m_PeerAddr.get(), token); } */ LOGC(cnlog.Note, log << CONID() << "Connection established to: " << m_PeerAddr.str()); return CONN_ACCEPT; } void srt::CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32_t typefield) { int enc_flags = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(typefield); // potentially 0-7 values are possible. // When 0, don't change anything - it should rely on the value 0. // When 1, 5, 6, 7, this is kinda internal error - ignore. if (enc_flags >= 2 && enc_flags <= 4) // 2 = 128, 3 = 192, 4 = 256 { int rcv_pbkeylen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(enc_flags); if (m_config.iSndCryptoKeyLen == 0) { m_config.iSndCryptoKeyLen = rcv_pbkeylen; HLOGC(cnlog.Debug, log << loghdr << ": PBKEYLEN adopted from advertised value: " << m_config.iSndCryptoKeyLen); } else if (m_config.iSndCryptoKeyLen != rcv_pbkeylen) { // Conflict. Use SRTO_SENDER flag to check if this side should accept // the enforcement, otherwise simply let it win. if (!m_config.bDataSender) { LOGC(cnlog.Warn, log << loghdr << ": PBKEYLEN conflict - OVERRIDDEN " << m_config.iSndCryptoKeyLen << " by " << rcv_pbkeylen << " from PEER (as AGENT is not SRTO_SENDER)"); m_config.iSndCryptoKeyLen = rcv_pbkeylen; } else { LOGC(cnlog.Warn, log << loghdr << ": PBKEYLEN conflict - keep " << m_config.iSndCryptoKeyLen << "; peer-advertised PBKEYLEN " << rcv_pbkeylen << " rejected because Agent is SRTO_SENDER"); } } } else if (enc_flags != 0) { LOGC(cnlog.Error, log << loghdr << ": IPE: enc_flags outside allowed 2, 3, 4: " << enc_flags); } else { HLOGC(cnlog.Debug, log << loghdr << ": No encryption flags found in type field: " << typefield); } } // Rendezvous void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_extension, bool& w_needs_hsrsp) { UDTRequestType req = m_ConnRes.m_iReqType; int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); bool has_extension = !!hs_flags; // it holds flags, if no flags, there are no extensions. const HandshakeSide &hsd = m_SrtHsSide; // Note important possibilities that are considered here: // 1. The serial arrangement. This happens when one party has missed the // URQ_WAVEAHAND message, it sent its own URQ_WAVEAHAND message, and then the // firstmost message it received from the peer is URQ_CONCLUSION, as a response // for agent's URQ_WAVEAHAND. // // In this case, Agent switches to RDV_FINE state and Peer switches to RDV_ATTENTION state. // // 2. The parallel arrangement. This happens when the URQ_WAVEAHAND message sent // by both parties are almost in a perfect synch (a rare, but possible case). In this // case, both parties receive one another's URQ_WAVEAHAND message and both switch to // RDV_ATTENTION state. // // It's not possible to predict neither which arrangement will happen, or which // party will be RDV_FINE in case when the serial arrangement has happened. What // will actually happen will depend on random conditions. // // No matter this randomity, we have a limited number of possible conditions: // // Stating that "agent" is the party that has received the URQ_WAVEAHAND in whatever // arrangement, we are certain, that "agent" switched to RDV_ATTENTION, and peer: // // - switched to RDV_ATTENTION state (so, both are in the same state independently) // - switched to RDV_FINE state (so, the message interchange is actually more-less sequenced) // // In particular, there's no possibility of a situation that both are in RDV_FINE state // because the agent can switch to RDV_FINE state only if it received URQ_CONCLUSION from // the peer, while the peer could not send URQ_CONCLUSION without switching off RDV_WAVING // (actually to RDV_ATTENTION). There's also no exit to RDV_FINE from RDV_ATTENTION. // DEFAULT STATEMENT: don't attach extensions to URQ_CONCLUSION, neither HSREQ nor HSRSP. w_needs_extension = false; w_needs_hsrsp = false; string reason; #if ENABLE_HEAVY_LOGGING HLOGC(cnlog.Debug, log << "rendezvousSwitchState: HS: " << m_ConnRes.show()); struct LogAtTheEnd { CHandShake::RendezvousState ost; UDTRequestType orq; const CHandShake::RendezvousState &nst; const UDTRequestType & nrq; bool & needext; bool & needrsp; string & reason; ~LogAtTheEnd() { HLOGC(cnlog.Debug, log << "rendezvousSwitchState: STATE[" << CHandShake::RdvStateStr(ost) << "->" << CHandShake::RdvStateStr(nst) << "] REQTYPE[" << RequestTypeStr(orq) << "->" << RequestTypeStr(nrq) << "] " << "ext:" << (needext ? (needrsp ? "HSRSP" : "HSREQ") : "NONE") << (reason == "" ? string() : "reason:" + reason)); } } l_logend = {m_RdvState, req, m_RdvState, w_rsptype, w_needs_extension, w_needs_hsrsp, reason}; #endif switch (m_RdvState) { case CHandShake::RDV_INVALID: return; case CHandShake::RDV_WAVING: { if (req == URQ_WAVEAHAND) { m_RdvState = CHandShake::RDV_ATTENTION; // NOTE: if this->isWinner(), attach HSREQ w_rsptype = URQ_CONCLUSION; if (hsd == HSD_INITIATOR) w_needs_extension = true; return; } if (req == URQ_CONCLUSION) { m_RdvState = CHandShake::RDV_FINE; w_rsptype = URQ_CONCLUSION; w_needs_extension = true; // (see below - this needs to craft either HSREQ or HSRSP) // if this->isWinner(), then craft HSREQ for that response. // if this->isLoser(), then this packet should bring HSREQ, so craft HSRSP for the response. if (hsd == HSD_RESPONDER) w_needs_hsrsp = true; return; } } reason = "WAVING -> WAVEAHAND or CONCLUSION"; break; case CHandShake::RDV_ATTENTION: { if (req == URQ_WAVEAHAND) { // This is only possible if the URQ_CONCLUSION sent to the peer // was lost on track. The peer is then simply unaware that the // agent has switched to ATTENTION state and continues sending // waveahands. In this case, just remain in ATTENTION state and // retry with URQ_CONCLUSION, as normally. w_rsptype = URQ_CONCLUSION; if (hsd == HSD_INITIATOR) w_needs_extension = true; return; } if (req == URQ_CONCLUSION) { // We have two possibilities here: // // WINNER (HSD_INITIATOR): send URQ_AGREEMENT if (hsd == HSD_INITIATOR) { // WINNER should get a response with HSRSP, otherwise this is kinda empty conclusion. // If no HSRSP attached, stay in this state. if (hs_flags == 0) { HLOGC( cnlog.Debug, log << "rendezvousSwitchState: " "{INITIATOR}[ATTENTION] awaits CONCLUSION+HSRSP, got CONCLUSION, remain in [ATTENTION]"); w_rsptype = URQ_CONCLUSION; w_needs_extension = true; // If you expect to receive HSRSP, continue sending HSREQ return; } m_RdvState = CHandShake::RDV_CONNECTED; w_rsptype = URQ_AGREEMENT; return; } // LOSER (HSD_RESPONDER): send URQ_CONCLUSION and attach HSRSP extension, then expect URQ_AGREEMENT if (hsd == HSD_RESPONDER) { // If no HSREQ attached, stay in this state. // (Although this seems completely impossible). if (hs_flags == 0) { LOGC( cnlog.Warn, log << "rendezvousSwitchState: (IPE!)" "{RESPONDER}[ATTENTION] awaits CONCLUSION+HSREQ, got CONCLUSION, remain in [ATTENTION]"); w_rsptype = URQ_CONCLUSION; w_needs_extension = false; // If you received WITHOUT extensions, respond WITHOUT extensions (wait // for the right message) return; } m_RdvState = CHandShake::RDV_INITIATED; w_rsptype = URQ_CONCLUSION; w_needs_extension = true; w_needs_hsrsp = true; return; } LOGC(cnlog.Error, log << "RENDEZVOUS COOKIE DRAW! Cannot resolve to a valid state."); // Fallback for cookie draw m_RdvState = CHandShake::RDV_INVALID; w_rsptype = URQFailure(SRT_REJ_RDVCOOKIE); return; } if (req == URQ_AGREEMENT) { // This means that the peer has received our URQ_CONCLUSION, but // the agent missed the peer's URQ_CONCLUSION (received only initial // URQ_WAVEAHAND). if (hsd == HSD_INITIATOR) { // In this case the missed URQ_CONCLUSION was sent without extensions, // whereas the peer received our URQ_CONCLUSION with HSREQ, and therefore // it sent URQ_AGREEMENT already with HSRSP. This isn't a problem for // us, we can go on with it, especially that the peer is already switched // into CHandShake::RDV_CONNECTED state. m_RdvState = CHandShake::RDV_CONNECTED; // Both sides are connected, no need to send anything anymore. w_rsptype = URQ_DONE; return; } if (hsd == HSD_RESPONDER) { // In this case the missed URQ_CONCLUSION was sent with extensions, so // we have to request this once again. Send URQ_CONCLUSION in order to // inform the other party that we need the conclusion message once again. // The ATTENTION state should be maintained. w_rsptype = URQ_CONCLUSION; w_needs_extension = true; w_needs_hsrsp = true; return; } } } reason = "ATTENTION -> WAVEAHAND(conclusion), CONCLUSION(agreement/conclusion), AGREEMENT (done/conclusion)"; break; case CHandShake::RDV_FINE: { // In FINE state we can't receive URQ_WAVEAHAND because if the peer has already // sent URQ_CONCLUSION, it's already in CHandShake::RDV_ATTENTION, and in this state it can // only send URQ_CONCLUSION, whereas when it isn't in CHandShake::RDV_ATTENTION, it couldn't // have sent URQ_CONCLUSION, and if it didn't, the agent wouldn't be in CHandShake::RDV_FINE state. if (req == URQ_CONCLUSION) { // There's only one case when it should receive CONCLUSION in FINE state: // When it's the winner. If so, it should then contain HSREQ extension. // In case of loser, it shouldn't receive CONCLUSION at all - it should // receive AGREEMENT. // The winner case, received CONCLUSION + HSRSP - switch to CONNECTED and send AGREEMENT. // So, check first if HAS EXTENSION bool correct_switch = false; if (hsd == HSD_INITIATOR && !has_extension) { // Received REPEATED empty conclusion that has initially switched it into FINE state. // To exit FINE state we need the CONCLUSION message with HSRSP. HLOGC(cnlog.Debug, log << "rendezvousSwitchState: {INITIATOR}[FINE] s_UDTUnited, self->m_parent); #endif UniqueLock recv_lock (self->m_RecvLock); CSync recvdata_cc (self->m_RecvDataCond, recv_lock); CSync tsbpd_cc (self->m_RcvTsbPdCond, recv_lock); self->m_bTsbPdAckWakeup = true; while (!self->m_bClosing) { int32_t current_pkt_seq = 0; steady_clock::time_point tsbpdtime; bool rxready = false; int32_t rcv_base_seq = SRT_SEQNO_NONE; #if ENABLE_EXPERIMENTAL_BONDING bool shall_update_group = false; if (gkeeper.group) { // Functions called below will lock m_GroupLock, which in hierarchy // lies after m_RecvLock. Must unlock m_RecvLock to be able to lock // m_GroupLock inside the calls. InvertedLock unrecv(self->m_RecvLock); rcv_base_seq = gkeeper.group->getRcvBaseSeqNo(); } #endif enterCS(self->m_RcvBufferLock); self->m_pRcvBuffer->updRcvAvgDataSize(steady_clock::now()); if (self->m_bTLPktDrop) { int32_t skiptoseqno = SRT_SEQNO_NONE; bool passack = true; // Get next packet to wait for even if not acked rxready = self->m_pRcvBuffer->getRcvFirstMsg((tsbpdtime), (passack), (skiptoseqno), (current_pkt_seq), rcv_base_seq); HLOGC(tslog.Debug, log << boolalpha << "NEXT PKT CHECK: rdy=" << rxready << " passack=" << passack << " skipto=%" << skiptoseqno << " current=%" << current_pkt_seq << " buf-base=%" << self->m_iRcvLastSkipAck); /* * VALUES RETURNED: * * rxready: if true, packet at head of queue ready to play * tsbpdtime: timestamp of packet at head of queue, ready or not. 0 if none. * passack: if true, ready head of queue not yet acknowledged * skiptoseqno: sequence number of packet at head of queue if ready to play but * some preceeding packets are missing (need to be skipped). -1 if none. */ if (rxready) { /* Packet ready to play according to time stamp but... */ int seqlen = CSeqNo::seqoff(self->m_iRcvLastSkipAck, skiptoseqno); if (skiptoseqno != SRT_SEQNO_NONE && seqlen > 0) { /* * skiptoseqno != SRT_SEQNO_NONE, * packet ready to play but preceeded by missing packets (hole). */ self->updateForgotten(seqlen, self->m_iRcvLastSkipAck, skiptoseqno); self->m_pRcvBuffer->skipData(seqlen); self->m_iRcvLastSkipAck = skiptoseqno; #if ENABLE_EXPERIMENTAL_BONDING shall_update_group = true; #endif #if ENABLE_LOGGING int64_t timediff_us = 0; if (!is_zero(tsbpdtime)) timediff_us = count_microseconds(steady_clock::now() - tsbpdtime); #if ENABLE_HEAVY_LOGGING HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(skiptoseqno) << " (" << seqlen << " packets) playable at " << FormatTime(tsbpdtime) << " delayed " << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') << (timediff_us % 1000) << " ms"); #endif LOGC(brlog.Warn, log << self->CONID() << "RCV-DROPPED " << seqlen << " packet(s), packet seqno %" << skiptoseqno << " delayed for " << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') << (timediff_us % 1000) << " ms"); #endif tsbpdtime = steady_clock::time_point(); //Next sent ack will unblock rxready = false; } else if (passack) { /* Packets ready to play but not yet acknowledged (should happen within 10ms) */ rxready = false; tsbpdtime = steady_clock::time_point(); // Next sent ack will unblock } /* else packet ready to play */ } /* else packets not ready to play */ } else { rxready = self->m_pRcvBuffer->isRcvDataReady((tsbpdtime), (current_pkt_seq), -1 /*get first ready*/); } leaveCS(self->m_RcvBufferLock); if (rxready) { HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << current_pkt_seq << " (belated " << (count_milliseconds(steady_clock::now() - tsbpdtime)) << "ms)"); /* * There are packets ready to be delivered * signal a waiting "recv" call if there is any data available */ if (self->m_config.bSynRecving) { recvdata_cc.signal_locked(recv_lock); } /* * Set EPOLL_IN to wakeup any thread waiting on epoll */ self->s_UDTUnited.m_EPoll.update_events(self->m_SocketID, self->m_sPollID, SRT_EPOLL_IN, true); #if ENABLE_EXPERIMENTAL_BONDING // If this is NULL, it means: // - the socket never was a group member // - the socket was a group member, but: // - was just removed as a part of closure // - and will never be member of the group anymore // If this is not NULL, it means: // - This socket is currently member of the group // - This socket WAS a member of the group, though possibly removed from it already, BUT: // - the group that this socket IS OR WAS member of is in the GroupKeeper // - the GroupKeeper prevents the group from being deleted // - it is then completely safe to access the group here, // EVEN IF THE SOCKET THAT WAS ITS MEMBER IS BEING DELETED. // It is ensured that the group object exists here because GroupKeeper // keeps it busy, even if you just closed the socket, remove it as a member // or even the group is empty and was explicitly closed. if (gkeeper.group) { // Functions called below will lock m_GroupLock, which in hierarchy // lies after m_RecvLock. Must unlock m_RecvLock to be able to lock // m_GroupLock inside the calls. InvertedLock unrecv(self->m_RecvLock); // The current "APP reader" needs to simply decide as to whether // the next CUDTGroup::recv() call should return with no blocking or not. // When the group is read-ready, it should update its pollers as it sees fit. // NOTE: this call will set lock to m_GroupOf->m_GroupLock HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: GROUP: checking if %" << current_pkt_seq << " makes group readable"); gkeeper.group->updateReadState(self->m_SocketID, current_pkt_seq); if (shall_update_group) { // A group may need to update the parallelly used idle links, // should it have any. Pass the current socket position in order // to skip it from the group loop. // NOTE: SELF LOCKING. gkeeper.group->updateLatestRcv(self->m_parent); } } #endif CGlobEvent::triggerEvent(); tsbpdtime = steady_clock::time_point(); } if (!is_zero(tsbpdtime)) { IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsbpdtime - steady_clock::now()); /* * Buffer at head of queue is not ready to play. * Schedule wakeup when it will be. */ self->m_bTsbPdAckWakeup = false; HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << current_pkt_seq << " T=" << FormatTime(tsbpdtime) << " - waiting " << count_milliseconds(timediff) << "ms"); THREAD_PAUSED(); tsbpd_cc.wait_until(tsbpdtime); THREAD_RESUMED(); } else { /* * We have just signaled epoll; or * receive queue is empty; or * next buffer to deliver is not in receive queue (missing packet in sequence). * * Block until woken up by one of the following event: * - All ready-to-play packets have been pulled and EPOLL_IN cleared (then loop to block until next pkt time * if any) * - New buffers ACKed * - Closing the connection */ HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); self->m_bTsbPdAckWakeup = true; THREAD_PAUSED(); tsbpd_cc.wait(); THREAD_RESUMED(); } HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP!!!"); } THREAD_EXIT(); HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING"); return NULL; } void srt::CUDT::updateForgotten(int seqlen, int32_t lastack, int32_t skiptoseqno) { /* Update drop/skip stats */ enterCS(m_StatsLock); m_stats.rcvDropTotal += seqlen; m_stats.traceRcvDrop += seqlen; /* Estimate dropped/skipped bytes from average payload */ const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); m_stats.rcvBytesDropTotal += seqlen * avgpayloadsz; m_stats.traceRcvBytesDrop += seqlen * avgpayloadsz; leaveCS(m_StatsLock); dropFromLossLists(lastack, CSeqNo::decseq(skiptoseqno)); //remove(from,to-inclusive) } bool srt::CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout) { // This will be lazily created due to being the common // code with HSv5 rendezvous, in which this will be run // in a little bit "randomly selected" moment, but must // be run once in the whole connection process. if (m_pSndBuffer) { HLOGC(rslog.Debug, log << "prepareConnectionObjects: (lazy) already created."); return true; } bool bidirectional = false; if (hs.m_iVersion > HS_VERSION_UDT4) { bidirectional = true; // HSv5 is always bidirectional } // HSD_DRAW is received only if this side is listener. // If this side is caller with HSv5, HSD_INITIATOR should be passed. // If this is a rendezvous connection with HSv5, the handshake role // is taken from m_SrtHsSide field. if (hsd == HSD_DRAW) { if (bidirectional) { hsd = HSD_RESPONDER; // In HSv5, listener is always RESPONDER and caller always INITIATOR. } else { hsd = m_config.bDataSender ? HSD_INITIATOR : HSD_RESPONDER; } } try { m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize); m_pRcvBuffer = new CRcvBuffer(&(m_pRcvQueue->m_UnitQueue), m_config.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_config.iFlightFlagSize); } catch (...) { // Simply reject. if (eout) { *eout = CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } m_RejectReason = SRT_REJ_RESOURCE; return false; } if (!createCrypter(hsd, bidirectional)) // Make sure CC is created (lazy) { m_RejectReason = SRT_REJ_RESOURCE; return false; } return true; } void srt::CUDT::rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs) { // this is a reponse handshake w_hs.m_iReqType = URQ_CONCLUSION; w_hs.m_iMSS = m_config.iMSS; w_hs.m_iFlightFlagSize = m_config.flightCapacity(); w_hs.m_iID = m_SocketID; if (w_hs.m_iVersion > HS_VERSION_UDT4) { // The version is agreed; this code is executed only in case // when AGENT is listener. In this case, conclusion response // must always contain HSv5 handshake extensions. w_hs.m_extension = true; } CIPAddress::ntop(peer, (w_hs.m_piPeerIP)); } void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs) { HLOGC(cnlog.Debug, log << "acceptAndRespond: setting up data according to handshake"); ScopedLock cg(m_ConnectionLock); m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly at SRT HS // Uses the smaller MSS between the peers m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS); // exchange info for maximum flow window size m_iFlowWindowSize = w_hs.m_iFlightFlagSize; m_iPeerISN = w_hs.m_iISN; setInitialRcvSeq(m_iPeerISN); m_iRcvCurrPhySeqNo = CSeqNo::decseq(w_hs.m_iISN); m_PeerID = w_hs.m_iID; // use peer's ISN and send it back for security check m_iISN = w_hs.m_iISN; setInitialSndSeq(m_iISN); m_SndLastAck2Time = steady_clock::now(); // get local IP address and send the peer its IP address (because UDP cannot get local IP address) memcpy((m_piSelfIP), w_hs.m_piPeerIP, sizeof m_piSelfIP); m_parent->m_SelfAddr = agent; CIPAddress::pton((m_parent->m_SelfAddr), m_piSelfIP, peer); rewriteHandshakeData(peer, (w_hs)); int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE; m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; HLOGC(cnlog.Debug, log << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); // Prepare all structures if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0)) { HLOGC(cnlog.Debug, log << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT."); // If the SRT Handshake extension was provided and wasn't interpreted // correctly, the connection should be rejected. // // Respond with the rejection message and exit with exception // so that the caller will know that this new socket should be deleted. w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } // Since now you can use m_pCryptoControl CInfoBlock ib; ib.m_iIPversion = peer.family(); CInfoBlock::convert(peer, ib.m_piIP); if (m_pCache->lookup(&ib) >= 0) { m_iSRTT = ib.m_iSRTT; m_iRTTVar = ib.m_iSRTT / 2; m_iBandwidth = ib.m_iBandwidth; } #if SRT_DEBUG_RTT s_rtt_trace.trace(steady_clock::now(), "Accept", -1, -1, m_bIsFirstRTTReceived, -1, m_iSRTT, m_iRTTVar); #endif m_PeerAddr = peer; // This should extract the HSREQ and KMREQ portion in the handshake packet. // This could still be a HSv4 packet and contain no such parts, which will leave // this entity as "non-SRT-handshaken", and await further HSREQ and KMREQ sent // as UMSG_EXT. uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; if (!interpretSrtHandshake(w_hs, hspkt, (kmdata), (&kmdatasize))) { HLOGC(cnlog.Debug, log << "acceptAndRespond: interpretSrtHandshake failed - responding with REJECT."); // If the SRT Handshake extension was provided and wasn't interpreted // correctly, the connection should be rejected. // // Respond with the rejection message and return false from // this function so that the caller will know that this new // socket should be deleted. w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } // Synchronize the time NOW because the following function is about // to use the start time to pass it to the receiver buffer data. bool have_group = false; { #if ENABLE_EXPERIMENTAL_BONDING ScopedLock cl (s_UDTUnited.m_GlobControlLock); CUDTGroup* g = m_parent->m_GroupOf; if (g) { // This is the last moment when this can be done. // The updateAfterSrtHandshake call will copy the receiver // start time to the receiver buffer data, so the correct // value must be set before this happens. synchronizeWithGroup(g); have_group = true; } #endif } if (!have_group) { // This function will be called internally inside // synchronizeWithGroup(). This is just more complicated. updateAfterSrtHandshake(w_hs.m_iVersion); } SRT_REJECT_REASON rr = setupCC(); // UNKNOWN used as a "no error" value if (rr != SRT_REJ_UNKNOWN) { w_hs.m_iReqType = URQFailure(rr); m_RejectReason = rr; throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } // 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); // Save the handshake in m_ConnRes in case when needs repeating. m_ConnRes = w_hs; // Send the response to the peer, see listen() for more discussions // about this. // TODO: Here create CONCLUSION RESPONSE with: // - just the UDT handshake, if HS_VERSION_UDT4, // - if higher, the UDT handshake, the SRT HSRSP, the SRT KMRSP. size_t size = m_iMaxSRTPayloadSize; // Allocate the maximum possible memory for an SRT payload. // This is a maximum you can send once. CPacket response; response.setControl(UMSG_HANDSHAKE); response.allocate(size); // This will serialize the handshake according to its current form. HLOGC(cnlog.Debug, log << "acceptAndRespond: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size); if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (response), (w_hs))) { LOGC(cnlog.Error, log << "acceptAndRespond: error creating handshake response"); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } #if ENABLE_HEAVY_LOGGING { // To make sure what REALLY is being sent, parse back the handshake // data that have been just written into the buffer. CHandShake debughs; debughs.load_from(response.m_pcData, response.getLength()); HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: sending HS from agent @" << debughs.m_iID << " to peer @" << response.m_iID << "HS:" << debughs.show()); } #endif // NOTE: BLOCK THIS instruction in order to cause the final // handshake to be missed and cause the problem solved in PR #417. // When missed this message, the caller should not accept packets // coming as connected, but continue repeated handshake until finally // received the listener's handshake. addressAndSend((response)); } // This function is required to be called when a caller receives an INDUCTION // response from the listener and would like to create a CONCLUSION that includes // the SRT handshake extension. This extension requires that the crypter object // be created, but it's still too early for it to be completely configured. // This function then precreates the object so that the handshake extension can // be created, as this happens before the completion of the connection (and // therefore configuration of the crypter object), which can only take place upon // reception of CONCLUSION response from the listener. bool srt::CUDT::createCrypter(HandshakeSide side, bool bidirectional) { // Lazy initialization if (m_pCryptoControl) return true; // Write back this value, when it was just determined. m_SrtHsSide = side; m_pCryptoControl.reset(new CCryptoControl(this, m_SocketID)); // XXX These below are a little bit controversial. // These data should probably be filled only upon // reception of the conclusion handshake - otherwise // they have outdated values. m_pCryptoControl->setCryptoSecret(m_config.CryptoSecret); if (bidirectional || m_config.bDataSender) { HLOGC(rslog.Debug, log << "createCrypter: setting RCV/SND KeyLen=" << m_config.iSndCryptoKeyLen); m_pCryptoControl->setCryptoKeylen(m_config.iSndCryptoKeyLen); } return m_pCryptoControl->init(side, bidirectional); } SRT_REJECT_REASON srt::CUDT::setupCC() { // Prepare configuration object, // Create the CCC object and configure it. // UDT also sets back the congestion window: ??? // m_dCongestionWindow = m_pCC->m_dCWndSize; // XXX Not sure about that. May happen that AGENT wants // tsbpd mode, but PEER doesn't, even in bidirectional mode. // This way, the reception side should get precedense. // if (bidirectional || m_config.bDataSender || m_bTwoWayData) // m_bPeerTsbPd = m_bTSBPD; // SrtCongestion will retrieve whatever parameters it needs // from *this. bool res = m_CongCtl.select(m_config.sCongestion.str()); if (!res || !m_CongCtl.configure(this)) { return SRT_REJ_CONGESTION; } // Configure filter module if (!m_config.sPacketFilterConfig.empty()) { // This string, when nonempty, defines that the corrector shall be // configured. Otherwise it's left uninitialized. // At this point we state everything is checked and the appropriate // corrector type is already selected, so now create it. HLOGC(pflog.Debug, log << "filter: Configuring: " << m_config.sPacketFilterConfig.c_str()); bool status = true; try { // The filter configurer is build the way that allows to quit immediately // exit by exception, but the exception is meant for the filter only. status = m_PacketFilter.configure(this, &(m_pRcvQueue->m_UnitQueue), m_config.sPacketFilterConfig.str()); } catch (CUDTException& ) { status = false; } if (!status) return SRT_REJ_FILTER; m_PktFilterRexmitLevel = m_PacketFilter.arqLevel(); } else { // When we have no filter, ARQ should work in ALWAYS mode. m_PktFilterRexmitLevel = SRT_ARQ_ALWAYS; } // Override the value of minimum NAK interval, per SrtCongestion's wish. // When default 0 value is returned, the current value set by CUDT // is preserved. const steady_clock::duration min_nak = microseconds_from(m_CongCtl->minNAKInterval()); if (min_nak != steady_clock::duration::zero()) m_tdMinNakInterval = min_nak; // Update timers const steady_clock::time_point currtime = steady_clock::now(); m_tsLastRspTime.store(currtime); m_tsNextACKTime.store(currtime + m_tdACKInterval); m_tsNextNAKTime.store(currtime + m_tdNAKInterval); m_tsLastRspAckTime = currtime; m_tsLastSndTime.store(currtime); HLOGC(rslog.Debug, log << "setupCC: setting parameters: mss=" << m_config.iMSS << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize << " rcvrate=" << m_iDeliveryRate << "p/s (" << m_iByteDeliveryRate << "B/S)" << " rtt=" << m_iSRTT << " bw=" << m_iBandwidth); if (!updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET))) { LOGC(rslog.Error, log << "setupCC: IPE: resrouces not yet initialized!"); return SRT_REJ_IPE; } return SRT_REJ_UNKNOWN; } void srt::CUDT::considerLegacySrtHandshake(const steady_clock::time_point &timebase) { // Do a fast pre-check first - this simply declares that agent uses HSv5 // and the legacy SRT Handshake is not to be done. Second check is whether // agent is sender (=initiator in HSv4). if (!isOPT_TsbPd() || !m_config.bDataSender) return; if (m_iSndHsRetryCnt <= 0) { HLOGC(cnlog.Debug, log << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); return; } const steady_clock::time_point now = steady_clock::now(); if (!is_zero(timebase)) { // Then this should be done only if it's the right time, // the TSBPD mode is on, and when the counter is "still rolling". /* * SRT Handshake with peer: * If... * - we want TsbPd mode; and * - we have not tried more than CSRTCC_MAXRETRY times (peer may not be SRT); and * - and did not get answer back from peer * - last sent handshake req should have been replied (RTT*1.5 elapsed); and * then (re-)send handshake request. */ if (timebase > now) // too early { HLOGC(cnlog.Debug, log << "Legacy HSREQ: TOO EARLY, will still retry " << m_iSndHsRetryCnt << " times"); return; } } // If 0 timebase, it means that this is the initial sending with the very first // payload packet sent. Send only if this is still set to maximum+1 value. else if (m_iSndHsRetryCnt < SRT_MAX_HSRETRY + 1) { HLOGC(cnlog.Debug, log << "Legacy HSREQ: INITIAL, REPEATED, so not to be done. Will repeat on sending " << m_iSndHsRetryCnt << " times"); return; } HLOGC(cnlog.Debug, log << "Legacy HSREQ: SENDING, will repeat " << m_iSndHsRetryCnt << " times if no response"); m_iSndHsRetryCnt--; m_tsSndHsLastTime = now; sendSrtMsg(SRT_CMD_HSREQ); } void srt::CUDT::checkSndTimers(Whether2RegenKm regen) { if (m_SrtHsSide == HSD_INITIATOR) { HLOGC(cnlog.Debug, log << "checkSndTimers: HS SIDE: INITIATOR, considering legacy handshake with timebase"); // Legacy method for HSREQ, only if initiator. considerLegacySrtHandshake(m_tsSndHsLastTime + microseconds_from(m_iSRTT * 3 / 2)); } else { HLOGC(cnlog.Debug, log << "checkSndTimers: HS SIDE: " << (m_SrtHsSide == HSD_RESPONDER ? "RESPONDER" : "DRAW (IPE?)") << " - not considering legacy handshake"); } // This must be done always on sender, regardless of HS side. // When regen == DONT_REGEN_KM, it's a handshake call, so do // it only for initiator. if (regen || m_SrtHsSide == HSD_INITIATOR) { // Don't call this function in "non-regen mode" (sending only), // if this side is RESPONDER. This shall be called only with // regeneration request, which is required by the sender. if (m_pCryptoControl) m_pCryptoControl->sendKeysToPeer(regen); } } void srt::CUDT::addressAndSend(CPacket& w_pkt) { w_pkt.m_iID = m_PeerID; setPacketTS(w_pkt, steady_clock::now()); // NOTE: w_pkt isn't modified in this call, // just in CChannel::sendto it's modified in place // before sending for performance purposes, // and then modification is undone. Logically then // there's no modification here. m_pSndQueue->sendto(m_PeerAddr, w_pkt); } // [[using maybe_locked(m_GlobControlLock, if called from GC)]] bool srt::CUDT::closeInternal() { // NOTE: this function is called from within the garbage collector thread. if (!m_bOpened) { return false; } // IMPORTANT: // This function may block indefinitely, if called for a socket // that has m_bBroken == false or m_bConnected == true. // If it is intended to forcefully close the socket, make sure // that it's in response to a broken connection. HLOGC(smlog.Debug, log << CONID() << " - closing socket:"); if (m_config.Linger.l_onoff != 0) { const steady_clock::time_point entertime = steady_clock::now(); HLOGC(smlog.Debug, log << CONID() << " ... (linger)"); while (!m_bBroken && m_bConnected && (m_pSndBuffer->getCurrBufSize() > 0) && (steady_clock::now() - entertime < seconds_from(m_config.Linger.l_linger))) { // linger has been checked by previous close() call and has expired if (m_tsLingerExpiration >= entertime) break; if (!m_config.bSynSending) { // if this socket enables asynchronous sending, return immediately and let GC to close it later if (is_zero(m_tsLingerExpiration)) m_tsLingerExpiration = entertime + seconds_from(m_config.Linger.l_linger); HLOGC(smlog.Debug, log << "CUDT::close: linger-nonblocking, setting expire time T=" << FormatTime(m_tsLingerExpiration)); return false; } #ifndef _WIN32 timespec ts; ts.tv_sec = 0; ts.tv_nsec = 1000000; nanosleep(&ts, NULL); #else Sleep(1); #endif } } // remove this socket from the snd queue if (m_bConnected) m_pSndQueue->m_pSndUList->remove(this); /* * update_events below useless * removing usock for EPolls right after (update_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. */ // Make a copy under a lock because other thread might access it // at the same time. enterCS(s_UDTUnited.m_EPoll.m_EPollLock); set epollid = m_sPollID; leaveCS(s_UDTUnited.m_EPoll.m_EPollLock); // trigger any pending IO events. HLOGC(smlog.Debug, log << "close: SETTING ERR readiness on E" << Printable(epollid) << " of @" << m_SocketID); s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_ERR, true); // then remove itself from all epoll monitoring int no_events = 0; for (set::iterator i = epollid.begin(); i != epollid.end(); ++i) { HLOGC(smlog.Debug, log << "close: CLEARING subscription on E" << (*i) << " of @" << m_SocketID); try { s_UDTUnited.m_EPoll.update_usock(*i, m_SocketID, &no_events); } catch (...) { // The goal of this loop is to remove all subscriptions in // the epoll system to this socket. If it's unsubscribed already, // that's even better. } HLOGC(smlog.Debug, log << "close: removing E" << (*i) << " from back-subscribers of @" << m_SocketID); } // Not deleting elements from m_sPollID inside the loop because it invalidates // the control iterator of the loop. Instead, all will be removed at once. // IMPORTANT: there's theoretically little time between setting ERR readiness // and unsubscribing, however if there's an application waiting on this event, // it should be informed before this below instruction locks the epoll mutex. enterCS(s_UDTUnited.m_EPoll.m_EPollLock); m_sPollID.clear(); leaveCS(s_UDTUnited.m_EPoll.m_EPollLock); // XXX What's this, could any of the above actions make it !m_bOpened? if (!m_bOpened) { return true; } // Inform the threads handler to stop. m_bClosing = true; HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock"); ScopedLock connectguard(m_ConnectionLock); // Signal the sender and recver if they are waiting for data. releaseSynch(); HLOGC(smlog.Debug, log << CONID() << "CLOSING, removing from listener/connector"); if (m_bListening) { m_bListening = false; m_pRcvQueue->removeListener(this); } else if (m_bConnecting) { m_pRcvQueue->removeConnector(m_SocketID); } if (m_bConnected) { if (!m_bShutdown) { HLOGC(smlog.Debug, log << CONID() << "CLOSING - sending SHUTDOWN to the peer @" << m_PeerID); sendCtrl(UMSG_SHUTDOWN); } // Store current connection information. CInfoBlock ib; ib.m_iIPversion = m_PeerAddr.family(); CInfoBlock::convert(m_PeerAddr, ib.m_piIP); ib.m_iSRTT = m_iSRTT; ib.m_iBandwidth = m_iBandwidth; m_pCache->update(&ib); #if SRT_DEBUG_RTT s_rtt_trace.trace(steady_clock::now(), "Cache", -1, -1, m_bIsFirstRTTReceived, -1, m_iSRTT, -1); #endif m_bConnected = false; } HLOGC(smlog.Debug, log << "CLOSING, joining send/receive threads"); // waiting all send and recv calls to stop ScopedLock sendguard(m_SendLock); ScopedLock recvguard(m_RecvLock); // Locking m_RcvBufferLock to protect calling to m_pCryptoControl->decrypt((packet)) // from the processData(...) function while resetting Crypto Control. enterCS(m_RcvBufferLock); if (m_pCryptoControl) m_pCryptoControl->close(); m_pCryptoControl.reset(); leaveCS(m_RcvBufferLock); m_uPeerSrtVersion = SRT_VERSION_UNK; m_tsRcvPeerStartTime = steady_clock::time_point(); m_bOpened = false; return true; } int srt::CUDT::receiveBuffer(char *data, int len) { if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); if (isOPT_TsbPd()) { LOGP(arlog.Error, "recv: This function is not intended to be used in Live mode with TSBPD."); throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); } UniqueLock recvguard(m_RecvLock); if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) { if (m_bShutdown) { // For stream API, return 0 as a sign of EOF for transmission. // That's a bit controversial because theoretically the // UMSG_SHUTDOWN message may be lost as every UDP packet, although // another theory states that this will never happen because this // packet has a total size of 42 bytes and such packets are // declared as never dropped - but still, this is UDP so there's no // guarantee. // The most reliable way to inform the party that the transmission // has ended would be to send a single empty packet (that is, // a data packet that contains only an SRT header in the UDP // payload), which is a normal data packet that can undergo // normal sequence check and retransmission rules, so it's ensured // that this packet will be received. Receiving such a packet should // make this function return 0, potentially also without breaking // the connection and potentially also with losing no ability to // send some larger portion of data next time. HLOGC(arlog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF"); return 0; } HLOGC(arlog.Debug, log << (m_config.bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") << " SHUTDOWN. Reporting as BROKEN."); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } CSync rcond (m_RecvDataCond, recvguard); CSync tscond (m_RcvTsbPdCond, recvguard); if (!m_pRcvBuffer->isRcvDataReady()) { if (!m_config.bSynRecving) { throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } else { /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_config.iRcvTimeOut < 0) { THREAD_PAUSED(); while (stillConnected() && !m_pRcvBuffer->isRcvDataReady()) { // Do not block forever, check connection status each 1 sec. rcond.wait_for(seconds_from(1)); } THREAD_RESUMED(); } else { const steady_clock::time_point exptime = steady_clock::now() + milliseconds_from(m_config.iRcvTimeOut); THREAD_PAUSED(); while (stillConnected() && !m_pRcvBuffer->isRcvDataReady()) { if (!rcond.wait_until(exptime)) // NOT means "not received a signal" break; // timeout } THREAD_RESUMED(); } } } // throw an exception if not connected if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) { // See at the beginning if (!m_config.bMessageAPI && m_bShutdown) { HLOGC(arlog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF"); return 0; } HLOGC(arlog.Debug, log << (m_config.bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") << " SHUTDOWN. Reporting as BROKEN."); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } enterCS(m_RcvBufferLock); const int res = m_pRcvBuffer->readBuffer(data, len); leaveCS(m_RcvBufferLock); /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_bTsbPd) { HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); tscond.signal_locked(recvguard); } else { HLOGP(tslog.Debug, "NOT pinging TSBPD - not set"); } if (!m_pRcvBuffer->isRcvDataReady()) { // read is not available any more s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } if ((res <= 0) && (m_config.iRcvTimeOut >= 0)) throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); return res; } // [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]]; // [[using locked(m_SendLock)]]; void srt::CUDT::checkNeedDrop(bool& w_bCongestion) { if (!m_bPeerTLPktDrop) return; if (!m_config.bMessageAPI) { LOGC(aslog.Error, log << "The SRTO_TLPKTDROP flag can only be used with message API."); throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); } int bytes, timespan_ms; // (returns buffer size in buffer units, ignored) m_pSndBuffer->getCurrBufSize((bytes), (timespan_ms)); // high threshold (msec) at tsbpd_delay plus sender/receiver reaction time (2 * 10ms) // Minimum value must accomodate an I-Frame (~8 x average frame size) // >>need picture rate or app to set min treshold // >>using 1 sec for worse case 1 frame using all bit budget. // picture rate would be useful in auto SRT setting for min latency // XXX Make SRT_TLPKTDROP_MINTHRESHOLD_MS option-configurable int threshold_ms = 0; if (m_config.iSndDropDelay >= 0) { threshold_ms = std::max(m_iPeerTsbPdDelay_ms + m_config.iSndDropDelay, +SRT_TLPKTDROP_MINTHRESHOLD_MS) + (2 * COMM_SYN_INTERVAL_US / 1000); } if (threshold_ms && timespan_ms > threshold_ms) { // protect packet retransmission enterCS(m_RecvAckLock); int dbytes; int32_t first_msgno; int dpkts = m_pSndBuffer->dropLateData((dbytes), (first_msgno), steady_clock::now() - milliseconds_from(threshold_ms)); if (dpkts > 0) { enterCS(m_StatsLock); m_stats.traceSndDrop += dpkts; m_stats.sndDropTotal += dpkts; m_stats.traceSndBytesDrop += dbytes; m_stats.sndBytesDropTotal += dbytes; leaveCS(m_StatsLock); IF_HEAVY_LOGGING(const int32_t realack = m_iSndLastDataAck); const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts); m_iSndLastAck = fakeack; m_iSndLastDataAck = fakeack; int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck); m_pSndLossList->removeUpTo(minlastack); /* If we dropped packets not yet sent, advance current position */ // THIS MEANS: m_iSndCurrSeqNo = MAX(m_iSndCurrSeqNo, m_iSndLastDataAck-1) if (CSeqNo::seqcmp(m_iSndCurrSeqNo, minlastack) < 0) { m_iSndCurrSeqNo = minlastack; } HLOGC(aslog.Debug, log << "SND-DROP: %(" << realack << "-" << m_iSndCurrSeqNo << ") n=" << dpkts << "pkt " << dbytes << "B, span=" << timespan_ms << " ms, FIRST #" << first_msgno); #if ENABLE_EXPERIMENTAL_BONDING // This is done with a presumption that the group // exists and if this is not NULL, it means that this // function was called with locked m_GroupLock, as sendmsg2 // function was called from inside CUDTGroup::send, which // locks the whole function. // // XXX This is true only because all existing groups are managed // groups, that is, sockets cannot be added or removed from group // manually, nor can send/recv operation be done on a single socket // from the API call directly. This should be extra verified, if that // changes in the future. // if (m_parent->m_GroupOf) { // What's important is that the lock on GroupLock cannot be applied // here, both because it might be applied already, that is, according // to the condition defined at this function's header, it is applied // under this condition. Hence ackMessage can be defined as 100% locked. m_parent->m_GroupOf->ackMessage(first_msgno); } #endif } w_bCongestion = true; leaveCS(m_RecvAckLock); } else if (timespan_ms > (m_iPeerTsbPdDelay_ms / 2)) { HLOGC(aslog.Debug, log << "cong, BYTES " << bytes << ", TMSPAN " << timespan_ms << "ms"); w_bCongestion = true; } } int srt::CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, int64_t srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; mctrl.msgttl = msttl; mctrl.inorder = inorder; mctrl.srctime = srctime; return this->sendmsg2(data, len, (mctrl)); } // [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]] // GroupLock is applied when this function is called from inside CUDTGroup::send, // which is the only case when the m_parent->m_GroupOf is not NULL. int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) { bool bCongestion = false; // throw an exception if not connected if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); else if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (len <= 0) { LOGC(aslog.Error, log << "INVALID: Data size for sending declared with length: " << len); return 0; } if (w_mctrl.msgno != -1) // most unlikely, unless you use balancing groups { if (w_mctrl.msgno < 1 || w_mctrl.msgno > MSGNO_SEQ_MAX) { LOGC(aslog.Error, log << "INVALID forced msgno " << w_mctrl.msgno << ": can be -1 (trap) or <1..." << MSGNO_SEQ_MAX << ">"); throw CUDTException(MJ_NOTSUP, MN_INVAL); } } int msttl = w_mctrl.msgttl; bool inorder = w_mctrl.inorder; // Sendmsg isn't restricted to the congctl type, however the congctl // may want to have something to say here. // NOTE: SrtCongestion is also allowed to throw CUDTException() by itself! { SrtCongestion::TransAPI api = SrtCongestion::STA_MESSAGE; CodeMinor mn = MN_INVALMSGAPI; if (!m_config.bMessageAPI) { api = SrtCongestion::STA_BUFFER; mn = MN_INVALBUFFERAPI; } if (!m_CongCtl->checkTransArgs(api, SrtCongestion::STAD_SEND, data, len, msttl, inorder)) throw CUDTException(MJ_NOTSUP, mn, 0); } // NOTE: the length restrictions differ in STREAM API and in MESSAGE API: // - STREAM API: // At least 1 byte free sending buffer space is needed // (in practice, one unit buffer of 1456 bytes). // This function will send as much as possible, and return // how much was actually sent. // - MESSAGE API: // At least so many bytes free in the sending buffer is needed, // as the length of the data, otherwise this function will block // or return MJ_AGAIN until this condition is satisfied. The EXACTLY // such number of data will be then written out, and this function // will effectively return either -1 (error) or the value of 'len'. // This call will be also rejected from upside when trying to send // out a message of a length that exceeds the total size of the sending // buffer (configurable by SRTO_SNDBUF). if (m_config.bMessageAPI && len > int(m_config.iSndBufSize * m_iMaxSRTPayloadSize)) { LOGC(aslog.Error, log << "Message length (" << len << ") exceeds the size of sending buffer: " << (m_config.iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed."); throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); } /* XXX This might be worth preserving for several occasions, but it must be at least conditional because it breaks backward compat. if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK()) { LOGC(aslog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0); } */ UniqueLock sendguard(m_SendLock); if (m_pSndBuffer->getCurrBufSize() == 0) { // delay the EXP timer to avoid mis-fired timeout ScopedLock ack_lock(m_RecvAckLock); m_tsLastRspAckTime = steady_clock::now(); m_iReXmitCount = 1; } // checkNeedDrop(...) may lock m_RecvAckLock // to modify m_pSndBuffer and m_pSndLossList checkNeedDrop((bCongestion)); int minlen = 1; // Minimum sender buffer space required for STREAM API if (m_config.bMessageAPI) { // For MESSAGE API the minimum outgoing buffer space required is // the size that can carry over the whole message as passed here. minlen = (len + m_iMaxSRTPayloadSize - 1) / m_iMaxSRTPayloadSize; } if (sndBuffersLeft() < minlen) { //>>We should not get here if SRT_ENABLE_TLPKTDROP // XXX Check if this needs to be removed, or put to an 'else' condition for m_bTLPktDrop. if (!m_config.bSynSending) throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); { // wait here during a blocking sending UniqueLock sendblock_lock (m_SendBlockLock); if (m_config.iSndTimeOut < 0) { while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth) m_SendBlockCond.wait(sendblock_lock); } else { const steady_clock::time_point exptime = steady_clock::now() + milliseconds_from(m_config.iSndTimeOut); THREAD_PAUSED(); while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth) { if (!m_SendBlockCond.wait_until(sendblock_lock, exptime)) break; } THREAD_RESUMED(); } } // check the connection status if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); else if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); else if (!m_bPeerHealth) { m_bPeerHealth = true; throw CUDTException(MJ_PEERERROR); } /* * The code below is to return ETIMEOUT when blocking mode could not get free buffer in time. * If no free buffer available in non-blocking mode, we alredy returned. If buffer availaible, * we test twice if this code is outside the else section. * This fix move it in the else (blocking-mode) section */ if (sndBuffersLeft() < minlen) { if (m_config.iSndTimeOut >= 0) throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); // XXX This looks very weird here, however most likely // this will happen only in the following case, when // the above loop has been interrupted, which happens when: // 1. The buffers left gets enough for minlen - but this is excluded // in the first condition here. // 2. In the case of sending timeout, the above loop was interrupted // due to reaching timeout, but this is excluded by the second // condition here // 3. The 'stillConnected()' or m_bPeerHealth condition is false, of which: // - broken/closing status is checked and responded with CONNECTION/CONNLOST // - not connected status is checked and responded with CONNECTION/NOCONN // - m_bPeerHealth condition is checked and responded with PEERERROR // // ERGO: never happens? LOGC(aslog.Fatal, log << "IPE: sendmsg: the loop exited, while not enough size, still connected, peer healthy. " "Impossible."); return 0; } } // If the sender's buffer is empty, // record total time used for sending if (m_pSndBuffer->getCurrBufSize() == 0) { ScopedLock lock(m_StatsLock); m_stats.sndDurationCounter = steady_clock::now(); } int size = len; if (!m_config.bMessageAPI) { // For STREAM API it's allowed to send less bytes than the given buffer. // Just return how many bytes were actually scheduled for writing. // XXX May be reasonable to add a flag that requires that the function // not return until the buffer is sent completely. size = min(len, sndBuffersLeft() * m_iMaxSRTPayloadSize); } { ScopedLock recvAckLock(m_RecvAckLock); // insert the user buffer into the sending list int32_t seqno = m_iSndNextSeqNo; IF_HEAVY_LOGGING(int32_t orig_seqno = seqno); IF_HEAVY_LOGGING(steady_clock::time_point ts_srctime = steady_clock::time_point() + microseconds_from(w_mctrl.srctime)); #if ENABLE_EXPERIMENTAL_BONDING // Check if seqno has been set, in case when this is a group sender. // If the sequence is from the past towards the "next sequence", // simply return the size, pretending that it has been sent. // NOTE: it's assumed that if this is a group member, then // an attempt to call srt_sendmsg2 has been rejected, and so // the pktseq field has been set by the internal group sender function. if (m_parent->m_GroupOf && w_mctrl.pktseq != SRT_SEQNO_NONE && m_iSndNextSeqNo != SRT_SEQNO_NONE) { if (CSeqNo::seqcmp(w_mctrl.pktseq, seqno) < 0) { HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (NOT): group-req %" << w_mctrl.pktseq << " OLDER THAN next expected %" << seqno << " - FAKE-SENDING."); return size; } } #endif // Set this predicted next sequence to the control information. // It's the sequence of the FIRST (!) packet from all packets used to send // this buffer. Values from this field will be monotonic only if you always // have one packet per buffer (as it's in live mode). w_mctrl.pktseq = seqno; // Now seqno is the sequence to which it was scheduled // XXX Conversion from w_mctrl.srctime -> steady_clock::time_point need not be accurrate. HLOGC(aslog.Debug, log << CONID() << "buf:SENDING (BEFORE) srctime:" << (w_mctrl.srctime ? FormatTime(ts_srctime) : "none") << " DATA SIZE: " << size << " sched-SEQUENCE: " << seqno << " STAMP: " << BufferStamp(data, size)); if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(m_stats.tsStartTime.time_since_epoch())) { LOGC(aslog.Error, log << "Wrong source time was provided. Sending is rejected."); throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); } if (w_mctrl.srctime && (!m_config.bMessageAPI || !m_bTsbPd)) { HLOGC(aslog.Warn, log << "Source time can only be used with TSBPD and Message API enabled. Using default time instead."); w_mctrl.srctime = 0; } // w_mctrl.seqno is INPUT-OUTPUT value: // - INPUT: the current sequence number to be placed for the next scheduled packet // - OUTPUT: value of the sequence number to be put on the first packet at the next sendmsg2 call. // We need to supply to the output the value that was STAMPED ON THE PACKET, // which is seqno. In the output we'll get the next sequence number. m_pSndBuffer->addBuffer(data, size, (w_mctrl)); m_iSndNextSeqNo = w_mctrl.pktseq; w_mctrl.pktseq = seqno; HLOGC(aslog.Debug, log << CONID() << "buf:SENDING srctime:" << FormatTime(ts_srctime) << " size=" << size << " #" << w_mctrl.msgno << " SCHED %" << orig_seqno << "(>> %" << seqno << ") !" << BufferStamp(data, size)); if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as requried. { // write is not available any more s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false); } } // insert this socket to the snd list if it is not on the list yet // m_pSndUList->pop may lock CSndUList::m_ListLock and then m_RecvAckLock m_pSndQueue->m_pSndUList->update(this, CSndUList::rescheduleIf(bCongestion)); #ifdef SRT_ENABLE_ECN if (bCongestion) { LOGC(aslog.Error, log << "sendmsg2: CONGESTION; reporting error"); throw CUDTException(MJ_AGAIN, MN_CONGESTION, 0); } #endif /* SRT_ENABLE_ECN */ HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (END): success, size=" << size); return size; } int srt::CUDT::recv(char* data, int len) { SRT_MSGCTRL mctrl = srt_msgctrl_default; return recvmsg2(data, len, (mctrl)); } int srt::CUDT::recvmsg(char* data, int len, int64_t& srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; int res = recvmsg2(data, len, (mctrl)); srctime = mctrl.srctime; return res; } // [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]] // GroupLock is applied when this function is called from inside CUDTGroup::recv, // which is the only case when the m_parent->m_GroupOf is not NULL. int srt::CUDT::recvmsg2(char* data, int len, SRT_MSGCTRL& w_mctrl) { // Check if the socket is a member of a receiver group. // If so, then reading by receiveMessage is disallowed. #if ENABLE_EXPERIMENTAL_BONDING if (m_parent->m_GroupOf && m_parent->m_GroupOf->isGroupReceiver()) { LOGP(arlog.Error, "recv*: This socket is a receiver group member. Use group ID, NOT socket ID."); throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); } #endif if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (len <= 0) { LOGC(arlog.Error, log << "Length of '" << len << "' supplied to srt_recvmsg."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } if (m_config.bMessageAPI) return receiveMessage(data, len, (w_mctrl)); return receiveBuffer(data, len); } // int by_exception: accepts values of CUDTUnited::ErrorHandling: // - 0 - by return value // - 1 - by exception // - 2 - by abort (unused) int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_exception) { // Recvmsg isn't restricted to the congctl type, it's the most // basic method of passing the data. You can retrieve data as // they come in, however you need to match the size of the buffer. // Note: if by_exception = ERH_RETURN, this would still break it // by exception. The intention of by_exception isn't to prevent // exceptions here, but to intercept the erroneous situation should // it be handled by the caller in a less than general way. As this // is only used internally, we state that the problem that would be // handled by exception here should not happen, and in case if it does, // it's a bug to fix, so the exception is nothing wrong. if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); UniqueLock recvguard (m_RecvLock); CSync tscond (m_RcvTsbPdCond, recvguard); /* 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_config.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) { HLOGC(arlog.Debug, log << CONID() << "receiveMessage: CONNECTION BROKEN - reading from recv buffer just for formality"); enterCS(m_RcvBufferLock); int res = m_pRcvBuffer->readMsg(data, len); leaveCS(m_RcvBufferLock); w_mctrl.srctime = 0; // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); tscond.signal_locked(recvguard); } else { HLOGP(tslog.Debug, "NOT pinging TSBPD - not set"); } if (!m_pRcvBuffer->isRcvDataReady()) { // read is not available any more s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } if (res == 0) { if (!m_config.bMessageAPI && m_bShutdown) return 0; // Forced to return error instead of throwing exception. if (!by_exception) return APIError(MJ_CONNECTION, MN_CONNLOST, 0); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } else return res; } const int seqdistance = -1; if (!m_config.bSynRecving) { HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN ASYNC MODE. Going to extract payload size=" << len); enterCS(m_RcvBufferLock); const int res = m_pRcvBuffer->readMsg(data, len, (w_mctrl), seqdistance); leaveCS(m_RcvBufferLock); HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (NON-BLOCKING) result=" << res); if (res == 0) { // read is not available any more // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { HLOGP(arlog.Debug, "receiveMessage: nothing to read, kicking TSBPD, return AGAIN"); tscond.signal_locked(recvguard); } else { HLOGP(arlog.Debug, "receiveMessage: nothing to read, return AGAIN"); } // Shut up EPoll if no more messages in non-blocking mode s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); // Forced to return 0 instead of throwing exception, in case of AGAIN/READ if (!by_exception) return 0; throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } if (!m_pRcvBuffer->isRcvDataReady()) { // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more - kicking TSBPD."); tscond.signal_locked(recvguard); } else { HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more"); } // Shut up EPoll if no more messages in non-blocking mode s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); // After signaling the tsbpd for ready data, report the bandwidth. #if ENABLE_HEAVY_LOGGING double bw = Bps2Mbps(int64_t(m_iBandwidth) * m_iMaxSRTPayloadSize ); HLOGC(arlog.Debug, log << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth << " buffers per second)"); #endif } return res; } HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN SYNC MODE. Going to extract payload size max=" << len); int res = 0; bool timeout = false; // Do not block forever, check connection status each 1 sec. const steady_clock::duration recv_timeout = m_config.iRcvTimeOut < 0 ? seconds_from(1) : milliseconds_from(m_config.iRcvTimeOut); CSync recv_cond (m_RecvDataCond, recvguard); do { steady_clock::time_point tstime SRT_ATR_UNUSED; int32_t seqno; if (stillConnected() && !timeout && !m_pRcvBuffer->isRcvDataReady((tstime), (seqno), seqdistance)) { /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_bTsbPd) { // XXX Experimental, so just inform: // Check if the last check of isRcvDataReady has returned any "next time for a packet". // If so, then it means that TSBPD has fallen asleep only up to this time, so waking it up // would be "spurious". If a new packet comes ahead of the packet which's time is returned // in tstime (as TSBPD sleeps up to then), the procedure that receives it is responsible // of kicking TSBPD. // bool spurious = (tstime != 0); HLOGC(tslog.Debug, log << CONID() << "receiveMessage: KICK tsbpd" << (is_zero(tstime) ? " (SPURIOUS!)" : "")); tscond.signal_locked(recvguard); } THREAD_PAUSED(); do { // `wait_for(recv_timeout)` wouldn't be correct here. Waiting should be // only until the time that is now + timeout since the first moment // when this started, or sliced-waiting for 1 second, if timtout is // higher than this. const steady_clock::time_point exptime = steady_clock::now() + recv_timeout; HLOGC(tslog.Debug, log << CONID() << "receiveMessage: fall asleep up to TS=" << FormatTime(exptime) << " lock=" << (&m_RecvLock) << " cond=" << (&m_RecvDataCond)); if (!recv_cond.wait_until(exptime)) { if (m_config.iRcvTimeOut >= 0) // otherwise it's "no timeout set" timeout = true; HLOGP(tslog.Debug, "receiveMessage: DATA COND: EXPIRED -- checking connection conditions and rolling again"); } else { HLOGP(tslog.Debug, "receiveMessage: DATA COND: KICKED."); } } while (stillConnected() && !timeout && (!m_pRcvBuffer->isRcvDataReady())); THREAD_RESUMED(); HLOGC(tslog.Debug, log << CONID() << "receiveMessage: lock-waiting loop exited: stillConntected=" << stillConnected() << " timeout=" << timeout << " data-ready=" << m_pRcvBuffer->isRcvDataReady()); } /* XXX DEBUG STUFF - enable when required LOGC(arlog.Debug, "RECVMSG/GO-ON BROKEN " << m_bBroken << " CONN " << m_bConnected << " CLOSING " << m_bClosing << " TMOUT " << timeout << " NMSG " << m_pRcvBuffer->getRcvMsgNum()); */ enterCS(m_RcvBufferLock); res = m_pRcvBuffer->readMsg((data), len, (w_mctrl), seqdistance); leaveCS(m_RcvBufferLock); HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (BLOCKING) result=" << res); if (m_bBroken || m_bClosing) { // Forced to return 0 instead of throwing exception. if (!by_exception) return APIError(MJ_CONNECTION, MN_CONNLOST, 0); if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } else if (!m_bConnected) { // Forced to return -1 instead of throwing exception. if (!by_exception) return APIError(MJ_CONNECTION, MN_NOCONN, 0); throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); } } while ((res == 0) && !timeout); if (!m_pRcvBuffer->isRcvDataReady()) { // Falling here means usually that res == 0 && timeout == true. // res == 0 would repeat the above loop, unless there was also a timeout. // timeout has interrupted the above loop, but with res > 0 this condition // wouldn't be satisfied. // read is not available any more // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { HLOGP(tslog.Debug, "recvmsg: KICK tsbpd() (buffer empty)"); tscond.signal_locked(recvguard); } // Shut up EPoll if no more messages in non-blocking mode s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } // Unblock when required // LOGC(tslog.Debug, "RECVMSG/EXIT RES " << res << " RCVTIMEOUT"); if ((res <= 0) && (m_config.iRcvTimeOut >= 0)) { // Forced to return -1 instead of throwing exception. if (!by_exception) return APIError(MJ_AGAIN, MN_XMTIMEOUT, 0); throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); } return res; } int64_t srt::CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) { if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); else if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (size <= 0 && size != -1) return 0; if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_SEND, 0, size, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK()) { LOGC(aslog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0); } ScopedLock sendguard (m_SendLock); if (m_pSndBuffer->getCurrBufSize() == 0) { // delay the EXP timer to avoid mis-fired timeout // XXX Lock ??? ScopedLock ack_lock(m_RecvAckLock); m_tsLastRspAckTime = steady_clock::now(); m_iReXmitCount = 1; } // positioning... try { if (size == -1) { ifs.seekg(0, std::ios::end); size = ifs.tellg(); if (offset > size) throw 0; // let it be caught below } // This will also set the position back to the beginning // in case when it was moved to the end for measuring the size. // This will also fail if the offset exceeds size, so measuring // the size can be skipped if not needed. ifs.seekg((streamoff)offset); if (!ifs.good()) throw 0; } catch (...) { // XXX It would be nice to note that this is reported // by exception only if explicitly requested by setting // the exception flags in the stream. Here it's fixed so // that when this isn't set, the exception is "thrown manually". throw CUDTException(MJ_FILESYSTEM, MN_SEEKGFAIL); } int64_t tosend = size; int unitsize; // sending block by block while (tosend > 0) { if (ifs.fail()) throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL); if (ifs.eof()) break; unitsize = int((tosend >= block) ? block : tosend); { UniqueLock lock(m_SendBlockLock); THREAD_PAUSED(); while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth) m_SendBlockCond.wait(lock); THREAD_RESUMED(); } 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) { ScopedLock lock(m_StatsLock); m_stats.sndDurationCounter = steady_clock::now(); } { ScopedLock recvAckLock(m_RecvAckLock); const int64_t sentsize = m_pSndBuffer->addBufferFromFile(ifs, unitsize); if (sentsize > 0) { tosend -= sentsize; offset += sentsize; } if (sndBuffersLeft() <= 0) { // write is not available any more s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false); } } // insert this socket to snd list if it is not on the list yet m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); } return size - tosend; } int64_t srt::CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block) { if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) { if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } if (size <= 0) return 0; if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_RECV, 0, size, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); if (isOPT_TsbPd()) { LOGC(arlog.Error, log << "Reading from file is incompatible with TSBPD mode and would cause a deadlock\n"); throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); } UniqueLock recvguard(m_RecvLock); // Well, actually as this works over a FILE (fstream), not just a stream, // the size can be measured anyway and predicted if setting the offset might // have a chance to work or not. // positioning... try { if (offset > 0) { // Don't do anything around here if the offset == 0, as this // is the default offset after opening. Whether this operation // is performed correctly, it highly depends on how the file // has been open. For example, if you want to overwrite parts // of an existing file, the file must exist, and the ios::trunc // flag must not be set. If the file is open for only ios::out, // then the file will be truncated since the offset position on // at the time when first written; if ios::in|ios::out, then // it won't be truncated, just overwritten. // What is required here is that if offset is 0, don't try to // change the offset because this might be impossible with // the current flag set anyway. // Also check the status and CAUSE exception manually because // you don't know, as well, whether the user has set exception // flags. ofs.seekp((streamoff)offset); if (!ofs.good()) throw 0; // just to get caught :) } } catch (...) { // XXX It would be nice to note that this is reported // by exception only if explicitly requested by setting // the exception flags in the stream. For a case, when it's not, // an additional explicit throwing happens when failbit is set. throw CUDTException(MJ_FILESYSTEM, MN_SEEKPFAIL); } int64_t torecv = size; int unitsize = block; int recvsize; // receiving... "recvfile" is always blocking while (torecv > 0) { if (ofs.fail()) { // send the sender a signal so it will not be blocked forever int32_t err_code = CUDTException::EFILE; sendCtrl(UMSG_PEERERROR, &err_code); throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL); } { CSync rcond (m_RecvDataCond, recvguard); THREAD_PAUSED(); while (stillConnected() && !m_pRcvBuffer->isRcvDataReady()) rcond.wait(); THREAD_RESUMED(); } if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) { if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } unitsize = int((torecv > block) ? block : torecv); enterCS(m_RcvBufferLock); recvsize = m_pRcvBuffer->readBufferToFile(ofs, unitsize); leaveCS(m_RcvBufferLock); 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, SRT_EPOLL_IN, false); } return size - torecv; } void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) { if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); ScopedLock statsguard(m_StatsLock); const steady_clock::time_point currtime = steady_clock::now(); perf->msTimeStamp = count_milliseconds(currtime - m_stats.tsStartTime); perf->pktSent = m_stats.traceSent; perf->pktSentUnique = m_stats.traceSentUniq; perf->pktRecv = m_stats.traceRecv; perf->pktRecvUnique = m_stats.traceRecvUniq; perf->pktSndLoss = m_stats.traceSndLoss; perf->pktRcvLoss = m_stats.traceRcvLoss; perf->pktRetrans = m_stats.traceRetrans; perf->pktRcvRetrans = m_stats.traceRcvRetrans; perf->pktSentACK = m_stats.sentACK; perf->pktRecvACK = m_stats.recvACK; perf->pktSentNAK = m_stats.sentNAK; perf->pktRecvNAK = m_stats.recvNAK; perf->usSndDuration = m_stats.sndDuration; perf->pktReorderDistance = m_stats.traceReorderDistance; perf->pktReorderTolerance = m_iReorderTolerance; perf->pktRcvAvgBelatedTime = m_stats.traceBelatedTime; perf->pktRcvBelated = m_stats.traceRcvBelated; perf->pktSndFilterExtra = m_stats.sndFilterExtra; perf->pktRcvFilterExtra = m_stats.rcvFilterExtra; perf->pktRcvFilterSupply = m_stats.rcvFilterSupply; perf->pktRcvFilterLoss = m_stats.rcvFilterLoss; /* perf byte counters include all headers (SRT+UDP+IP) */ const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; perf->byteSent = m_stats.traceBytesSent + (m_stats.traceSent * pktHdrSize); perf->byteSentUnique = m_stats.traceBytesSentUniq + (m_stats.traceSentUniq * pktHdrSize); perf->byteRecv = m_stats.traceBytesRecv + (m_stats.traceRecv * pktHdrSize); perf->byteRecvUnique = m_stats.traceBytesRecvUniq + (m_stats.traceRecvUniq * pktHdrSize); perf->byteRetrans = m_stats.traceBytesRetrans + (m_stats.traceRetrans * pktHdrSize); perf->byteRcvLoss = m_stats.traceRcvBytesLoss + (m_stats.traceRcvLoss * pktHdrSize); perf->pktSndDrop = m_stats.traceSndDrop; perf->pktRcvDrop = m_stats.traceRcvDrop + m_stats.traceRcvUndecrypt; perf->byteSndDrop = m_stats.traceSndBytesDrop + (m_stats.traceSndDrop * pktHdrSize); perf->byteRcvDrop = m_stats.traceRcvBytesDrop + (m_stats.traceRcvDrop * pktHdrSize) + m_stats.traceRcvBytesUndecrypt; perf->pktRcvUndecrypt = m_stats.traceRcvUndecrypt; perf->byteRcvUndecrypt = m_stats.traceRcvBytesUndecrypt; perf->pktSentTotal = m_stats.sentTotal; perf->pktSentUniqueTotal = m_stats.sentUniqTotal; perf->pktRecvTotal = m_stats.recvTotal; perf->pktRecvUniqueTotal = m_stats.recvUniqTotal; perf->pktSndLossTotal = m_stats.sndLossTotal; perf->pktRcvLossTotal = m_stats.rcvLossTotal; perf->pktRetransTotal = m_stats.retransTotal; perf->pktSentACKTotal = m_stats.sentACKTotal; perf->pktRecvACKTotal = m_stats.recvACKTotal; perf->pktSentNAKTotal = m_stats.sentNAKTotal; perf->pktRecvNAKTotal = m_stats.recvNAKTotal; perf->usSndDurationTotal = m_stats.m_sndDurationTotal; perf->byteSentTotal = m_stats.bytesSentTotal + (m_stats.sentTotal * pktHdrSize); perf->byteSentUniqueTotal = m_stats.bytesSentUniqTotal + (m_stats.sentUniqTotal * pktHdrSize); perf->byteRecvTotal = m_stats.bytesRecvTotal + (m_stats.recvTotal * pktHdrSize); perf->byteRecvUniqueTotal = m_stats.bytesRecvUniqTotal + (m_stats.recvUniqTotal * pktHdrSize); perf->byteRetransTotal = m_stats.bytesRetransTotal + (m_stats.retransTotal * pktHdrSize); perf->pktSndFilterExtraTotal = m_stats.sndFilterExtraTotal; perf->pktRcvFilterExtraTotal = m_stats.rcvFilterExtraTotal; perf->pktRcvFilterSupplyTotal = m_stats.rcvFilterSupplyTotal; perf->pktRcvFilterLossTotal = m_stats.rcvFilterLossTotal; perf->byteRcvLossTotal = m_stats.rcvBytesLossTotal + (m_stats.rcvLossTotal * pktHdrSize); perf->pktSndDropTotal = m_stats.sndDropTotal; perf->pktRcvDropTotal = m_stats.rcvDropTotal + m_stats.m_rcvUndecryptTotal; perf->byteSndDropTotal = m_stats.sndBytesDropTotal + (m_stats.sndDropTotal * pktHdrSize); perf->byteRcvDropTotal = m_stats.rcvBytesDropTotal + (m_stats.rcvDropTotal * pktHdrSize) + m_stats.m_rcvBytesUndecryptTotal; perf->pktRcvUndecryptTotal = m_stats.m_rcvUndecryptTotal; perf->byteRcvUndecryptTotal = m_stats.m_rcvBytesUndecryptTotal; const double interval = (double) count_microseconds(currtime - m_stats.tsLastSampleTime); perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; perf->usPktSndPeriod = (double) count_microseconds(m_tdSendInterval.load()); perf->pktFlowWindow = m_iFlowWindowSize.load(); perf->pktCongestionWindow = (int)m_dCongestionWindow; perf->pktFlightSize = getFlightSpan(); perf->msRTT = (double)m_iSRTT / 1000.0; perf->msSndTsbPdDelay = m_bPeerTsbPd ? m_iPeerTsbPdDelay_ms : 0; perf->msRcvTsbPdDelay = isOPT_TsbPd() ? m_iTsbPdDelay_ms : 0; perf->byteMSS = m_config.iMSS; perf->mbpsMaxBW = m_config.llMaxBW > 0 ? Bps2Mbps(m_config.llMaxBW) : m_CongCtl.ready() ? Bps2Mbps(m_CongCtl->sndBandwidth()) : 0; const int64_t availbw = m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth.load(); perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxSRTPayloadSize + pktHdrSize)); if (tryEnterCS(m_ConnectionLock)) { if (m_pSndBuffer) { if (instantaneous) { /* Get instant SndBuf instead of moving average for application-based Algorithm (such as NAE) in need of fast reaction to network condition changes. */ perf->pktSndBuf = m_pSndBuffer->getCurrBufSize((perf->byteSndBuf), (perf->msSndBuf)); } else { perf->pktSndBuf = m_pSndBuffer->getAvgBufSize((perf->byteSndBuf), (perf->msSndBuf)); } perf->byteSndBuf += (perf->pktSndBuf * pktHdrSize); //< perf->byteAvailSndBuf = (m_config.iSndBufSize - perf->pktSndBuf) * m_config.iMSS; } else { perf->byteAvailSndBuf = 0; perf->pktSndBuf = 0; perf->byteSndBuf = 0; perf->msSndBuf = 0; } if (m_pRcvBuffer) { perf->byteAvailRcvBuf = m_pRcvBuffer->getAvailBufSize() * m_config.iMSS; if (instantaneous) // no need for historical API for Rcv side { perf->pktRcvBuf = m_pRcvBuffer->getRcvDataSize(perf->byteRcvBuf, perf->msRcvBuf); } else { perf->pktRcvBuf = m_pRcvBuffer->getRcvAvgDataSize(perf->byteRcvBuf, perf->msRcvBuf); } } else { perf->byteAvailRcvBuf = 0; perf->pktRcvBuf = 0; perf->byteRcvBuf = 0; perf->msRcvBuf = 0; } leaveCS(m_ConnectionLock); } else { perf->byteAvailSndBuf = 0; perf->byteAvailRcvBuf = 0; perf->pktSndBuf = 0; perf->byteSndBuf = 0; perf->msSndBuf = 0; perf->byteRcvBuf = 0; perf->msRcvBuf = 0; } if (clear) { m_stats.traceSndDrop = 0; m_stats.traceRcvDrop = 0; m_stats.traceSndBytesDrop = 0; m_stats.traceRcvBytesDrop = 0; m_stats.traceRcvUndecrypt = 0; m_stats.traceRcvBytesUndecrypt = 0; m_stats.traceBytesSent = m_stats.traceBytesRecv = m_stats.traceBytesRetrans = 0; m_stats.traceBytesSentUniq = m_stats.traceBytesRecvUniq = 0; m_stats.traceSent = m_stats.traceRecv = m_stats.traceSentUniq = m_stats.traceRecvUniq = m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans = m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0; m_stats.sndDuration = 0; m_stats.traceRcvRetrans = 0; m_stats.traceRcvBelated = 0; m_stats.traceRcvBytesLoss = 0; m_stats.sndFilterExtra = 0; m_stats.rcvFilterExtra = 0; m_stats.rcvFilterSupply = 0; m_stats.rcvFilterLoss = 0; m_stats.tsLastSampleTime = currtime; } } bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) { // Special things that must be done HERE, not in SrtCongestion, // because it involves the input buffer in CUDT. It would be // slightly dangerous to give SrtCongestion access to it. // According to the rules, the congctl should be ready at the same // time when the sending buffer. For sanity check, check both first. if (!m_CongCtl.ready() || !m_pSndBuffer) { LOGC(rslog.Error, log << CONID() << "updateCC: CAN'T DO UPDATE - congctl " << (m_CongCtl.ready() ? "ready" : "NOT READY") << "; sending buffer " << (m_pSndBuffer ? "NOT CREATED" : "created")); return false; } HLOGC(rslog.Debug, log << "updateCC: EVENT:" << TransmissionEventStr(evt)); if (evt == TEV_INIT) { // only_input uses: // 0: in the beginning and when SRTO_MAXBW was changed // 1: SRTO_INPUTBW was changed // 2: SRTO_OHEADBW was changed EInitEvent only_input = arg.get(); // false = TEV_INIT_RESET: in the beginning, or when MAXBW was changed. if (only_input && m_config.llMaxBW) { HLOGC(rslog.Debug, log << CONID() << "updateCC/TEV_INIT: non-RESET stage and m_config.llMaxBW already set to " << m_config.llMaxBW); // Don't change } else // either m_config.llMaxBW == 0 or only_input == TEV_INIT_RESET { // Use the values: // - if SRTO_MAXBW is >0, use it. // - if SRTO_MAXBW == 0, use SRTO_INPUTBW + SRTO_OHEADBW // - if SRTO_INPUTBW == 0, pass 0 to requst in-buffer sampling // Bytes/s const int64_t bw = m_config.llMaxBW != 0 ? m_config.llMaxBW : // When used SRTO_MAXBW m_config.llInputBW != 0 ? withOverhead(m_config.llInputBW) : // SRTO_INPUTBW + SRT_OHEADBW 0; // When both MAXBW and INPUTBW are 0, request in-buffer sampling // Note: setting bw == 0 uses BW_INFINITE value in LiveCC m_CongCtl->updateBandwidth(m_config.llMaxBW, bw); if (only_input == TEV_INIT_OHEADBW) { // On updated SRTO_OHEADBW don't change input rate. // This only influences the call to withOverhead(). } else { // No need to calculate input rate if the bandwidth is set const bool disable_in_rate_calc = (bw != 0); m_pSndBuffer->resetInputRateSmpPeriod(disable_in_rate_calc); } HLOGC(rslog.Debug, log << CONID() << "updateCC/TEV_INIT: updating BW=" << m_config.llMaxBW << (only_input == TEV_INIT_RESET ? " (UNCHANGED)" : only_input == TEV_INIT_OHEADBW ? " (only Overhead)" : " (updated sampling rate)")); } } // This part is also required only by LiveCC, however not // moved there due to that it needs access to CSndBuffer. if (evt == TEV_ACK || evt == TEV_LOSSREPORT || evt == TEV_CHECKTIMER) { // Specific part done when MaxBW is set to 0 (auto) and InputBW is 0. // This requests internal input rate sampling. if (m_config.llMaxBW == 0 && m_config.llInputBW == 0) { // Get auto-calculated input rate, Bytes per second const int64_t inputbw = m_pSndBuffer->getInputRate(); /* * On blocked transmitter (tx full) and until connection closes, * auto input rate falls to 0 but there may be still lot of packet to retransmit * Calling updateBandwidth with 0 sets maxBW to default BW_INFINITE (1 Gbps) * and sendrate skyrockets for retransmission. * Keep previously set maximum in that case (inputbw == 0). */ if (inputbw >= 0) m_CongCtl->updateBandwidth(0, withOverhead(std::max(m_config.llMinInputBW, inputbw))); // Bytes/sec } } HLOGC(rslog.Debug, log << CONID() << "updateCC: emitting signal for EVENT:" << TransmissionEventStr(evt)); // Now execute a congctl-defined action for that event. EmitSignal(evt, arg); // This should be done with every event except ACKACK and SEND/RECEIVE // After any action was done by the congctl, update the congestion window and sending interval. if (evt != TEV_ACKACK && evt != TEV_SEND && evt != TEV_RECEIVE) { // This part comes from original UDT. // NOTE: THESE things come from CCC class: // - m_dPktSndPeriod // - m_dCWndSize m_tdSendInterval = microseconds_from((int64_t)m_CongCtl->pktSndPeriod_us()); m_dCongestionWindow = m_CongCtl->cgWindowSize(); #if ENABLE_HEAVY_LOGGING HLOGC(rslog.Debug, log << CONID() << "updateCC: updated values from congctl: interval=" << count_microseconds(m_tdSendInterval) << " us (" << "tk (" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" << std::setprecision(3) << m_dCongestionWindow); #endif } HLOGC(rslog.Debug, log << "udpateCC: finished handling for EVENT:" << TransmissionEventStr(evt)); return true; } void srt::CUDT::initSynch() { setupMutex(m_SendBlockLock, "SendBlock"); setupCond(m_SendBlockCond, "SendBlock"); setupCond(m_RecvDataCond, "RecvData"); setupMutex(m_SendLock, "Send"); setupMutex(m_RecvLock, "Recv"); setupMutex(m_RcvLossLock, "RcvLoss"); setupMutex(m_RecvAckLock, "RecvAck"); setupMutex(m_RcvBufferLock, "RcvBuffer"); setupMutex(m_ConnectionLock, "Connection"); setupMutex(m_StatsLock, "Stats"); setupCond(m_RcvTsbPdCond, "RcvTsbPd"); } void srt::CUDT::destroySynch() { releaseMutex(m_SendBlockLock); // Just in case, signal the CV, on which some // other thread is possibly waiting, because a // process hanging on a pthread_cond_wait would // cause the call to destroy a CV hang up. m_SendBlockCond.notify_all(); releaseCond(m_SendBlockCond); m_RecvDataCond.notify_all(); releaseCond(m_RecvDataCond); releaseMutex(m_SendLock); releaseMutex(m_RecvLock); releaseMutex(m_RcvLossLock); releaseMutex(m_RecvAckLock); releaseMutex(m_RcvBufferLock); releaseMutex(m_ConnectionLock); releaseMutex(m_StatsLock); m_RcvTsbPdCond.notify_all(); releaseCond(m_RcvTsbPdCond); } void srt::CUDT::releaseSynch() { SRT_ASSERT(m_bClosing); // wake up user calls CSync::lock_signal(m_SendBlockCond, m_SendBlockLock); enterCS(m_SendLock); leaveCS(m_SendLock); // Awake tsbpd() and srt_recv*(..) threads for them to check m_bClosing. CSync::lock_signal(m_RecvDataCond, m_RecvLock); CSync::lock_signal(m_RcvTsbPdCond, m_RecvLock); // Azquiring m_RcvTsbPdStartupLock protects race in starting // the tsbpd() thread in CUDT::processData(). // Wait for tsbpd() thread to finish. enterCS(m_RcvTsbPdStartupLock); if (m_RcvTsbPdThread.joinable()) { m_RcvTsbPdThread.join(); } leaveCS(m_RcvTsbPdStartupLock); // Acquiring the m_RecvLock it is assumed that both tsbpd() // and srt_recv*(..) threads will be aware about the state of m_bClosing. enterCS(m_RecvLock); leaveCS(m_RecvLock); } // [[using locked(m_RcvBufferLock)]]; int32_t srt::CUDT::ackDataUpTo(int32_t ack) { int acksize = CSeqNo::seqoff(m_iRcvLastSkipAck, ack); HLOGC(xtlog.Debug, log << "ackDataUpTo: %" << ack << " vs. current %" << m_iRcvLastSkipAck << " (signing off " << acksize << " packets)"); m_iRcvLastAck = ack; m_iRcvLastSkipAck = ack; // NOTE: This is new towards UDT and prevents spurious // wakeup of select/epoll functions when no new packets // were signed off for extraction. if (acksize > 0) { const int distance = m_pRcvBuffer->ackData(acksize); return CSeqNo::decseq(ack, distance); } // If nothing was confirmed, then use the current buffer span const int distance = m_pRcvBuffer->getRcvDataSize(); if (distance > 0) return CSeqNo::decseq(ack, distance); return ack; } namespace srt { #if ENABLE_HEAVY_LOGGING static void DebugAck(string hdr, int prev, int ack) { if (!prev) { HLOGC(xtlog.Debug, log << hdr << "ACK " << ack); return; } prev = CSeqNo::incseq(prev); int diff = CSeqNo::seqoff(prev, ack); if (diff < 0) { HLOGC(xtlog.Debug, log << hdr << "ACK ERROR: " << prev << "-" << ack << "(diff " << diff << ")"); return; } bool shorted = diff > 100; // sanity if (shorted) ack = CSeqNo::incseq(prev, 100); ostringstream ackv; for (; prev != ack; prev = CSeqNo::incseq(prev)) ackv << prev << " "; if (shorted) ackv << "..."; HLOGC(xtlog.Debug, log << hdr << "ACK (" << (diff + 1) << "): " << ackv.str() << ack); } #else static inline void DebugAck(string, int, int) {} #endif } void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rparam, int size) { CPacket ctrlpkt; setPacketTS(ctrlpkt, steady_clock::now()); int nbsent = 0; switch (pkttype) { case UMSG_ACK: // 010 - Acknowledgement { nbsent = sendCtrlAck(ctrlpkt, size); break; } case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement ctrlpkt.pack(pkttype, lparam); ctrlpkt.m_iID = m_PeerID; nbsent = m_pSndQueue->sendto(m_PeerAddr, 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_PeerAddr, ctrlpkt); enterCS(m_StatsLock); ++m_stats.sentNAK; ++m_stats.sentNAKTotal; leaveCS(m_StatsLock); } // Call with no arguments - get loss list from internal data. else if (m_pRcvLossList->getLossLength() > 0) { ScopedLock lock(m_RcvLossLock); // this is periodically NAK report; make sure NAK cannot be sent back too often // read loss list from the local receiver loss list int32_t *data = new int32_t[m_iMaxSRTPayloadSize / 4]; int losslen; m_pRcvLossList->getLossArray(data, losslen, m_iMaxSRTPayloadSize / 4); if (0 < losslen) { ctrlpkt.pack(pkttype, NULL, data, losslen * 4); ctrlpkt.m_iID = m_PeerID; nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); enterCS(m_StatsLock); ++m_stats.sentNAK; ++m_stats.sentNAKTotal; leaveCS(m_StatsLock); } delete[] data; } // update next NAK time, which should wait enough time for the retansmission, but not too long m_tdNAKInterval = microseconds_from(m_iSRTT + 4 * m_iRTTVar); // Fix the NAKreport period according to the congctl m_tdNAKInterval = microseconds_from(m_CongCtl->updateNAKInterval(count_microseconds(m_tdNAKInterval), m_RcvTimeWindow.getPktRcvSpeed(), m_pRcvLossList->getLossLength())); // This is necessary because a congctl need not wish to define // its own minimum interval, in which case the default one is used. if (m_tdNAKInterval < m_tdMinNakInterval) m_tdNAKInterval = m_tdMinNakInterval; break; } case UMSG_CGWARNING: // 100 - Congestion Warning ctrlpkt.pack(pkttype); ctrlpkt.m_iID = m_PeerID; nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); m_tsLastWarningTime = steady_clock::now(); break; case UMSG_KEEPALIVE: // 001 - Keep-alive ctrlpkt.pack(pkttype); ctrlpkt.m_iID = m_PeerID; nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); break; case UMSG_HANDSHAKE: // 000 - Handshake ctrlpkt.pack(pkttype, NULL, rparam, sizeof(CHandShake)); ctrlpkt.m_iID = m_PeerID; nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); break; case UMSG_SHUTDOWN: // 101 - Shutdown if (m_PeerID == 0) // Dont't send SHUTDOWN if we don't know peer ID. break; ctrlpkt.pack(pkttype); ctrlpkt.m_iID = m_PeerID; nbsent = m_pSndQueue->sendto(m_PeerAddr, 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_PeerAddr, 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_PeerAddr, ctrlpkt); break; case UMSG_EXT: // 0x7FFF - Resevered for future use break; default: break; } // Fix keepalive if (nbsent) m_tsLastSndTime.store(steady_clock::now()); } int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { SRT_ASSERT(ctrlpkt.getMsgTimeStamp() != 0); int32_t ack; int nbsent = 0; int local_prevack = 0; #if ENABLE_HEAVY_LOGGING struct SaveBack { int& target; const int& source; ~SaveBack() { target = source; } } l_saveback = { m_iDebugPrevLastAck, m_iRcvLastAck }; (void)l_saveback; // kill compiler warning: unused variable `l_saveback` [-Wunused-variable] local_prevack = m_iDebugPrevLastAck; string reason = "first lost"; // just for "a reason" of giving particular % for ACK #endif { // 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. ScopedLock lock(m_RcvLossLock); ack = m_pRcvLossList->getFirstLostSeq(); } // We don't need to check the length prematurely, // if length is 0, this will return SRT_SEQNO_NONE. // If so happened, simply use the latest received pkt + 1. if (ack == SRT_SEQNO_NONE) { ack = CSeqNo::incseq(m_iRcvCurrSeqNo); IF_HEAVY_LOGGING(reason = "expected next"); } if (m_iRcvLastAckAck == ack) { HLOGC(xtlog.Debug, log << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); return nbsent; } // 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(UMSG_ACK, NULL, &ack, size); ctrlpkt.m_iID = m_PeerID; nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); DebugAck("sendCtrl(lite):" + CONID(), local_prevack, ack); return nbsent; } // There are new received packets to acknowledge, update related information. /* tsbpd thread may also call ackData when skipping packet so protect code */ UniqueLock bufflock(m_RcvBufferLock); // IF ack %> m_iRcvLastAck if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0) { const int32_t first_seq SRT_ATR_UNUSED = ackDataUpTo(ack); InvertedLock un_bufflock (m_RcvBufferLock); #if ENABLE_EXPERIMENTAL_BONDING // This actually should be done immediately after the ACK pointers were // updated in this socket, but it can't be done inside this function due // to being run under a lock. // At this moment no locks are applied. The only lock used so far // was m_RcvBufferLock, but this was lifed above. At this moment // it is safe to apply any locks here. This function is affined // to CRcvQueue::worker thread, so it is free to apply locks as // required in the defined order. At present we only need the lock // on m_GlobControlLock to prevent the group from being deleted // in the meantime if (m_parent->m_GroupOf) { // Check is first done before locking to avoid unnecessary // mutex locking. The condition for this field is that it // can be either never set, already reset, or ever set // and possibly dangling. The re-check after lock eliminates // the dangling case. ScopedLock glock (s_UDTUnited.m_GlobControlLock); // Note that updateLatestRcv will lock m_GroupOf->m_GroupLock, // but this is an intended order. if (m_parent->m_GroupOf) { // A group may need to update the parallelly used idle links, // should it have any. Pass the current socket position in order // to skip it from the group loop. m_parent->m_GroupOf->updateLatestRcv(m_parent); } } #endif IF_HEAVY_LOGGING(int32_t oldack = m_iRcvLastSkipAck); // If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond, // signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which // will signal m_RecvDataCond when there's time to play for particular // data packet. HLOGC(xtlog.Debug, log << "ACK: clip %" << oldack << "-%" << ack << ", REVOKED " << CSeqNo::seqoff(ack, m_iRcvLastAck) << " from RCV buffer"); if (m_bTsbPd) { /* Newly acknowledged data, signal TsbPD thread */ UniqueLock rcvlock(m_RecvLock); CSync tscond(m_RcvTsbPdCond, rcvlock); // m_bTsbPdAckWakeup is protected by m_RecvLock in the tsbpd() thread if (m_bTsbPdAckWakeup) tscond.signal_locked(rcvlock); } else { { UniqueLock rdlock (m_RecvLock); CSync rdcond (m_RecvDataCond, rdlock); if (m_config.bSynRecving) { // signal a waiting "recv" call if there is any data available rdcond.signal_locked(rdlock); } // acknowledge any waiting epolls to read // fix SRT_EPOLL_IN event loss but rcvbuffer still have data: // 1. user call receive/receivemessage(about line number:6482) // 2. after read/receive, if rcvbuffer is empty, will set SRT_EPOLL_IN event to false // 3. but if we do not do some lock work here, will cause some sync problems between threads: // (1) user thread: call receive/receivemessage // (2) user thread: read data // (3) user thread: no data in rcvbuffer, set SRT_EPOLL_IN event to false // (4) receive thread: receive data and set SRT_EPOLL_IN to true // (5) user thread: set SRT_EPOLL_IN to false // 4. so , m_RecvLock must be used here to protect epoll event s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } #if ENABLE_EXPERIMENTAL_BONDING if (m_parent->m_GroupOf) { // See above explanation for double-checking ScopedLock glock (s_UDTUnited.m_GlobControlLock); if (m_parent->m_GroupOf) { // The current "APP reader" needs to simply decide as to whether // the next CUDTGroup::recv() call should return with no blocking or not. // When the group is read-ready, it should update its pollers as it sees fit. m_parent->m_GroupOf->updateReadState(m_SocketID, first_seq); } } #endif CGlobEvent::triggerEvent(); } } else if (ack == m_iRcvLastAck) { // If the ACK was just sent already AND elapsed time did not exceed RTT, if ((steady_clock::now() - m_tsLastAckTime) < (microseconds_from(m_iSRTT + 4 * m_iRTTVar))) { HLOGC(xtlog.Debug, log << "sendCtrl(UMSG_ACK): ACK %" << ack << " just sent - too early to repeat"); return nbsent; } } else { // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) LOGC(xtlog.Error, log << "sendCtrl(UMSG_ACK): IPE: curr %" << ack << " <% last %" << m_iRcvLastAck); return nbsent; } // [[using assert( ack >= m_iRcvLastAck && is_periodic_ack ) ]]; // [[using locked(m_RcvBufferLock)]]; // Send out the ACK only if has not been received by the sender before if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0) { // NOTE: The BSTATS feature turns on extra fields above size 6 // also known as ACKD_TOTAL_SIZE_VER100. int32_t data[ACKD_TOTAL_SIZE]; // Case you care, CAckNo::incack does exactly the same thing as // CSeqNo::incseq. Logically the ACK number is a different thing // than sequence number (it's a "journal" for ACK request-response, // and starts from 0, unlike sequence, which starts from a random // number), but still the numbers are from exactly the same domain. m_iAckSeqNo = CAckNo::incack(m_iAckSeqNo); data[ACKD_RCVLASTACK] = m_iRcvLastAck; data[ACKD_RTT] = m_iSRTT; 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 (steady_clock::now() - m_tsLastAckTime > m_tdACKInterval) { int rcvRate; int ctrlsz = ACKD_TOTAL_SIZE_UDTBASE * ACKD_FIELD_SIZE; // Minimum required size data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed((rcvRate)); data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth(); //>>Patch while incompatible (1.0.2) receiver floating around if (m_uPeerSrtVersion == SrtVersion(1, 0, 2)) { data[ACKD_RCVRATE] = rcvRate; // bytes/sec data[ACKD_XMRATE_VER102_ONLY] = data[ACKD_BANDWIDTH] * m_iMaxSRTPayloadSize; // bytes/sec ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102_ONLY; } else if (m_uPeerSrtVersion >= SrtVersion(1, 0, 3)) { // Normal, currently expected version. data[ACKD_RCVRATE] = rcvRate; // bytes/sec ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER101; } // ELSE: leave the buffer with ...UDTBASE size. ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ctrlsz); m_tsLastAckTime = steady_clock::now(); } else { ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL); } ctrlpkt.m_iID = m_PeerID; setPacketTS(ctrlpkt, steady_clock::now()); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); DebugAck("sendCtrl(UMSG_ACK): " + CONID(), local_prevack, ack); m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); enterCS(m_StatsLock); ++m_stats.sentACK; ++m_stats.sentACKTotal; leaveCS(m_StatsLock); } else { HLOGC(xtlog.Debug, log << "sendCtrl(UMSG_ACK): " << CONID() << "ACK %" << m_iRcvLastAck << " <=% ACKACK %" << m_iRcvLastAckAck << " - NOT SENDING ACK"); } return nbsent; } void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) { #if ENABLE_EXPERIMENTAL_BONDING // This is for the call of CSndBuffer::getMsgNoAt that returns // this value as a notfound-trap. int32_t msgno_at_last_acked_seq = SRT_MSGNO_CONTROL; bool is_group = m_parent->m_GroupOf; #endif // Update sender's loss list and acknowledge packets in the sender's buffer { // m_RecvAckLock protects sender's loss list and epoll ScopedLock ack_lock(m_RecvAckLock); const int offset = CSeqNo::seqoff(m_iSndLastDataAck, ackdata_seqno); // IF distance between m_iSndLastDataAck and ack is nonempty... if (offset <= 0) return; // update sending variables m_iSndLastDataAck = ackdata_seqno; #if ENABLE_EXPERIMENTAL_BONDING if (is_group) { // Get offset-1 because 'offset' points actually to past-the-end // of the sender buffer. We have already checked that offset is // at least 1. msgno_at_last_acked_seq = m_pSndBuffer->getMsgNoAt(offset-1); // Just keep this value prepared; it can't be updated exactly right // now because accessing the group needs some locks to be applied // with preserved the right locking order. } #endif // remove any loss that predates 'ack' (not to be considered loss anymore) m_pSndLossList->removeUpTo(CSeqNo::decseq(m_iSndLastDataAck)); // acknowledge the sending buffer (remove data that predate 'ack') m_pSndBuffer->ackData(offset); // acknowledde any waiting epolls to write s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); CGlobEvent::triggerEvent(); } #if ENABLE_EXPERIMENTAL_BONDING if (is_group) { // m_RecvAckLock is ordered AFTER m_GlobControlLock, so this can only // be done now that m_RecvAckLock is unlocked. ScopedLock glock (s_UDTUnited.m_GlobControlLock); if (m_parent->m_GroupOf) { HLOGC(inlog.Debug, log << "ACK: acking group sender buffer for #" << msgno_at_last_acked_seq); // Guard access to m_iSndAckedMsgNo field // Note: This can't be done inside CUDTGroup::ackMessage // because this function is also called from CUDT::checkNeedDrop // called from CUDT::sendmsg2 called from CUDTGroup::send, which // applies the lock on m_GroupLock already. ScopedLock glk (*m_parent->m_GroupOf->exp_groupLock()); // NOTE: ackMessage also accepts and ignores the trap representation // which is SRT_MSGNO_CONTROL. m_parent->m_GroupOf->ackMessage(msgno_at_last_acked_seq); } } #endif // insert this socket to snd list if it is not on the list yet const steady_clock::time_point currtime = steady_clock::now(); m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE, currtime); if (m_config.bSynSending) { CSync::lock_signal(m_SendBlockCond, m_SendBlockLock); } // record total time used for sending enterCS(m_StatsLock); m_stats.sndDuration += count_microseconds(currtime - m_stats.sndDurationCounter); m_stats.m_sndDurationTotal += count_microseconds(currtime - m_stats.sndDurationCounter); m_stats.sndDurationCounter = currtime; leaveCS(m_StatsLock); } void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime) { const int32_t* ackdata = (const int32_t*)ctrlpkt.m_pcData; const int32_t ackdata_seqno = ackdata[ACKD_RCVLASTACK]; // Check the value of ACK in case when it was some rogue peer if (ackdata_seqno < 0) { // This embraces all cases when the most significant bit is set, // as the variable is of a signed type. So, SRT_SEQNO_NONE is // included, but it also triggers for any other kind of invalid value. // This check MUST BE DONE before making any operation on this number. LOGC(inlog.Error, log << CONID() << "ACK: IPE/EPE: received invalid ACK value: " << ackdata_seqno << " " << std::hex << ackdata_seqno << " (IGNORED)"); return; } const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK; HLOGC(inlog.Debug, log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck << "]" << (isLiteAck ? "[LITE]" : "[FULL]")); updateSndLossListOnACK(ackdata_seqno); // Process a lite ACK if (isLiteAck) { if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { ScopedLock ack_lock(m_RecvAckLock); m_iFlowWindowSize = m_iFlowWindowSize - CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno); m_iSndLastAck = ackdata_seqno; // TODO: m_tsLastRspAckTime should be protected with m_RecvAckLock // because the sendmsg2 may want to change it at the same time. m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } return; } // Decide to send ACKACK or not { // Sequence number of the ACK packet const int32_t ack_seqno = ctrlpkt.getAckSeqNo(); // Send ACK acknowledgement (UMSG_ACKACK). // There can be less ACKACK packets in the stream, than the number of ACK packets. // Only send ACKACK every syn interval or if ACK packet with the sequence number // already acknowledged (with ACKACK) has come again, which probably means ACKACK was lost. if ((currtime - m_SndLastAck2Time > microseconds_from(COMM_SYN_INTERVAL_US)) || (ack_seqno == m_iSndLastAck2)) { sendCtrl(UMSG_ACKACK, &ack_seqno); m_iSndLastAck2 = ack_seqno; m_SndLastAck2Time = currtime; } } // // Begin of the new code with TLPKTDROP. // // Protect packet retransmission enterCS(m_RecvAckLock); // Check the validation of the ack if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0) { leaveCS(m_RecvAckLock); // this should not happen: attack or bug LOGC(gglog.Error, log << CONID() << "ATTACK/IPE: incoming ack seq " << ackdata_seqno << " exceeds current " << m_iSndCurrSeqNo << " by " << (CSeqNo::seqoff(m_iSndCurrSeqNo, ackdata_seqno) - 1) << "!"); m_bBroken = true; m_iBrokenCounter = 0; return; } if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { // Update Flow Window Size, must update before and together with m_iSndLastAck m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; m_iSndLastAck = ackdata_seqno; m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } /* * We must not ignore full ack received by peer * if data has been artificially acked by late packet drop. * Therefore, a distinct ack state is used for received Ack (iSndLastFullAck) * and ack position in send buffer (m_iSndLastDataAck). * Otherwise, when severe congestion causing packet drops (and m_iSndLastDataAck update) * occures, we drop received acks (as duplicates) and do not update stats like RTT, * which may go crazy and stay there, preventing proper stream recovery. */ if (CSeqNo::seqoff(m_iSndLastFullAck, ackdata_seqno) <= 0) { // discard it if it is a repeated ACK leaveCS(m_RecvAckLock); return; } m_iSndLastFullAck = ackdata_seqno; // // END of the new code with TLPKTDROP // leaveCS(m_RecvAckLock); #if ENABLE_EXPERIMENTAL_BONDING if (m_parent->m_GroupOf) { ScopedLock glock (s_UDTUnited.m_GlobControlLock); if (m_parent->m_GroupOf) { // Will apply m_GroupLock, ordered after m_GlobControlLock. // m_GlobControlLock is necessary for group existence. m_parent->m_GroupOf->updateWriteState(); } } #endif size_t acksize = ctrlpkt.getLength(); // TEMPORARY VALUE FOR CHECKING bool wrongsize = 0 != (acksize % ACKD_FIELD_SIZE); acksize = acksize / ACKD_FIELD_SIZE; // ACTUAL VALUE if (wrongsize) { // Issue a log, but don't do anything but skipping the "odd" bytes from the payload. LOGC(inlog.Warn, log << CONID() << "Received UMSG_ACK payload is not evened up to 4-byte based field size - cutting to " << acksize << " fields"); } // Start with checking the base size. if (acksize < ACKD_TOTAL_SIZE_SMALL) { LOGC(inlog.Warn, log << CONID() << "Invalid ACK size " << acksize << " fields - less than minimum required!"); // Ack is already interpreted, just skip further parts. return; } // This check covers fields up to ACKD_BUFFERLEFT. // Extract RTT estimate and RTTVar from the ACK packet. const int rtt = ackdata[ACKD_RTT]; const int rttvar = ackdata[ACKD_RTTVAR]; // Update the values of smoothed RTT and the variation in RTT samples // on subsequent RTT estimates extracted from the ACK packets // (during transmission). if (m_bIsFirstRTTReceived) { // Suppose transmission is bidirectional if sender is also receiving // data packets. enterCS(m_StatsLock); const bool bPktsReceived = m_stats.recvTotal != 0; leaveCS(m_StatsLock); if (bPktsReceived) // Transmission is bidirectional. { // RTT value extracted from the ACK packet (rtt) is already smoothed // RTT obtained at the receiver side. Apply EWMA anyway for the second // time on the sender side. Ignore initial values which might arrive // after the smoothed RTT on the sender side has been // reset to the very first RTT sample received from the receiver. // TODO: The case of bidirectional transmission requires further // improvements and testing. Double smoothing is applied here to be // consistent with the previous behavior. int crtt = m_iSRTT.load(), crttvar = m_iRTTVar.load(); if (crtt != INITIAL_RTT && rttvar != INITIAL_RTTVAR) { crttvar = avg_iir<4>(crttvar, abs(crtt - crtt)); crtt = avg_iir<8>(crtt, crtt); } m_iSRTT = crtt; m_iRTTVar = crttvar; } else // Transmission is unidirectional. { // Simply take the values of smoothed RTT and RTT variance from // the ACK packet. m_iSRTT = rtt; m_iRTTVar = rttvar; } } // Reset the value of smoothed RTT to the first real RTT estimate extracted // from an ACK after initialization (at the beginning of transmission). // In case of resumed connection over the same network, the very first RTT // value sent within an ACK will be taken from cache and equal to previous // connection's final smoothed RTT value. The reception of such a value // will also trigger the smoothed RTT reset at the sender side. else if (rtt != INITIAL_RTT && rttvar != INITIAL_RTTVAR) { m_iSRTT = rtt; m_iRTTVar = rttvar; m_bIsFirstRTTReceived = true; } #if SRT_DEBUG_RTT s_rtt_trace.trace(currtime, "ACK", rtt, rttvar, m_bIsFirstRTTReceived, m_stats.recvTotal, m_iSRTT, m_iRTTVar); #endif /* Version-dependent fields: * Original UDT (total size: ACKD_TOTAL_SIZE_SMALL): * ACKD_RCVLASTACK * ACKD_RTT * ACKD_RTTVAR * ACKD_BUFFERLEFT * Additional UDT fields, not always attached: * ACKD_RCVSPEED * ACKD_BANDWIDTH * SRT extension since v1.0.1: * ACKD_RCVRATE * SRT extension in v1.0.2 only: * ACKD_XMRATE_VER102_ONLY */ if (acksize > ACKD_TOTAL_SIZE_SMALL) { // This means that ACKD_RCVSPEED and ACKD_BANDWIDTH fields are available. int pktps = ackdata[ACKD_RCVSPEED]; int bandwidth = ackdata[ACKD_BANDWIDTH]; int bytesps; /* SRT v1.0.2 Bytes-based stats: bandwidth (pcData[ACKD_XMRATE_VER102_ONLY]) and delivery rate (pcData[ACKD_RCVRATE]) in * bytes/sec instead of pkts/sec */ /* SRT v1.0.3 Bytes-based stats: only delivery rate (pcData[ACKD_RCVRATE]) in bytes/sec instead of pkts/sec */ if (acksize > ACKD_TOTAL_SIZE_UDTBASE) bytesps = ackdata[ACKD_RCVRATE]; else bytesps = pktps * m_iMaxSRTPayloadSize; m_iBandwidth = avg_iir<8>(m_iBandwidth.load(), bandwidth); m_iDeliveryRate = avg_iir<8>(m_iDeliveryRate.load(), pktps); m_iByteDeliveryRate = avg_iir<8>(m_iByteDeliveryRate.load(), bytesps); // Update Estimated Bandwidth and packet delivery rate // m_iRcvRate = m_iDeliveryRate; // ^^ This has been removed because with the SrtCongestion class // instead of reading the m_iRcvRate local field this will read // cudt->deliveryRate() instead. } checkSndTimers(REGEN_KM); updateCC(TEV_ACK, EventVariant(ackdata_seqno)); enterCS(m_StatsLock); ++m_stats.recvACK; ++m_stats.recvACKTotal; leaveCS(m_StatsLock); } void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival) { int32_t ack = 0; // Calculate RTT estimate on the receiver side based on ACK/ACKACK pair. const int rtt = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ack, tsArrival); if (rtt == -1) { if (ctrlpkt.getAckSeqNo() > (m_iAckSeqNo - static_cast(ACK_WND_SIZE)) && ctrlpkt.getAckSeqNo() <= m_iAckSeqNo) { LOGC(inlog.Warn, log << CONID() << "ACKACK out of order, skipping RTT calculation " << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo << ", RTT (EWMA): " << m_iSRTT << ")"); return; } LOGC(inlog.Error, log << CONID() << "IPE: ACK record not found, can't estimate RTT " << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo << ", RTT (EWMA): " << m_iSRTT << ")"); return; } if (rtt <= 0) { LOGC(inlog.Error, log << CONID() << "IPE: invalid RTT estimate " << rtt << ", possible time shift. Clock: " << SRT_SYNC_CLOCK_STR); return; } // If increasing delay is detected. // sendCtrl(UMSG_CGWARNING); // Update the values of smoothed RTT and the variation in RTT samples // on subsequent RTT samples (during transmission). if (m_bIsFirstRTTReceived) { m_iRTTVar = avg_iir<4>(m_iRTTVar.load(), abs(rtt - m_iSRTT.load())); m_iSRTT = avg_iir<8>(m_iSRTT.load(), rtt); } // Reset the value of smoothed RTT on the first RTT sample after initialization // (at the beginning of transmission). // In case of resumed connection over the same network, the initial RTT // value will be taken from cache and equal to previous connection's // final smoothed RTT value. else { m_iSRTT = rtt; m_iRTTVar = rtt / 2; m_bIsFirstRTTReceived = true; } #if SRT_DEBUG_RTT s_rtt_trace.trace(tsArrival, "ACKACK", rtt, -1, m_bIsFirstRTTReceived, -1, m_iSRTT, m_iRTTVar); #endif updateCC(TEV_ACKACK, EventVariant(ack)); // This function will put a lock on m_RecvLock by itself, as needed. // It must be done inside because this function reads the current time // and if waiting for the lock has caused a delay, the time will be // inaccurate. Additionally it won't lock if TSBPD mode is off, and // won't update anything. Note that if you set TSBPD mode and use // srt_recvfile (which doesn't make any sense), you'll have a deadlock. if (m_config.bDriftTracer) { const bool drift_updated SRT_ATR_UNUSED = m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), rtt); #if ENABLE_EXPERIMENTAL_BONDING if (drift_updated && m_parent->m_GroupOf) { ScopedLock glock(s_UDTUnited.m_GlobControlLock); if (m_parent->m_GroupOf) { m_parent->m_GroupOf->synchronizeDrift(this); } } #endif } // Update last ACK that has been received by the sender if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0) m_iRcvLastAckAck = ack; } void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { const int32_t* losslist = (int32_t*)(ctrlpkt.m_pcData); const size_t losslist_len = ctrlpkt.getLength() / 4; bool secure = true; // This variable is used in "normal" logs, so it may cause a warning // when logging is forcefully off. int32_t wrong_loss SRT_ATR_UNUSED = CSeqNo::m_iMaxSeqNo; // protect packet retransmission { ScopedLock ack_lock(m_RecvAckLock); // 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. const int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]); const int32_t losslist_hi = losslist[i + 1]; // specification means that the consecutive cell has been already interpreted. ++i; HLOGF(inlog.Debug, "%sreceived UMSG_LOSSREPORT: %d-%d (%d packets)...", CONID().c_str(), losslist_lo, losslist_hi, CSeqNo::seqoff(losslist_lo, losslist_hi) + 1); if ((CSeqNo::seqcmp(losslist_lo, losslist_hi) > 0) || (CSeqNo::seqcmp(losslist_hi, m_iSndCurrSeqNo) > 0)) { LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT rng " << losslist_lo << " - " << losslist_hi << " with last sent " << m_iSndCurrSeqNo << " - DISCARDING"); // seq_a must not be greater than seq_b; seq_b must not be greater than the most recent sent seq secure = false; wrong_loss = losslist_hi; break; } int num = 0; // IF losslist_lo %>= m_iSndLastAck if (CSeqNo::seqcmp(losslist_lo, m_iSndLastAck) >= 0) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << losslist_lo << " - " << losslist_hi << " to loss list"); num = m_pSndLossList->insert(losslist_lo, losslist_hi); } // ELSE IF losslist_hi %>= m_iSndLastAck 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. HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list"); num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); } else { // This should be treated as IPE, but this may happen in one situtation: // - redundancy second link (ISN was screwed up initially, but late towards last sent) // - initial DROPREQ was lost // This just causes repeating DROPREQ, as when the receiver continues sending // LOSSREPORT, it's probably UNAWARE OF THE SITUATION. // // When this DROPREQ gets lost in UDP again, the receiver will do one of these: // - repeatedly send LOSSREPORT (as per NAKREPORT), so this will happen again // - finally give up rexmit request as per TLPKTDROP (DROPREQ should make // TSBPD wake up should it still wait for new packets to get ACK-ed) HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: IGNORED with SndLastAck=%" << m_iSndLastAck << ": %" << losslist_lo << "-" << losslist_hi << " - sending DROPREQ (IPE or DROPREQ lost with ISN screw)"); // This means that the loss touches upon a range that wasn't ever sent. // Normally this should never happen, but this might be a case when the // ISN FIX for redundant connection was missed. // In distinction to losslist, DROPREQ has always a range // always just one range, and the data are , with no range bit. int32_t seqpair[2] = { losslist_lo, losslist_hi }; const int32_t no_msgno = 0; // We don't know - this wasn't ever sent sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); } enterCS(m_StatsLock); m_stats.traceSndLoss += num; m_stats.sndLossTotal += num; leaveCS(m_StatsLock); } else if (CSeqNo::seqcmp(losslist[i], m_iSndLastAck) >= 0) { if (CSeqNo::seqcmp(losslist[i], m_iSndCurrSeqNo) > 0) { LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT pkt %" << losslist[i] << " with last sent %" << m_iSndCurrSeqNo << " - DISCARDING"); // seq_a must not be greater than the most recent sent seq secure = false; wrong_loss = losslist[i]; break; } HLOGC(inlog.Debug, log << CONID() << "rcv LOSSREPORT: %" << losslist[i] << " (1 packet)"); const int num = m_pSndLossList->insert(losslist[i], losslist[i]); enterCS(m_StatsLock); m_stats.traceSndLoss += num; m_stats.sndLossTotal += num; leaveCS(m_StatsLock); } } } updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len)); if (!secure) { LOGC(inlog.Warn, log << CONID() << "out-of-band LOSSREPORT received; BUG or ATTACK - last sent %" << m_iSndCurrSeqNo << " vs loss %" << wrong_loss); // this should not happen: attack or bug m_bBroken = true; m_iBrokenCounter = 0; return; } // the lost packet (retransmission) should be sent out immediately m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE); enterCS(m_StatsLock); ++m_stats.recvNAK; ++m_stats.recvNAKTotal; leaveCS(m_StatsLock); } void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) { CHandShake req; req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength()); HLOGC(inlog.Debug, log << CONID() << "processCtrl: got HS: " << req.show()); if ((req.m_iReqType > URQ_INDUCTION_TYPES) // acually it catches URQ_INDUCTION and URQ_ERROR_* symbols...??? || (m_config.bRendezvous && (req.m_iReqType != URQ_AGREEMENT))) // rnd sends AGREEMENT in rsp to CONCLUSION { // The peer side has not received the handshake message, so it keeps querying // resend the handshake packet // This condition embraces cases when: // - this is normal accept() and URQ_INDUCTION was received // - this is rendezvous accept() and there's coming any kind of URQ except AGREEMENT (should be RENDEZVOUS // or CONCLUSION) // - this is any of URQ_ERROR_* - well... CHandShake initdata; initdata.m_iISN = m_iISN; initdata.m_iMSS = m_config.iMSS; initdata.m_iFlightFlagSize = m_config.iFlightFlagSize; // For rendezvous we do URQ_WAVEAHAND/URQ_CONCLUSION --> URQ_AGREEMENT. // For client-server we do URQ_INDUCTION --> URQ_CONCLUSION. initdata.m_iReqType = (!m_config.bRendezvous) ? URQ_CONCLUSION : URQ_AGREEMENT; initdata.m_iID = m_SocketID; uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; bool have_hsreq = false; if (req.m_iVersion > HS_VERSION_UDT4) { initdata.m_iVersion = HS_VERSION_SRT1; // if I remember correctly, this is induction/listener... const int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); if (hs_flags != 0) // has SRT extensions { HLOGC(inlog.Debug, log << CONID() << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType) << " WITH SRT ext"); have_hsreq = interpretSrtHandshake(req, ctrlpkt, (kmdata), (&kmdatasize)); if (!have_hsreq) { initdata.m_iVersion = 0; m_RejectReason = SRT_REJ_ROGUE; initdata.m_iReqType = URQFailure(m_RejectReason); } else { // Extensions are added only in case of CONCLUSION (not AGREEMENT). // Actually what is expected here is that this may either process the // belated-repeated handshake from a caller (and then it's CONCLUSION, // and should be added with HSRSP/KMRSP), or it's a belated handshake // of Rendezvous when it has already considered itself connected. // Sanity check - according to the rules, there should be no such situation if (m_config.bRendezvous && m_SrtHsSide == HSD_RESPONDER) { LOGC(inlog.Error, log << CONID() << "processCtrl/HS: IPE???: RESPONDER should receive all its handshakes in " "handshake phase."); } // The 'extension' flag will be set from this variable; set it to false // in case when the AGREEMENT response is to be sent. have_hsreq = initdata.m_iReqType == URQ_CONCLUSION; HLOGC(inlog.Debug, log << CONID() << "processCtrl/HS: processing ok, reqtype=" << RequestTypeStr(initdata.m_iReqType) << " kmdatasize=" << kmdatasize); } } else { HLOGC(inlog.Debug, log << CONID() << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType)); } } else { initdata.m_iVersion = HS_VERSION_UDT4; kmdatasize = 0; // HSv4 doesn't add any extensions, no KMX } initdata.m_extension = have_hsreq; HLOGC(inlog.Debug, log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(initdata.m_iReqType) << (have_hsreq ? " WITH SRT HS response extensions" : "")); CPacket response; response.setControl(UMSG_HANDSHAKE); response.allocate(m_iMaxSRTPayloadSize); // If createSrtHandshake failed, don't send anything. Actually it can only fail on IPE. // There is also no possible IPE condition in case of HSv4 - for this version it will always return true. if (createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (response), (initdata))) { response.m_iID = m_PeerID; setPacketTS(response, steady_clock::now()); const int nbsent = m_pSndQueue->sendto(m_PeerAddr, response); if (nbsent) { m_tsLastSndTime.store(steady_clock::now()); } } } else { HLOGC(inlog.Debug, log << CONID() << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED."); } } void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) { { const bool using_rexmit_flag = m_bPeerRexmitFlag; UniqueLock rlock(m_RecvLock); m_pRcvBuffer->dropMsg(ctrlpkt.getMsgSeq(using_rexmit_flag), using_rexmit_flag); // When the drop request was received, it means that there are // packets for which there will never be ACK sent; if the TSBPD thread // is currently in the ACK-waiting state, it may never exit. if (m_bTsbPd) { HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); CSync cc(m_RcvTsbPdCond, rlock); cc.signal_locked(rlock); } } const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData; dropFromLossLists(dropdata[0], dropdata[1]); // move forward with current recv seq no. // SYMBOLIC: // if (dropdata[0] <=% 1 +% m_iRcvCurrSeqNo // && dropdata[1] >% m_iRcvCurrSeqNo ) if ((CSeqNo::seqcmp(dropdata[0], CSeqNo::incseq(m_iRcvCurrSeqNo)) <= 0) && (CSeqNo::seqcmp(dropdata[1], m_iRcvCurrSeqNo) > 0)) { HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %" << dropdata[0] << "-" << dropdata[1] << " <-- set as current seq"); m_iRcvCurrSeqNo = dropdata[1]; } else { HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %" << dropdata[0] << "-" << dropdata[1] << " current %" << m_iRcvCurrSeqNo); } } void srt::CUDT::processCtrlShutdown() { m_bShutdown = true; m_bClosing = true; m_bBroken = true; m_iBrokenCounter = 60; // This does the same as it would happen on connection timeout, // just we know about this state prematurely thanks to this message. updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS! } void srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "CONTROL EXT MSG RECEIVED:" << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ", value=" << ctrlpkt.getExtendedType()); // This has currently two roles in SRT: // - HSv4 (legacy) handshake // - refreshed KMX (initial KMX is done still in the HS process in HSv5) const bool understood = processSrtMsg(&ctrlpkt); // CAREFUL HERE! This only means that this update comes from the UMSG_EXT // message received, REGARDLESS OF WHAT IT IS. This version doesn't mean // the handshake version, but the reason of calling this function. // // Fortunately, the only messages taken into account in this function // are HSREQ and HSRSP, which should *never* be interchanged when both // parties are HSv5. if (understood) { if (ctrlpkt.getExtendedType() == SRT_CMD_HSREQ || ctrlpkt.getExtendedType() == SRT_CMD_HSRSP) { updateAfterSrtHandshake(HS_VERSION_UDT4); } } else { updateCC(TEV_CUSTOM, EventVariant(&ctrlpkt)); } } void srt::CUDT::processCtrl(const CPacket &ctrlpkt) { // Just heard from the peer, reset the expiration count. m_iEXPCount = 1; const steady_clock::time_point currtime = steady_clock::now(); m_tsLastRspTime = currtime; HLOGC(inlog.Debug, log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.m_iID); switch (ctrlpkt.getType()) { case UMSG_ACK: // 010 - Acknowledgement processCtrlAck(ctrlpkt, currtime); break; case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement processCtrlAckAck(ctrlpkt, currtime); break; case UMSG_LOSSREPORT: // 011 - Loss Report processCtrlLossReport(ctrlpkt); break; case UMSG_CGWARNING: // 100 - Delay Warning // One way packet delay is increasing, so decrease the sending rate m_tdSendInterval = (m_tdSendInterval.load() * 1125) / 1000; // XXX Note as interesting fact: this is only prepared for handling, // but nothing in the code is sending this message. Probably predicted // for a custom congctl. There's a predicted place to call it under // UMSG_ACKACK handling, but it's commented out. break; case UMSG_KEEPALIVE: // 001 - Keep-alive handleKeepalive(ctrlpkt.m_pcData, ctrlpkt.getLength()); break; case UMSG_HANDSHAKE: // 000 - Handshake processCtrlHS(ctrlpkt); break; case UMSG_SHUTDOWN: // 101 - Shutdown processCtrlShutdown(); break; case UMSG_DROPREQ: // 111 - Msg drop request processCtrlDropReq(ctrlpkt); break; case UMSG_PEERERROR: // 1000 - An error has happened to the peer side // int err_type = packet.getAddInfo(); // currently only this error is signalled from the peer side // if recvfile() failes (e.g., due to disk fail), blcoked sendfile/send should return immediately // giving the app a chance to fix the issue m_bPeerHealth = false; break; case UMSG_EXT: // 0x7FFF - reserved and user defined messages processCtrlUserDefined(ctrlpkt); break; default: break; } } void srt::CUDT::updateSrtRcvSettings() { // CHANGED: we need to apply the tsbpd delay only for socket TSBPD. // For Group TSBPD the buffer will have to deliver packets always on request // by sequence number, although the buffer will have to solve all the TSBPD // things internally anyway. Extracting by sequence number means only that // the packet can be retrieved from the buffer before its time to play comes // (unlike in normal situation when reading directly from socket), however // its time to play shall be properly defined. // XXX m_bGroupTsbPd is ignored with SRT_ENABLE_APP_READER if (m_bTsbPd || m_bGroupTsbPd) { /* We are TsbPd receiver */ enterCS(m_RecvLock); m_pRcvBuffer->setRcvTsbPdMode(m_tsRcvPeerStartTime, milliseconds_from(m_iTsbPdDelay_ms)); leaveCS(m_RecvLock); HLOGF(cnlog.Debug, "AFTER HS: Set Rcv TsbPd mode%s: delay=%u.%03us RCV START: %s", (m_bGroupTsbPd ? " (AS GROUP MEMBER)" : ""), m_iTsbPdDelay_ms/1000, // XXX use FormatDuration ? m_iTsbPdDelay_ms%1000, FormatTime(m_tsRcvPeerStartTime).c_str()); } else { HLOGC(cnlog.Debug, log << "AFTER HS: Rcv TsbPd mode not set"); } } void srt::CUDT::updateSrtSndSettings() { if (m_bPeerTsbPd) { /* We are TsbPd sender */ // XXX Check what happened here. // m_iPeerTsbPdDelay_ms = m_CongCtl->getSndPeerTsbPdDelay();// + ((m_iSRTT + (4 * m_iRTTVar)) / 1000); /* * For sender to apply Too-Late Packet Drop * option (m_bTLPktDrop) must be enabled and receiving peer shall support it */ HLOGF(cnlog.Debug, "AFTER HS: Set Snd TsbPd mode %s TLPktDrop: delay=%d.%03ds START TIME: %s", m_bPeerTLPktDrop ? "with" : "without", m_iPeerTsbPdDelay_ms/1000, m_iPeerTsbPdDelay_ms%1000, FormatTime(m_stats.tsStartTime).c_str()); } else { HLOGC(cnlog.Debug, log << "AFTER HS: Snd TsbPd mode not set"); } } void srt::CUDT::updateAfterSrtHandshake(int hsv) { HLOGC(cnlog.Debug, log << "updateAfterSrtHandshake: HS version " << hsv); // This is blocked from being run in the "app reader" version because here // every socket does its TsbPd independently, just the sequence screwup is // done and the application reader sorts out packets by sequence numbers, // but only when they are signed off by TsbPd. // The only possibility here is one of these two: // - Agent is RESPONDER and it receives HSREQ. // - Agent is INITIATOR and it receives HSRSP. // // In HSv4, INITIATOR is sender and RESPONDER is receiver. // In HSv5, both are sender AND receiver. // // This function will be called only ONCE in this // instance, through either HSREQ or HSRSP. #if ENABLE_HEAVY_LOGGING const char* hs_side[] = { "DRAW", "INITIATOR", "RESPONDER" }; #if ENABLE_EXPERIMENTAL_BONDING string grpspec; if (m_parent->m_GroupOf) { ScopedLock glock (s_UDTUnited.m_GlobControlLock); grpspec = m_parent->m_GroupOf ? " group=$" + Sprint(m_parent->m_GroupOf->id()) : string(); } #else const char* grpspec = ""; #endif HLOGC(cnlog.Debug, log << "updateAfterSrtHandshake: version=" << m_ConnRes.m_iVersion << " side=" << hs_side[m_SrtHsSide] << grpspec); #endif if (hsv > HS_VERSION_UDT4) { updateSrtRcvSettings(); updateSrtSndSettings(); } else if (m_SrtHsSide == HSD_INITIATOR) { // HSv4 INITIATOR is sender updateSrtSndSettings(); } else { // HSv4 RESPONDER is receiver updateSrtRcvSettings(); } } int srt::CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origintime) { // protect m_iSndLastDataAck from updating by ACK processing UniqueLock ackguard(m_RecvAckLock); const steady_clock::time_point time_now = steady_clock::now(); const steady_clock::time_point time_nak = time_now - microseconds_from(m_iSRTT - 4 * m_iRTTVar); while ((w_packet.m_iSeqNo = m_pSndLossList->popLostSeq()) >= 0) { // XXX See the note above the m_iSndLastDataAck declaration in core.h // This is the place where the important sequence numbers for // sender buffer are actually managed by this field here. const int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_packet.m_iSeqNo); if (offset < 0) { // XXX Likely that this will never be executed because if the upper // sequence is not in the sender buffer, then most likely the loss // was completely ignored. LOGC(qrlog.Error, log << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " << w_packet.m_iSeqNo << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset << ". Continue"); // No matter whether this is right or not (maybe the attack case should be // considered, and some LOSSREPORT flood prevention), send the drop request // to the peer. int32_t seqpair[2] = { w_packet.m_iSeqNo, CSeqNo::decseq(m_iSndLastDataAck) }; w_packet.m_iMsgNo = 0; // Message number is not known, setting all 32 bits to 0. HLOGC(qrlog.Debug, log << "PEER reported LOSS not from the sending buffer - requesting DROP: " << "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:" << seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)"); sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair)); continue; } if (m_bPeerNakReport && m_config.iRetransmitAlgo != 0) { const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset); if (tsLastRexmit >= time_nak) { HLOGC(qrlog.Debug, log << CONID() << "REXMIT: ignoring seqno " << w_packet.m_iSeqNo << ", last rexmit " << (is_zero(tsLastRexmit) ? "never" : FormatTime(tsLastRexmit)) << " RTT=" << m_iSRTT << " RTTVar=" << m_iRTTVar << " now=" << FormatTime(time_now)); continue; } } int msglen; const int payload = m_pSndBuffer->readData(offset, (w_packet), (w_origintime), (msglen)); if (payload == -1) { int32_t seqpair[2]; seqpair[0] = w_packet.m_iSeqNo; SRT_ASSERT(msglen >= 1); seqpair[1] = CSeqNo::incseq(seqpair[0], msglen - 1); HLOGC(qrlog.Debug, log << "IPE: loss-reported packets not found in SndBuf - requesting DROP: " << "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " msglen=" << msglen << " SEQ:" << seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)"); sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair)); // only one msg drop request is necessary m_pSndLossList->removeUpTo(seqpair[1]); // skip all dropped packets m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, seqpair[1]); continue; } else if (payload == 0) continue; // At this point we no longer need the ACK lock, // because we are going to return from the function. // Therefore unlocking in order not to block other threads. ackguard.unlock(); enterCS(m_StatsLock); ++m_stats.traceRetrans; ++m_stats.retransTotal; m_stats.traceBytesRetrans += payload; m_stats.bytesRetransTotal += payload; leaveCS(m_StatsLock); // Despite the contextual interpretation of packet.m_iMsgNo around // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular // case we can be sure that this is exactly the value of PH_MSGNO as a bitset. // So, set here the rexmit flag if the peer understands it. if (m_bPeerRexmitFlag) { w_packet.m_iMsgNo |= PACKET_SND_REXMIT; } return payload; } return 0; } std::pair srt::CUDT::packData(CPacket& w_packet) { int payload = 0; bool probe = false; steady_clock::time_point origintime; bool new_packet_packed = false; bool filter_ctl_pkt = false; int kflg = EK_NOENC; const steady_clock::time_point enter_time = steady_clock::now(); if (!is_zero(m_tsNextSendTime) && enter_time > m_tsNextSendTime) { m_tdSendTimeDiff = m_tdSendTimeDiff.load() + (enter_time - m_tsNextSendTime); } string reason = "reXmit"; ScopedLock connectguard(m_ConnectionLock); // If a closing action is done simultaneously, then // m_bOpened should already be false, and it's set // just before releasing this lock. // // If this lock is caught BEFORE the closing could // start the dissolving process, this process will // not be started until this function is finished. if (!m_bOpened) return std::make_pair(0, enter_time); payload = packLostData((w_packet), (origintime)); if (payload > 0) { reason = "reXmit"; } else if (m_PacketFilter && m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_pCryptoControl->getSndCryptoFlags(), (w_packet))) { HLOGC(qslog.Debug, log << "filter: filter/CTL packet ready - packing instead of data."); payload = (int) w_packet.getLength(); reason = "filter"; filter_ctl_pkt = true; // Mark that this packet ALREADY HAS timestamp field and it should not be set // Stats { ScopedLock lg(m_StatsLock); ++m_stats.sndFilterExtra; ++m_stats.sndFilterExtraTotal; } } else { // If no loss, and no packetfilter control packet, pack a new packet. // Check the congestion/flow window limit const int cwnd = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow)); const int flightspan = getFlightSpan(); if (cwnd > flightspan) { // XXX Here it's needed to set kflg to msgno_bitset in the block stored in the // send buffer. This should be somehow avoided, the crypto flags should be set // together with encrypting, and the packet should be sent as is, when rexmitting. // It would be nice to research as to whether CSndBuffer::Block::m_iMsgNoBitset field // isn't a useless redundant state copy. If it is, then taking the flags here can be removed. kflg = m_pCryptoControl->getSndCryptoFlags(); payload = m_pSndBuffer->readData((w_packet), (origintime), kflg); if (payload) { // A CHANGE. The sequence number is currently added to the packet // when scheduling, not when extracting. This is a inter-migration form, // so still override the value, but trace it. m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo); // Do this checking only for groups and only at the very first moment, // when there's still nothing in the buffer. Otherwise there will be // a serious data discrepancy between the agent and the peer. // After increasing by 1, but being previously set as ISN-1, this should be == ISN, // if this is the very first packet to send. #if ENABLE_EXPERIMENTAL_BONDING // Fortunately here is only the procedure that verifies if the extraction // sequence is moved due to the difference between ISN caught during the existing // transmission and the first sequence possible to be used at the first sending // instruction. The group itself isn't being accessed. if (m_parent->m_GroupOf && m_iSndCurrSeqNo != w_packet.m_iSeqNo && m_iSndCurrSeqNo == m_iISN) { const int packetspan = CSeqNo::seqcmp(w_packet.m_iSeqNo, m_iSndCurrSeqNo); HLOGC(qslog.Debug, log << CONID() << "packData: Fixing EXTRACTION sequence " << m_iSndCurrSeqNo << " from SCHEDULING sequence " << w_packet.m_iSeqNo << " DIFF: " << packetspan << " STAMP:" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); // This is the very first packet to be sent; so there's nothing in // the sending buffer yet, and therefore we are in a situation as just // after connection. No packets in the buffer, no packets are sent, // no ACK to be awaited. We can screw up all the variables that are // initialized from ISN just after connection. // // Additionally send the drop request to the peer so that it // won't stupidly request the packets to be retransmitted. // Don't do it if the difference isn't positive or exceeds the threshold. if (packetspan > 0) { int32_t seqpair[2]; seqpair[0] = m_iSndCurrSeqNo; seqpair[1] = w_packet.m_iSeqNo; HLOGC(qslog.Debug, log << "... sending INITIAL DROP (ISN FIX): " << "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:" << seqpair[0] << " - " << seqpair[1] << "(" << packetspan << " packets)"); sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair)); // In case when this message is lost, the peer will still get the // UMSG_DROPREQ message when the agent realizes that the requested // packet are not present in the buffer (preadte the send buffer). } } else #endif { HLOGC(qslog.Debug, log << CONID() << "packData: Applying EXTRACTION sequence " << m_iSndCurrSeqNo << " over SCHEDULING sequence " << w_packet.m_iSeqNo << " DIFF: " << CSeqNo::seqcmp(m_iSndCurrSeqNo, w_packet.m_iSeqNo) << " STAMP:" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); #if ENABLE_EXPERIMENTAL_BONDING HLOGC(qslog.Debug, log << "... CONDITION: IN GROUP: " << (m_parent->m_GroupOf ? "yes":"no") << " extraction-seq=" << m_iSndCurrSeqNo << " scheduling-seq=" << w_packet.m_iSeqNo << " ISN=" << m_iISN); #endif // Do this always when not in a group, w_packet.m_iSeqNo = m_iSndCurrSeqNo; } // every 16 (0xF) packets, a packet pair is sent if ((w_packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) probe = true; new_packet_packed = true; } else { m_tsNextSendTime = steady_clock::time_point(); m_tdSendTimeDiff = steady_clock::duration(); return std::make_pair(0, enter_time); } } else { HLOGC(qslog.Debug, log << "packData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_dCongestionWindow << ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << flightspan); m_tsNextSendTime = steady_clock::time_point(); m_tdSendTimeDiff = steady_clock::duration(); return std::make_pair(0, enter_time); } reason = "normal"; } // Normally packet.m_iTimeStamp field is set exactly here, // usually as taken from m_stats.tsStartTime and current time, unless live // mode in which case it is based on 'origintime' as set during scheduling. // In case when this is a filter control packet, the m_iTimeStamp field already // contains the exactly needed value, and it's a timestamp clip, not a real // timestamp. if (!filter_ctl_pkt) { if (m_bPeerTsbPd) { /* * When timestamp is carried over in this sending stream from a received stream, * it may be older than the session start time causing a negative packet time * that may block the receiver's Timestamp-based Packet Delivery. * XXX Isn't it then better to not decrease it by m_stats.tsStartTime? As long as it * doesn't screw up the start time on the other side. */ if (origintime >= m_stats.tsStartTime) { setPacketTS(w_packet, origintime); } else { setPacketTS(w_packet, steady_clock::now()); LOGC(qslog.Warn, log << "packData: reference time=" << FormatTime(origintime) << " is in the past towards start time=" << FormatTime(m_stats.tsStartTime) << " - setting NOW as reference time for the data packet"); } } else { setPacketTS(w_packet, steady_clock::now()); } } w_packet.m_iID = m_PeerID; /* Encrypt if 1st time this packet is sent and crypto is enabled */ if (kflg) { // XXX Encryption flags are already set on the packet before calling this. // See readData() above. if (m_pCryptoControl->encrypt((w_packet))) { // Encryption failed //>>Add stats for crypto failure LOGC(qslog.Warn, log << "ENCRYPT FAILED - packet won't be sent, size=" << payload); // Encryption failed return std::make_pair(-1, enter_time); } payload = (int) w_packet.getLength(); /* Cipher may change length */ reason += " (encrypted)"; } if (new_packet_packed && m_PacketFilter) { HLOGC(qslog.Debug, log << "filter: Feeding packet for source clip"); m_PacketFilter.feedSource((w_packet)); } #if ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() HLOGC(qslog.Debug, log << CONID() << "packData: " << reason << " packet seq=" << w_packet.m_iSeqNo << " (ACK=" << m_iSndLastAck << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); #endif // Fix keepalive m_tsLastSndTime.store(enter_time); considerLegacySrtHandshake(steady_clock::time_point()); // WARNING: TEV_SEND is the only event that is reported from // the CSndQueue::worker thread. All others are reported from // CRcvQueue::worker. If you connect to this signal, make sure // that you are aware of prospective simultaneous access. updateCC(TEV_SEND, EventVariant(&w_packet)); // XXX This was a blocked code also originally in UDT. Probably not required. // Left untouched for historical reasons. // Might be possible that it was because of that this is send from // different thread than the rest of the signals. // m_pSndTimeWindow->onPktSent(w_packet.m_iTimeStamp); enterCS(m_StatsLock); m_stats.traceBytesSent += payload; m_stats.bytesSentTotal += payload; ++m_stats.traceSent; ++m_stats.sentTotal; if (new_packet_packed) { ++m_stats.traceSentUniq; ++m_stats.sentUniqTotal; m_stats.traceBytesSentUniq += payload; m_stats.bytesSentUniqTotal += payload; } leaveCS(m_StatsLock); if (probe) { // sends out probing packet pair m_tsNextSendTime = enter_time; probe = false; } else { #if USE_BUSY_WAITING m_tsNextSendTime = enter_time + m_tdSendInterval.load(); #else const duration sendint = m_tdSendInterval; const duration sendbrw = m_tdSendTimeDiff; if (sendbrw >= sendint) { // Send immidiately m_tsNextSendTime = enter_time; // ATOMIC NOTE: this is the only thread that // modifies this field m_tdSendTimeDiff = sendbrw - sendint; } else { m_tsNextSendTime = enter_time + (sendint - sendbrw); m_tdSendTimeDiff = duration(); } #endif } return std::make_pair(payload, m_tsNextSendTime); } // This is a close request, but called from the void srt::CUDT::processClose() { sendCtrl(UMSG_SHUTDOWN); m_bShutdown = true; m_bClosing = true; m_bBroken = true; m_iBrokenCounter = 60; HLOGP(smlog.Debug, "processClose: sent message and set flags"); if (m_bTsbPd) { HLOGP(smlog.Debug, "processClose: lock-and-signal TSBPD"); CSync::lock_signal(m_RcvTsbPdCond, m_RecvLock); } // 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, SRT_EPOLL_ERR, true); HLOGP(smlog.Debug, "processClose: triggering timer event to spread the bad news"); CGlobEvent::triggerEvent(); } void srt::CUDT::sendLossReport(const std::vector > &loss_seqs) { typedef vector > loss_seqs_t; vector seqbuffer; seqbuffer.reserve(2 * loss_seqs.size()); // pessimistic for (loss_seqs_t::const_iterator i = loss_seqs.begin(); i != loss_seqs.end(); ++i) { if (i->first == i->second) { seqbuffer.push_back(i->first); HLOGF(qrlog.Debug, "lost packet %d: sending LOSSREPORT", i->first); } else { seqbuffer.push_back(i->first | LOSSDATA_SEQNO_RANGE_FIRST); seqbuffer.push_back(i->second); HLOGF(qrlog.Debug, "lost packets %d-%d (%d packets): sending LOSSREPORT", i->first, i->second, 1 + CSeqNo::seqcmp(i->second, i->first)); } } if (!seqbuffer.empty()) { sendCtrl(UMSG_LOSSREPORT, NULL, &seqbuffer[0], (int) seqbuffer.size()); } } bool srt::CUDT::overrideSndSeqNo(int32_t seq) { // This function is intended to be called from the socket // group managmenet functions to synchronize the sequnece in // all sockes in the bonding group. THIS sequence given // here is the sequence TO BE STAMPED AT THE EXACTLY NEXT // sent payload. Therefore, screw up the ISN to exactly this // value, and the send sequence to the value one less - because // the m_iSndCurrSeqNo is increased by one immediately before // stamping it to the packet. // This function can only be called: // - from the operation on an idle socket in the socket group // - IMMEDIATELY after connection established and BEFORE the first payload // - The corresponding socket at the peer side must be also // in this idle state! ScopedLock cg (m_RecvAckLock); // Both the scheduling and sending sequences should be fixed. // The new sequence normally should jump over several sequence numbers // towards what is currently in m_iSndCurrSeqNo. // Therefore it's not allowed that: // - the jump go backward: backward packets should be already there // - the jump go forward by a value larger than half the period: DISCREPANCY. const int diff = CSeqNo(seq) - CSeqNo(m_iSndCurrSeqNo); if (diff < 0 || diff > CSeqNo::m_iSeqNoTH) { LOGC(gslog.Error, log << CONID() << "IPE: Overridding with seq %" << seq << " DISCREPANCY against current %" << m_iSndCurrSeqNo << " and next sched %" << m_iSndNextSeqNo << " - diff=" << diff); return false; } // // The peer will have to do the same, as a reaction on perceived // packet loss. When it recognizes that this initial screwing up // has happened, it should simply ignore the loss and go on. // ISN isn't being changed here - it doesn't make much sense now. setInitialSndSeq(seq); // m_iSndCurrSeqNo will be most likely lower than m_iSndNextSeqNo because // the latter is ahead with the number of packets already scheduled, but // not yet sent. HLOGC(gslog.Debug, log << CONID() << "overrideSndSeqNo: sched-seq=" << m_iSndNextSeqNo << " send-seq=" << m_iSndCurrSeqNo << " (unchanged)" ); return true; } int srt::CUDT::processData(CUnit* in_unit) { if (m_bClosing) return -1; CPacket &packet = in_unit->m_Packet; // Just heard from the peer, reset the expiration count. m_iEXPCount = 1; m_tsLastRspTime.store(steady_clock::now()); const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd; // We are receiving data, start tsbpd thread if TsbPd is enabled if (need_tsbpd && !m_RcvTsbPdThread.joinable()) { ScopedLock lock(m_RcvTsbPdStartupLock); if (m_bClosing) // Check again to protect join() in CUDT::releaseSync() return -1; HLOGP(qrlog.Debug, "Spawning Socket TSBPD thread"); #if ENABLE_HEAVY_LOGGING std::ostringstream tns1, tns2; // Take the last 2 ciphers from the socket ID. tns1 << m_SocketID; std::string s = tns1.str(); tns2 << "SRT:TsbPd:@" << s.substr(s.size()-2, 2); const string& tn = tns2.str(); ThreadName tnkeep(tn); const string& thname = tn; #else const string thname = "SRT:TsbPd"; #endif if (!StartThread(m_RcvTsbPdThread, CUDT::tsbpd, this, thname)) return -1; } const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; #if ENABLE_HEAVY_LOGGING static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; string rexmit_reason; #endif if (pktrexmitflag == 1) { // This packet was retransmitted enterCS(m_StatsLock); m_stats.traceRcvRetrans++; leaveCS(m_StatsLock); #if ENABLE_HEAVY_LOGGING // Check if packet was retransmitted on request or on ack timeout // Search the sequence in the loss record. rexmit_reason = " by "; if (!m_pRcvLossList->find(packet.m_iSeqNo, packet.m_iSeqNo)) rexmit_reason += "BLIND"; else rexmit_reason += "NAKREPORT"; #endif } #if ENABLE_HEAVY_LOGGING { steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) // It's easier to remove the latency factor from this value than to add a function // that exposes the details basing on which this value is calculated. steady_clock::time_point pts = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); steady_clock::time_point ets = pts - tsbpddelay; HLOGC(qrlog.Debug, log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() << " seq=" << packet.getSeqNo() // XXX FIX IT. OTS should represent the original sending time, but it's relative. //<< " OTS=" << FormatTime(packet.getMsgTimeStamp()) << " ETS=" << FormatTime(ets) << " PTS=" << FormatTime(pts)); } #endif updateCC(TEV_RECEIVE, EventVariant(&packet)); ++m_iPktCount; const int pktsz = (int) packet.getLength(); // Update time information // XXX Note that this adds the byte size of a packet // of which we don't yet know as to whether this has // carried out some useful data or some excessive data // that will be later discarded. // FIXME: before adding this on the rcv time window, // make sure that this packet isn't going to be // effectively discarded, as repeated retransmission, // for example, burdens the link, but doesn't better the speed. m_RcvTimeWindow.onPktArrival(pktsz); // Probe the packet pair if needed. // Conditions and any extra data required for the packet // this function will extract and test as needed. const bool unordered = CSeqNo::seqcmp(packet.m_iSeqNo, m_iRcvCurrSeqNo) <= 0; const bool retransmitted = m_bPeerRexmitFlag && packet.getRexmitFlag(); // Retransmitted and unordered packets do not provide expected measurement. // We expect the 16th and 17th packet to be sent regularly, // otherwise measurement must be rejected. m_RcvTimeWindow.probeArrival(packet, unordered || retransmitted); enterCS(m_StatsLock); m_stats.traceBytesRecv += pktsz; m_stats.bytesRecvTotal += pktsz; ++m_stats.traceRecv; ++m_stats.recvTotal; leaveCS(m_StatsLock); loss_seqs_t filter_loss_seqs; loss_seqs_t srt_loss_seqs; vector incoming; bool was_sent_in_order = true; bool reorder_prevent_lossreport = false; // If the peer doesn't understand REXMIT flag, send rexmit request // always immediately. int initial_loss_ttl = 0; if (m_bPeerRexmitFlag) initial_loss_ttl = m_iReorderTolerance; // After introduction of packet filtering, the "recordable loss detection" // does not exactly match the true loss detection. When a FEC filter is // working, for example, then getting one group filled with all packet but // the last one and the FEC control packet, in this special case this packet // won't be notified at all as lost because it will be recovered by the // filter immediately before anyone notices what happened (and the loss // detection for the further functionality is checked only afterwards, // and in this case the immediate recovery makes the loss to not be noticed // at all). // // Because of that the check for losses must happen BEFORE passing the packet // to the filter and before the filter could recover the packet before anyone // notices :) if (packet.getMsgSeq() != SRT_MSGNO_CONTROL) // disregard filter-control packets, their seq may mean nothing { int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo); // Difference between these two sequence numbers is expected to be: // 0 - duplicated last packet (theory only) // 1 - subsequent packet (alright) // <0 - belated or recovered packet // >1 - jump over a packet loss (loss = seqdiff-1) if (diff > 1) { ScopedLock lg(m_StatsLock); int loss = diff - 1; // loss is all that is above diff == 1 m_stats.traceRcvLoss += loss; m_stats.rcvLossTotal += loss; const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); const uint64_t lossbytes = loss * avgpayloadsz; m_stats.traceRcvBytesLoss += lossbytes; m_stats.rcvBytesLossTotal += lossbytes; HLOGC(qrlog.Debug, log << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " " << CSeqNo::decseq(packet.m_iSeqNo) << "]"); } if (diff > 0) { // Record if it was further than latest m_iRcvCurrPhySeqNo = packet.m_iSeqNo; } } bool need_notify_loss = true; // [[using locked()]]; // (NOTHING locked) #if ENABLE_EXPERIMENTAL_BONDING // Switch to RUNNING even if there was a discrepancy, unless // it was long way forward. // XXX Important: This code is in the dead function defaultPacketArrival // but normally it should be called here regardless if the packet was // accepted or rejected because if it was belated it may result in a // "runaway train" problem as the IDLE links are being updated the base // reception sequence pointer stating that this link is not receiving. if (m_parent->m_GroupOf) { ScopedLock protect_group_existence (s_UDTUnited.m_GlobControlLock); groups::SocketData* gi = m_parent->m_GroupMemberData; // This check is needed as after getting the lock the socket // could be potentially removed. It is however granted that as long // as gi is non-NULL iterator, the group does exist and it does contain // this socket as member (that is, 'gi' cannot be a dangling iterator). if (gi != NULL) { if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely { HLOGC(qrlog.Debug, log << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] << " -> RUNNING. NOT checking for loss"); gi->rcvstate = SRT_GST_RUNNING; // The function unfortunately can't return here. // We just need to skip loss reporting. need_notify_loss = false; } else { HLOGC(qrlog.Debug, log << "processData: IN-GROUP rcv state transition NOT DONE - state:" << srt_log_grp_state[gi->rcvstate]); } } } #endif { // 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() UniqueLock recvbuf_acklock(m_RcvBufferLock); // vector undec_units; if (m_PacketFilter) { // Stuff this data into the filter m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); HLOGC(qrlog.Debug, log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) << " loss to report, " << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" : "REPORT ONLY THOSE")); } else { // Stuff in just one packet that has come in. incoming.push_back(in_unit); } bool excessive = true; // stays true unless it was successfully added // Needed for possibly check for needsQuickACK. bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_iRcvLastSkipAck) < 0); // Loop over all incoming packets that were filtered out. // In case when there is no filter, there's just one packet in 'incoming', // the one that came in the input of this function. for (vector::iterator i = incoming.begin(); i != incoming.end(); ++i) { CUnit * u = *i; CPacket &rpkt = u->m_Packet; // m_iRcvLastSkipAck is the base sequence number for the receiver buffer. // This is the offset in the buffer; if this is negative, it means that // this sequence is already in the past and the buffer is not interested. // Meaning, this packet will be rejected, even if it could potentially be // one of missing packets in the transmission. int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, rpkt.m_iSeqNo); IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); if (offset < 0) { IF_HEAVY_LOGGING(exc_type = "BELATED"); steady_clock::time_point tsbpdtime = m_pRcvBuffer->getPktTsbPdTime(rpkt.getMsgTimeStamp()); const double bltime = (double) CountIIR( uint64_t(m_stats.traceBelatedTime) * 1000, count_microseconds(steady_clock::now() - tsbpdtime), 0.2); enterCS(m_StatsLock); m_stats.traceBelatedTime = bltime / 1000.0; m_stats.traceRcvBelated++; leaveCS(m_StatsLock); HLOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset << " (BELATED/" << rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " << packet.MessageFlagStr()); continue; } const int avail_bufsize = m_pRcvBuffer->getAvailBufSize(); if (offset >= avail_bufsize) { // This is already a sequence discrepancy. Probably there could be found // some way to make it continue reception by overriding the sequence and // make a kinda TLKPTDROP, but there has been found no reliable way to do this. if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) { // Only in live mode. In File mode this shall not be possible // because the sender should stop sending in this situation. // In Live mode this means that there is a gap between the // lowest sequence in the empty buffer and the incoming sequence // that exceeds the buffer size. Receiving data in this situation // is no longer possible and this is a point of no return. LOGC(qrlog.Error, log << CONID() << "SEQUENCE DISCREPANCY. BREAKING CONNECTION." " seq=" << rpkt.m_iSeqNo << " buffer=(" << m_iRcvLastSkipAck << ":" << m_iRcvCurrSeqNo // -1 = size to last index << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) << "), " << (offset-avail_bufsize+1) << " past max. Reception no longer possible. REQUESTING TO CLOSE."); // This is a scoped lock with AckLock, but for the moment // when processClose() is called this lock must be taken out, // otherwise this will cause a deadlock. We don't need this // lock anymore, and at 'return' it will be unlocked anyway. recvbuf_acklock.unlock(); processClose(); return -1; } else { LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo << ", insert offset " << offset << ". " << m_pRcvBuffer->strFullnessState(steady_clock::now()) ); return -1; } } bool adding_successful = true; if (m_pRcvBuffer->addData(*i, offset) < 0) { // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. // So this packet is "redundant". IF_HEAVY_LOGGING(exc_type = "UNACKED"); adding_successful = false; } else { IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); excessive = false; if (u->m_Packet.getMsgCryptoFlags()) { EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; if (rc != ENCS_CLEAR) { // Could not decrypt // Keep packet in received buffer // Crypto flags are still set // It will be acknowledged { ScopedLock lg(m_StatsLock); m_stats.traceRcvUndecrypt += 1; m_stats.traceRcvBytesUndecrypt += pktsz; m_stats.m_rcvUndecryptTotal += 1; m_stats.m_rcvBytesUndecryptTotal += pktsz; } // Log message degraded to debug because it may happen very often HLOGC(qrlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data."); adding_successful = false; IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); } } } if (adding_successful) { ScopedLock statslock(m_StatsLock); ++m_stats.traceRecvUniq; ++m_stats.recvUniqTotal; m_stats.traceBytesRecvUniq += u->m_Packet.getLength(); m_stats.bytesRecvUniqTotal += u->m_Packet.getLength(); } #if ENABLE_HEAVY_LOGGING std::ostringstream timebufspec; if (m_bTsbPd) { int dsize = m_pRcvBuffer->getRcvDataSize(); timebufspec << "(" << FormatTime(m_pRcvBuffer->debugGetDeliveryTime(0)) << "-" << FormatTime(m_pRcvBuffer->debugGetDeliveryTime(dsize-1)) << ")"; } std::ostringstream expectspec; if (excessive) expectspec << "EXCESSIVE(" << exc_type << rexmit_reason << ")"; else expectspec << "ACCEPTED"; LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo << " offset=" << offset << " BUFr=" << avail_bufsize << " avail=" << m_pRcvBuffer->getAvailBufSize() << " buffer=(" << m_iRcvLastSkipAck << ":" << m_iRcvCurrSeqNo // -1 = size to last index << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) << ") " << " RSL=" << expectspec.str() << " SN=" << rexmitstat[pktrexmitflag] << " DLVTM=" << timebufspec.str() << " FLAGS: " << rpkt.MessageFlagStr()); #endif // Decryption should have made the crypto flags EK_NOENC. // Otherwise it's an error. if (adding_successful) { // XXX move this code do CUDT::defaultPacketArrival and call it from here: // srt_loss_seqs = CALLBACK_CALL(m_cbPacketArrival, rpkt); HLOGC(qrlog.Debug, log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); if (need_notify_loss && CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. { int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); if (initial_loss_ttl) { // pack loss list for (possibly belated) NAK // The LOSSREPORT will be sent in a while. for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) { m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); } HLOGC(qrlog.Debug, log << "FreshLoss: added sequences: " << Printable(srt_loss_seqs) << " tolerance: " << initial_loss_ttl); reorder_prevent_lossreport = true; } } } // Update the current largest sequence number that has been received. // Or it is a retransmitted packet, remove it from receiver loss list. if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0) { m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received } else { unlose(rpkt); // was BELATED or RETRANSMITTED was_sent_in_order &= 0 != pktrexmitflag; } } // This is moved earlier after introducing filter because it shouldn't // be executed in case when the packet was rejected by the receiver buffer. // However now the 'excessive' condition may be true also in case when // a truly non-excessive packet has been received, just it has been temporarily // stored for better times by the filter module. This way 'excessive' is also true, // although the old condition that a packet with a newer sequence number has arrived // or arrived out of order may still be satisfied. if (!incoming_belated && was_sent_in_order) { // Basing on some special case in the packet, it might be required // to enforce sending ACK immediately (earlier than normally after // a given period). if (m_CongCtl->needsQuickACK(packet)) { m_tsNextACKTime.store(steady_clock::now()); } } if (excessive) { return -1; } } // End of recvbuf_acklock if (m_bClosing) { // RcvQueue worker thread can call processData while closing (or close while processData) // This race condition exists in the UDT design but the protection against TsbPd thread // (with AckLock) and decryption enlarged the probability window. // Application can crash deep in decrypt stack since crypto context is deleted in close. // RcvQueue worker thread will not necessarily be deleted with this connection as it can be // used by others (socket multiplexer). return -1; } if (incoming.empty()) { // Treat as excessive. This is when a filter cumulates packets // until the loss is rebuilt, or eats up a filter control packet return -1; } if (!srt_loss_seqs.empty()) { // A loss is detected { // TODO: Can unlock rcvloss after m_pRcvLossList->insert(...)? // And probably protect m_FreshLoss as well. HLOGC(qrlog.Debug, log << "processData: LOSS DETECTED, %: " << Printable(srt_loss_seqs) << " - RECORDING."); // if record_loss == false, nothing will be contained here // Insert lost sequence numbers to the receiver loss list ScopedLock lg(m_RcvLossLock); for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) { // If loss found, insert them to the receiver loss list m_pRcvLossList->insert(i->first, i->second); } } const bool report_recorded_loss = !m_PacketFilter || m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS; if (!reorder_prevent_lossreport && report_recorded_loss) { HLOGC(qrlog.Debug, log << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); sendLossReport(srt_loss_seqs); } if (m_bTsbPd) { HLOGC(qrlog.Debug, log << "loss: signaling TSBPD cond"); CSync::lock_signal(m_RcvTsbPdCond, m_RecvLock); } else { HLOGC(qrlog.Debug, log << "loss: socket is not TSBPD, not signaling"); } } // Separately report loss records of those reported by a filter. // ALWAYS report whatever has been reported back by a filter. Note that // the filter never reports anything when rexmit fallback level is ALWAYS or NEVER. // With ALWAYS only those are reported that were recorded here by SRT. // With NEVER, nothing is to be reported. if (!filter_loss_seqs.empty()) { HLOGC(qrlog.Debug, log << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); sendLossReport(filter_loss_seqs); if (m_bTsbPd) { HLOGC(qrlog.Debug, log << "loss: signaling TSBPD cond"); CSync::lock_signal(m_RcvTsbPdCond, m_RecvLock); } } // Now review the list of FreshLoss to see if there's any "old enough" to send UMSG_LOSSREPORT to it. // PERFORMANCE CONSIDERATIONS: // This list is quite inefficient as a data type and finding the candidate to send UMSG_LOSSREPORT // is linear time. On the other hand, there are some special cases that are important for performance: // - only the first (plus some following) could have had TTL drown to 0 // - the only (little likely) possibility that the next-to-first record has TTL=0 is when there was // a loss range split (due to dropFromLossLists() 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; { ScopedLock 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 dropFromLossLists) sequence that has split one detected loss // into two records. for (; i != m_FreshLoss.end() && i->ttl <= 0; ++i) { HLOGF(qrlog.Debug, "Packet seq %d-%d (%d packets) considered lost - sending LOSSREPORT", i->seq[0], i->seq[1], CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1); addLossRecord(lossdata, i->seq[0], i->seq[1]); } // Remove elements that have been processed and prepared for lossreport. if (i != m_FreshLoss.begin()) { m_FreshLoss.erase(m_FreshLoss.begin(), i); i = m_FreshLoss.begin(); } if (m_FreshLoss.empty()) { HLOGP(qrlog.Debug, "NO MORE FRESH LOSS RECORDS."); } else { HLOGF(qrlog.Debug, "STILL %" PRIzu " FRESH LOSS RECORDS, FIRST: %d-%d (%d) TTL: %d", m_FreshLoss.size(), i->seq[0], i->seq[1], 1 + CSeqNo::seqoff(i->seq[0], i->seq[1]), i->ttl); } // Phase 2: rest of the records should have TTL decreased. for (; i != m_FreshLoss.end(); ++i) --i->ttl; } } if (!lossdata.empty()) { sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], (int) lossdata.size()); } // was_sent_in_order means either of: // - packet was sent in order (first if branch above) // - packet was sent as old, but was a retransmitted packet if (m_bPeerRexmitFlag && was_sent_in_order) { ++m_iConsecOrderedDelivery; if (m_iConsecOrderedDelivery >= 50) { m_iConsecOrderedDelivery = 0; if (m_iReorderTolerance > 0) { m_iReorderTolerance--; enterCS(m_StatsLock); m_stats.traceReorderDistance--; leaveCS(m_StatsLock); HLOGF(qrlog.Debug, "ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to %d", m_iReorderTolerance); } } } return 0; } #if ENABLE_EXPERIMENTAL_BONDING void srt::CUDT::updateIdleLinkFrom(CUDT* source) { ScopedLock lg (m_RecvLock); if (!m_pRcvBuffer->empty()) { HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID << ": receiver buffer not empty"); return; } // XXX Try to optimize this. Note that here happens: // - decseq just to have a value to compare directly // - seqcmp with that value // - if passed, in setInitialRcvSeq there's the same decseq again int32_t new_last_rcv = CSeqNo::decseq(source->m_iRcvLastSkipAck); // if (new_last_rcv <% m_iRcvCurrSeqNo) if (CSeqNo::seqcmp(new_last_rcv, m_iRcvCurrSeqNo) < 0) { // Reject the change because that would shift the reception pointer backwards. HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID << ": backward setting rejected: %" << m_iRcvCurrSeqNo << " -> %" << new_last_rcv); return; } HLOGC(grlog.Debug, log << "grp: updating rcv-seq in @" << m_SocketID << " from @" << source->m_SocketID << ": %" << source->m_iRcvLastSkipAck); setInitialRcvSeq(source->m_iRcvLastSkipAck); } // XXX This function is currently unused. It should be fixed and put into use. // See the blocked call in CUDT::processData(). // XXX REVIEW LOCKS WHEN REACTIVATING! srt::CUDT::loss_seqs_t srt::CUDT::defaultPacketArrival(void* vself, CPacket& pkt) { // [[using affinity(m_pRcvBuffer->workerThread())]]; CUDT* self = (CUDT*)vself; loss_seqs_t output; // XXX When an alternative packet arrival callback is installed // in case of groups, move this part to the groupwise version. if (self->m_parent->m_GroupOf) { groups::SocketData* gi = self->m_parent->m_GroupMemberData; if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely { HLOGC(qrlog.Debug, log << "defaultPacketArrival: IN-GROUP rcv state transition to RUNNING. NOT checking for loss"); gi->rcvstate = SRT_GST_RUNNING; return output; } } const int initial_loss_ttl = (self->m_bPeerRexmitFlag) ? self->m_iReorderTolerance : 0; int seqdiff = CSeqNo::seqcmp(pkt.m_iSeqNo, self->m_iRcvCurrSeqNo); HLOGC(qrlog.Debug, log << "defaultPacketArrival: checking sequence " << pkt.m_iSeqNo << " against latest " << self->m_iRcvCurrSeqNo << " (distance: " << seqdiff << ")"); // Loss detection. if (seqdiff > 1) // packet is later than the very subsequent packet { const int32_t seqlo = CSeqNo::incseq(self->m_iRcvCurrSeqNo); const int32_t seqhi = CSeqNo::decseq(pkt.m_iSeqNo); { // If loss found, insert them to the receiver loss list ScopedLock lg (self->m_RcvLossLock); self->m_pRcvLossList->insert(seqlo, seqhi); if (initial_loss_ttl) { // pack loss list for (possibly belated) NAK // The LOSSREPORT will be sent in a while. self->m_FreshLoss.push_back(CRcvFreshLoss(seqlo, seqhi, initial_loss_ttl)); HLOGF(qrlog.Debug, "defaultPacketArrival: added loss sequence %d-%d (%d) with tolerance %d", seqlo, seqhi, 1+CSeqNo::seqcmp(seqhi, seqlo), initial_loss_ttl); } } if (!initial_loss_ttl) { // old code; run immediately when tolerance = 0 // or this feature isn't used because of the peer output.push_back(make_pair(seqlo, seqhi)); } } return output; } #endif /// 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 iMaxReorderTolerance. /// This value can be set in options - SRT_LOSSMAXTTL. void srt::CUDT::unlose(const CPacket &packet) { ScopedLock lg(m_RcvLossLock); int32_t sequence = packet.m_iSeqNo; m_pRcvLossList->remove(sequence); // Rest of this code concerns only the "belated lossreport" feature. bool has_increased_tolerance = false; bool was_reordered = false; if (m_bPeerRexmitFlag) { // If the peer understands the REXMIT flag, it means that the REXMIT flag is contained // in the PH_MSGNO field. // The packet is considered coming originally (just possibly out of order), if REXMIT // flag is NOT set. was_reordered = !packet.getRexmitFlag(); if (was_reordered) { HLOGF(qrlog.Debug, "received out-of-band packet seq %d", sequence); const int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.m_iSeqNo)); enterCS(m_StatsLock); m_stats.traceReorderDistance = max(seqdiff, m_stats.traceReorderDistance); leaveCS(m_StatsLock); if (seqdiff > m_iReorderTolerance) { const int new_tolerance = min(seqdiff, m_config.iMaxReorderTolerance); HLOGF(qrlog.Debug, "Belated by %d seqs - Reorder tolerance %s %d", seqdiff, (new_tolerance == m_iReorderTolerance) ? "REMAINS with" : "increased to", new_tolerance); m_iReorderTolerance = new_tolerance; has_increased_tolerance = true; // Yes, even if reorder tolerance is already at maximum - this prevents decreasing tolerance. } } else { HLOGC(qrlog.Debug, log << CONID() << "received reXmitted packet seq=" << sequence); } } else { HLOGF(qrlog.Debug, "received reXmitted or belated packet seq %d (distinction not supported by peer)", sequence); } // Don't do anything if "belated loss report" feature is not used. // In that case the FreshLoss list isn't being filled in at all, the // loss report is sent directly. // Note that this condition blocks two things being done in this function: // - remove given sequence from the fresh loss record // (in this case it's empty anyway) // - decrease current reorder tolerance based on whether packets come in order // (current reorder tolerance is 0 anyway) if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) return; size_t i = 0; int had_ttl = 0; for (i = 0; i < m_FreshLoss.size(); ++i) { had_ttl = m_FreshLoss[i].ttl; switch (m_FreshLoss[i].revoke(sequence)) { case CRcvFreshLoss::NONE: continue; // Not found. Search again. case CRcvFreshLoss::STRIPPED: goto breakbreak; // Found and the modification is applied. We're done here. case CRcvFreshLoss::DELETE: // No more elements. Kill it. m_FreshLoss.erase(m_FreshLoss.begin() + i); // Every loss is unique. We're done here. goto breakbreak; case CRcvFreshLoss::SPLIT: // Oh, this will be more complicated. This means that it was in between. { // So create a new element that will hold the upper part of the range, // and this one modify to be the lower part of the range. // Keep the current end-of-sequence value for the second element int32_t next_end = m_FreshLoss[i].seq[1]; // seq-1 set to the end of this element m_FreshLoss[i].seq[1] = CSeqNo::decseq(sequence); // seq+1 set to the begin of the next element int32_t next_begin = CSeqNo::incseq(sequence); // Use position of the NEXT element because insertion happens BEFORE pointed element. // Use the same TTL (will stay the same in the other one). m_FreshLoss.insert(m_FreshLoss.begin() + i + 1, CRcvFreshLoss(next_begin, next_end, m_FreshLoss[i].ttl)); } goto breakbreak; } } // Could have made the "return" instruction instead of goto, but maybe there will be something // to add in future, so keeping that. breakbreak:; if (i != m_FreshLoss.size()) { HLOGF(qrlog.Debug, "sequence %d removed from belated lossreport record", sequence); } if (was_reordered) { m_iConsecOrderedDelivery = 0; if (has_increased_tolerance) { m_iConsecEarlyDelivery = 0; // reset counter } else if (had_ttl > 2) { ++m_iConsecEarlyDelivery; // otherwise, and if it arrived quite earlier, increase counter HLOGF(qrlog.Debug, "... arrived at TTL %d case %d", had_ttl, m_iConsecEarlyDelivery); // After 10 consecutive if (m_iConsecEarlyDelivery >= 10) { m_iConsecEarlyDelivery = 0; if (m_iReorderTolerance > 0) { m_iReorderTolerance--; enterCS(m_StatsLock); m_stats.traceReorderDistance--; leaveCS(m_StatsLock); HLOGF(qrlog.Debug, "... reached %d times - decreasing tolerance to %d", m_iConsecEarlyDelivery, m_iReorderTolerance); } } } // If hasn't increased tolerance, but the packet appeared at TTL less than 2, do nothing. } } void srt::CUDT::dropFromLossLists(int32_t from, int32_t to) { ScopedLock lg(m_RcvLossLock); m_pRcvLossList->remove(from, to); HLOGF(qrlog.Debug, "%sTLPKTDROP seq %d-%d (%d packets)", CONID().c_str(), from, to, CSeqNo::seqoff(from, to)); if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) return; // All code below concerns only "belated lossreport" feature. // It's highly unlikely that this is waiting to send a belated UMSG_LOSSREPORT, // so treat it rather as a sanity check. // It's enough to check if the first element of the list starts with a sequence older than 'to'. // If not, just do nothing. size_t delete_index = 0; for (size_t i = 0; i < m_FreshLoss.size(); ++i) { CRcvFreshLoss::Emod result = m_FreshLoss[i].revoke(from, to); switch (result) { case CRcvFreshLoss::DELETE: delete_index = i + 1; // PAST THE END continue; // There may be further ranges that are included in this one, so check on. case CRcvFreshLoss::NONE: case CRcvFreshLoss::STRIPPED: break; // THIS BREAKS ONLY 'switch', not 'for'! case CRcvFreshLoss::SPLIT:; // This function never returns it. It's only a compiler shut-up. } break; // Now this breaks also FOR. } m_FreshLoss.erase(m_FreshLoss.begin(), m_FreshLoss.begin() + delete_index); // with delete_index == 0 will do nothing } // This function, as the name states, should bake a new cookie. int32_t srt::CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int correction) { static unsigned int distractor = 0; unsigned int rollover = distractor + 10; for (;;) { // SYN cookie char clienthost[NI_MAXHOST]; char clientport[NI_MAXSERV]; getnameinfo(addr.get(), addr.size(), clienthost, sizeof(clienthost), clientport, sizeof(clientport), NI_NUMERICHOST | NI_NUMERICSERV); int64_t timestamp = (count_microseconds(steady_clock::now() - m_stats.tsStartTime) / 60000000) + distractor - correction; // secret changes every one minute stringstream cookiestr; cookiestr << clienthost << ":" << clientport << ":" << timestamp; union { unsigned char cookie[16]; int32_t cookie_val; }; CMD5::compute(cookiestr.str().c_str(), cookie); if (cookie_val != current_cookie) return cookie_val; ++distractor; // This is just to make the loop formally breakable, // but this is virtually impossible to happen. if (distractor == rollover) return cookie_val; } } // XXX This is quite a mystery, why this function has a return value // and what the purpose for it was. There's just one call of this // function in the whole code and in that call the return value is // ignored. Actually this call happens in the CRcvQueue::worker thread, // where it makes a response for incoming UDP packet that might be // a connection request. Should any error occur in this process, there // is no way to "report error" that happened here. Basing on that // these values in original UDT code were quite like the values // for m_iReqType, they have been changed to URQ_* symbols, which // may mean that the intent for the return value was to send this // value back as a control packet back to the connector. // // This function is run when the CRcvQueue object is reading packets // from the multiplexer (@c CRcvQueue::worker_RetrieveUnit) and the // target socket ID is 0. // // XXX Make this function return EConnectStatus enum type (extend if needed), // and this will be directly passed to the caller. // [[using locked(m_pRcvQueue->m_LSLock)]]; int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) { // XXX ASSUMPTIONS: // [[using assert(packet.m_iID == 0)]] HLOGC(cnlog.Debug, log << "processConnectRequest: received a connection request"); if (m_bClosing) { m_RejectReason = SRT_REJ_CLOSE; HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Rejecting because closing."); return m_RejectReason; } /* * Closing a listening socket only set bBroken * If a connect packet is received while closing it gets through * processing and crashes later. */ if (m_bBroken) { m_RejectReason = SRT_REJ_CLOSE; HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Rejecting because broken."); return m_RejectReason; } size_t exp_len = CHandShake::m_iContentSize; // When CHandShake::m_iContentSize is used in log, the file fails to link! // NOTE!!! Old version of SRT code checks if the size of the HS packet // is EQUAL to the above CHandShake::m_iContentSize. // Changed to < exp_len because we actually need that the packet // be at least of a size for handshake, although it may contain // more data, depending on what's inside. if (packet.getLength() < exp_len) { m_RejectReason = SRT_REJ_ROGUE; HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() << " (expected: " << exp_len << ")"); return m_RejectReason; } // Dunno why the original UDT4 code only MUCH LATER was checking if the packet was UMSG_HANDSHAKE. // It doesn't seem to make sense to deserialize it into the handshake structure if we are not // sure that the packet contains the handshake at all! if (!packet.isControl(UMSG_HANDSHAKE)) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << "processConnectRequest: the packet received as handshake is not a handshake message"); return m_RejectReason; } CHandShake hs; hs.load_from(packet.m_pcData, packet.getLength()); // XXX MOST LIKELY this hs should be now copied into m_ConnRes field, which holds // the handshake structure sent from the peer (no matter the role or mode). // This should simplify the createSrtHandshake() function which can this time // simply write the crafted handshake structure into m_ConnReq, which needs no // participation of the local handshake and passing it as a parameter through // newConnection() -> acceptAndRespond() -> createSrtHandshake(). This is also // required as a source of the peer's information used in processing in other // structures. int32_t cookie_val = bake(addr); HLOGC(cnlog.Debug, log << "processConnectRequest: new cookie: " << hex << cookie_val); // REQUEST:INDUCTION. // Set a cookie, a target ID, and send back the same as // RESPONSE:INDUCTION. if (hs.m_iReqType == URQ_INDUCTION) { HLOGC(cnlog.Debug, log << "processConnectRequest: received type=induction, sending back with cookie+socket"); // XXX That looks weird - the calculated md5 sum out of the given host/port/timestamp // is 16 bytes long, but CHandShake::m_iCookie has 4 bytes. This then effectively copies // only the first 4 bytes. Moreover, it's dangerous on some platforms because the char // array need not be aligned to int32_t - changed to union in a hope that using int32_t // inside a union will enforce whole union to be aligned to int32_t. hs.m_iCookie = cookie_val; packet.m_iID = hs.m_iID; // Ok, now's the time. The listener sets here the version 5 handshake, // even though the request was 4. This is because the old client would // simply return THE SAME version, not even looking into it, giving the // listener false impression as if it supported version 5. // // If the caller was really HSv4, it will simply ignore the version 5 in INDUCTION; // it will respond with CONCLUSION, but with its own set version, which is version 4. // // If the caller was really HSv5, it will RECOGNIZE this version 5 in INDUCTION, so // it will respond with version 5 when sending CONCLUSION. hs.m_iVersion = HS_VERSION_SRT1; // Additionally, set this field to a MAGIC value. This field isn't used during INDUCTION // by HSv4 client, HSv5 client can use it to additionally verify that this is a HSv5 listener. // In this field we also advertise the PBKEYLEN value. When 0, it's considered not advertised. hs.m_iType = SrtHSRequest::wrapFlags(true /*put SRT_MAGIC_CODE in HSFLAGS*/, m_config.iSndCryptoKeyLen); bool whether SRT_ATR_UNUSED = m_config.iSndCryptoKeyLen != 0; HLOGC(cnlog.Debug, log << "processConnectRequest: " << (whether ? "" : "NOT ") << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); size_t size = packet.getLength(); hs.store_to((packet.m_pcData), (size)); setPacketTS(packet, steady_clock::now()); // Display the HS before sending it to peer HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (i): " << hs.show()); m_pSndQueue->sendto(addr, packet); return SRT_REJ_UNKNOWN; // EXCEPTION: this is a "no-error" code. } // Otherwise this should be REQUEST:CONCLUSION. // Should then come with the correct cookie that was // set in the above INDUCTION, in the HS_VERSION_SRT1 // should also contain extra data. if (!hs.valid()) { LOGC(cnlog.Error, log << "processConnectRequest: ROGUE HS RECEIVED. Rejecting"); m_RejectReason = SRT_REJ_ROGUE; return SRT_REJ_ROGUE; } HLOGC(cnlog.Debug, log << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) << " - checking cookie..."); if (hs.m_iCookie != cookie_val) { cookie_val = bake(addr, cookie_val, -1); // SHOULD generate an earlier, distracted cookie if (hs.m_iCookie != cookie_val) { m_RejectReason = SRT_REJ_RDVCOOKIE; HLOGC(cnlog.Debug, log << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring."); return m_RejectReason; } HLOGC(cnlog.Debug, log << "processConnectRequest: ... correct (FIXED) cookie. Proceeding."); } else { HLOGC(cnlog.Debug, log << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."); } int32_t id = hs.m_iID; // HANDSHAKE: The old client sees the version that does not match HS_VERSION_UDT4 (5). // In this case it will respond with URQ_ERROR_REJECT. Rest of the data are the same // as in the handshake request. When this message is received, the connector side should // switch itself to the version number HS_VERSION_UDT4 and continue the old way (that is, // continue sending URQ_INDUCTION, but this time with HS_VERSION_UDT4). bool accepted_hs = true; if (hs.m_iVersion == HS_VERSION_SRT1) { // No further check required. // The m_iType contains handshake extension flags. } else if (hs.m_iVersion == HS_VERSION_UDT4) { // In UDT, and so in older SRT version, the hs.m_iType field should contain // the socket type, although SRT only allowed this field to be UDT_DGRAM. // Older SRT version contained that value in a field, but now that this can // only contain UDT_DGRAM the field itself has been abandoned. // For the sake of any old client that reports version 4 handshake, interpret // this hs.m_iType field as a socket type and check if it's UDT_DGRAM. // Note that in HSv5 hs.m_iType contains extension flags. if (hs.m_iType != UDT_DGRAM) { m_RejectReason = SRT_REJ_ROGUE; accepted_hs = false; } } else { // Unsupported version // (NOTE: This includes "version=0" which is a rejection flag). m_RejectReason = SRT_REJ_VERSION; accepted_hs = false; } if (!accepted_hs) { HLOGC(cnlog.Debug, log << "processConnectRequest: version/type mismatch. Sending REJECT code:" << m_RejectReason << " MSG: " << srt_rejectreason_str(m_RejectReason)); // mismatch, reject the request hs.m_iReqType = URQFailure(m_RejectReason); size_t size = CHandShake::m_iContentSize; hs.store_to((packet.m_pcData), (size)); packet.m_iID = id; setPacketTS(packet, steady_clock::now()); HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (e): " << hs.show()); m_pSndQueue->sendto(addr, packet); } else { int error = SRT_REJ_UNKNOWN; CUDT* acpu = NULL; int result = s_UDTUnited.newConnection(m_SocketID, addr, packet, (hs), (error), (acpu)); // This is listener - m_RejectReason need not be set // because listener has no functionality of giving the app // insight into rejected callers. // ---> // (global.) CUDTUnited::updateListenerMux // (new Socket.) CUDT::acceptAndRespond if (result == -1) { hs.m_iReqType = URQFailure(error); LOGF(cnlog.Warn, "processConnectRequest: rsp(REJECT): %d - %s", hs.m_iReqType, srt_rejectreason_str(error)); } // CONFUSION WARNING! // // The newConnection() will call acceptAndRespond() if the processing // was successful - IN WHICH CASE THIS PROCEDURE SHOULD DO NOTHING. // Ok, almost nothing - see update_events below. // // If newConnection() failed, acceptAndRespond() will not be called. // Ok, more precisely, the thing that acceptAndRespond() is expected to do // will not be done (this includes sending any response to the peer). // // Now read CAREFULLY. The newConnection() will return: // // - -1: The connection processing failed due to errors like: // - memory alloation error // - listen backlog exceeded // - any error propagated from CUDT::open and CUDT::acceptAndRespond // - 0: The connection already exists // - 1: Connection accepted. // // So, update_events is called only if the connection is established. // Both 0 (repeated) and -1 (error) require that a response be sent. // The CPacket object that has arrived as a connection request is here // reused for the connection rejection response (see URQ_ERROR_REJECT set // as m_iReqType). // The 'acpu' should be set to a new socket, if found; // this means simultaneously that result == 0, but it's safest to // check this condition only. This means that 'newConnection' found // that the connection attempt has already been accepted, just the // caller side somehow didn't get the answer. The rule is that every // connection request HS must be completed with a symmetric HS response, // so craft one here. // Note that this function runs in the listener socket context, while 'acpu' // is the CUDT entity for the accepted socket. if (acpu) { // This is an existing connection, so the handshake is only needed // because of the rule that every handshake request must be covered // by the handshake response. It wouldn't be good to call interpretSrtHandshake // here because the data from the handshake have been already interpreted // and recorded. We just need to craft a response. HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: sending REPEATED handshake response req=" << RequestTypeStr(hs.m_iReqType)); // Rewrite already updated previously data in acceptAndRespond acpu->rewriteHandshakeData(acpu->m_PeerAddr, (hs)); uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; EConnectStatus conn = CONN_ACCEPT; if (hs.m_iVersion >= HS_VERSION_SRT1) { // Always attach extension. hs.m_extension = true; conn = acpu->craftKmResponse((kmdata), (kmdatasize)); } else { kmdatasize = 0; } if (conn != CONN_ACCEPT) return conn; packet.setLength(m_iMaxSRTPayloadSize); if (!acpu->createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (packet), (hs))) { HLOGC(cnlog.Debug, log << "processConnectRequest: rejecting due to problems in createSrtHandshake."); result = -1; // enforce fallthrough for the below condition! hs.m_iReqType = URQFailure(m_RejectReason == SRT_REJ_UNKNOWN ? int(SRT_REJ_IPE) : m_RejectReason.load()); } else { // Send the crafted handshake HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING (repeated) HS (a): " << hs.show()); acpu->addressAndSend((packet)); } } // send back a response if connection failed or connection already existed // (or the above procedure failed) if (result == -1) { if (hs.m_iVersion < HS_VERSION_SRT1) { HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: HSv4 caller, sending SHUTDOWN after rejection with " << RequestTypeStr(hs.m_iReqType)); // The HSv4 clients do not interpret the error handshake response correctly. // In order to really disallow them to connect there's needed the shutdown response. CPacket rsp; setPacketTS((rsp), steady_clock::now()); rsp.pack(UMSG_SHUTDOWN); rsp.m_iID = m_PeerID; m_pSndQueue->sendto(addr, rsp); } else { HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: sending ABNORMAL handshake info req=" << RequestTypeStr(hs.m_iReqType)); size_t size = CHandShake::m_iContentSize; hs.store_to((packet.m_pcData), (size)); packet.setLength(size); packet.m_iID = id; setPacketTS(packet, steady_clock::now()); HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (a): " << hs.show()); m_pSndQueue->sendto(addr, packet); } } // new connection response should be sent in acceptAndRespond() // turn the socket writable if this is the first time when this was found out. else { // a new connection has been created, enable epoll for write HLOGC(cnlog.Debug, log << "processConnectRequest: @" << m_SocketID << " connected, setting epoll to connect:"); // Note: not using SRT_EPOLL_CONNECT symbol because this is a procedure // executed for the accepted socket. s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); } } LOGC(cnlog.Note, log << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType)); return RejectReasonForURQ(hs.m_iReqType); } void srt::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); } } int srt::CUDT::checkACKTimer(const steady_clock::time_point &currtime) { int because_decision = BECAUSE_NO_REASON; if (currtime > m_tsNextACKTime.load() // ACK time has come // OR the number of sent packets since last ACK has reached // the congctl-defined value of ACK Interval // (note that none of the builtin congctls defines ACK Interval) || (m_CongCtl->ACKMaxPackets() > 0 && m_iPktCount >= m_CongCtl->ACKMaxPackets())) { // ACK timer expired or ACK interval is reached sendCtrl(UMSG_ACK); const steady_clock::duration ack_interval = m_CongCtl->ACKTimeout_us() > 0 ? microseconds_from(m_CongCtl->ACKTimeout_us()) : m_tdACKInterval; m_tsNextACKTime.store(currtime + ack_interval); m_iPktCount = 0; m_iLightACKCount = 1; because_decision = BECAUSE_ACK; } // Or the transfer rate is so high that the number of packets // have reached the value of SelfClockInterval * LightACKCount before // the time has come according to m_tsNextACKTime. In this case a "lite ACK" // is sent, which doesn't contain statistical data and nothing more // than just the ACK number. The "fat ACK" packets will be still sent // normally according to the timely rules. else if (m_iPktCount >= SELF_CLOCK_INTERVAL * m_iLightACKCount) { // send a "light" ACK sendCtrl(UMSG_ACK, NULL, NULL, SEND_LITE_ACK); ++m_iLightACKCount; because_decision = BECAUSE_LITEACK; } return because_decision; } int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) { // XXX The problem with working NAKREPORT with SRT_ARQ_ONREQ // is not that it would be inappropriate, but because it's not // implemented. The reason for it is that the structure of the // loss list container (m_pRcvLossList) is such that it is expected // that the loss records are ordered by sequence numbers (so // that two ranges sticking together are merged in place). // Unfortunately in case of SRT_ARQ_ONREQ losses must be recorded // as before, but they should not be reported, until confirmed // by the filter. By this reason they appear often out of order // and for adding them properly the loss list container wasn't // prepared. This then requires some more effort to implement. if (!m_config.bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS) return BECAUSE_NO_REASON; /* * m_config.bRcvNakReport enables NAK reports for SRT. * Retransmission based on timeout is bandwidth consuming, * not knowing what to retransmit when the only NAK sent by receiver is lost, * all packets past last ACK are retransmitted (rexmitMethod() == SRM_FASTREXMIT). */ const int loss_len = m_pRcvLossList->getLossLength(); SRT_ASSERT(loss_len >= 0); int debug_decision = BECAUSE_NO_REASON; if (loss_len > 0) { if (currtime <= m_tsNextNAKTime.load()) return BECAUSE_NO_REASON; // wait for next NAK time sendCtrl(UMSG_LOSSREPORT); debug_decision = BECAUSE_NAKREPORT; } m_tsNextNAKTime.store(currtime + m_tdNAKInterval); return debug_decision; } bool srt::CUDT::checkExpTimer(const steady_clock::time_point& currtime, int check_reason SRT_ATR_UNUSED) { // VERY HEAVY LOGGING #if ENABLE_HEAVY_LOGGING & 1 static const char* const decisions [] = { "ACK", "LITE-ACK", "NAKREPORT" }; string decision = "NOTHING"; if (check_reason) { ostringstream decd; decision = ""; for (int i = 0; i < LAST_BECAUSE_BIT; ++i) { int flag = 1 << i; if (check_reason & flag) decd << decisions[i] << " "; } decision = decd.str(); } HLOGC(xtlog.Debug, log << CONID() << "checkTimer: ACTIVITIES PERFORMED: " << decision); #endif // In UDT the m_bUserDefinedRTO and m_iRTO were in CCC class. // There's nothing in the original code that alters these values. steady_clock::time_point next_exp_time; if (m_CongCtl->RTO()) { next_exp_time = m_tsLastRspTime.load() + microseconds_from(m_CongCtl->RTO()); } else { steady_clock::duration exp_timeout = microseconds_from(m_iEXPCount * (m_iSRTT + 4 * m_iRTTVar) + COMM_SYN_INTERVAL_US); if (exp_timeout < (m_iEXPCount * m_tdMinExpInterval)) exp_timeout = m_iEXPCount * m_tdMinExpInterval; next_exp_time = m_tsLastRspTime.load() + exp_timeout; } if (currtime <= next_exp_time && !m_bBreakAsUnstable) return false; // ms -> us const int PEER_IDLE_TMO_US = m_config.iPeerIdleTimeout * 1000; // Haven't received any information from the peer, is it dead?! // timeout: at least 16 expirations and must be greater than 5 seconds time_point last_rsp_time = m_tsLastRspTime.load(); if (m_bBreakAsUnstable || ((m_iEXPCount > COMM_RESPONSE_MAX_EXP) && (currtime - last_rsp_time > microseconds_from(PEER_IDLE_TMO_US)))) { // // Connection is broken. // UDT does not signal any information about this instead of to stop quietly. // Application will detect this when it calls any UDT methods next time. // HLOGC(xtlog.Debug, log << "CONNECTION EXPIRED after " << count_milliseconds(currtime - last_rsp_time) << "ms"); m_bClosing = true; m_bBroken = true; m_iBrokenCounter = 30; // update snd U list to remove this socket m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE); updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS! return true; } HLOGC(xtlog.Debug, log << "EXP TIMER: count=" << m_iEXPCount << "/" << (+COMM_RESPONSE_MAX_EXP) << " elapsed=" << (count_microseconds(currtime - last_rsp_time)) << "/" << (+PEER_IDLE_TMO_US) << "us"); ++m_iEXPCount; /* * (keepalive fix) * duB: * It seems there is confusion of the direction of the Response here. * lastRspTime is supposed to be when receiving (data/ctrl) from peer * as shown in processCtrl and processData, * Here we set because we sent something? * * Disabling this code that prevent quick reconnection when peer disappear */ // Reset last response time since we've just sent a heart-beat. // (fixed) m_tsLastRspTime = currtime_tk; return false; } void srt::CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) { // There are two algorithms of blind packet retransmission: LATEREXMIT and FASTREXMIT. // // LATEREXMIT is only used with FileCC. // The RTO is triggered when some time has passed since the last ACK from // the receiver, while there is still some unacknowledged data in the sender's buffer, // and the loss list is empty at the moment of RTO (nothing to retransmit yet). // // FASTREXMIT is only used with LiveCC. // The RTO is triggered if the receiver is not configured to send periodic NAK reports, // when some time has passed since the last ACK from the receiver, // while there is still some unacknowledged data in the sender's buffer. // // In case the above conditions are met, the unacknowledged packets // in the sender's buffer will be added to the SND loss list and retransmitted. // const uint64_t rtt_syn = (m_iSRTT + 4 * m_iRTTVar + 2 * COMM_SYN_INTERVAL_US); const uint64_t exp_int_us = (m_iReXmitCount * rtt_syn + COMM_SYN_INTERVAL_US); if (currtime <= (m_tsLastRspAckTime + microseconds_from(exp_int_us))) return; // If there is no unacknowledged data in the sending buffer, // then there is nothing to retransmit. if (m_pSndBuffer->getCurrBufSize() <= 0) return; const bool is_laterexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_LATEREXMIT; // FileCC const bool is_fastrexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_FASTREXMIT; // LiveCC // If the receiver will send periodic NAK reports, then FASTREXMIT (live) is inactive. // TODO: Probably some method of "blind rexmit" MUST BE DONE, when TLPKTDROP is off. if (is_fastrexmit && m_bPeerNakReport) return; // Schedule a retransmission IF: // - there are packets in flight (getFlightSpan() > 0); // - in case of LATEREXMIT (File Mode): the sender loss list is empty // (the receiver didn't send any LOSSREPORT, or LOSSREPORT was lost on track). // - in case of FASTREXMIT (Live Mode): the RTO (rtt_syn) was triggered, therefore // schedule unacknowledged packets for retransmission regardless of the loss list emptiness. if (getFlightSpan() > 0 && (!is_laterexmit || m_pSndLossList->getLossLength() == 0)) { // Sender: Insert all the packets sent after last received acknowledgement into the sender loss list. ScopedLock acklock(m_RecvAckLock); // Protect packet retransmission // Resend all unacknowledged packets on timeout, but only if there is no packet in the loss list const int32_t csn = m_iSndCurrSeqNo; const int num = m_pSndLossList->insert(m_iSndLastAck, csn); if (num > 0) { enterCS(m_StatsLock); m_stats.traceSndLoss += num; m_stats.sndLossTotal += num; leaveCS(m_StatsLock); HLOGC(xtlog.Debug, log << CONID() << "ENFORCED " << (is_laterexmit ? "LATEREXMIT" : "FASTREXMIT") << " by ACK-TMOUT (scheduling): " << CSeqNo::incseq(m_iSndLastAck) << "-" << csn << " (" << CSeqNo::seqoff(m_iSndLastAck, csn) << " packets)"); } } ++m_iReXmitCount; checkSndTimers(DONT_REGEN_KM); const ECheckTimerStage stage = is_fastrexmit ? TEV_CHT_FASTREXMIT : TEV_CHT_REXMIT; updateCC(TEV_CHECKTIMER, EventVariant(stage)); // immediately restart transmission m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE); } void srt::CUDT::checkTimers() { // update CC parameters updateCC(TEV_CHECKTIMER, EventVariant(TEV_CHT_INIT)); const steady_clock::time_point currtime = steady_clock::now(); // This is a very heavy log, unblock only for temporary debugging! #if 0 HLOGC(xtlog.Debug, log << CONID() << "checkTimers: nextacktime=" << FormatTime(m_tsNextACKTime) << " AckInterval=" << m_iACKInterval << " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount); #endif // Check if it is time to send ACK int debug_decision = checkACKTimer(currtime); // Check if it is time to send a loss report debug_decision |= checkNAKTimer(currtime); // Check if the connection is expired if (checkExpTimer(currtime, debug_decision)) return; // Check if FAST or LATE packet retransmission is required checkRexmitTimer(currtime); if (currtime > m_tsLastSndTime.load() + microseconds_from(COMM_KEEPALIVE_PERIOD_US)) { sendCtrl(UMSG_KEEPALIVE); #if ENABLE_EXPERIMENTAL_BONDING if (m_parent->m_GroupOf) { ScopedLock glock (s_UDTUnited.m_GlobControlLock); if (m_parent->m_GroupOf) { // Pass socket ID because it's about changing group socket data m_parent->m_GroupOf->internalKeepalive(m_parent->m_GroupMemberData); // NOTE: GroupLock is unnecessary here because the only data read and // modified is the target of the iterator from m_GroupMemberData. The // iterator will be valid regardless of any container modifications. } } #endif HLOGP(xtlog.Debug, "KEEPALIVE"); } } void srt::CUDT::updateBrokenConnection() { m_bClosing = true; releaseSynch(); // app can call any UDT API to learn the connection_broken error s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); CGlobEvent::triggerEvent(); } void srt::CUDT::completeBrokenConnectionDependencies(int errorcode) { int token = -1; #if ENABLE_EXPERIMENTAL_BONDING bool pending_broken = false; { ScopedLock guard_group_existence (s_UDTUnited.m_GlobControlLock); if (m_parent->m_GroupOf) { token = m_parent->m_GroupMemberData->token; if (m_parent->m_GroupMemberData->sndstate == SRT_GST_PENDING) { HLOGC(gmlog.Debug, log << "updateBrokenConnection: a pending link was broken - will be removed"); pending_broken = true; } else { HLOGC(gmlog.Debug, log << "updateBrokenConnection: state=" << CUDTGroup::StateStr(m_parent->m_GroupMemberData->sndstate) << " a used link was broken - not closing automatically"); } m_parent->m_GroupMemberData->sndstate = SRT_GST_BROKEN; m_parent->m_GroupMemberData->rcvstate = SRT_GST_BROKEN; } } #endif if (m_cbConnectHook) { CALLBACK_CALL(m_cbConnectHook, m_SocketID, errorcode, m_PeerAddr.get(), token); } #if ENABLE_EXPERIMENTAL_BONDING { // Lock GlobControlLock in order to make sure that // the state if the socket having the group and the // existence of the group will not be changed during // the operation. The attempt of group deletion will // have to wait until this operation completes. ScopedLock lock(s_UDTUnited.m_GlobControlLock); CUDTGroup* pg = m_parent->m_GroupOf; if (pg) { // Bound to one call because this requires locking pg->updateFailedLink(); } } // Sockets that never succeeded to connect must be deleted // explicitly, otherwise they will never be deleted. if (pending_broken) { // XXX This somehow can cause a deadlock, even without GlobControlLock // s_UDTUnited.close(m_parent); m_parent->setBrokenClosed(); } #endif } void srt::CUDT::addEPoll(const int eid) { enterCS(s_UDTUnited.m_EPoll.m_EPollLock); m_sPollID.insert(eid); leaveCS(s_UDTUnited.m_EPoll.m_EPollLock); if (!stillConnected()) return; enterCS(m_RecvLock); if (m_pRcvBuffer->isRcvDataReady()) { s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } leaveCS(m_RecvLock); if (m_config.iSndBufSize > m_pSndBuffer->getCurrBufSize()) { s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); } } void srt::CUDT::removeEPollEvents(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, SRT_EPOLL_IN | SRT_EPOLL_OUT, false); } void srt::CUDT::removeEPollID(const int eid) { enterCS(s_UDTUnited.m_EPoll.m_EPollLock); m_sPollID.erase(eid); leaveCS(s_UDTUnited.m_EPoll.m_EPollLock); } void srt::CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl) { if (evt >= TEV_E_SIZE) return; // sanity check m_Slots[evt].push_back(sl); } void srt::CUDT::DisconnectSignal(ETransmissionEvent evt) { if (evt >= TEV_E_SIZE) return; // sanity check m_Slots[evt].clear(); } void srt::CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var) { for (std::vector::iterator i = m_Slots[tev].begin(); i != m_Slots[tev].end(); ++i) { i->emit(tev, var); } } int srt::CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes) { CUDTSocket *s = s_UDTUnited.locateSocket(u); if (!s) return -1; CSndBuffer *b = s->core().m_pSndBuffer; if (!b) return -1; int bytecount, timespan; int count = b->getCurrBufSize((bytecount), (timespan)); if (blocks) *blocks = count; if (bytes) *bytes = bytecount; return std::abs(timespan); } int srt::CUDT::rejectReason(SRTSOCKET u) { CUDTSocket* s = s_UDTUnited.locateSocket(u); if (!s) return SRT_REJ_UNKNOWN; return s->core().m_RejectReason; } int srt::CUDT::rejectReason(SRTSOCKET u, int value) { CUDTSocket* s = s_UDTUnited.locateSocket(u); if (!s) return APIError(MJ_NOTSUP, MN_SIDINVAL); if (value < SRT_REJC_PREDEFINED) return APIError(MJ_NOTSUP, MN_INVAL); s->core().m_RejectReason = value; return 0; } int64_t srt::CUDT::socketStartTime(SRTSOCKET u) { CUDTSocket* s = s_UDTUnited.locateSocket(u); if (!s) return APIError(MJ_NOTSUP, MN_SIDINVAL); return count_microseconds(s->core().m_stats.tsStartTime.time_since_epoch()); } bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt) { // Prepare the information for the hook. // We need streamid. char target[CSrtConfig::MAX_SID_LENGTH + 1]; memset((target), 0, CSrtConfig::MAX_SID_LENGTH + 1); // Just for a case, check the length. // This wasn't done before, and we could risk memory crash. // In case of error, this will remain unset and the empty // string will be passed as streamid. int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType); #if ENABLE_EXPERIMENTAL_BONDING bool have_group = false; SRT_GROUP_TYPE gt = SRT_GTYPE_UNDEFINED; #endif // This tests if there are any extensions. if (hspkt.getLength() > CHandShake::m_iContentSize + 4 && IsSet(ext_flags, CHandShake::HS_EXT_CONFIG)) { uint32_t *begin = reinterpret_cast(hspkt.m_pcData + CHandShake::m_iContentSize); size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0 uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // ONE SHOT, but continuable loop { int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); const size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_SID) { if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH) { LOGC(cnlog.Error, log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // See comment at CUDT::interpretSrtHandshake(). memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines ItoHLA(((uint32_t *)target), (uint32_t *)target, blocklen); } #if ENABLE_EXPERIMENTAL_BONDING else if (cmd == SRT_CMD_GROUP) { uint32_t* groupdata = begin + 1; have_group = true; // Even if parse error happes if (bytelen / sizeof(int32_t) >= GRPD_E_SIZE) { uint32_t gd = groupdata[GRPD_GROUPDATA]; gt = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd)); } } #endif else if (cmd == SRT_CMD_NONE) { // End of blocks break; } // Any other kind of message extracted. Search on. if (!NextExtensionBlock((begin), next, (length))) break; } } #if ENABLE_EXPERIMENTAL_BONDING if (have_group && acore->m_config.iGroupConnect == 0) { HLOGC(cnlog.Debug, log << "runAcceptHook: REJECTING connection WITHOUT calling the hook - groups not allowed"); return false; } // Update the groupconnect flag acore->m_config.iGroupConnect = have_group ? 1 : 0; acore->m_HSGroupType = gt; #endif try { int result = CALLBACK_CALL(m_cbAcceptHook, acore->m_SocketID, hs.m_iVersion, peer, target); if (result == -1) return false; } catch (...) { LOGP(cnlog.Warn, "runAcceptHook: hook interrupted by exception"); return false; } return true; } void srt::CUDT::handleKeepalive(const char* /*data*/, size_t /*size*/) { // Here can be handled some protocol definition // for extra data sent through keepalive. #if ENABLE_EXPERIMENTAL_BONDING if (m_parent->m_GroupOf) { // Lock GlobControlLock in order to make sure that // the state if the socket having the group and the // existence of the group will not be changed during // the operation. The attempt of group deletion will // have to wait until this operation completes. ScopedLock lock(s_UDTUnited.m_GlobControlLock); CUDTGroup* pg = m_parent->m_GroupOf; if (pg) { // Whether anything is to be done with this socket // about the fact that keepalive arrived, let the // group handle it pg->handleKeepalive(m_parent->m_GroupMemberData); } } #endif } srt-1.4.4/srtcore/core.h000066400000000000000000001541531412557703600151350ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 02/28/2012 modified by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_CORE_H #define INC_SRT_CORE_H #include #include #include "srt.h" #include "common.h" #include "list.h" #include "buffer.h" #include "window.h" #include "packet.h" #include "channel.h" #include "cache.h" #include "queue.h" #include "handshake.h" #include "congctl.h" #include "packetfilter.h" #include "socketconfig.h" #include "utilities.h" #include "logger_defs.h" #include // TODO: Utility function - to be moved to utilities.h? template inline T CountIIR(T base, T newval, double factor) { if ( base == 0.0 ) return newval; T diff = newval - base; return base+T(diff*factor); } // TODO: Probably a better rework for that can be done - this can be // turned into a serializable structure, just like it's done for CHandShake. enum AckDataItem { ACKD_RCVLASTACK = 0, ACKD_RTT = 1, ACKD_RTTVAR = 2, ACKD_BUFFERLEFT = 3, ACKD_TOTAL_SIZE_SMALL = 4, // Size of the Small ACK, packet length = 16. // Extra fields for Full ACK. ACKD_RCVSPEED = 4, ACKD_BANDWIDTH = 5, ACKD_TOTAL_SIZE_UDTBASE = 6, // Packet length = 24. // Extra stats since SRT v1.0.1. ACKD_RCVRATE = 6, ACKD_TOTAL_SIZE_VER101 = 7, // Packet length = 28. // Only in SRT v1.0.2. ACKD_XMRATE_VER102_ONLY = 7, ACKD_TOTAL_SIZE_VER102_ONLY = 8, // Packet length = 32. ACKD_TOTAL_SIZE = ACKD_TOTAL_SIZE_VER102_ONLY // The maximum known ACK length is 32 bytes. }; const size_t ACKD_FIELD_SIZE = sizeof(int32_t); static const size_t SRT_SOCKOPT_NPOST = 12; extern const SRT_SOCKOPT srt_post_opt_list []; enum GroupDataItem { GRPD_GROUPID, GRPD_GROUPDATA, GRPD_E_SIZE }; const size_t GRPD_MIN_SIZE = 2; // ID and GROUPTYPE as backward compat const size_t GRPD_FIELD_SIZE = sizeof(int32_t); // For HSv4 legacy handshake #define SRT_MAX_HSRETRY 10 /* Maximum SRT handshake retry */ enum SeqPairItems { SEQ_BEGIN = 0, SEQ_END = 1, SEQ_SIZE = 2 }; // Extended SRT Congestion control class - only an incomplete definition required class CCryptoControl; namespace srt { class CUDTUnited; class CUDTSocket; #if ENABLE_EXPERIMENTAL_BONDING class CUDTGroup; #endif // XXX REFACTOR: The 'CUDT' class is to be merged with 'CUDTSocket'. // There's no reason for separating them, there's no case of having them // anyhow managed separately. After this is done, with a small help with // separating the internal abnormal path management (exceptions) from the // API (return values), through CUDTUnited, this class may become in future // an officially exposed C++ API. class CUDT { friend class CUDTSocket; friend class CUDTUnited; friend class CCC; friend struct CUDTComp; friend class CCache; friend class CRendezvousQueue; friend class CSndQueue; friend class CRcvQueue; friend class CSndUList; friend class CRcvUList; friend class PacketFilter; friend class CUDTGroup; friend struct FByOldestActive; // this functional will use private fields friend class TestMockCUDT; // unit tests typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; typedef sync::AtomicClock atomic_time_point; typedef sync::AtomicDuration atomic_duration; private: // constructor and desctructor void construct(); void clearData(); CUDT(CUDTSocket* parent); CUDT(CUDTSocket* parent, const CUDT& ancestor); const CUDT& operator=(const CUDT&) {return *this;} // = delete ? ~CUDT(); public: //API static int startup(); static int cleanup(); static SRTSOCKET socket(); #if ENABLE_EXPERIMENTAL_BONDING static SRTSOCKET createGroup(SRT_GROUP_TYPE); static int addSocketToGroup(SRTSOCKET socket, SRTSOCKET group); static int removeSocketFromGroup(SRTSOCKET socket); static SRTSOCKET getGroupOfSocket(SRTSOCKET socket); static int getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize); static int configureGroup(SRTSOCKET groupid, const char* str); static bool isgroup(SRTSOCKET sock) { return (sock & SRTGROUP_MASK) != 0; } #endif static int bind(SRTSOCKET u, const sockaddr* name, int namelen); static int bind(SRTSOCKET u, UDPSOCKET udpsock); static int listen(SRTSOCKET u, int backlog); static SRTSOCKET accept(SRTSOCKET u, sockaddr* addr, int* addrlen); static SRTSOCKET accept_bond(const SRTSOCKET listeners [], int lsize, int64_t msTimeOut); static int connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); static int connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen); #if ENABLE_EXPERIMENTAL_BONDING static int connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG links [], int arraysize); #endif static int close(SRTSOCKET u); static int getpeername(SRTSOCKET u, sockaddr* name, int* namelen); static int getsockname(SRTSOCKET u, sockaddr* name, int* namelen); static int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); static int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); static int send(SRTSOCKET u, const char* buf, int len, int flags); static int recv(SRTSOCKET u, char* buf, int len, int flags); static int sendmsg(SRTSOCKET u, const char* buf, int len, int ttl = SRT_MSGTTL_INF, bool inorder = false, int64_t srctime = 0); static int recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime); static int sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& mctrl); static int recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_mctrl); static int64_t sendfile(SRTSOCKET u, std::fstream& ifs, int64_t& offset, int64_t size, int block = SRT_DEFAULT_SENDFILE_BLOCK); static int64_t recvfile(SRTSOCKET u, std::fstream& ofs, int64_t& offset, int64_t size, int block = SRT_DEFAULT_RECVFILE_BLOCK); static int select(int nfds, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* 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_clear_usocks(int eid); static int epoll_add_usock(const int eid, const SRTSOCKET 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 SRTSOCKET u); static int epoll_remove_ssock(const int eid, const SYSSOCKET s); static int epoll_update_usock(const int eid, const SRTSOCKET u, const int* events = NULL); static int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); static int epoll_wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); static int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); static int32_t epoll_set(const int eid, int32_t flags); static int epoll_release(const int eid); static CUDTException& getlasterror(); static int bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear = true, bool instantaneous = false); #if ENABLE_EXPERIMENTAL_BONDING static int groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear = true); #endif static SRT_SOCKSTATUS getsockstate(SRTSOCKET u); static bool setstreamid(SRTSOCKET u, const std::string& sid); static std::string getstreamid(SRTSOCKET u); static int getsndbuffer(SRTSOCKET u, size_t* blocks, size_t* bytes); static int rejectReason(SRTSOCKET s); static int rejectReason(SRTSOCKET s, int value); static int64_t socketStartTime(SRTSOCKET s); public: // internal API // This is public so that it can be used directly in API implementation functions. struct APIError { APIError(const CUDTException&); APIError(CodeMajor, CodeMinor, int = 0); operator int() const { return SRT_ERROR; } }; static const SRTSOCKET INVALID_SOCK = -1; // Invalid socket descriptor static const int ERROR = -1; // Socket api error returned value static const int HS_VERSION_UDT4 = 4; static const int HS_VERSION_SRT1 = 5; // Parameters // // NOTE: Use notation with X*1000*1000*... instead of // million zeros in a row. static const int COMM_RESPONSE_MAX_EXP = 16; static const int SRT_TLPKTDROP_MINTHRESHOLD_MS = 1000; static const uint64_t COMM_KEEPALIVE_PERIOD_US = 1*1000*1000; static const int32_t COMM_SYN_INTERVAL_US = 10*1000; static const int COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS = 3000; static const uint16_t MAX_WEIGHT = 32767; static const size_t ACK_WND_SIZE = 1024; static const int INITIAL_RTT = 10 * COMM_SYN_INTERVAL_US; static const int INITIAL_RTTVAR = INITIAL_RTT / 2; int handshakeVersion() { return m_ConnRes.m_iVersion; } std::string CONID() const { #if ENABLE_LOGGING std::ostringstream os; os << "@" << m_SocketID << ":"; return os.str(); #else return ""; #endif } SRTSOCKET socketID() const { return m_SocketID; } static CUDT* getUDTHandle(SRTSOCKET u); static std::vector existingSockets(); void addressAndSend(CPacket& pkt); SRT_ATTR_REQUIRES(m_ConnectionLock) void sendSrtMsg(int cmd, uint32_t *srtdata_in = NULL, size_t srtlen_in = 0); bool isOPT_TsbPd() const { return m_config.bTSBPD; } int SRTT() const { return m_iSRTT; } int RTTVar() const { return m_iRTTVar; } int32_t sndSeqNo() const { return m_iSndCurrSeqNo; } int32_t schedSeqNo() const { return m_iSndNextSeqNo; } bool overrideSndSeqNo(int32_t seq); sync::steady_clock::time_point lastRspTime() const { return m_tsLastRspTime.load(); } sync::steady_clock::time_point freshActivationStart() const { return m_tsFreshActivation; } int32_t rcvSeqNo() const { return m_iRcvCurrSeqNo; } int flowWindowSize() const { return m_iFlowWindowSize; } int32_t deliveryRate() const { return m_iDeliveryRate; } int bandwidth() const { return m_iBandwidth; } int64_t maxBandwidth() const { return m_config.llMaxBW; } int MSS() const { return m_config.iMSS; } uint32_t peerLatency_us() const { return m_iPeerTsbPdDelay_ms * 1000; } int peerIdleTimeout_ms() const { return m_config.iPeerIdleTimeout; } size_t maxPayloadSize() const { return m_iMaxSRTPayloadSize; } size_t OPT_PayloadSize() const { return m_config.zExpPayloadSize; } int sndLossLength() { return m_pSndLossList->getLossLength(); } int32_t ISN() const { return m_iISN; } int32_t peerISN() const { return m_iPeerISN; } duration minNAKInterval() const { return m_tdMinNakInterval; } sockaddr_any peerAddr() const { return m_PeerAddr; } /// Returns the number of packets in flight (sent, but not yet acknowledged). /// @param lastack is the sequence number of the first unacknowledged packet. /// @param curseq is the sequence number of the latest original packet sent /// /// @note When there are no packets in flight, lastack = incseq(curseq). /// /// @returns The number of packets in flight belonging to the interval [0; ...) static int32_t getFlightSpan(int32_t lastack, int32_t curseq) { // Packets sent: // | 1 | 2 | 3 | 4 | 5 | // ^ ^ // | | // lastack | // curseq // // In Flight: [lastack; curseq] // // Normally 'lastack' should be PAST the 'curseq', // however in a case when the sending stopped and all packets were // ACKed, the 'lastack' is one sequence ahead of 'curseq'. // Therefore we increase 'curseq' by 1 forward and then // get the distance towards the last ACK. This way this value may // be only positive as seqlen() includes endpoints. // Finally, we subtract 1 to exclude the increment added earlier. return CSeqNo::seqlen(lastack, CSeqNo::incseq(curseq)) - 1; } /// Returns the number of packets in flight (sent, but not yet acknowledged). /// @returns The number of packets in flight belonging to the interval [0; ...) int32_t getFlightSpan() const { return getFlightSpan(m_iSndLastAck, m_iSndCurrSeqNo); } int minSndSize(int len = 0) const { const int ps = (int) maxPayloadSize(); if (len == 0) // wierd, can't use non-static data member as default argument! len = ps; return m_config.bMessageAPI ? (len+ps-1)/ps : 1; } int32_t makeTS(const time_point& from_time) const { // NOTE: // - This calculates first the time difference towards start time. // - This difference value is also CUT OFF THE SEGMENT information // (a multiple of MAX_TIMESTAMP+1) // So, this can be simply defined as: TS = (RTS - STS) % (MAX_TIMESTAMP+1) // XXX Would be nice to check if local_time > m_tsStartTime, // otherwise it may go unnoticed with clock skew. return (int32_t) sync::count_microseconds(from_time - m_stats.tsStartTime); } void setPacketTS(CPacket& p, const time_point& local_time) { p.m_iTimeStamp = makeTS(local_time); } // Utility used for closing a listening socket // immediately to free the socket void notListening() { sync::ScopedLock cg(m_ConnectionLock); m_bListening = false; m_pRcvQueue->removeListener(this); } static int32_t generateISN() { using namespace sync; return genRandomInt(0, CSeqNo::m_iMaxSeqNo); } // For SRT_tsbpdLoop static CUDTUnited* uglobal() { return &s_UDTUnited; } // needed by tsbpdLoop std::set& pollset() { return m_sPollID; } CSrtConfig m_config; SRTU_PROPERTY_RO(SRTSOCKET, id, m_SocketID); SRTU_PROPERTY_RO(bool, isClosing, m_bClosing); SRTU_PROPERTY_RO(CRcvBuffer*, rcvBuffer, m_pRcvBuffer); SRTU_PROPERTY_RO(bool, isTLPktDrop, m_bTLPktDrop); SRTU_PROPERTY_RO(bool, isSynReceiving, m_config.bSynRecving); SRTU_PROPERTY_RR(sync::Condition*, recvDataCond, &m_RecvDataCond); SRTU_PROPERTY_RR(sync::Condition*, recvTsbPdCond, &m_RcvTsbPdCond); /// @brief Request a socket to be broken due to too long instability (normally by a group). void breakAsUnstable() { m_bBreakAsUnstable = true; } void ConnectSignal(ETransmissionEvent tev, EventSlot sl); void DisconnectSignal(ETransmissionEvent tev); // This is in public section so prospective overriding it can be // done by directly assigning to a field. typedef std::vector< std::pair > loss_seqs_t; typedef loss_seqs_t packetArrival_cb(void*, CPacket&); CallbackHolder m_cbPacketArrival; 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 startConnect(const sockaddr_any& 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 SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) EConnectStatus processConnectResponse(const CPacket& pkt, CUDTException* eout) ATR_NOEXCEPT; // This function works in case of HSv5 rendezvous. It changes the state // according to the present state and received message type, as well as the // INITIATOR/RESPONDER side resolved through cookieContest(). // The resulting data are: // - rsptype: handshake message type that should be sent back to the peer (nothing if URQ_DONE) // - needs_extension: the HSREQ/KMREQ or HSRSP/KMRSP extensions should be attached to the handshake message. // - RETURNED VALUE: if true, it means a URQ_CONCLUSION message was received with HSRSP/KMRSP extensions and needs HSRSP/KMRSP. void rendezvousSwitchState(UDTRequestType& rsptype, bool& needs_extension, bool& needs_hsrsp); void cookieContest(); /// Interpret the incoming handshake packet in order to perform appropriate /// rendezvous FSM state transition if needed, and craft the response, serialized /// into the packet to be next sent. /// @param reqpkt Packet to be written with handshake data /// @param response incoming handshake response packet to be interpreted /// @param serv_addr incoming packet's address /// @param rst Current read status to know if the HS packet was freshly received from the peer, or this is only a periodic update (RST_AGAIN) SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) EConnectStatus processRendezvous(const CPacket* response, const sockaddr_any& serv_addr, EReadStatus, CPacket& reqpkt); SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout); SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) EConnectStatus postConnect(const CPacket* response, bool rendezvous, CUDTException* eout) ATR_NOEXCEPT; SRT_ATR_NODISCARD bool applyResponseSettings() ATR_NOEXCEPT; SRT_ATR_NODISCARD EConnectStatus processAsyncConnectResponse(const CPacket& pkt) ATR_NOEXCEPT; SRT_ATR_NODISCARD bool processAsyncConnectRequest(EReadStatus rst, EConnectStatus cst, const CPacket* response, const sockaddr_any& serv_addr); SRT_ATR_NODISCARD EConnectStatus craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatasize); void checkUpdateCryptoKeyLen(const char* loghdr, int32_t typefield); SRT_ATR_NODISCARD size_t fillSrtHandshake_HSREQ(uint32_t* srtdata, size_t srtlen, int hs_version); SRT_ATR_NODISCARD size_t fillSrtHandshake_HSRSP(uint32_t* srtdata, size_t srtlen, int hs_version); SRT_ATR_NODISCARD size_t fillSrtHandshake(uint32_t* srtdata, size_t srtlen, int msgtype, int hs_version); SRT_ATR_NODISCARD bool createSrtHandshake(int srths_cmd, int srtkm_cmd, const uint32_t* data, size_t datalen, CPacket& w_reqpkt, CHandShake& w_hs); SRT_ATR_NODISCARD size_t fillHsExtConfigString(uint32_t *pcmdspec, int cmd, const std::string &str); #if ENABLE_EXPERIMENTAL_BONDING SRT_ATR_NODISCARD size_t fillHsExtGroup(uint32_t *pcmdspec); #endif SRT_ATR_NODISCARD size_t fillHsExtKMREQ(uint32_t *pcmdspec, size_t ki); SRT_ATR_NODISCARD size_t fillHsExtKMRSP(uint32_t *pcmdspec, const uint32_t *kmdata, size_t kmdata_wordsize); SRT_ATR_NODISCARD size_t prepareSrtHsMsg(int cmd, uint32_t* srtdata, size_t size); SRT_ATR_NODISCARD bool processSrtMsg(const CPacket *ctrlpkt); SRT_ATR_NODISCARD int processSrtMsg_HSREQ(const uint32_t* srtdata, size_t bytelen, uint32_t ts, int hsv); SRT_ATR_NODISCARD int processSrtMsg_HSRSP(const uint32_t* srtdata, size_t bytelen, uint32_t ts, int hsv); SRT_ATR_NODISCARD bool interpretSrtHandshake(const CHandShake& hs, const CPacket& hspkt, uint32_t* out_data, size_t* out_len); SRT_ATR_NODISCARD bool checkApplyFilterConfig(const std::string& cs); #if ENABLE_EXPERIMENTAL_BONDING static CUDTGroup& newGroup(const int); // defined EXCEPTIONALLY in api.cpp for convenience reasons // Note: This is an "interpret" function, which should treat the tp as // "possibly group type" that might be out of the existing values. SRT_ATR_NODISCARD bool interpretGroup(const int32_t grpdata[], size_t data_size, int hsreq_type_cmd); SRT_ATR_NODISCARD SRTSOCKET makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE tp, uint32_t link_flags); void synchronizeWithGroup(CUDTGroup* grp); #endif void updateAfterSrtHandshake(int hsv); void updateSrtRcvSettings(); void updateSrtSndSettings(); void updateIdleLinkFrom(CUDT* source); void checkNeedDrop(bool& bCongestion); /// Connect to a UDT entity as per hs request. This will update /// required data in the entity, then update them also in the hs structure, /// and then send the response back to the caller. /// @param agent [in] The address to which the UDT entity is bound. /// @param peer [in] The address of the listening UDT entity. /// @param hspkt [in] The original packet that brought the handshake. /// @param hs [in/out] The handshake information sent by the peer side (in), negotiated value (out). void acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& hs); /// Write back to the hs structure the data after they have been /// negotiated by acceptAndRespond. void rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs); bool runAcceptHook(CUDT* acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt); /// Close the opened UDT entity. bool closeInternal(); void updateBrokenConnection(); void completeBrokenConnectionDependencies(int errorcode); /// 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. SRT_ATR_NODISCARD int send(const char* data, int len) { return sendmsg(data, len, SRT_MSGTTL_INF, false, 0); } /// 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. SRT_ATR_NODISCARD 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. SRT_ATR_NODISCARD int sendmsg(const char* data, int len, int ttl, bool inorder, int64_t srctime); /// Receive a message to buffer "data". /// @param data [out] data received. /// @param len [in] size of the buffer. /// @return Actual size of data received. SRT_ATR_NODISCARD int sendmsg2(const char* data, int len, SRT_MSGCTRL& w_m); SRT_ATR_NODISCARD int recvmsg(char* data, int len, int64_t& srctime); SRT_ATR_NODISCARD int recvmsg2(char* data, int len, SRT_MSGCTRL& w_m); SRT_ATR_NODISCARD int receiveMessage(char* data, int len, SRT_MSGCTRL& w_m, int erh = 1 /*throw exception*/); SRT_ATR_NODISCARD int receiveBuffer(char* data, int len); size_t dropMessage(int32_t seqtoskip); /// 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. SRT_ATR_NODISCARD 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. SRT_ATR_NODISCARD 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(SRT_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(SRT_SOCKOPT optName, void* optval, int& w_optlen); #if ENABLE_EXPERIMENTAL_BONDING /// Applies the configuration set on the socket. /// Any errors in this process are reported by exception. SRT_ERRNO applyMemberConfigObject(const SRT_SocketOptionObject& opt); #endif /// read the performance data with bytes counters since bstats() /// /// @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. /// @param instantaneous [in] flag to request instantaneous data /// instead of moving averages. void bstats(CBytePerfMon* perf, bool clear = true, bool instantaneous = false); /// 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 dropFromLossLists(int32_t from, int32_t to); void checkSndTimers(Whether2RegenKm regen = DONT_REGEN_KM); void handshakeDone() { m_iSndHsRetryCnt = 0; } int64_t withOverhead(int64_t basebw) { return (basebw * (100 + m_config.iOverheadBW))/100; } static double Bps2Mbps(int64_t basebw) { return double(basebw) * 8.0/1000000.0; } bool stillConnected() { // Still connected is when: // - no "broken" condition appeared (security, protocol error, response timeout) return !m_bBroken // - still connected (no one called srt_close()) && m_bConnected // - isn't currently closing (srt_close() called, response timeout, shutdown) && !m_bClosing; } int sndSpaceLeft() { return static_cast(sndBuffersLeft() * maxPayloadSize()); } int sndBuffersLeft() { return m_config.iSndBufSize - m_pSndBuffer->getCurrBufSize(); } time_point socketStartTime() { return m_stats.tsStartTime; } // TSBPD thread main function. static void* tsbpd(void* param); void updateForgotten(int seqlen, int32_t lastack, int32_t skiptoseqno); static loss_seqs_t defaultPacketArrival(void* vself, CPacket& pkt); static loss_seqs_t groupPacketArrival(void* vself, CPacket& pkt); static CUDTUnited s_UDTUnited; // UDT global management base private: // Identification CUDTSocket* const m_parent; // Temporary, until the CUDTSocket class is merged with CUDT SRTSOCKET m_SocketID; // UDT socket number SRTSOCKET m_PeerID; // Peer ID, for multiplexer // HSv4 (legacy handshake) support) time_point m_tsSndHsLastTime; // Last SRT handshake request time int m_iSndHsRetryCnt; // SRT handshake retries left #if ENABLE_EXPERIMENTAL_BONDING SRT_GROUP_TYPE m_HSGroupType; // Group type about-to-be-set in the handshake #endif private: int m_iMaxSRTPayloadSize; // Maximum/regular payload size, in bytes int m_iTsbPdDelay_ms; // Rx delay to absorb burst, in milliseconds int m_iPeerTsbPdDelay_ms; // Tx delay that the peer uses to absorb burst, in milliseconds bool m_bTLPktDrop; // Enable Too-late Packet Drop SRT_ATTR_PT_GUARDED_BY(m_ConnectionLock) UniquePtr m_pCryptoControl; // Crypto control module CCache* m_pCache; // Network information cache // Congestion control std::vector m_Slots[TEV_E_SIZE]; SrtCongestion m_CongCtl; // Packet filtering PacketFilter m_PacketFilter; SRT_ARQLevel m_PktFilterRexmitLevel; std::string m_sPeerPktFilterConfigString; // Attached tool function void EmitSignal(ETransmissionEvent tev, EventVariant var); // Internal state sync::atomic m_bListening; // If the UDT entity is listening to connection sync::atomic m_bConnecting; // The short phase when connect() is called but not yet completed sync::atomic m_bConnected; // Whether the connection is on or off sync::atomic m_bClosing; // If the UDT entity is closing sync::atomic m_bShutdown; // If the peer side has shutdown the connection sync::atomic m_bBroken; // If the connection has been broken sync::atomic m_bBreakAsUnstable; // A flag indicating that the socket should become broken because it has been unstable for too long. sync::atomic m_bPeerHealth; // If the peer status is normal sync::atomic m_RejectReason; bool m_bOpened; // If the UDT entity has been opened sync::atomic m_iBrokenCounter; // A counter (number of GC checks) to let the GC tag this socket as disconnected int m_iEXPCount; // Expiration counter sync::atomic m_iBandwidth; // Estimated bandwidth, number of packets per second sync::atomic m_iSRTT; // Smoothed RTT (an exponentially-weighted moving average (EWMA) // of an endpoint's RTT samples), in microseconds sync::atomic m_iRTTVar; // The variation in the RTT samples (RTT variance), in microseconds sync::atomic m_bIsFirstRTTReceived; // True if the first RTT sample was obtained from the ACK/ACKACK pair // at the receiver side or received by the sender from an ACK packet. // It's used to reset the initial value of smoothed RTT (m_iSRTT) // at the beginning of transmission (including the one taken from // cache). False by default. sync::atomic m_iDeliveryRate; // Packet arrival rate at the receiver side sync::atomic m_iByteDeliveryRate; // Byte arrival rate at the receiver side CHandShake m_ConnReq; // Connection request CHandShake m_ConnRes; // Connection response CHandShake::RendezvousState m_RdvState; // HSv5 rendezvous state HandshakeSide m_SrtHsSide; // HSv5 rendezvous handshake side resolved from cookie contest (DRAW if not yet resolved) private: // Sending related data CSndBuffer* m_pSndBuffer; // Sender buffer CSndLossList* m_pSndLossList; // Sender loss list CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window atomic_duration m_tdSendInterval; // Inter-packet time, in CPU clock cycles atomic_duration m_tdSendTimeDiff; // Aggregate difference in inter-packet sending time SRT_ATTR_GUARDED_BY(m_RecvAckLock) sync::atomic m_iFlowWindowSize; // Flow control window size double m_dCongestionWindow; // Congestion window size private: // Timers atomic_time_point m_tsNextACKTime; // Next ACK time, in CPU clock cycles, same below atomic_time_point m_tsNextNAKTime; // Next NAK time duration m_tdACKInterval; // ACK interval duration m_tdNAKInterval; // NAK interval SRT_ATTR_GUARDED_BY(m_RecvAckLock) atomic_time_point m_tsLastRspTime; // Timestamp of last response from the peer time_point m_tsLastRspAckTime; // Timestamp of last ACK from the peer atomic_time_point m_tsLastSndTime; // Timestamp of last data/ctrl sent (in system ticks) time_point m_tsLastWarningTime; // Last time that a warning message is sent atomic_time_point m_tsLastReqTime; // last time when a connection request is sent time_point m_tsRcvPeerStartTime; time_point m_tsLingerExpiration; // Linger expiration time (for GC to close a socket with data in sending buffer) time_point m_tsLastAckTime; // Timestamp of last ACK duration m_tdMinNakInterval; // NAK timeout lower bound; too small value can cause unnecessary retransmission duration m_tdMinExpInterval; // Timeout lower bound threshold: too small timeout can cause problem int m_iPktCount; // Packet counter for ACK int m_iLightACKCount; // Light ACK counter time_point m_tsNextSendTime; // Scheduled time of next packet sending sync::atomic m_iSndLastFullAck; // Last full ACK received SRT_ATTR_GUARDED_BY(m_RecvAckLock) sync::atomic m_iSndLastAck; // Last ACK received // NOTE: m_iSndLastDataAck is the value strictly bound to the CSndBufer object (m_pSndBuffer) // and this is the sequence number that refers to the block at position [0]. Upon acknowledgement, // this value is shifted to the acknowledged position, and the blocks are removed from the // m_pSndBuffer buffer up to excluding this sequence number. // XXX CONSIDER removing this field and give up the maintenance of this sequence number // to the sending buffer. This way, extraction of an old packet for retransmission should // require only the lost sequence number, and how to find the packet with this sequence // will be up to the sending buffer. sync::atomic m_iSndLastDataAck; // The real last ACK that updates the sender buffer and loss list sync::atomic m_iSndCurrSeqNo; // The largest sequence number that HAS BEEN SENT sync::atomic m_iSndNextSeqNo; // The sequence number predicted to be placed at the currently scheduled packet // Note important differences between Curr and Next fields: // - m_iSndCurrSeqNo: this is used by SRT:SndQ:worker thread and it's operated from CUDT::packData // function only. This value represents the sequence number that has been stamped on a packet directly // before it is sent over the network. // - m_iSndNextSeqNo: this is used by the user's thread and it's operated from CUDT::sendmsg2 // function only. This value represents the sequence number that is PREDICTED to be stamped on the // first block out of the block series that will be scheduled for later sending over the network // out of the data passed in this function. For a special case when the length of the data is // short enough to be passed in one UDP packet (always the case for live mode), this value is // always increased by one in this call, otherwise it will be increased by the number of blocks // scheduled for sending. int32_t m_iSndLastAck2; // Last ACK2 sent back time_point m_SndLastAck2Time; // The time when last ACK2 was sent back void setInitialSndSeq(int32_t isn) { m_iSndLastAck = isn; m_iSndLastDataAck = isn; m_iSndLastFullAck = isn; m_iSndCurrSeqNo = CSeqNo::decseq(isn); m_iSndNextSeqNo = isn; m_iSndLastAck2 = isn; } void setInitialRcvSeq(int32_t isn) { m_iRcvLastAck = isn; #ifdef ENABLE_LOGGING m_iDebugPrevLastAck = m_iRcvLastAck; #endif m_iRcvLastSkipAck = m_iRcvLastAck; m_iRcvLastAckAck = isn; m_iRcvCurrSeqNo = CSeqNo::decseq(isn); } int32_t m_iISN; // Initial Sequence Number bool m_bPeerTsbPd; // Peer accept TimeStamp-Based Rx mode bool m_bPeerTLPktDrop; // Enable sender late packet dropping bool m_bPeerNakReport; // Sender's peer (receiver) issues Periodic NAK Reports bool m_bPeerRexmitFlag; // Receiver supports rexmit flag in payload packets SRT_ATTR_GUARDED_BY(m_RecvAckLock) int32_t m_iReXmitCount; // Re-Transmit Count since last ACK private: // Receiving related data CRcvBuffer* m_pRcvBuffer; // Receiver buffer CRcvLossList* m_pRcvLossList; // Receiver loss list 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_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 int32_t m_iRcvLastSkipAck; // Last dropped sequence ACK int32_t m_iRcvLastAckAck; // Last sent ACK that has been acknowledged int32_t m_iAckSeqNo; // Last ACK sequence number sync::atomic m_iRcvCurrSeqNo; // Largest received sequence number int32_t m_iRcvCurrPhySeqNo; // Same as m_iRcvCurrSeqNo, but physical only (disregarding a filter) int32_t m_iPeerISN; // Initial Sequence Number of the peer side uint32_t m_uPeerSrtVersion; uint32_t m_uPeerSrtFlags; bool m_bTsbPd; // Peer sends TimeStamp-Based Packet Delivery Packets bool m_bGroupTsbPd; // TSBPD should be used for GROUP RECEIVER instead sync::CThread m_RcvTsbPdThread; // Rcv TsbPD Thread handle sync::Condition m_RcvTsbPdCond; // TSBPD signals if reading is ready. Use together with m_RecvLock bool m_bTsbPdAckWakeup; // Signal TsbPd thread on Ack sent sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creating and joining CallbackHolder m_cbAcceptHook; CallbackHolder m_cbConnectHook; // FORWARDER public: static int installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); static int installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); private: void installAcceptHook(srt_listen_callback_fn* hook, void* opaq) { m_cbAcceptHook.set(opaq, hook); } void installConnectHook(srt_connect_callback_fn* hook, void* opaq) { m_cbConnectHook.set(opaq, hook); } private: // synchronization: mutexes and conditions sync::Mutex m_ConnectionLock; // used to synchronize connection operation sync::Condition m_SendBlockCond; // used to block "send" call sync::Mutex m_SendBlockLock; // lock associated to m_SendBlockCond sync::Mutex m_RcvBufferLock; // Protects the state of the m_pRcvBuffer // Protects access to m_iSndCurrSeqNo, m_iSndLastAck sync::Mutex m_RecvAckLock; // Protects the state changes while processing incomming ACK (SRT_EPOLL_OUT) sync::Condition m_RecvDataCond; // used to block "srt_recv*" when there is no data. Use together with m_RecvLock sync::Mutex m_RecvLock; // used to synchronize "srt_recv*" call, protects TSBPD drift updates (CRcvBuffer::isRcvDataReady()) sync::Mutex m_SendLock; // used to synchronize "send" call sync::Mutex m_RcvLossLock; // Protects the receiver loss list (access: CRcvQueue::worker, CUDT::tsbpd) mutable sync::Mutex m_StatsLock; // used to synchronize access to trace statistics void initSynch(); void destroySynch(); void releaseSynch(); private: // Common connection Congestion Control setup // This can fail only when it failed to create a congctl // which only may happen when the congctl list is extended // with user-supplied congctl modules, not a case so far. SRT_ATR_NODISCARD SRT_REJECT_REASON setupCC(); // for updateCC it's ok to discard the value. This returns false only if // the congctl isn't created, and this can be prevented from. bool updateCC(ETransmissionEvent, const EventVariant arg); // Failure to create the crypter means that an encrypted // connection should be rejected if ENFORCEDENCRYPTION is on. SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) bool createCrypter(HandshakeSide side, bool bidi); private: // Generation and processing of packets void sendCtrl(UDTMessageType pkttype, const int32_t* lparam = NULL, void* rparam = NULL, int size = 0); /// Forms and sends ACK packet /// @note Assumes @ctrlpkt already has a timestamp. /// /// @param ctrlpkt A control packet structure to fill. It must have a timestemp already set. /// @param size Sends lite ACK if size is SEND_LITE_ACK, Full ACK otherwise /// /// @returns the nmber of packets sent. int sendCtrlAck(CPacket& ctrlpkt, int size); void sendLossReport(const std::vector< std::pair >& losslist); void processCtrl(const CPacket& ctrlpkt); /// @brief Process incoming control ACK packet. /// @param ctrlpkt incoming ACK packet /// @param currtime current clock time void processCtrlAck(const CPacket& ctrlpkt, const time_point& currtime); /// @brief Process incoming control ACKACK packet. /// @param ctrlpkt incoming ACKACK packet /// @param tsArrival time when packet has arrived (used to calculate RTT) void processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival); /// @brief Process incoming loss report (NAK) packet. /// @param ctrlpkt incoming NAK packet void processCtrlLossReport(const CPacket& ctrlpkt); /// @brief Process incoming handshake control packet /// @param ctrlpkt incoming HS packet void processCtrlHS(const CPacket& ctrlpkt); /// @brief Process incoming drop request control packet /// @param ctrlpkt incoming drop request packet void processCtrlDropReq(const CPacket& ctrlpkt); /// @brief Process incoming shutdown control packet void processCtrlShutdown(); /// @brief Process incoming user defined control packet /// @param ctrlpkt incoming user defined packet void processCtrlUserDefined(const CPacket& ctrlpkt); /// @brief Update sender's loss list on an incoming acknowledgement. /// @param ackdata_seqno sequence number of a data packet being acknowledged void updateSndLossListOnACK(int32_t ackdata_seqno); /// Pack a packet from a list of lost packets. /// /// @param packet [in, out] a packet structure to fill /// @param origintime [in, out] origin timestamp of the packet /// /// @return payload size on success, <=0 on failure int packLostData(CPacket &packet, time_point &origintime); /// Pack in CPacket the next data to be send. /// /// @param packet [in, out] a CPacket structure to fill /// /// @return A pair of values is returned (payload, timestamp). /// The payload tells the size of the payload, packed in CPacket. /// The timestamp is the full source/origin timestamp of the data. /// If payload is <= 0, consider the timestamp value invalid. std::pair packData(CPacket& packet); int processData(CUnit* unit); void processClose(); /// Process the request after receiving the handshake from caller. /// The @a packet param is passed here as non-const because this function /// will need to make a temporary back-and-forth endian swap; it doesn't intend to /// modify the object permanently. /// @param addr source address from where the request came /// @param packet contents of the packet /// @return URQ code, possibly containing reject reason int processConnectRequest(const sockaddr_any& addr, CPacket& packet); static void addLossRecord(std::vector& lossrecord, int32_t lo, int32_t hi); int32_t bake(const sockaddr_any& addr, int32_t previous_cookie = 0, int correction = 0); int32_t ackDataUpTo(int32_t seq); void handleKeepalive(const char* data, size_t lenghth); private: // Trace struct CoreStats { time_point tsStartTime; // timestamp when the UDT entity is started int64_t sentTotal; // total number of sent data packets, including retransmissions int64_t sentUniqTotal; // total number of sent data packets, excluding rexmit and filter control int64_t recvTotal; // total number of received packets int64_t recvUniqTotal; // total number of received and delivered packets int sndLossTotal; // total number of lost packets (sender side) int rcvLossTotal; // total number of lost packets (receiver side) int retransTotal; // total number of retransmitted packets int sentACKTotal; // total number of sent ACK packets int recvACKTotal; // total number of received ACK packets int sentNAKTotal; // total number of sent NAK packets int recvNAKTotal; // total number of received NAK packets int sndDropTotal; int rcvDropTotal; uint64_t bytesSentTotal; // total number of bytes sent, including retransmissions uint64_t bytesSentUniqTotal; // total number of bytes sent, including retransmissions uint64_t bytesRecvTotal; // total number of received bytes uint64_t bytesRecvUniqTotal; // total number of received bytes uint64_t rcvBytesLossTotal; // total number of loss bytes (estimate) uint64_t bytesRetransTotal; // total number of retransmitted bytes uint64_t sndBytesDropTotal; uint64_t rcvBytesDropTotal; int m_rcvUndecryptTotal; uint64_t m_rcvBytesUndecryptTotal; int sndFilterExtraTotal; int rcvFilterExtraTotal; int rcvFilterSupplyTotal; int rcvFilterLossTotal; int64_t m_sndDurationTotal; // total real time for sending time_point tsLastSampleTime; // last performance sample time int64_t traceSent; // number of packets sent in the last trace interval int64_t traceSentUniq; // number of original packets sent in the last trace interval int64_t traceRecv; // number of packets received in the last trace interval int64_t traceRecvUniq; // number of packets received AND DELIVERED in the last trace interval int traceSndLoss; // number of lost packets in the last trace interval (sender side) int traceRcvLoss; // number of lost packets in the last trace interval (receiver side) int traceRetrans; // number of retransmitted packets in the last trace interval int sentACK; // number of ACKs sent in the last trace interval int recvACK; // number of ACKs received in the last trace interval int sentNAK; // number of NAKs sent in the last trace interval int recvNAK; // number of NAKs received in the last trace interval int traceSndDrop; int traceRcvDrop; int traceRcvRetrans; int traceReorderDistance; double traceBelatedTime; int64_t traceRcvBelated; uint64_t traceBytesSent; // number of bytes sent in the last trace interval uint64_t traceBytesSentUniq; // number of bytes sent in the last trace interval uint64_t traceBytesRecv; // number of bytes sent in the last trace interval uint64_t traceBytesRecvUniq; // number of bytes sent in the last trace interval uint64_t traceRcvBytesLoss; // number of bytes bytes lost in the last trace interval (estimate) uint64_t traceBytesRetrans; // number of bytes retransmitted in the last trace interval uint64_t traceSndBytesDrop; uint64_t traceRcvBytesDrop; int traceRcvUndecrypt; uint64_t traceRcvBytesUndecrypt; int sndFilterExtra; int rcvFilterExtra; int rcvFilterSupply; int rcvFilterLoss; int64_t sndDuration; // real time for sending time_point sndDurationCounter; // timers to record the sending Duration } m_stats; public: static const int SELF_CLOCK_INTERVAL = 64; // ACK interval for self-clocking static const int SEND_LITE_ACK = sizeof(int32_t); // special size for ack containing only ack seq static const int PACKETPAIR_MASK = 0xF; private: // Timers functions time_point m_tsFreshActivation; // GROUPS: time of fresh activation of the link, or 0 if past the activation phase or idle time_point m_tsUnstableSince; // GROUPS: time since unexpected ACK delay experienced, or 0 if link seems healthy time_point m_tsWarySince; // GROUPS: time since an unstable link has first some response static const int BECAUSE_NO_REASON = 0, // NO BITS BECAUSE_ACK = 1 << 0, BECAUSE_LITEACK = 1 << 1, BECAUSE_NAKREPORT = 1 << 2, LAST_BECAUSE_BIT = 3; void checkTimers(); void considerLegacySrtHandshake(const time_point &timebase); int checkACKTimer (const time_point& currtime); int checkNAKTimer(const time_point& currtime); bool checkExpTimer (const time_point& currtime, int check_reason); // returns true if the connection is expired void checkRexmitTimer(const time_point& currtime); private: // for UDP multiplexer CSndQueue* m_pSndQueue; // packet sending queue CRcvQueue* m_pRcvQueue; // packet receiving queue sockaddr_any m_PeerAddr; // 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 public: // For SrtCongestion const CSndQueue* sndQueue() { return m_pSndQueue; } const CRcvQueue* rcvQueue() { return m_pRcvQueue; } private: // for epoll std::set m_sPollID; // set of epoll ID to trigger void addEPoll(const int eid); void removeEPollEvents(const int eid); void removeEPollID(const int eid); }; } // namespace srt #endif srt-1.4.4/srtcore/crypto.cpp000066400000000000000000000776351412557703600160710ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include #include #include #include #include "udt.h" #include "utilities.h" #include #include "crypto.h" #include "logging.h" #include "core.h" using namespace srt; using namespace srt_logging; #define SRT_MAX_KMRETRY 10 //#define SRT_CMD_KMREQ 3 /* HaiCryptTP SRT Keying Material */ //#define SRT_CMD_KMRSP 4 /* HaiCryptTP SRT Keying Material ACK */ #define SRT_CMD_KMREQ_SZ HCRYPT_MSG_KM_MAX_SZ /* */ #if SRT_CMD_KMREQ_SZ > SRT_CMD_MAXSZ #error SRT_CMD_MAXSZ too small #endif /* Key Material Request (Network Order) See HaiCryptTP SRT (hcrypt_xpt_srt.c) */ // 10* HAICRYPT_DEF_KM_PRE_ANNOUNCE const int SRT_CRYPT_KM_PRE_ANNOUNCE SRT_ATR_UNUSED = 0x10000; namespace srt_logging { std::string KmStateStr(SRT_KM_STATE state) { switch (state) { #define TAKE(val) case SRT_KM_S_##val : return #val TAKE(UNSECURED); TAKE(SECURED); TAKE(SECURING); TAKE(NOSECRET); TAKE(BADSECRET); #undef TAKE default: { char buf[256]; sprintf(buf, "??? (%d)", state); return buf; } } } } // namespace using srt_logging::KmStateStr; #if ENABLE_LOGGING std::string CCryptoControl::FormatKmMessage(std::string hdr, int cmd, size_t srtlen) { std::ostringstream os; os << hdr << ": cmd=" << cmd << "(" << (cmd == SRT_CMD_KMREQ ? "KMREQ":"KMRSP") <<") len=" << size_t(srtlen*sizeof(int32_t)) << " KmState: SND=" << KmStateStr(m_SndKmState) << " RCV=" << KmStateStr(m_RcvKmState); return os.str(); } #endif void CCryptoControl::updateKmState(int cmd, size_t srtlen SRT_ATR_UNUSED) { if (cmd == SRT_CMD_KMREQ) { if ( SRT_KM_S_UNSECURED == m_SndKmState) { m_SndKmState = SRT_KM_S_SECURING; } LOGP(cnlog.Note, FormatKmMessage("sendSrtMsg", cmd, srtlen)); } else { LOGP(cnlog.Note, FormatKmMessage("sendSrtMsg", cmd, srtlen)); } } void CCryptoControl::createFakeSndContext() { if (!m_iSndKmKeyLen) m_iSndKmKeyLen = 16; if (!createCryptoCtx(m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX, (m_hSndCrypto))) { HLOGC(cnlog.Debug, log << "Error: Can't create fake crypto context for sending - sending will return ERROR!"); m_hSndCrypto = 0; } } int CCryptoControl::processSrtMsg_KMREQ( const uint32_t* srtdata SRT_ATR_UNUSED, size_t bytelen SRT_ATR_UNUSED, int hsv SRT_ATR_UNUSED, uint32_t pw_srtdata_out[], size_t& w_srtlen) { //Receiver /* All 32-bit msg fields swapped on reception * But HaiCrypt expect network order message * Re-swap to cancel it. */ #ifdef SRT_ENABLE_ENCRYPTION w_srtlen = bytelen/sizeof(srtdata[SRT_KMR_KMSTATE]); HtoNLA((pw_srtdata_out), srtdata, w_srtlen); unsigned char* kmdata = reinterpret_cast(pw_srtdata_out); std::vector kmcopy(kmdata, kmdata + bytelen); // The side that has received KMREQ is always an HSD_RESPONDER, regardless of // what has called this function. The HSv5 handshake only enforces bidirectional // connection. bool bidirectional = hsv > CUDT::HS_VERSION_UDT4; // Local macro to return rejection appropriately. // CHANGED. The first version made HSv5 reject the connection. // This isn't well handled by applications, so the connection is // still established, but unable to handle any transport. //#define KMREQ_RESULT_REJECTION() if (bidirectional) { return SRT_CMD_NONE; } else { w_srtlen = 1; goto HSv4_ErrorReport; } #define KMREQ_RESULT_REJECTION() { w_srtlen = 1; goto HSv4_ErrorReport; } int rc = HAICRYPT_OK; // needed before 'goto' run from KMREQ_RESULT_REJECTION macro bool wasb4 SRT_ATR_UNUSED = false; size_t sek_len = 0; // What we have to do: // If encryption is on (we know that by having m_KmSecret nonempty), create // the crypto context (if bidirectional, create for both sending and receiving). // Both crypto contexts should be set with the same length of the key. // The problem with interpretinting this should be reported as SRT_CMD_NONE, // should be appropriately handled by the caller, as it expects that this // function normally return SRT_CMD_KMRSP. if ( bytelen <= HCRYPT_MSG_KM_OFS_SALT ) //Sanity on message { LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too small, must be >" << HCRYPT_MSG_KM_OFS_SALT); m_RcvKmState = SRT_KM_S_BADSECRET; KMREQ_RESULT_REJECTION(); } HLOGC(cnlog.Debug, log << "KMREQ: getting SEK and creating receiver crypto"); sek_len = hcryptMsg_KM_GetSekLen(kmdata); if ( sek_len == 0 ) { LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Received SEK is empty - REJECTING!"); m_RcvKmState = SRT_KM_S_BADSECRET; KMREQ_RESULT_REJECTION(); } // Write the key length m_iRcvKmKeyLen = sek_len; // Overwrite the key length anyway - it doesn't make sense to somehow // keep the original setting because it will only make KMX impossible. #if ENABLE_HEAVY_LOGGING if (m_iSndKmKeyLen != m_iRcvKmKeyLen) { LOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: Agent's PBKEYLEN=" << m_iSndKmKeyLen << " overwritten by Peer's PBKEYLEN=" << m_iRcvKmKeyLen); } #endif m_iSndKmKeyLen = m_iRcvKmKeyLen; // This is checked only now so that the SRTO_PBKEYLEN return always the correct value, // even if encryption is not possible because Agent didn't set a password, or supplied // a wrong password. if (m_KmSecret.len == 0) //We have a shared secret <==> encryption is on { LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Agent does not declare encryption - won't decrypt incoming packets!"); m_RcvKmState = SRT_KM_S_NOSECRET; KMREQ_RESULT_REJECTION(); } wasb4 = m_hRcvCrypto; if (!createCryptoCtx(m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX, (m_hRcvCrypto))) { LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject..."); m_RcvKmState = SRT_KM_S_NOSECRET; KMREQ_RESULT_REJECTION(); } if (!wasb4) { HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: created RX ENC with KeyLen=" << m_iRcvKmKeyLen); } // We have both sides set with password, so both are pending for security m_RcvKmState = SRT_KM_S_SECURING; // m_SndKmState is set to SECURING or UNSECURED in init(), // or it might have been set to SECURED, NOSECRET or BADSECRET in the previous // handshake iteration (handshakes may be sent multiple times for the same connection). rc = HaiCrypt_Rx_Process(m_hRcvCrypto, kmdata, bytelen, NULL, NULL, 0); switch(rc >= 0 ? HAICRYPT_OK : rc) { case HAICRYPT_OK: m_RcvKmState = SRT_KM_S_SECURED; HLOGC(cnlog.Debug, log << "KMREQ/rcv: (snd) Rx process successful - SECURED."); //Send back the whole message to confirm break; case HAICRYPT_ERROR_WRONG_SECRET: //Unmatched shared secret to decrypt wrapped key m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET; //Send status KMRSP message to tel error w_srtlen = 1; LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADSECRET"); break; case HAICRYPT_ERROR: //Other errors default: m_RcvKmState = m_SndKmState = SRT_KM_S_NOSECRET; w_srtlen = 1; LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure (IPE) - NOSECRET"); break; } LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen)); // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE, // until the next KMREQ is received as a key regeneration. m_bErrorReported = false; if (w_srtlen == 1) goto HSv4_ErrorReport; // Configure the sender context also, if it succeeded to configure the // receiver context and we are using bidirectional mode. if ( bidirectional ) { // Note: 'bidirectional' means that we want a bidirectional key update, // which happens only and exclusively with HSv5 handshake - not when the // usual key update through UMSG_EXT+SRT_CMD_KMREQ was done (which is used // in HSv4 versions also to initialize the first key, unlike HSv5). if (m_RcvKmState == SRT_KM_S_SECURED) { if (m_SndKmState == SRT_KM_S_SECURING && !m_hSndCrypto) { m_iSndKmKeyLen = m_iRcvKmKeyLen; if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, &m_hSndCrypto) != HAICRYPT_OK) { LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - WILL NOT SEND-ENCRYPT correctly!"); if (hasPassphrase()) m_SndKmState = SRT_KM_S_BADSECRET; else m_SndKmState = SRT_KM_S_NOSECRET; } else { m_SndKmState = SRT_KM_S_SECURED; } LOGC(cnlog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen) << " SndKeyLen=" << m_iSndKmKeyLen << " TX CRYPTO CTX CLONED FROM RX" ); // Write the KM message into the field from which it will be next sent. memcpy((m_SndKmMsg[0].Msg), kmdata, bytelen); m_SndKmMsg[0].MsgLen = bytelen; m_SndKmMsg[0].iPeerRetry = 0; // Don't start sending them upon connection :) } else { HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in " << KmStateStr(m_SndKmState) << " state"); } } else { HLOGP(cnlog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX"); } } else { HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update to TX CRYPTO CTX."); } return SRT_CMD_KMRSP; HSv4_ErrorReport: if (bidirectional && hasPassphrase()) { // If the Forward KMX process has failed, the reverse-KMX process was not done at all. // This will lead to incorrect object configuration and will fail to properly declare // the transmission state. // Create the "fake crypto" with the passphrsae you currently have. createFakeSndContext(); } #undef KMREQ_RESULT_REJECTION #else // It's ok that this is reported as error because this happens in a scenario, // when non-encryption-enabled SRT application is contacted by encryption-enabled SRT // application which tries to make a security association. LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Encryption not enabled at compile time - must reject..."); m_RcvKmState = SRT_KM_S_NOSECRET; #endif w_srtlen = 1; pw_srtdata_out[SRT_KMR_KMSTATE] = m_RcvKmState; return SRT_CMD_KMRSP; } int CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int /* XXX unused? hsv*/) { /* All 32-bit msg fields (if present) swapped on reception * But HaiCrypt expect network order message * Re-swap to cancel it. */ uint32_t srtd[SRTDATA_MAXSIZE]; size_t srtlen = len/sizeof(uint32_t); HtoNLA(srtd, srtdata, srtlen); int retstatus = -1; // Unused? //bool bidirectional = hsv > CUDT::HS_VERSION_UDT4; // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE, // until the next KMREQ is received as a key regeneration. m_bErrorReported = false; if (srtlen == 1) // Error report. Set accordingly. { SRT_KM_STATE peerstate = SRT_KM_STATE(srtd[SRT_KMR_KMSTATE]); /* Bad or no passphrase */ m_SndKmMsg[0].iPeerRetry = 0; m_SndKmMsg[1].iPeerRetry = 0; switch (peerstate) { case SRT_KM_S_BADSECRET: m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET; retstatus = -1; break; // Default embraces two cases: // NOSECRET: this KMRSP was sent by secured Peer, but Agent supplied no password. // UNSECURED: this KMRSP was sent by unsecure Peer because Agent sent KMREQ. case SRT_KM_S_NOSECRET: // This means that the peer did not set the password, while Agent did. m_RcvKmState = SRT_KM_S_UNSECURED; m_SndKmState = SRT_KM_S_NOSECRET; retstatus = -1; break; case SRT_KM_S_UNSECURED: // This means that KMRSP was sent without KMREQ, to inform the Agent, // that the Peer, unlike Agent, does use password. Agent can send then, // but can't decrypt what Peer would send. m_RcvKmState = SRT_KM_S_NOSECRET; m_SndKmState = SRT_KM_S_UNSECURED; retstatus = 0; break; default: LOGC(cnlog.Fatal, log << "processSrtMsg_KMRSP: IPE: unknown peer error state: " << KmStateStr(peerstate) << " (" << int(peerstate) << ")"); m_RcvKmState = SRT_KM_S_NOSECRET; m_SndKmState = SRT_KM_S_NOSECRET; retstatus = -1; //This is IPE break; } LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: received failure report. STATE: " << KmStateStr(m_RcvKmState)); } else { HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: received key response len=" << len); // XXX INSECURE << ": [" << FormatBinaryString((uint8_t*)srtd, len) << "]"; bool key1 = getKmMsg_acceptResponse(0, srtd, len); bool key2 = true; if ( !key1 ) key2 = getKmMsg_acceptResponse(1, srtd, len); // <--- NOTE SEQUENCING! if (key1 || key2) { m_SndKmState = m_RcvKmState = SRT_KM_S_SECURED; HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: KM response matches " << (key1 ? "EVEN" : "ODD") << " key"); retstatus = 1; } else { retstatus = -1; LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: IPE??? KM response key matches no key"); /* XXX INSECURE LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: KM response: [" << FormatBinaryString((uint8_t*)srtd, len) << "] matches no key 0=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[0].Msg, m_SndKmMsg[0].MsgLen) << "] 1=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[1].Msg, m_SndKmMsg[1].MsgLen) << "]"); */ m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET; } HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry << "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry); } LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMRSP", SRT_CMD_KMRSP, len)); return retstatus; } void CCryptoControl::sendKeysToPeer(Whether2RegenKm regen SRT_ATR_UNUSED) { if ( !m_hSndCrypto || m_SndKmState == SRT_KM_S_UNSECURED) { HLOGC(cnlog.Debug, log << "sendKeysToPeer: NOT sending/regenerating keys: " << (m_hSndCrypto ? "CONNECTION UNSECURED" : "NO TX CRYPTO CTX created")); return; } #ifdef SRT_ENABLE_ENCRYPTION srt::sync::steady_clock::time_point now = srt::sync::steady_clock::now(); /* * Crypto Key Distribution to peer: * If... * - we want encryption; and * - we have not tried more than CSRTCC_MAXRETRY times (peer may not be SRT); and * - and did not get answer back from peer; and * - last sent Keying Material req should have been replied (RTT*1.5 elapsed); * then (re-)send handshake request. */ if (((m_SndKmMsg[0].iPeerRetry > 0) || (m_SndKmMsg[1].iPeerRetry > 0)) && ((m_SndKmLastTime + srt::sync::microseconds_from((m_parent->SRTT() * 3)/2)) <= now)) { for (int ki = 0; ki < 2; ki++) { if (m_SndKmMsg[ki].iPeerRetry > 0 && m_SndKmMsg[ki].MsgLen > 0) { m_SndKmMsg[ki].iPeerRetry--; HLOGC(cnlog.Debug, log << "sendKeysToPeer: SENDING ki=" << ki << " len=" << m_SndKmMsg[ki].MsgLen << " retry(updated)=" << m_SndKmMsg[ki].iPeerRetry); m_SndKmLastTime = now; m_parent->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen / sizeof(uint32_t)); } } } if (regen) { regenCryptoKm( true, // send UMSG_EXT + SRT_CMD_KMREQ to the peer, if regenerated the key false // Do not apply the regenerated key to the to the receiver context ); // regenerate and send } #endif } #ifdef SRT_ENABLE_ENCRYPTION void CCryptoControl::regenCryptoKm(bool sendit, bool bidirectional) { if (!m_hSndCrypto) return; void *out_p[2]; size_t out_len_p[2]; int nbo = HaiCrypt_Tx_ManageKeys(m_hSndCrypto, out_p, out_len_p, 2); int sent = 0; HLOGC(cnlog.Debug, log << "regenCryptoKm: regenerating crypto keys nbo=" << nbo << " THEN=" << (sendit ? "SEND" : "KEEP") << " DIR=" << (bidirectional ? "BOTH" : "SENDER")); for (int i = 0; i < nbo && i < 2; i++) { /* * New connection keying material * or regenerated after crypto_cfg.km_refresh_rate_pkt packets . * Send to peer */ // XXX Need to make it clearer and less hardcoded values int kix = hcryptMsg_KM_GetKeyIndex((unsigned char *)(out_p[i])); int ki = kix & 0x1; if ((out_len_p[i] != m_SndKmMsg[ki].MsgLen) || (0 != memcmp(out_p[i], m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen))) { uint8_t* oldkey SRT_ATR_UNUSED = m_SndKmMsg[ki].Msg; HLOGC(cnlog.Debug, log << "new key[" << ki << "] index=" << kix << " OLD=[" << m_SndKmMsg[ki].MsgLen << "]" << FormatBinaryString(m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen) << " NEW=[" << out_len_p[i] << "]" << FormatBinaryString((const uint8_t*)out_p[i], out_len_p[i])); /* New Keying material, send to peer */ memcpy((m_SndKmMsg[ki].Msg), out_p[i], out_len_p[i]); m_SndKmMsg[ki].MsgLen = out_len_p[i]; m_SndKmMsg[ki].iPeerRetry = SRT_MAX_KMRETRY; if (bidirectional && !sendit) { // "Send" this key also to myself, just to be applied to the receiver crypto, // exactly the same way how this key is interpreted on the peer side into its receiver crypto int rc = HaiCrypt_Rx_Process(m_hRcvCrypto, m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen, NULL, NULL, 0); if ( rc < 0 ) { LOGC(cnlog.Fatal, log << "regenCryptoKm: IPE: applying key generated in snd crypto into rcv crypto: failed code=" << rc); // The party won't be able to decrypt incoming data! // Not sure if anything has to be reported. } } if (sendit) { HLOGC(cnlog.Debug, log << "regenCryptoKm: SENDING ki=" << ki << " len=" << m_SndKmMsg[ki].MsgLen << " retry(updated)=" << m_SndKmMsg[ki].iPeerRetry); m_parent->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen / sizeof(uint32_t)); sent++; } } else if (out_len_p[i] == 0) { HLOGC(cnlog.Debug, log << "no key[" << ki << "] index=" << kix << ": not generated"); } else { HLOGC(cnlog.Debug, log << "no key[" << ki << "] index=" << kix << ": key unchanged"); } } HLOGC(cnlog.Debug, log << "regenCryptoKm: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry << "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry); if (sent) m_SndKmLastTime = srt::sync::steady_clock::now(); } #endif CCryptoControl::CCryptoControl(CUDT* parent, SRTSOCKET id): m_parent(parent), // should be initialized in createCC() m_SocketID(id), m_iSndKmKeyLen(0), m_iRcvKmKeyLen(0), m_SndKmState(SRT_KM_S_UNSECURED), m_RcvKmState(SRT_KM_S_UNSECURED), m_KmRefreshRatePkt(0), m_KmPreAnnouncePkt(0), m_bErrorReported(false) { m_KmSecret.len = 0; //send m_SndKmMsg[0].MsgLen = 0; m_SndKmMsg[0].iPeerRetry = 0; m_SndKmMsg[1].MsgLen = 0; m_SndKmMsg[1].iPeerRetry = 0; m_hSndCrypto = NULL; //recv m_hRcvCrypto = NULL; } bool CCryptoControl::init(HandshakeSide side, bool bidirectional SRT_ATR_UNUSED) { // NOTE: initiator creates m_hSndCrypto. When bidirectional, // it creates also m_hRcvCrypto with the same key length. // Acceptor creates nothing - it will create appropriate // contexts when receiving KMREQ from the initiator. HLOGC(cnlog.Debug, log << "CCryptoControl::init: HS SIDE:" << (side == HSD_INITIATOR ? "INITIATOR" : "RESPONDER") << " DIRECTION:" << (bidirectional ? "BOTH" : (side == HSD_INITIATOR) ? "SENDER" : "RECEIVER")); // Set UNSECURED state as default m_RcvKmState = SRT_KM_S_UNSECURED; // Set security-pending state, if a password was set. m_SndKmState = hasPassphrase() ? SRT_KM_S_SECURING : SRT_KM_S_UNSECURED; m_KmPreAnnouncePkt = m_parent->m_config.uKmPreAnnouncePkt; m_KmRefreshRatePkt = m_parent->m_config.uKmRefreshRatePkt; if ( side == HSD_INITIATOR ) { if (hasPassphrase()) { #ifdef SRT_ENABLE_ENCRYPTION if (m_iSndKmKeyLen == 0) { HLOGC(cnlog.Debug, log << "CCryptoControl::init: PBKEYLEN still 0, setting default 16"); m_iSndKmKeyLen = 16; } bool ok = createCryptoCtx(m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX, (m_hSndCrypto)); HLOGC(cnlog.Debug, log << "CCryptoControl::init: creating SND crypto context: " << ok); if (ok && bidirectional) { m_iRcvKmKeyLen = m_iSndKmKeyLen; int st = HaiCrypt_Clone(m_hSndCrypto, HAICRYPT_CRYPTO_DIR_RX, &m_hRcvCrypto); HLOGC(cnlog.Debug, log << "CCryptoControl::init: creating CLONED RCV crypto context: status=" << st); ok = st == 0; } // Note: this is sanity check, it should never happen. if (!ok) { m_SndKmState = SRT_KM_S_NOSECRET; // wanted to secure, but error occurred. if (bidirectional) m_RcvKmState = SRT_KM_S_NOSECRET; return false; } regenCryptoKm( false, // Do not send the key (will be attached it to the HSv5 handshake) bidirectional // replicate the key to the receiver context, if bidirectional ); #else // This error would be a consequence of setting the passphrase, while encryption // is turned off at compile time. Setting the password itself should be not allowed // so this could only happen as a consequence of an IPE. LOGC(cnlog.Error, log << "CCryptoControl::init: IPE: encryption not supported"); return true; #endif } else { HLOGC(cnlog.Debug, log << "CCryptoControl::init: CAN'T CREATE crypto: key length for SND = " << m_iSndKmKeyLen); } } else { HLOGC(cnlog.Debug, log << "CCryptoControl::init: NOT creating crypto contexts - will be created upon reception of KMREQ"); } return true; } void CCryptoControl::close() { /* Wipeout secrets */ memset(&m_KmSecret, 0, sizeof(m_KmSecret)); } std::string CCryptoControl::CONID() const { if ( m_SocketID == 0 ) return ""; std::ostringstream os; os << "@" << m_SocketID << ":"; return os.str(); } #ifdef SRT_ENABLE_ENCRYPTION #if ENABLE_HEAVY_LOGGING static std::string CryptoFlags(int flg) { using namespace std; vector f; if (flg & HAICRYPT_CFG_F_CRYPTO) f.push_back("crypto"); if (flg & HAICRYPT_CFG_F_TX) f.push_back("TX"); if (flg & HAICRYPT_CFG_F_FEC) f.push_back("fec"); ostringstream os; copy(f.begin(), f.end(), ostream_iterator(os, "|")); return os.str(); } #endif // ENABLE_HEAVY_LOGGING bool CCryptoControl::createCryptoCtx(size_t keylen, HaiCrypt_CryptoDir cdir, HaiCrypt_Handle& w_hCrypto) { if (w_hCrypto) { // XXX You can check here if the existing handle represents // a correctly defined crypto. But this doesn't seem to be // necessary - the whole CCryptoControl facility seems to be valid only // within the frames of one connection. return true; } if ((m_KmSecret.len <= 0) || (keylen <= 0)) { LOGC(cnlog.Error, log << CONID() << "cryptoCtx: IPE missing secret (" << m_KmSecret.len << ") or key length (" << keylen << ")"); return false; } HaiCrypt_Cfg crypto_cfg; memset(&crypto_cfg, 0, sizeof(crypto_cfg)); #if 0//test key refresh (fast rate) m_KmRefreshRatePkt = 2000; m_KmPreAnnouncePkt = 500; #endif crypto_cfg.flags = HAICRYPT_CFG_F_CRYPTO | (cdir == HAICRYPT_CRYPTO_DIR_TX ? HAICRYPT_CFG_F_TX : 0); crypto_cfg.xport = HAICRYPT_XPT_SRT; crypto_cfg.cryspr = HaiCryptCryspr_Get_Instance(); 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 = m_KmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : m_KmRefreshRatePkt; crypto_cfg.km_pre_announce_pkt = m_KmPreAnnouncePkt == 0 ? SRT_CRYPT_KM_PRE_ANNOUNCE : m_KmPreAnnouncePkt; crypto_cfg.secret = m_KmSecret; //memcpy(&crypto_cfg.secret, &m_KmSecret, sizeof(crypto_cfg.secret)); HLOGC(cnlog.Debug, log << "CRYPTO CFG: flags=" << CryptoFlags(crypto_cfg.flags) << " xport=" << crypto_cfg.xport << " cryspr=" << crypto_cfg.cryspr << " keylen=" << crypto_cfg.key_len << " passphrase_length=" << crypto_cfg.secret.len); if (HaiCrypt_Create(&crypto_cfg, (&w_hCrypto)) != HAICRYPT_OK) { LOGC(cnlog.Error, log << CONID() << "cryptoCtx: could not create " << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " crypto ctx"); return false; } HLOGC(cnlog.Debug, log << CONID() << "cryptoCtx: CREATED crypto for dir=" << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " keylen=" << keylen); return true; } #else bool CCryptoControl::createCryptoCtx(size_t, HaiCrypt_CryptoDir, HaiCrypt_Handle&) { return false; } #endif EncryptionStatus CCryptoControl::encrypt(CPacket& w_packet SRT_ATR_UNUSED) { #ifdef SRT_ENABLE_ENCRYPTION // Encryption not enabled - do nothing. if ( getSndCryptoFlags() == EK_NOENC ) return ENCS_CLEAR; int rc = HaiCrypt_Tx_Data(m_hSndCrypto, ((uint8_t*)w_packet.getHeader()), ((uint8_t*)w_packet.m_pcData), w_packet.getLength()); if (rc < 0) { return ENCS_FAILED; } else if ( rc > 0 ) { // XXX what happens if the encryption is said to be "succeeded", // but the length is 0? Shouldn't this be treated as unwanted? w_packet.setLength(rc); } return ENCS_CLEAR; #else return ENCS_NOTSUP; #endif } EncryptionStatus CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNUSED) { #ifdef SRT_ENABLE_ENCRYPTION if (w_packet.getMsgCryptoFlags() == EK_NOENC) { HLOGC(cnlog.Debug, log << "CPacket::decrypt: packet not encrypted"); return ENCS_CLEAR; // not encrypted, no need do decrypt, no flags to be modified } if (m_RcvKmState == SRT_KM_S_UNSECURED) { if (m_KmSecret.len != 0) { // We were unaware that the peer has set password, // but now here we are. m_RcvKmState = SRT_KM_S_SECURING; LOGC(cnlog.Note, log << "SECURITY UPDATE: Peer has surprised Agent with encryption, but KMX is pending - current packet size=" << w_packet.getLength() << " dropped"); return ENCS_FAILED; } else { // Peer has set a password, but Agent did not, // which means that it will be unable to decrypt // sent payloads anyway. m_RcvKmState = SRT_KM_S_NOSECRET; LOGP(cnlog.Warn, "SECURITY FAILURE: Agent has no PW, but Peer sender has declared one, can't decrypt"); // This only informs about the state change; it will be also caught by the condition below } } if (m_RcvKmState != SRT_KM_S_SECURED) { // If not "secured", it means that it won't be able to decrypt packets, // so there's no point to even try to send them to HaiCrypt_Rx_Data. // Actually the current conditions concerning m_hRcvCrypto are such that this object // is cretaed in case of SRT_KM_S_BADSECRET, so it will simply fail to decrypt, // but with SRT_KM_S_NOSECRET m_hRcvCrypto is not even created (is NULL), which // will then cause an error to be reported, misleadingly. Simply don't try to // decrypt anything as long as you are not sure that the connection is secured. // This problem will occur every time a packet comes in, it's worth reporting, // but not with every single packet arriving. Print it once and turn off the flag; // it will be restored at the next attempt of KMX. if (!m_bErrorReported) { m_bErrorReported = true; LOGC(cnlog.Error, log << "SECURITY STATUS: " << KmStateStr(m_RcvKmState) << " - can't decrypt w_packet."); } HLOGC(cnlog.Debug, log << "Packet still not decrypted, status=" << KmStateStr(m_RcvKmState) << " - dropping size=" << w_packet.getLength()); return ENCS_FAILED; } int rc = HaiCrypt_Rx_Data(m_hRcvCrypto, ((uint8_t *)w_packet.getHeader()), ((uint8_t *)w_packet.m_pcData), w_packet.getLength()); if ( rc <= 0 ) { LOGC(cnlog.Error, log << "decrypt ERROR (IPE): HaiCrypt_Rx_Data failure=" << rc << " - returning failed decryption"); // -1: decryption failure // 0: key not received yet return ENCS_FAILED; } // Otherwise: rc == decrypted text length. w_packet.setLength(rc); /* In case clr txt size is different from cipher txt */ // Decryption succeeded. Update flags. w_packet.setMsgCryptoFlags(EK_NOENC); HLOGC(cnlog.Debug, log << "decrypt: successfully decrypted, resulting length=" << rc); return ENCS_CLEAR; #else return ENCS_NOTSUP; #endif } CCryptoControl::~CCryptoControl() { #ifdef SRT_ENABLE_ENCRYPTION close(); if (m_hSndCrypto) { HaiCrypt_Close(m_hSndCrypto); } if (m_hRcvCrypto) { HaiCrypt_Close(m_hRcvCrypto); } #endif } 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", "StreamAPI" }; 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; } srt-1.4.4/srtcore/crypto.h000066400000000000000000000207341412557703600155220ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_CRYPTO_H #define INC_SRT_CRYPTO_H #include #include // UDT #include "udt.h" #include "packet.h" #include "utilities.h" #include "logging.h" #include #include namespace srt_logging { std::string KmStateStr(SRT_KM_STATE state); #if ENABLE_LOGGING extern Logger cnlog; #endif } namespace srt { class CUDT; } // For KMREQ/KMRSP. Only one field is used. const size_t SRT_KMR_KMSTATE = 0; #define SRT_CMD_MAXSZ HCRYPT_MSG_KM_MAX_SZ /* Maximum SRT custom messages payload size (bytes) */ const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ/sizeof(uint32_t); enum Whether2RegenKm {DONT_REGEN_KM = 0, REGEN_KM = 1}; class CCryptoControl { srt::CUDT* m_parent; SRTSOCKET m_SocketID; size_t m_iSndKmKeyLen; //Key length size_t m_iRcvKmKeyLen; //Key length from rx KM // Temporarily allow these to be accessed. public: SRT_KM_STATE m_SndKmState; //Sender Km State (imposed by agent) SRT_KM_STATE m_RcvKmState; //Receiver Km State (informed by peer) private: // Partial haicrypt configuration, consider // putting the whole HaiCrypt_Cfg object here. int m_KmRefreshRatePkt; int m_KmPreAnnouncePkt; HaiCrypt_Secret m_KmSecret; //Key material shared secret // Sender srt::sync::steady_clock::time_point m_SndKmLastTime; struct { unsigned char Msg[HCRYPT_MSG_KM_MAX_SZ]; size_t MsgLen; int iPeerRetry; } m_SndKmMsg[2]; HaiCrypt_Handle m_hSndCrypto; // Receiver HaiCrypt_Handle m_hRcvCrypto; bool m_bErrorReported; public: bool sendingAllowed() { // This function is called to state as to whether the // crypter allows the packet to be sent over the link. // This is possible in two cases: // - when Agent didn't set a password, no matter the crypto state if (m_KmSecret.len == 0) return true; // - when Agent did set a password and the crypto state is SECURED. if (m_KmSecret.len > 0 && m_SndKmState == SRT_KM_S_SECURED // && m_iRcvPeerKmState == SRT_KM_S_SECURED ? ) return true; return false; } bool hasPassphrase() const { return m_KmSecret.len > 0; } private: #ifdef SRT_ENABLE_ENCRYPTION void regenCryptoKm(bool sendit, bool bidirectional); #endif public: size_t KeyLen() { return m_iSndKmKeyLen; } // Needed for CUDT void updateKmState(int cmd, size_t srtlen); // Detailed processing int processSrtMsg_KMREQ(const uint32_t* srtdata, size_t len, int hsv, uint32_t srtdata_out[], size_t&); // This returns: // 1 - the given payload is the same as the currently used key // 0 - there's no key in agent or the payload is error message with agent NOSECRET. // -1 - the payload is error message with other state or it doesn't match the key int processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int hsv); void createFakeSndContext(); const unsigned char* getKmMsg_data(size_t ki) const { return m_SndKmMsg[ki].Msg; } size_t getKmMsg_size(size_t ki) const { return m_SndKmMsg[ki].MsgLen; } /// Check if the key stored at @c ki shall be sent. When during the handshake, /// it only matters if the KM message for that index is recorded at all. /// Otherwise returns true only if also the retry counter didn't expire. /// /// @param ki Key index (0 or 1) /// @param runtime True, if this happens as a key update /// during transmission (otherwise it's during the handshake) /// @return Whether the KM message at given index needs to be sent. bool getKmMsg_needSend(size_t ki, bool runtime) const { if (runtime) return (m_SndKmMsg[ki].iPeerRetry > 0 && m_SndKmMsg[ki].MsgLen > 0); else return m_SndKmMsg[ki].MsgLen > 0; } /// Mark the key as already sent. When no 'runtime' (during the handshake) /// it actually does nothing so that this will be retried as long as the handshake /// itself is being retried. Otherwise this is during transmission and will expire /// after several retries. /// /// @param ki Key index (0 or 1) /// @param runtime True, if this happens as a key update /// during transmission (otherwise it's during the handshake) void getKmMsg_markSent(size_t ki, bool runtime) { #if ENABLE_LOGGING using srt_logging::cnlog; #endif m_SndKmLastTime = srt::sync::steady_clock::now(); if (runtime) { m_SndKmMsg[ki].iPeerRetry--; HLOGC(cnlog.Debug, log << "getKmMsg_markSent: key[" << ki << "]: len=" << m_SndKmMsg[ki].MsgLen << " retry=" << m_SndKmMsg[ki].iPeerRetry); } else { HLOGC(cnlog.Debug, log << "getKmMsg_markSent: key[" << ki << "]: len=" << m_SndKmMsg[ki].MsgLen << " STILL IN USE."); } } /// Check if the response returned by KMRSP matches the recorded KM message. /// When it is, set also the retry counter to 0 to prevent further retries. /// /// @param ki KM message index (0 or 1) /// @param srtmsg Message received through KMRSP /// @param bytesize Size of the message /// @return True if the message is identical to the recorded KM message at given index. bool getKmMsg_acceptResponse(size_t ki, const uint32_t* srtmsg, size_t bytesize) { if ( m_SndKmMsg[ki].MsgLen == bytesize && 0 == memcmp(m_SndKmMsg[ki].Msg, srtmsg, m_SndKmMsg[ki].MsgLen)) { m_SndKmMsg[ki].iPeerRetry = 0; return true; } return false; } CCryptoControl(srt::CUDT* parent, SRTSOCKET id); // DEBUG PURPOSES: std::string CONID() const; std::string FormatKmMessage(std::string hdr, int cmd, size_t srtlen); bool init(HandshakeSide, bool); void close(); // This function is used in: // - HSv4 (initial key material exchange - in HSv5 it's attached to handshake) // - case of key regeneration, which should be then exchanged again void sendKeysToPeer(Whether2RegenKm regen); void setCryptoSecret(const HaiCrypt_Secret& secret) { m_KmSecret = secret; //memcpy(&m_KmSecret, &secret, sizeof(m_KmSecret)); } void setCryptoKeylen(size_t keylen) { m_iSndKmKeyLen = keylen; m_iRcvKmKeyLen = keylen; } bool createCryptoCtx(size_t keylen, HaiCrypt_CryptoDir tx, HaiCrypt_Handle& rh); int getSndCryptoFlags() const { #ifdef SRT_ENABLE_ENCRYPTION return(m_hSndCrypto ? HaiCrypt_Tx_GetKeyFlags(m_hSndCrypto) : // When encryption isn't on, check if it was required // If it was, return -1 as flags, which means that // encryption was requested and not possible. hasPassphrase() ? -1 : 0); #else return 0; #endif } bool isSndEncryptionOK() const { // Similar to this above, just quickly check if the encryption // is required and possible, or not possible if (!hasPassphrase()) return true; // no encryption required if (m_hSndCrypto) return true; // encryption is required and possible return false; } /// Encrypts the packet. If encryption is not turned on, it /// does nothing. If the encryption is not correctly configured, /// the encryption will fail. /// XXX Encryption flags in the PH_MSGNO /// field in the header must be correctly set before calling. EncryptionStatus encrypt(srt::CPacket& w_packet); /// Decrypts the packet. If the packet has ENCKEYSPEC part /// in PH_MSGNO set to EK_NOENC, it does nothing. It decrypts /// only if the encryption correctly configured, otherwise it /// fails. After successful decryption, the ENCKEYSPEC part // in PH_MSGNO is set to EK_NOENC. EncryptionStatus decrypt(srt::CPacket& w_packet); ~CCryptoControl(); }; #endif // SRT_CONGESTION_CONTROL_H srt-1.4.4/srtcore/epoll.cpp000066400000000000000000001010021412557703600156340ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 01/01/2011 modified by Haivision Systems Inc. *****************************************************************************/ #define SRT_IMPORT_EVENT #include "platform_sys.h" #include #include #include #include #if defined(__FreeBSD_kernel__) #include #endif #include "common.h" #include "epoll.h" #include "logging.h" #include "udt.h" #include "logging.h" using namespace std; using namespace srt::sync; #if ENABLE_HEAVY_LOGGING static ostream& PrintEpollEvent(ostream& os, int events, int et_events = 0); #endif namespace srt_logging { extern Logger eilog, ealog; } using namespace srt_logging; #if ENABLE_HEAVY_LOGGING #define IF_DIRNAME(tested, flag, name) (tested & flag ? name : "") #endif CEPoll::CEPoll(): m_iIDSeed(0) { // Exception -> CUDTUnited ctor. setupMutex(m_EPollLock, "EPoll"); } CEPoll::~CEPoll() { releaseMutex(m_EPollLock); } int CEPoll::create(CEPollDesc** pout) { ScopedLock pg(m_EPollLock); if (++ m_iIDSeed >= 0x7FFFFFFF) m_iIDSeed = 0; // Check if an item already exists. Should not ever happen. if (m_mPolls.find(m_iIDSeed) != m_mPolls.end()) throw CUDTException(MJ_SETUP, MN_NONE); int localid = 0; #ifdef LINUX // NOTE: epoll_create1() and EPOLL_CLOEXEC were introduced in GLIBC-2.9. // So earlier versions of GLIBC, must use epoll_create() and set // FD_CLOEXEC on the file descriptor returned by it after the fact. #if defined(EPOLL_CLOEXEC) int flags = 0; #if ENABLE_SOCK_CLOEXEC flags |= EPOLL_CLOEXEC; #endif localid = epoll_create1(flags); #else localid = epoll_create(1); #if ENABLE_SOCK_CLOEXEC if (localid != -1) { int fdFlags = fcntl(localid, F_GETFD); if (fdFlags != -1) { fdFlags |= FD_CLOEXEC; fcntl(localid, F_SETFD, fdFlags); } } #endif #endif /* 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(BSD) || TARGET_OS_MAC localid = kqueue(); if (localid < 0) throw CUDTException(MJ_SETUP, MN_NONE, errno); #else // TODO: Solaris, use port_getn() // https://docs.oracle.com/cd/E86824_01/html/E54766/port-get-3c.html // on Windows, select #endif pair::iterator, bool> res = m_mPolls.insert(make_pair(m_iIDSeed, CEPollDesc(m_iIDSeed, localid))); if (!res.second) // Insertion failed (no memory?) throw CUDTException(MJ_SETUP, MN_NONE); if (pout) *pout = &res.first->second; return m_iIDSeed; } int CEPoll::clear_usocks(int eid) { // This should remove all SRT sockets from given eid. ScopedLock pg (m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); CEPollDesc& d = p->second; d.clearAll(); return 0; } void CEPoll::clear_ready_usocks(CEPollDesc& d, int direction) { if ((direction & ~SRT_EPOLL_EVENTTYPES) != 0) { // This is internal function, so simply report an IPE on incorrect usage. LOGC(eilog.Error, log << "CEPoll::clear_ready_usocks: IPE, event flags exceed event types: " << direction); return; } ScopedLock pg (m_EPollLock); vector cleared; CEPollDesc::enotice_t::iterator i = d.enotice_begin(); while (i != d.enotice_end()) { IF_HEAVY_LOGGING(SRTSOCKET subsock = i->fd); SRTSOCKET rs = d.clearEventSub(i++, direction); // This function returns: // - a valid socket - if there are no other subscription after 'direction' was cleared // - SRT_INVALID_SOCK otherwise // Valid sockets should be collected as sockets that no longer // have a subscribed event should be deleted from subscriptions. if (rs != SRT_INVALID_SOCK) { HLOGC(eilog.Debug, log << "CEPoll::clear_ready_usocks: @" << rs << " got all subscription cleared"); cleared.push_back(rs); } else { HLOGC(eilog.Debug, log << "CEPoll::clear_ready_usocks: @" << subsock << " is still subscribed"); } } for (size_t i = 0; i < cleared.size(); ++i) d.removeSubscription(cleared[i]); } int CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) { ScopedLock 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 & SRT_EPOLL_IN) ev.events |= EPOLLIN; if (*events & SRT_EPOLL_OUT) ev.events |= EPOLLOUT; if (*events & SRT_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(BSD) || TARGET_OS_MAC 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 & SRT_EPOLL_IN) { EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL); } if (*events & SRT_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 // fake use 'events' to prevent warning. Remove when implemented. (void)events; (void)s; #ifdef _MSC_VER // Microsoft Visual Studio doesn't support the #warning directive - nonstandard anyway. // Use #pragma message with the same text. // All other compilers should be ok :) #pragma message("WARNING: Unsupported system for epoll. The epoll_add_ssock() API call won't work on this platform.") #else #warning "Unsupported system for epoll. The epoll_add_ssock() API call won't work on this platform." #endif #endif p->second.m_sLocals.insert(s); return 0; } int CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) { ScopedLock 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(BSD) || TARGET_OS_MAC 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; } // Need this to atomically modify polled events (ex: remove write/keep read) int CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) { ScopedLock pg(m_EPollLock); IF_HEAVY_LOGGING(ostringstream evd); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); CEPollDesc& d = p->second; int32_t evts = events ? *events : uint32_t(SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR); bool edgeTriggered = evts & SRT_EPOLL_ET; evts &= ~SRT_EPOLL_ET; // et_evts = all events, if SRT_EPOLL_ET, or only those that are always ET otherwise. int32_t et_evts = edgeTriggered ? evts : evts & SRT_EPOLL_ETONLY; if (evts) { pair iter_new = d.addWatch(u, evts, et_evts); CEPollDesc::Wait& wait = iter_new.first->second; if (!iter_new.second) { // The object exists. We only are certain about the `u` // parameter, but others are probably unchanged. Change them // forcefully and take out notices that are no longer valid. const int removable = wait.watch & ~evts; IF_HEAVY_LOGGING(PrintEpollEvent(evd, evts & (~wait.watch))); // Check if there are any events that would be removed. // If there are no removed events watched (for example, when // only new events are being added to existing socket), // there's nothing to remove, but might be something to update. if (removable) { d.removeExcessEvents(wait, evts); } // Update the watch configuration, including edge wait.watch = evts; wait.edge = et_evts; // Now it should look exactly like newly added // and the state is also updated HLOGC(ealog.Debug, log << "srt_epoll_update_usock: UPDATED E" << eid << " for @" << u << " +" << evd.str()); } else { IF_HEAVY_LOGGING(PrintEpollEvent(evd, evts)); HLOGC(ealog.Debug, log << "srt_epoll_update_usock: ADDED E" << eid << " for @" << u << " " << evd.str()); } const int newstate = wait.watch & wait.state; if (newstate) { d.addEventNotice(wait, u, newstate); } } else if (edgeTriggered) { LOGC(ealog.Error, log << "srt_epoll_update_usock: Specified only SRT_EPOLL_ET flag, but no event flag. Error."); throw CUDTException(MJ_NOTSUP, MN_INVAL); } else { // Update with no events means to remove subscription HLOGC(ealog.Debug, log << "srt_epoll_update_usock: REMOVED E" << eid << " socket @" << u); d.removeSubscription(u); } return 0; } int CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) { ScopedLock 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 & SRT_EPOLL_IN) ev.events |= EPOLLIN; if (*events & SRT_EPOLL_OUT) ev.events |= EPOLLOUT; if (*events & SRT_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(BSD) || TARGET_OS_MAC 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 & SRT_EPOLL_IN) { EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL); } if (*events & SRT_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 // fake use 'events' to prevent warning. Remove when implemented. (void)events; (void)s; #endif // Assuming add is used if not inserted // p->second.m_sLocals.insert(s); return 0; } int CEPoll::setflags(const int eid, int32_t flags) { ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); CEPollDesc& ed = p->second; int32_t oflags = ed.flags(); if (flags == -1) return oflags; if (flags == 0) { ed.clr_flags(~int32_t()); } else { ed.set_flags(flags); } return oflags; } int CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { // It is allowed to call this function witn fdsSize == 0 // and therefore also NULL fdsSet. This will then only report // the number of ready sockets, just without information which. if (fdsSize < 0 || (fdsSize > 0 && !fdsSet)) throw CUDTException(MJ_NOTSUP, MN_INVAL); steady_clock::time_point entertime = steady_clock::now(); while (true) { { ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); CEPollDesc& ed = p->second; if (!ed.flags(SRT_EPOLL_ENABLE_EMPTY) && ed.watch_empty()) { // Empty EID is not allowed, report error. throw CUDTException(MJ_NOTSUP, MN_EEMPTY); } if (ed.flags(SRT_EPOLL_ENABLE_OUTPUTCHECK) && (fdsSet == NULL || fdsSize == 0)) { // Empty EID is not allowed, report error. throw CUDTException(MJ_NOTSUP, MN_INVAL); } if (!ed.m_sLocals.empty()) { // XXX Add error log // uwait should not be used with EIDs subscribed to system sockets throw CUDTException(MJ_NOTSUP, MN_INVAL); } int total = 0; // This is a list, so count it during iteration CEPollDesc::enotice_t::iterator i = ed.enotice_begin(); while (i != ed.enotice_end()) { int pos = total; // previous past-the-end position ++total; if (total > fdsSize) break; fdsSet[pos] = *i; ed.checkEdge(i++); // NOTE: potentially deletes `i` } if (total) return total; } if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000))) break; // official wait does: throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); CGlobEvent::waitForEvent(); } return 0; } 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; srt::sync::steady_clock::time_point entertime = srt::sync::steady_clock::now(); while (true) { { ScopedLock epollock(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) { LOGC(ealog.Error, log << "EID:" << eid << " INVALID."); throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); } CEPollDesc& ed = p->second; if (!ed.flags(SRT_EPOLL_ENABLE_EMPTY) && ed.watch_empty() && ed.m_sLocals.empty()) { // Empty EID is not allowed, report error. //throw CUDTException(MJ_NOTSUP, MN_INVAL); LOGC(ealog.Error, log << "EID:" << eid << " no sockets to check, this would deadlock"); throw CUDTException(MJ_NOTSUP, MN_EEMPTY, 0); } if (ed.flags(SRT_EPOLL_ENABLE_OUTPUTCHECK)) { // Empty report is not allowed, report error. if (!ed.m_sLocals.empty() && (!lrfds || !lwfds)) throw CUDTException(MJ_NOTSUP, MN_INVAL); if (!ed.watch_empty() && (!readfds || !writefds)) throw CUDTException(MJ_NOTSUP, MN_INVAL); } IF_HEAVY_LOGGING(int total_noticed = 0); IF_HEAVY_LOGGING(ostringstream debug_sockets); // Sockets with exceptions are returned to both read and write sets. for (CEPollDesc::enotice_t::iterator it = ed.enotice_begin(), it_next = it; it != ed.enotice_end(); it = it_next) { ++it_next; IF_HEAVY_LOGGING(++total_noticed); if (readfds && ((it->events & SRT_EPOLL_IN) || (it->events & SRT_EPOLL_ERR))) { if (readfds->insert(it->fd).second) ++total; } if (writefds && ((it->events & SRT_EPOLL_OUT) || (it->events & SRT_EPOLL_ERR))) { if (writefds->insert(it->fd).second) ++total; } IF_HEAVY_LOGGING(debug_sockets << " " << it->fd << ":" << IF_DIRNAME(it->events, SRT_EPOLL_IN, "R") << IF_DIRNAME(it->events, SRT_EPOLL_OUT, "W") << IF_DIRNAME(it->events, SRT_EPOLL_ERR, "E")); if (ed.checkEdge(it)) // NOTE: potentially erases 'it'. { IF_HEAVY_LOGGING(debug_sockets << "!"); } } HLOGC(ealog.Debug, log << "CEPoll::wait: REPORTED " << total << "/" << total_noticed << debug_sockets.str()); if ((lrfds || lwfds) && !ed.m_sLocals.empty()) { #ifdef LINUX const int max_events = ed.m_sLocals.size(); SRT_ASSERT(max_events > 0); epoll_event ev[max_events]; int nfds = ::epoll_wait(ed.m_iLocalID, ev, max_events, 0); IF_HEAVY_LOGGING(const int prev_total = total); 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; } } HLOGC(ealog.Debug, log << "CEPoll::wait: LINUX: picking up " << (total - prev_total) << " ready fds."); #elif defined(BSD) || TARGET_OS_MAC struct timespec tmout = {0, 0}; const int max_events = ed.m_sLocals.size(); SRT_ASSERT(max_events > 0); struct kevent ke[max_events]; int nfds = kevent(ed.m_iLocalID, NULL, 0, ke, max_events, &tmout); IF_HEAVY_LOGGING(const int prev_total = total); 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; } } HLOGC(ealog.Debug, log << "CEPoll::wait: Darwin/BSD: picking up " << (total - prev_total) << " ready fds."); #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 int max_fd = 0; fd_set rqreadfds; fd_set rqwritefds; FD_ZERO(&rqreadfds); FD_ZERO(&rqwritefds); for (set::const_iterator i = ed.m_sLocals.begin(); i != ed.m_sLocals.end(); ++ i) { if (lrfds) FD_SET(*i, &rqreadfds); if (lwfds) FD_SET(*i, &rqwritefds); if ((int)*i > max_fd) max_fd = *i; } IF_HEAVY_LOGGING(const int prev_total = total); timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; if (::select(max_fd + 1, &rqreadfds, &rqwritefds, NULL, &tv) > 0) { for (set::const_iterator i = ed.m_sLocals.begin(); i != ed.m_sLocals.end(); ++ i) { if (lrfds && FD_ISSET(*i, &rqreadfds)) { lrfds->insert(*i); ++ total; } if (lwfds && FD_ISSET(*i, &rqwritefds)) { lwfds->insert(*i); ++ total; } } } HLOGC(ealog.Debug, log << "CEPoll::wait: select(otherSYS): picking up " << (total - prev_total) << " ready fds."); #endif } } // END-LOCK: m_EPollLock HLOGC(ealog.Debug, log << "CEPoll::wait: Total of " << total << " READY SOCKETS"); if (total > 0) return total; if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000))) { HLOGC(ealog.Debug, log << "EID:" << eid << ": TIMEOUT."); throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); } const bool wait_signaled SRT_ATR_UNUSED = CGlobEvent::waitForEvent(); HLOGC(ealog.Debug, log << "CEPoll::wait: EVENT WAITING: " << (wait_signaled ? "TRIGGERED" : "CHECKPOINT")); } return 0; } int CEPoll::swait(CEPollDesc& d, map& st, int64_t msTimeOut, bool report_by_exception) { { ScopedLock lg (m_EPollLock); if (!d.flags(SRT_EPOLL_ENABLE_EMPTY) && d.watch_empty() && msTimeOut < 0) { // no socket is being monitored, this may be a deadlock LOGC(ealog.Error, log << "EID:" << d.m_iID << " no sockets to check, this would deadlock"); if (report_by_exception) throw CUDTException(MJ_NOTSUP, MN_EEMPTY, 0); return -1; } } st.clear(); steady_clock::time_point entertime = steady_clock::now(); while (true) { { // Not extracting separately because this function is // for internal use only and we state that the eid could // not be deleted or changed the target CEPollDesc in the // meantime. // Here we only prevent the pollset be updated simultaneously // with unstable reading. ScopedLock lg (m_EPollLock); if (!d.flags(SRT_EPOLL_ENABLE_EMPTY) && d.watch_empty()) { // Empty EID is not allowed, report error. throw CUDTException(MJ_NOTSUP, MN_EEMPTY); } if (!d.m_sLocals.empty()) { // XXX Add error log // uwait should not be used with EIDs subscribed to system sockets throw CUDTException(MJ_NOTSUP, MN_INVAL); } bool empty = d.enotice_empty(); if (!empty || msTimeOut == 0) { IF_HEAVY_LOGGING(ostringstream singles); // If msTimeOut == 0, it means that we need the information // immediately, we don't want to wait. Therefore in this case // report also when none is ready. int total = 0; // This is a list, so count it during iteration CEPollDesc::enotice_t::iterator i = d.enotice_begin(); while (i != d.enotice_end()) { ++total; st[i->fd] = i->events; IF_HEAVY_LOGGING(singles << "@" << i->fd << ":"); IF_HEAVY_LOGGING(PrintEpollEvent(singles, i->events, i->parent->edgeOnly())); const bool edged SRT_ATR_UNUSED = d.checkEdge(i++); // NOTE: potentially deletes `i` IF_HEAVY_LOGGING(singles << (edged ? "<^> " : " ")); } // Logging into 'singles' because it notifies as to whether // the edge-triggered event has been cleared HLOGC(ealog.Debug, log << "E" << d.m_iID << " rdy=" << total << ": " << singles.str() << " TRACKED: " << d.DisplayEpollWatch()); return total; } // Don't report any updates because this check happens // extremely often. } if ((msTimeOut >= 0) && ((steady_clock::now() - entertime) >= microseconds_from(msTimeOut * int64_t(1000)))) { HLOGC(ealog.Debug, log << "EID:" << d.m_iID << ": TIMEOUT."); if (report_by_exception) throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); return 0; // meaning "none is ready" } CGlobEvent::waitForEvent(); } return 0; } bool CEPoll::empty(const CEPollDesc& d) const { ScopedLock lg (m_EPollLock); return d.watch_empty(); } int CEPoll::release(const int eid) { ScopedLock 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(BSD) || TARGET_OS_MAC ::close(i->second.m_iLocalID); #endif m_mPolls.erase(i); return 0; } int CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const int events, const bool enable) { // As event flags no longer contain only event types, check now. if ((events & ~SRT_EPOLL_EVENTTYPES) != 0) { LOGC(eilog.Fatal, log << "epoll/update: IPE: 'events' parameter shall not contain special flags!"); return -1; // still, ignored. } int nupdated = 0; vector lost; IF_HEAVY_LOGGING(ostringstream debug); IF_HEAVY_LOGGING(debug << "epoll/update: @" << uid << " " << (enable ? "+" : "-")); IF_HEAVY_LOGGING(PrintEpollEvent(debug, events)); ScopedLock pg (m_EPollLock); for (set::iterator i = eids.begin(); i != eids.end(); ++ i) { map::iterator p = m_mPolls.find(*i); if (p == m_mPolls.end()) { HLOGC(eilog.Note, log << "epoll/update: E" << *i << " was deleted in the meantime"); // EID invalid, though still present in the socket's subscriber list // (dangling in the socket). Postpone to fix the subscruption and continue. lost.push_back(*i); continue; } CEPollDesc& ed = p->second; // Check if this EID is subscribed for this socket. CEPollDesc::Wait* pwait = ed.watch_find(uid); if (!pwait) { // As this is mapped in the socket's data, it should be impossible. LOGC(eilog.Error, log << "epoll/update: IPE: update struck E" << (*i) << " which is NOT SUBSCRIBED to @" << uid); continue; } IF_HEAVY_LOGGING(string tracking = " TRACKING: " + ed.DisplayEpollWatch()); // compute new states // New state to be set into the permanent state const int newstate = enable ? pwait->state | events // SET event bits if enable : pwait->state & (~events); // CLEAR event bits // compute states changes! int changes = pwait->state ^ newstate; // oldState XOR newState if (!changes) { HLOGC(eilog.Debug, log << debug.str() << ": E" << (*i) << tracking << " NOT updated: no changes"); continue; // no changes! } // assign new state pwait->state = newstate; // filter change relating what is watching changes &= pwait->watch; if (!changes) { HLOGC(eilog.Debug, log << debug.str() << ": E" << (*i) << tracking << " NOT updated: not subscribed"); continue; // no change watching } // set events changes! // This function will update the notice object associated with // the given events, that is: // - if enable, it will set event flags, possibly in a new notice object // - if !enable, it will clear event flags, possibly remove notice if resulted in 0 ed.updateEventNotice(*pwait, uid, events, enable); ++nupdated; HLOGC(eilog.Debug, log << debug.str() << ": E" << (*i) << " TRACKING: " << ed.DisplayEpollWatch()); } for (vector::iterator i = lost.begin(); i != lost.end(); ++ i) eids.erase(*i); return nupdated; } // Debug use only. #if ENABLE_HEAVY_LOGGING static ostream& PrintEpollEvent(ostream& os, int events, int et_events) { static pair const namemap [] = { make_pair(SRT_EPOLL_IN, "R"), make_pair(SRT_EPOLL_OUT, "W"), make_pair(SRT_EPOLL_ERR, "E"), make_pair(SRT_EPOLL_UPDATE, "U") }; int N = Size(namemap); for (int i = 0; i < N; ++i) { if (events & namemap[i].first) { os << "["; if (et_events & namemap[i].first) os << "^"; os << namemap[i].second << "]"; } } return os; } string DisplayEpollResults(const std::map& sockset) { typedef map fmap_t; ostringstream os; for (fmap_t::const_iterator i = sockset.begin(); i != sockset.end(); ++i) { os << "@" << i->first << ":"; PrintEpollEvent(os, i->second); os << " "; } return os.str(); } string CEPollDesc::DisplayEpollWatch() { ostringstream os; for (ewatch_t::const_iterator i = m_USockWatchState.begin(); i != m_USockWatchState.end(); ++i) { os << "@" << i->first << ":"; PrintEpollEvent(os, i->second.watch, i->second.edge); os << " "; } return os.str(); } #endif srt-1.4.4/srtcore/epoll.h000066400000000000000000000421561412557703600153170ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 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 INC_SRT_EPOLL_H #define INC_SRT_EPOLL_H #include #include #include #include "udt.h" class CEPollDesc { const int m_iID; // epoll ID struct Wait; struct Notice: public SRT_EPOLL_EVENT { Wait* parent; Notice(Wait* p, SRTSOCKET sock, int ev): parent(p) { fd = sock; events = ev; } }; /// The type for `m_USockEventNotice`, the pair contains: /// * The back-pointer to the subscriber object for which this event notice serves /// * The events currently being on typedef std::list enotice_t; struct Wait { /// Events the subscriber is interested with. Only those will be /// regarded when updating event flags. int32_t watch; /// Which events should be edge-triggered. When the event isn't /// mentioned in `watch`, this bit flag is disregarded. Otherwise /// it means that the event is to be waited for persistent state /// if this flag is not present here, and for edge trigger, if /// the flag is present here. int32_t edge; /// The current persistent state. This is usually duplicated in /// a dedicated state object in `m_USockEventNotice`, however the state /// here will stay forever as is, regardless of the edge/persistent /// subscription mode for the event. int32_t state; /// The iterator to `m_USockEventNotice` container that contains the /// event notice object for this subscription, or the value from /// `nullNotice()` if there is no such object. enotice_t::iterator notit; Wait(explicit_t sub, explicit_t etr, enotice_t::iterator i) :watch(sub) ,edge(etr) ,state(0) ,notit(i) { } int edgeOnly() { return edge & watch; } /// Clear all flags for given direction from the notices /// and subscriptions, and checks if this made the event list /// for this watch completely empty. /// @param direction event type that has to be cleared /// @return true, if this cleared the last event (the caller /// want to remove the subscription for this socket) bool clear(int32_t direction) { if (watch & direction) { watch &= ~direction; edge &= ~direction; state &= ~direction; return watch == 0; } return false; } }; typedef std::map ewatch_t; #if ENABLE_HEAVY_LOGGING std::string DisplayEpollWatch(); #endif /// Sockets that are subscribed for events in this eid. ewatch_t m_USockWatchState; /// Objects representing changes in SRT sockets. /// Objects are removed from here when an event is registerred as edge-triggered. /// Otherwise it is removed only when all events as per subscription /// are no longer on. enotice_t m_USockEventNotice; // Special behavior int32_t m_Flags; enotice_t::iterator nullNotice() { return m_USockEventNotice.end(); } // Only CEPoll class should have access to it. // Guarding private access to the class is not necessary // within the epoll module. friend class CEPoll; CEPollDesc(int id, int localID) : m_iID(id) , m_Flags(0) , m_iLocalID(localID) { } static const int32_t EF_NOCHECK_EMPTY = 1 << 0; static const int32_t EF_CHECK_REP = 1 << 1; int32_t flags() const { return m_Flags; } bool flags(int32_t f) const { return (m_Flags & f) != 0; } void set_flags(int32_t flg) { m_Flags |= flg; } void clr_flags(int32_t flg) { m_Flags &= ~flg; } // Container accessors for ewatch_t. bool watch_empty() const { return m_USockWatchState.empty(); } Wait* watch_find(SRTSOCKET sock) { ewatch_t::iterator i = m_USockWatchState.find(sock); if (i == m_USockWatchState.end()) return NULL; return &i->second; } // Container accessors for enotice_t. enotice_t::iterator enotice_begin() { return m_USockEventNotice.begin(); } enotice_t::iterator enotice_end() { return m_USockEventNotice.end(); } enotice_t::const_iterator enotice_begin() const { return m_USockEventNotice.begin(); } enotice_t::const_iterator enotice_end() const { return m_USockEventNotice.end(); } bool enotice_empty() const { return m_USockEventNotice.empty(); } const int m_iLocalID; // local system epoll ID std::set m_sLocals; // set of local (non-UDT) descriptors std::pair addWatch(SRTSOCKET sock, explicit_t events, explicit_t et_events) { return m_USockWatchState.insert(std::make_pair(sock, Wait(events, et_events, nullNotice()))); } void addEventNotice(Wait& wait, SRTSOCKET sock, int events) { // `events` contains bits to be set, so: // // 1. If no notice object exists, add it exactly with `events`. // 2. If it exists, only set the bits from `events`. // ASSUME: 'events' is not 0, that is, we have some readiness if (wait.notit == nullNotice()) // No notice object { // Add new event notice and bind to the wait object. m_USockEventNotice.push_back(Notice(&wait, sock, events)); wait.notit = --m_USockEventNotice.end(); return; } // We have an existing event notice, so update it wait.notit->events |= events; } // This function only updates the corresponding event notice object // according to the change in the events. void updateEventNotice(Wait& wait, SRTSOCKET sock, int events, bool enable) { if (enable) { addEventNotice(wait, sock, events); } else { removeExcessEvents(wait, ~events); } } void removeSubscription(SRTSOCKET u) { std::map::iterator i = m_USockWatchState.find(u); if (i == m_USockWatchState.end()) return; if (i->second.notit != nullNotice()) { m_USockEventNotice.erase(i->second.notit); // NOTE: no need to update the Wait::notit field // because the Wait object is about to be removed anyway. } m_USockWatchState.erase(i); } void clearAll() { m_USockEventNotice.clear(); m_USockWatchState.clear(); } void removeExistingNotices(Wait& wait) { m_USockEventNotice.erase(wait.notit); wait.notit = nullNotice(); } void removeEvents(Wait& wait) { if (wait.notit == nullNotice()) return; removeExistingNotices(wait); } // This function removes notices referring to // events that are NOT present in @a nevts, but // may be among subscriptions and therefore potentially // have an associated notice. void removeExcessEvents(Wait& wait, int nevts) { // Update the event notice, should it exist // If the watch points to a null notice, there's simply // no notice there, so nothing to update or prospectively // remove - but may be something to add. if (wait.notit == nullNotice()) return; // `events` contains bits to be cleared. // 1. If there is no notice event, do nothing - clear already. // 2. If there is a notice event, update by clearing the bits // 2.1. If this made resulting state to be 0, also remove the notice. const int newstate = wait.notit->events & nevts; if (newstate) { wait.notit->events = newstate; } else { // If the new state is full 0 (no events), // then remove the corresponding notice object removeExistingNotices(wait); } } bool checkEdge(enotice_t::iterator i) { // This function should check if this event was subscribed // as edge-triggered, and if so, clear the event from the notice. // Update events and check edge mode at the subscriber i->events &= ~i->parent->edgeOnly(); if(!i->events) { removeExistingNotices(*i->parent); return true; } return false; } /// This should work in a loop around the notice container of /// the given eid container and clear out the notice for /// particular event type. If this has cleared effectively the /// last existing event, it should return the socket id /// so that the caller knows to remove it also from subscribers. /// /// @param i iterator in the notice container /// @param event event type to be cleared /// @retval (socket) Socket to be removed from subscriptions /// @retval SRT_INVALID_SOCK Nothing to be done (associated socket /// still has other subscriptions) SRTSOCKET clearEventSub(enotice_t::iterator i, int event) { // We need to remove the notice and subscription // for this event. The 'i' iterator is safe to // delete, even indirectly. // This works merely like checkEdge, just on request to clear the // identified event, if found. if (i->events & event) { // The notice has a readiness flag on this event. // This means that there exists also a subscription. Wait* w = i->parent; if (w->clear(event)) return i->fd; } return SRT_INVALID_SOCK; } }; namespace srt { class CUDT; class CRendezvousQueue; class CUDTGroup; } class CEPoll { friend class srt::CUDT; friend class srt::CUDTGroup; friend class srt::CRendezvousQueue; public: CEPoll(); ~CEPoll(); public: // for CUDTUnited API /// create a new EPoll. /// @return new EPoll ID if success, otherwise an error number. int create(CEPollDesc** ppd = 0); /// delete all user sockets (SRT sockets) from an EPoll /// @param [in] eid EPoll ID. /// @return 0 int clear_usocks(int eid); /// 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 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); /// 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 SRTSOCKET& u, const int* events); /// 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); /// 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); typedef std::map fmap_t; /// Lightweit and more internal-reaching version of `uwait` for internal use only. /// This function wait for sockets to be ready and reports them in `st` map. /// /// @param d the internal structure of the epoll container /// @param st output container for the results: { socket_type, event } /// @param msTimeOut timeout after which return with empty output is allowed /// @param report_by_exception if true, errors will result in exception intead of returning -1 /// @retval -1 error occurred /// @retval >=0 number of ready sockets (actually size of `st`) int swait(CEPollDesc& d, fmap_t& st, int64_t msTimeOut, bool report_by_exception = true); /// Empty subscription check - for internal use only. bool empty(const CEPollDesc& d) const; /// Reports which events are ready on the given socket. /// @param mp socket event map retirned by `swait` /// @param sock which socket to ask /// @return event flags for given socket, or 0 if none static int ready(const fmap_t& mp, SRTSOCKET sock) { fmap_t::const_iterator y = mp.find(sock); if (y == mp.end()) return 0; return y->second; } /// Reports whether socket is ready for given event. /// @param mp socket event map retirned by `swait` /// @param sock which socket to ask /// @param event which events it should be ready for /// @return true if the given socket is ready for given event static bool isready(const fmap_t& mp, SRTSOCKET sock, SRT_EPOLL_OPT event) { return (ready(mp, sock) & event) != 0; } // Could be a template directly, but it's now hidden in the imp file. void clear_ready_usocks(CEPollDesc& d, int direction); /// wait for EPoll events or timeout optimized with explicit EPOLL_ERR event and the edge mode option. /// @param [in] eid EPoll ID. /// @param [out] fdsSet array of user socket events (SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR). /// @param [int] fdsSize of fds array /// @param [in] msTimeOut timeout threshold, in milliseconds. /// @return total of available events in the epoll system (can be greater than fdsSize) int uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); /// 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. At the end this function /// counts the number of updated EIDs with given events. /// @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 -1 if invalid events, otherwise the number of changes int update_events(const SRTSOCKET& uid, std::set& eids, int events, bool enable); int setflags(const int eid, int32_t flags); private: int m_iIDSeed; // seed to generate a new ID srt::sync::Mutex m_SeedLock; std::map m_mPolls; // all epolls mutable srt::sync::Mutex m_EPollLock; }; #if ENABLE_HEAVY_LOGGING std::string DisplayEpollResults(const std::map& sockset); #endif #endif srt-1.4.4/srtcore/fec.cpp000066400000000000000000002712031412557703600152710ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include "platform_sys.h" #include #include #include #include #include #include "packetfilter.h" #include "core.h" #include "packet.h" #include "logging.h" #include "fec.h" // Maximum allowed "history" remembered in the receiver groups. // This is calculated in series, that is, this number will be // multiplied by sizeRow() and sizeCol() to get the value being // a maximum distance between the FEC group base sequence and // the sequence to which a request comes in. // XXX Might be that this parameter should be configurable #define SRT_FEC_MAX_RCV_HISTORY 10 using namespace std; using namespace srt_logging; namespace srt { const char FECFilterBuiltin::defaultConfig [] = "fec,rows:1,layout:staircase,arq:onreq"; struct StringKeys { string operator()(const pair item) { return item.first; } }; bool FECFilterBuiltin::verifyConfig(const SrtFilterConfig& cfg, string& w_error) { string arspec = map_get(cfg.parameters, "layout"); if (arspec != "" && arspec != "even" && arspec != "staircase") { w_error = "value for 'layout' must be 'even' or 'staircase'"; return false; } string colspec = map_get(cfg.parameters, "cols"), rowspec = map_get(cfg.parameters, "rows"); int out_rows = 1; if (colspec != "") { int out_cols = atoi(colspec.c_str()); if (out_cols < 2) { w_error = "at least 'cols' must be specified and > 1"; return false; } } if (rowspec != "") { out_rows = atoi(rowspec.c_str()); if (out_rows >= -1 && out_rows < 1) { w_error = "'rows' must be >=1 or negative < -1"; return false; } } // Extra interpret level, if found, default never. // Check only those that are managed. string level = map_get(cfg.parameters, "arq"); if (level != "") { static const char* const levelnames [] = {"never", "onreq", "always"}; size_t i = 0; for (i = 0; i < Size(levelnames); ++i) { if (strcmp(level.c_str(), levelnames[i]) == 0) break; } if (i == Size(levelnames)) { w_error = "'arq' value '" + level + "' invalid. Allowed: never, onreq, always"; return false; } } set keys; transform(cfg.parameters.begin(), cfg.parameters.end(), inserter(keys, keys.begin()), StringKeys()); // Delete all default parameters SrtFilterConfig defconf; ParseFilterConfig(defaultConfig, (defconf)); for (map::const_iterator i = defconf.parameters.begin(); i != defconf.parameters.end(); ++i) keys.erase(i->first); // Delete mandatory parameters keys.erase("cols"); if (!keys.empty()) { w_error = "Extra parameters. Allowed only: cols, rows, layout, arq"; return false; } return true; } FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector &provided, const string &confstr) : SrtPacketFilterBase(init) , m_fallback_level(SRT_ARQ_ONREQ) , m_arrangement_staircase(true) , rcv(provided) { if (!ParseFilterConfig(confstr, cfg)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); string ermsg; if (!verifyConfig(cfg, (ermsg))) { LOGC(pflog.Error, log << "IPE: Filter config failed: " << ermsg); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } // Configuration supported: // - row only (number_rows == 1) // - columns only, no row FEC/CTL (number_rows < -1) // - columns and rows (both > 1) // Disallowed configurations: // - number_cols < 1 // - number_rows [-1, 0] string arspec = map_get(cfg.parameters, "layout"); string shorter = arspec.size() > 5 ? arspec.substr(0, 5) : arspec; if (shorter == "even") m_arrangement_staircase = false; string colspec = map_get(cfg.parameters, "cols"), rowspec = map_get(cfg.parameters, "rows"); if (colspec == "") { LOGC(pflog.Error, log << "FEC filter config: parameter 'cols' is mandatory"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } int out_rows = 1; int out_cols = atoi(colspec.c_str()); m_number_cols = out_cols; if (rowspec != "") { out_rows = atoi(rowspec.c_str()); } if (out_rows < 0) { m_number_rows = -out_rows; m_cols_only = true; } else { m_number_rows = out_rows; m_cols_only = false; } // Extra interpret level, if found, default never. // Check only those that are managed. string level = cfg.parameters["arq"]; int lv = -1; if (level != "") { static const char* levelnames [] = { "never", "onreq", "always" }; for (size_t i = 0; i < Size(levelnames); ++i) { if (level == levelnames[i]) { lv = int(i); break; } } } if (lv != -1) { m_fallback_level = SRT_ARQLevel(lv); } else { m_fallback_level = SRT_ARQ_ONREQ; } // Required to store in the header when rebuilding rcv.id = socketID(); // Setup the bit matrix, initialize everything with false. // Vertical size (y) rcv.cells.resize(sizeCol() * sizeRow(), false); // These sequence numbers are both the value of ISN-1 at the moment // when the handshake is done. The sender ISN is generated here, the // receiver ISN by the peer. Both should be known after the handshake. // Later they will be updated as packets are transmitted. int32_t snd_isn = CSeqNo::incseq(sndISN()); int32_t rcv_isn = CSeqNo::incseq(rcvISN()); // Alright, now we need to get the ISN from m_parent // to extract the sequence number allowing qualification to the group. // The base values must be prepared so that feedSource can qualify them. // SEPARATE FOR SENDING AND RECEIVING! // Now, assignment of the groups requires: // For row groups, simply the size of the group suffices. // For column groups, you need a whole matrix of all sequence // numbers that are base sequence numbers for the group. // Sequences that belong to this group are: // 1. First packet has seq+1 towards the base. // 2. Every next packet has this value + the size of the row group. // So: group dispatching is: // - get the column number // - extract the group data for that column // - check if the sequence is later than the group base sequence, if not, report no group for the packet // - sanity check, if the seqdiff divided by row size gets 0 remainder // - The result from the above division can't exceed the column size, otherwise // it's another group. The number of currently collected data should be in 'collected'. // Now set up the group starting sequences. // The very first group in both dimensions will have the value of ISN in particular direction. // Set up sender part. // // Size: rows // Step: 1 (next packet in group is 1 past the previous one) // Slip: rows (first packet in the next group is distant to first packet in the previous group by 'rows') HLOGC(pflog.Debug, log << "FEC: INIT: ISN { snd=" << snd_isn << " rcv=" << rcv_isn << " }; sender single row"); ConfigureGroup(snd.row, snd_isn, 1, sizeRow()); // In the beginning we need just one reception group. New reception // groups will be created in tact with receiving packets outside this one. // The value of rcv.row[0].base will be used as an absolute base for calculating // the index of the group for a given received packet. rcv.rowq.resize(1); HLOGP(pflog.Debug, "FEC: INIT: receiver first row"); ConfigureGroup(rcv.rowq[0], rcv_isn, 1, sizeRow()); if (sizeCol() > 1) { // Size: cols // Step: rows (the next packet in the group is one row later) // Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down) HLOGP(pflog.Debug, "FEC: INIT: sender first N columns"); ConfigureColumns(snd.cols, snd_isn); HLOGP(pflog.Debug, "FEC: INIT: receiver first N columns"); ConfigureColumns(rcv.colq, rcv_isn); } // The bit markers that mark the received/lost packets will be expanded // as packets come in. rcv.cell_base = rcv_isn; } template void FECFilterBuiltin::ConfigureColumns(Container& which, int32_t isn) { // This is to initialize the first set of groups. // which: group vector. // numberCols(): number of packets in one group // sizeCol(): seqdiff between two packets consecutive in the group // m_column_slip: seqdiff between the first packet in one group and first packet in the next group // isn: sequence number of the first packet in the first group size_t zero = which.size(); // The first series of initialization should embrace: // - if multiplyer == 1, EVERYTHING (also the case of SOLID matrix) // - if more, ONLY THE FIRST SQUARE. which.resize(zero + numberCols()); if (!m_arrangement_staircase) { HLOGC(pflog.Debug, log << "ConfigureColumns: new " << numberCols() << " columns, START AT: " << zero); // With even arrangement, just use a plain loop. // Initialize straight way all groups in the size. int32_t seqno = isn; for (size_t i = zero; i < which.size(); ++i) { // ARGS: // - seqno: sequence number of the first packet in the group // - step: distance between two consecutive packets in the group // - drop: distance between base sequence numbers in groups in consecutive series // (meaning: with row size 6, group with index 2 and 8 are in the // same column 2, lying in 0 and 1 series respectively). ConfigureGroup(which[i], seqno, sizeRow(), sizeCol() * numberCols()); seqno = CSeqNo::incseq(seqno); } return; } // With staircase, the next column's base sequence is // shifted by 1 AND the length of the row. When this shift // becomes below the column 0 bottom, reset it to the row 0 // and continue. // Start here. The 'isn' is still the absolute base sequence value. size_t offset = 0; HLOGC(pflog.Debug, log << "ConfigureColumns: " << (which.size() - zero) << " columns, START AT: " << zero); for (size_t i = zero; i < which.size(); ++i) { int32_t seq = CSeqNo::incseq(isn, int(offset)); size_t col = i - zero; HLOGC(pflog.Debug, log << "ConfigureColumns: [" << col << "]: -> ConfigureGroup..."); ConfigureGroup(which[i], seq, sizeRow(), sizeCol() * numberCols()); if (col % numberRows() == numberRows() - 1) { offset = col + 1; // +1 because we want it for the next column HLOGC(pflog.Debug, log << "ConfigureColumns: [" << (col+1) << "]... (resetting to row 0: +" << offset << " %" << CSeqNo::incseq(isn, offset) << ")"); } else { offset += 1 + sizeRow(); HLOGC(pflog.Debug, log << "ConfigureColumns: [" << (col+1) << "] ... (continue +" << offset << " %" << CSeqNo::incseq(isn, offset) << ")"); } } } void FECFilterBuiltin::ConfigureGroup(Group& g, int32_t seqno, size_t gstep, size_t drop) { g.base = seqno; g.step = gstep; // This actually rewrites the size of the group here, but // by having this value precalculated we simply close the // group by adding this value to the base sequence. g.drop = drop; g.collected = 0; // Now the buffer spaces for clips. g.payload_clip.resize(payloadSize()); g.length_clip = 0; g.flag_clip = 0; g.timestamp_clip = 0; HLOGC(pflog.Debug, log << "FEC: ConfigureGroup: base %" << seqno << " step=" << gstep << " drop=" << drop); // Preallocate the buffer that will be used for storing it for // the needs of passing the data through the network. // This will be filled with zeros initially, which is unnecessary, // but it happeens just once after connection. } void FECFilterBuiltin::ResetGroup(Group& g) { const int32_t new_seq_base = CSeqNo::incseq(g.base, int(g.drop)); HLOGC(pflog.Debug, log << "FEC: ResetGroup (step=" << g.step << "): base %" << g.base << " -> %" << new_seq_base); g.base = new_seq_base; g.collected = 0; // This isn't necessary for ConfigureGroup because the // vector after resizing is filled with a given value, // by default the default value of the type, char(), that is 0. g.length_clip = 0; g.flag_clip = 0; g.timestamp_clip = 0; memset(&g.payload_clip[0], 0, g.payload_clip.size()); } void FECFilterBuiltin::feedSource(CPacket& packet) { // Hang on the matrix. Find by packet->getSeqNo(). // (The "absolute base" is the cell 0 in vertical groups) int32_t base = snd.row.base; // (we are guaranteed that this packet is a data packet, so // we don't have to check if this isn't a control packet) int baseoff = CSeqNo::seqoff(base, packet.getSeqNo()); int horiz_pos = baseoff; if (CheckGroupClose(snd.row, horiz_pos, sizeRow())) { HLOGC(pflog.Debug, log << "FEC:... HORIZ group closed, B=%" << snd.row.base); } ClipPacket(snd.row, packet); snd.row.collected++; // Don't do any column feeding if using column size 1 if (sizeCol() < 2) { // The above logging instruction in case of no columns HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " B:%" << baseoff << " H:*[" << horiz_pos << "]" << " size=" << packet.size() << " TS=" << packet.getMsgTimeStamp() << " !" << BufferStamp(packet.data(), packet.size())); HLOGC(pflog.Debug, log << "FEC collected: H: " << snd.row.collected); return; } // 1. Get the number of group in both vertical and horizontal groups: // - Vertical: offset towards base (% row size, but with updated Base seq unnecessary) // (Just for a case). int vert_gx = baseoff % sizeRow(); // 2. Define the position of this packet in the group // - Horizontal: offset towards base (of the given group, not absolute!) // - Vertical: (seq-base)/column_size int32_t vert_base = snd.cols[vert_gx].base; int vert_off = CSeqNo::seqoff(vert_base, packet.getSeqNo()); // It MAY HAPPEN that the base is newer than the sequence of the packet. // This may normally happen in the beginning period, where the bases // set up initially for all columns got the shift, so they are kinda from // the future, and "this sequence" is in a group that is already closed. // In this case simply can't clip the packet in the column group. HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " rowoff=" << baseoff << " column=" << vert_gx << " .base=%" << vert_base << " coloff=" << vert_off); if (vert_off >= 0 && sizeCol() > 1) { // BEWARE! X % Y with different signedness upgrades int to unsigned! // SANITY: check if the rule applies on the group if (vert_off % sizeRow()) { LOGC(pflog.Fatal, log << "FEC:feedSource: IPE: VGroup #" << vert_gx << " base=%" << vert_base << " WRONG with horiz base=%" << base << "coloff(" << vert_off << ") % sizeRow(" << sizeRow() << ") = " << (vert_off % sizeRow())); // Do not place it, it would be wrong. return; } SRT_ASSERT(vert_off >= 0); int vert_pos = vert_off / int(sizeRow()); HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " B:%" << baseoff << " H:*[" << horiz_pos << "] V(B=%" << vert_base << ")[col=" << vert_gx << "][" << vert_pos << "/" << sizeCol() << "] " << " size=" << packet.size() << " TS=" << packet.getMsgTimeStamp() << " !" << BufferStamp(packet.data(), packet.size())); // 3. The group should be check for the necessity of being closed. // Note that FEC packet extraction doesn't change the state of the // VERTICAL groups (it can be potentially extracted multiple times), // only the horizontal in order to mark that the vertical FEC is // extracted already. So, anyway, check if the group limit was reached // and it wasn't closed. // 4. Apply the clip // 5. Increase collected. if (CheckGroupClose(snd.cols[vert_gx], vert_pos, sizeCol())) { HLOGC(pflog.Debug, log << "FEC:... VERT group closed, B=%" << snd.cols[vert_gx].base); } ClipPacket(snd.cols[vert_gx], packet); snd.cols[vert_gx].collected++; } else { HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " B:%" << baseoff << " H:*[" << horiz_pos << "] V(B=%" << vert_base << ")[col=" << vert_gx << "]" << " size=" << packet.size() << " TS=" << packet.getMsgTimeStamp() << " !" << BufferStamp(packet.data(), packet.size())); } HLOGC(pflog.Debug, log << "FEC collected: H: " << snd.row.collected << " V[" << vert_gx << "]: " << snd.cols[vert_gx].collected); } bool FECFilterBuiltin::CheckGroupClose(Group& g, size_t pos, size_t size) { if (pos < size) return false; ResetGroup(g); return true; } void FECFilterBuiltin::ClipPacket(Group& g, const CPacket& pkt) { // Both length and timestamp must be taken as NETWORK ORDER // before applying the clip. uint16_t length_net = htons(uint16_t(pkt.size())); uint8_t kflg = uint8_t(pkt.getMsgCryptoFlags()); // NOTE: Unlike length, the TIMESTAMP is NOT endian-reordered // because it will be written into the TIMESTAMP field in the // header, and header is inverted automatically when sending, // unlike the contents of the payload, where the length will be written. uint32_t timestamp_hw = pkt.getMsgTimeStamp(); ClipData(g, length_net, kflg, timestamp_hw, pkt.data(), pkt.size()); HLOGC(pflog.Debug, log << "FEC DATA PKT CLIP: " << hex << "FLAGS=" << unsigned(kflg) << " LENGTH[ne]=" << (length_net) << " TS[he]=" << timestamp_hw << " CLIP STATE: FLAGS=" << unsigned(g.flag_clip) << " LENGTH[ne]=" << g.length_clip << " TS[he]=" << g.timestamp_clip << " PL4=" << (*(uint32_t*)&g.payload_clip[0])); } // Clipping a control packet does merely the same, just the packet has // different contents, so it must be differetly interpreted. void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt) { // Both length and timestamp must be taken as NETWORK ORDER // before applying the clip. const char* fec_header = pkt.data(); const char* payload = fec_header + 4; size_t payload_clip_len = pkt.size() - 4; const uint8_t* flag_clip = (const uint8_t*)(fec_header + 1); const uint16_t* length_clip = (const uint16_t*)(fec_header + 2); uint32_t timestamp_hw = pkt.getMsgTimeStamp(); ClipData(g, *length_clip, *flag_clip, timestamp_hw, payload, payload_clip_len); HLOGC(pflog.Debug, log << "FEC/CTL CLIP: " << hex << "FLAGS=" << unsigned(*flag_clip) << " LENGTH[ne]=" << (*length_clip) << " TS[he]=" << timestamp_hw << " CLIP STATE: FLAGS=" << unsigned(g.flag_clip) << " LENGTH[ne]=" << g.length_clip << " TS[he]=" << g.timestamp_clip << " PL4=" << (*(uint32_t*)&g.payload_clip[0])); } void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt) { uint16_t length_net = htons(uint16_t(pkt.length)); uint8_t kflg = MSGNO_ENCKEYSPEC::unwrap(pkt.hdr[SRT_PH_MSGNO]); // NOTE: Unlike length, the TIMESTAMP is NOT endian-reordered // because it will be written into the TIMESTAMP field in the // header, and header is inverted automatically when sending, // unlike the contents of the payload, where the length will be written. uint32_t timestamp_hw = pkt.hdr[SRT_PH_TIMESTAMP]; ClipData(g, length_net, kflg, timestamp_hw, pkt.buffer, pkt.length); HLOGC(pflog.Debug, log << "FEC REBUILT DATA CLIP: " << hex << "FLAGS=" << unsigned(kflg) << " LENGTH[ne]=" << (length_net) << " TS[he]=" << timestamp_hw << " CLIP STATE: FLAGS=" << unsigned(g.flag_clip) << " LENGTH[ne]=" << g.length_clip << " TS[he]=" << g.timestamp_clip << " PL4=" << (*(uint32_t*)&g.payload_clip[0])); } void FECFilterBuiltin::ClipData(Group& g, uint16_t length_net, uint8_t kflg, uint32_t timestamp_hw, const char* payload, size_t payload_size) { g.length_clip = g.length_clip ^ length_net; g.flag_clip = g.flag_clip ^ kflg; g.timestamp_clip = g.timestamp_clip ^ timestamp_hw; // Payload goes "as is". for (size_t i = 0; i < payload_size; ++i) { g.payload_clip[i] = g.payload_clip[i] ^ payload[i]; } // Fill the rest with zeros. When this packet is going to be // recovered, the payload extraced from this process will have // the maximum lenght, but it will be cut to the right length // and these padding 0s taken out. for (size_t i = payload_size; i < payloadSize(); ++i) g.payload_clip[i] = g.payload_clip[i] ^ 0; } bool FECFilterBuiltin::packControlPacket(SrtPacket& rpkt, int32_t seq) { // If the FEC packet is not yet ready for extraction, do nothing and return false. // Check if seq is the last sequence of the group. // Check VERTICAL group first, then HORIZONTAL. // // This is because when it happens that HORIZONTAL group is to be // FEC-CTL reported, it also shifts the base to the next row, whereas // this base sequence is used to determine the column index that is // needed to reach the right column group and it must stay unupdated // until the last packet in this row is checked for VERTICAL groups. // If it's ready for extraction, extract it, and write into the packet. // // NOTE: seq is the sequence number of the LAST PACKET SENT regularly. // This is only about to be shifted forward by 1 to be placed on the // data packet. The packet in `r_packet` doesn't have the sequence number // installed yet // For BOTH vertical and horizontal snd groups: // - Check if the "full group" condition is satisfied (all packets from the group are clipped) // - If not, simply return false and do nothing // - If so, store the current clip state into the referenced packet, give it the 'seq' sequence // After packing the FEC packet: // - update the base sequence in the group for which it's packed // - make sure that pointers are reset to not suggest the packet is ready // Handle the special case of m_number_rows == 1, which // means we don't use columns. if (m_number_rows <= 1) { HLOGC(pflog.Debug, log << "FEC/CTL not checking VERT group - rows only config"); // PASS ON to Horizontal group check } else { int offset_to_row_base = CSeqNo::seqoff(snd.row.base, seq); int vert_gx = (offset_to_row_base + int(m_number_cols)) % int(m_number_cols); // This can actually happen only for the very first sent packet. // It looks like "following the last packet from the previous group", // however there was no previous group because this is the first packet. if (offset_to_row_base < 0) { HLOGC(pflog.Debug, log << "FEC/CTL not checking VERT group [" << vert_gx << "] - negative offset_to_row_base %" << snd.row.base << " -> %" << seq << " (" << offset_to_row_base << ") (collected " << snd.cols[abs(vert_gx)].collected << "/" << sizeCol() << ")"); // PASS ON to Horizontal group check } else { if (snd.cols[vert_gx].collected >= m_number_rows) { HLOGC(pflog.Debug, log << "FEC/CTL ready for VERT group [" << vert_gx << "]: %" << seq << " (base %" << snd.cols[vert_gx].base << ")"); // SHIP THE VERTICAL FEC packet. PackControl(snd.cols[vert_gx], vert_gx, rpkt, seq); // RESET THE GROUP THAT WAS SENT ResetGroup(snd.cols[vert_gx]); return true; } HLOGC(pflog.Debug, log << "FEC/CTL NOT ready for VERT group [" << vert_gx << "]: %" << seq << " (base %" << snd.cols[vert_gx].base << ")" << " - collected " << snd.cols[vert_gx].collected << "/" << m_number_rows); } } if (snd.row.collected >= m_number_cols) { if (!m_cols_only) { HLOGC(pflog.Debug, log << "FEC/CTL ready for HORIZ group: %" << seq << " (base %" << snd.row.base << ")"); // SHIP THE HORIZONTAL FEC packet. PackControl(snd.row, -1, rpkt, seq); HLOGC(pflog.Debug, log << "...PACKET size=" << rpkt.length << " TS=" << rpkt.hdr[SRT_PH_TIMESTAMP] << " !" << BufferStamp(rpkt.buffer, rpkt.length)); } // RESET THE HORIZONTAL GROUP. // ALWAYS, even in columns-only. ResetGroup(snd.row); if (!m_cols_only) { // In columns-only you didn't pack anything, so check // for column control. return true; } } else { HLOGC(pflog.Debug, log << "FEC/CTL NOT ready for HORIZ group: %" << seq << " (base %" << snd.row.base << ")" << " - collected " << snd.row.collected << "/" << m_number_cols); } return false; } void FECFilterBuiltin::PackControl(const Group& g, signed char index, SrtPacket& pkt, int32_t seq) { // Allocate as much space as needed, regardless of the PAYLOADSIZE value. static const size_t INDEX_SIZE = 1; size_t total_size = INDEX_SIZE + sizeof(g.flag_clip) + sizeof(g.length_clip) + g.payload_clip.size(); // Sanity #if ENABLE_DEBUG if (g.output_buffer.size() < total_size) { LOGC(pflog.Fatal, log << "OUTPUT BUFFER TOO SMALL!"); abort(); } #endif char* out = pkt.buffer; size_t off = 0; // Spread the index. This is the index of the payload in the vertical group. // For horizontal group this value is always -1. out[off++] = index; // Flags, currently only the encryption flags out[off++] = g.flag_clip; // Ok, now the length clip memcpy((out + off), &g.length_clip, sizeof g.length_clip); off += sizeof g.length_clip; // And finally the payload clip memcpy((out + off), &g.payload_clip[0], g.payload_clip.size()); // Ready. Now fill the header and finalize other data. pkt.length = total_size; pkt.hdr[SRT_PH_TIMESTAMP] = g.timestamp_clip; pkt.hdr[SRT_PH_SEQNO] = seq; HLOGC(pflog.Debug, log << "FEC: PackControl: hdr(" << (total_size - g.payload_clip.size()) << "): INDEX=" << int(index) << " LENGTH[ne]=" << hex << g.length_clip << " FLAGS=" << int(g.flag_clip) << " TS=" << g.timestamp_clip << " PL(" << dec << g.payload_clip.size() << ")[0-4]=" << hex << (*(uint32_t*)&g.payload_clip[0])); } bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs) { // Add this packet to the group where it belongs. // Light up the cell of this packet to mark it received. // Check if any of the groups to which the packet belongs // have changed the status into RECOVERABLE. // // The group has RECOVERABLE status when it has FEC // packet received and the number of collected packets counts // exactly group_size - 1. bool want_packet = false; struct IsFec { bool row; bool col; signed char colx; } isfec = { false, false, -1 }; // The sequence number must be checked prematurely, or it can otherwise // cause large resource allocation. This might be even survived, provided // that this will make the packet seen as exceeding the series 0 matrix, // so all matrices in previous series should be dismissed thereafter. But // this short living resource spike may be destructive, so let's do // matrix dismissal FIRST before this packet is going to be handled. CheckLargeDrop(rpkt.getSeqNo()); if (rpkt.getMsgSeq() == SRT_MSGNO_CONTROL) { // Interpret the first byte of the contents. const char* payload = rpkt.data(); isfec.colx = payload[0]; if (isfec.colx == -1) { isfec.row = true; } else { isfec.col = true; } HLOGC(pflog.Debug, log << "FEC: RECEIVED %" << rpkt.getSeqNo() << " msgno=0, FEC/CTL packet. INDEX=" << int(payload[0])); // This marks the cell as NOT received, but still does extend the // cell container up to this sequence. The HangHorizontal and HangVertical // functions that would also do cell dismissal, RELY ON IT. MarkCellReceived(rpkt.getSeqNo(), CELL_EXTEND); } else { // Data packet, check if this packet was already received. // If so, ignore it. This may happen if you have configured // FEC and ARQ to cooperate, so a packet once rebuilt might // be simultaneously also retransmitted. This may confuse the tables. int celloff = CSeqNo::seqoff(rcv.cell_base, rpkt.getSeqNo()); bool past = celloff < 0; bool exists = celloff < int(rcv.cells.size()) && !past && rcv.cells[celloff]; if (past || exists) { HLOGC(pflog.Debug, log << "FEC: packet %" << rpkt.getSeqNo() << " " << (past ? "in the PAST" : "already known") << ", IGNORING."); return true; } want_packet = true; HLOGC(pflog.Debug, log << "FEC: RECEIVED %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() << " DATA PACKET."); MarkCellReceived(rpkt.getSeqNo()); // Remember this simply every time a packet comes in. In live mode usually // this flag is ORD_RELAXED (false), but some earlier versions used ORD_REQUIRED. // Even though this flag is now usually ORD_RELAXED, it's fate in live mode // isn't completely decided yet, so stay flexible. We believe at least that this // flag will stay unchanged during whole connection. rcv.order_required = rpkt.getMsgOrderFlag(); } loss_seqs_t irrecover_row, irrecover_col; #if ENABLE_HEAVY_LOGGING static string hangname [] = {"SUCCESS", "PAST", "CRAZY", "NOT-DONE"}; #endif // Required for EHangStatus using namespace std::rel_ops; EHangStatus okh = HANG_NOTDONE; if (!isfec.col) // == regular packet or FEC/ROW { // Don't manage this packet for horizontal group, // if it was a vertical FEC/CTL packet. okh = HangHorizontal(rpkt, isfec.row, irrecover_row); HLOGC(pflog.Debug, log << "FEC: HangHorizontal %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() << " RESULT=" << hangname[okh] << " IRRECOVERABLE: " << Printable(irrecover_row)); } if (okh > HANG_SUCCESS) { // Just informative. LOGC(pflog.Warn, log << "FEC/H: rebuilding/hanging FAILED."); } EHangStatus okv = HANG_NOTDONE; // Don't do HangVertical in case of row-only configuration if (!isfec.row && m_number_rows > 1) // == regular packet or FEC/COL { // NOTE FOR IPE REPORTING: // It is allowed that // - Both HangVertical and HangHorizontal okv = HangVertical(rpkt, isfec.colx, irrecover_col); IF_HEAVY_LOGGING(bool discrep = (okv == HANG_CRAZY) ? int(okh) < HANG_CRAZY : false); HLOGC(pflog.Debug, log << "FEC: HangVertical %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() << " RESULT=" << hangname[okh] << (discrep ? " IPE: H successul and V failed!" : "") << " IRRECOVERABLE: " << Printable(irrecover_col)); } if (okv > HANG_SUCCESS) { // Just informative. LOGC(pflog.Warn, log << "FEC/V: rebuilding/hanging FAILED."); } if (okv == HANG_CRAZY || okh == HANG_CRAZY) { // Mark the cell not received, if it was rejected by the // FEC group facility, otherwise it will deny to try to rebuild an // allegedly existing packet. MarkCellReceived(rpkt.getSeqNo(), CELL_REMOVE); } // Pack the following packets as irrecoverable: if (m_fallback_level == SRT_ARQ_ONREQ) { // Use irrecover_row with rows only because there is // never anything collected in irrecover_col. if (m_number_rows == 1) loss_seqs = irrecover_row; else loss_seqs = irrecover_col; } return want_packet; // Get the packet from the incoming stream, already recognized // as data packet, and then: // // (Note that the default builtin FEC mechanism uses such rules: // - allows SRT to get the packet, even if it follows the loss // - depending on m_fallback_level, confirms or denies the need that SRT handle the loss // - in loss_seqs we return those that are not recoverable at the current level // - FEC has no extra header provided, so regular data are passed as is //) // So, the needs to implement: // // 1. If this is a FEC packet, close the group, check for lost packets, try to recover. // Check if there is recovery possible, if so, request a new unit and pack the recovered packet there. // Report the loss to be reported by SRT according to m_fallback_level: // - ARQ_ALWAYS: N/A for a FEC packet // - ARQ_EARLY: When Horizontal group is closed and the packet is not recoverable, report this in loss_seqs // - ARQ_LATELY: When Horizontal and Vertical group is closed and the packet is not recoverable, report it. // - ARQ_NEVER: Always return empty loss_seqs // // 2. If this is a regular packet, use it for building the FEC group. // - ARQ_ALWAYS: always return true and leave loss_seqs empty. // - others: return false and return nothing in loss_seqs } void FECFilterBuiltin::CheckLargeDrop(int32_t seqno) { // Ok, first try to pick up the column and series int offset = CSeqNo::seqoff(rcv.rowq[0].base, seqno); if (offset < 0) { return; } // For row-only configuration, check only parts referring // to a row. if (m_number_rows == 1) { // We have no columns. So just check if exceeds 5* the row size. // If so, clear the rows and reconfigure them. if (offset > int(5 * sizeRow())) { // Calculate the new row base, without breaking the current // layout. Make a skip by some number of rows so that the new // first row is prepared to receive this packet. int32_t oldbase = rcv.rowq[0].base; size_t rowdist = offset / sizeRow(); int32_t newbase = CSeqNo::incseq(oldbase, int(rowdist * sizeRow())); LOGC(pflog.Warn, log << "FEC: LARGE DROP detected! Resetting row groups. Base: %" << oldbase << " -> %" << newbase << "(shift by " << CSeqNo::seqoff(oldbase, newbase) << ")"); rcv.rowq.clear(); rcv.cells.clear(); rcv.rowq.resize(1); HLOGP(pflog.Debug, "FEC: RE-INIT: receiver first row"); ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow()); } return; } bool reset_anyway = false; if (offset != CSeqNo::seqoff(rcv.colq[0].base, seqno)) { reset_anyway = true; HLOGC(pflog.Debug, log << "FEC: IPE: row.base %" << rcv.rowq[0].base << " != %" << rcv.colq[0].base << " - resetting"); } // Number of column - regardless of series. int colx = offset % numberCols(); // Base sequence from the group series 0 in this column // [[assert rcv.colq.size() >= numberCols()]]; int32_t colbase = rcv.colq[colx].base; // Offset between this base and seqno int coloff = CSeqNo::seqoff(colbase, seqno); // Might be that it's in the row above the column, // still it's not a large-drop if (coloff < 0) { return; } const size_t size_in_packets = colx * numberRows(); const size_t matrix = numberRows() * numberCols(); const int colseries = coloff / int(matrix); if (size_in_packets > rcvBufferSize()/2 || colseries > SRT_FEC_MAX_RCV_HISTORY || reset_anyway) { // Ok, now define the new ABSOLUTE BASE. This is the base of the column 0 // column group from the series previous towards this one. int32_t oldbase = rcv.colq[0].base; int32_t newbase = CSeqNo::incseq(oldbase, (colseries-1) * int(matrix)); LOGC(pflog.Warn, log << "FEC: LARGE DROP detected! Resetting all groups. Base: %" << oldbase << " -> %" << newbase << "(shift by " << CSeqNo::seqoff(oldbase, newbase) << ")"); rcv.rowq.clear(); rcv.colq.clear(); rcv.cells.clear(); rcv.rowq.resize(1); HLOGP(pflog.Debug, "FEC: RE-INIT: receiver first row"); ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow()); // Size: cols // Step: rows (the next packet in the group is one row later) // Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down) HLOGP(pflog.Debug, "FEC: RE-INIT: receiver first N columns"); ConfigureColumns(rcv.colq, newbase); rcv.cell_base = newbase; } } void FECFilterBuiltin::CollectIrrecoverRow(RcvGroup& g, loss_seqs_t& irrecover) const { if (g.dismissed) return; // already collected // Obtain the group's packet shift int32_t base = rcv.cell_base; int offset = CSeqNo::seqoff(base, g.base); if (offset < 0) { LOGC(pflog.Error, log << "FEC: IPE: row base %" << g.base << " is PAST to cell base %" << base); return; } size_t maxoff = offset + m_number_cols; // Sanity check, if all cells are really filled. if (maxoff > rcv.cells.size()) { LOGC(pflog.Error, log << "FEC: IPE: Collecting loss from row %" << g.base << "+" << m_number_cols << " while cells <= %" << CSeqNo::seqoff(rcv.cell_base, int(rcv.cells.size())-1)); return; } bool last = true; loss_seqs_t::value_type val; for (size_t i = offset; i < maxoff; ++i) { bool gone = last; last = rcv.cells[i]; if (gone && !last) { // Switch full -> loss. Store the sequence, as single (for now) val.first = val.second = CSeqNo::incseq(base, int(i)); } else if (last && !gone) { val.second = CSeqNo::incseq(base, int(i)); irrecover.push_back(val); } } // If it happened that 0 cells were until the end, we are // sure that we have the val.first set to the first of the loss list // and we've reached the end. Otherwise 'last' would be true. if (!last) { val.second = CSeqNo::incseq(base, int(maxoff)-1); irrecover.push_back(val); } g.dismissed = true; } #if ENABLE_HEAVY_LOGGING static inline char CellMark(const std::deque& cells, int index) { if (index >= int(cells.size())) return '/'; return cells[index] ? '#' : '.'; } static void DebugPrintCells(int32_t base, const std::deque& cells, size_t row_size) { size_t i = 0; // Shift to the first empty cell for ( ; i < cells.size(); ++i) if (cells[i] == false) break; if (i == cells.size()) { LOGC(pflog.Debug, log << "FEC: ... cell[0-" << (cells.size()-1) << "]: ALL CELLS EXIST"); return; } // Ok, we have some empty cells, so just adjust to the start of a row. size_t bstep = i % row_size; if (i < bstep) // you never know... i = 0; else i -= bstep; for ( ; i < cells.size(); i += row_size ) { std::ostringstream os; os << "cell[" << i << "-" << (i+row_size-1) << "] %" << CSeqNo::incseq(base, i) << ":"; for (size_t y = 0; y < row_size; ++y) { os << " " << CellMark(cells, i+y); } LOGP(pflog.Debug, os.str()); } } #else static void DebugPrintCells(int32_t /*base*/, const std::deque& /*cells*/, size_t /*row_size*/) {} #endif FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs_t& irrecover) { const int32_t seq = rpkt.getSeqNo(); EHangStatus stat; const int rowx = RcvGetRowGroupIndex(seq, (stat)); if (rowx == -1) return stat; RcvGroup& rowg = rcv.rowq[rowx]; // Clip the packet into the horizontal group. // If this was a regular packet, increase the number of collected. // If this was a FEC/CTL packet, keep this number, just set the fec flag. if (isfec) { if (!rowg.fec) { ClipControlPacket(rowg, rpkt); rowg.fec = true; HLOGC(pflog.Debug, log << "FEC/H: FEC/CTL packet clipped, %" << seq << " base=%" << rowg.base); } else { HLOGC(pflog.Debug, log << "FEC/H: FEC/CTL at %" << seq << " DUPLICATED, skipping."); } } else { ClipPacket(rowg, rpkt); rowg.collected++; HLOGC(pflog.Debug, log << "FEC/H: DATA packet clipped, %" << seq << ", received " << rowg.collected << "/" << sizeRow() << " base=%" << rowg.base); } if (rowg.fec && rowg.collected == m_number_cols - 1) { HLOGC(pflog.Debug, log << "FEC/H: HAVE " << rowg.collected << " collected & FEC; REBUILDING..."); // The group will provide the information for rebuilding. // The sequence of the lost packet can be checked in cells. // With the condition of 'collected == m_number_cols - 1', there // should be only one lacking packet, so just rely on first found. RcvRebuild(rowg, RcvGetLossSeqHoriz(rowg), m_number_rows == 1 ? Group::SINGLE : Group::HORIZ); #if ENABLE_HEAVY_LOGGING std::ostringstream os; for (size_t i = 0; i < rcv.rebuilt.size(); ++i) { os << " " << rcv.rebuilt[i].hdr[SRT_PH_SEQNO]; } LOGC(pflog.Debug, log << "FEC: ... cached rebuilt packets (" << rcv.rebuilt.size() << "):" << os.str()); #endif } // When there are only rows, dismiss the oldest row when you have // collected at least 1 packet in the next group. Do not dismiss // any groups here otherwise - all will be decided during column // processing. bool want_collect_irrecover = false; bool want_remove_cells = false; if (rcv.rowq.size() > 1) { if (m_number_rows == 1) { want_remove_cells = true; want_collect_irrecover = true; } else if (m_fallback_level == SRT_ARQ_ONREQ) { want_collect_irrecover = true; } } if (want_collect_irrecover) // AND rcv.rowq.size() > 1 { int current = int(rcv.rowq.size()) - 2; // We know we have at least 2 rows. // This value is then 0 or more. int past = current - 1; // To trigger irrecoverable collection, the current sequence // must be further than 1/3 of the row size to start from // the previous row. Otherwise, start with the past-previous // one, as long as it still exists. bool early SRT_ATR_UNUSED = false; if (past > 0) { // If you already have at least 3 rows, sweep starting from // the before-previous one (this will become 0 when the number // of rows is exactly 3). --past; } else { // If you have 2 rows, then in the current row (1) there must // be the sequence passing already the 1/3 of the size. Otherwise // decrease past to make it -1 and not pass the next test. if (CSeqNo::seqoff(rcv.rowq[1].base, seq) <= int(m_number_cols/3)) { --past; } else { early = true; } } if (past >= 0) { // Collect irrecoverable since the 'past' index up to 0. // If want_remove_cells, also remove these rows and corresponding cells. int nrowremove = 1 + past; HLOGC(pflog.Debug, log << "Collecting irrecoverable packets from " << nrowremove << " ROWS per offset " << CSeqNo::seqoff(rcv.rowq[1].base, seq) << " vs. " << m_number_cols << "/3"); for (int i = 0; i <= past; ++i) { CollectIrrecoverRow(rcv.rowq[i], irrecover); } // Sanity check condition - rcv.rowq must be of size // greater than the number of rows to remove so that // the rcv.rowq[0] exists after the operation. if (want_remove_cells && rcv.rowq.size() > size_t(nrowremove)) { // nrowremove >= 1 size_t npktremove = sizeRow() * nrowremove; size_t ersize = min(npktremove, rcv.cells.size()); // ersize <= rcv.cells.size() HLOGC(pflog.Debug, log << "FEC/H: Dismissing rows n=" << nrowremove << ", starting at %" << rcv.rowq[0].base << " AND " << npktremove << " CELLS, base switch %" << rcv.cell_base << " -> %" << rcv.rowq[past].base); rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + nrowremove); rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + ersize); // We state that we have removed as many cells as for the removed // rows. In case when the number of cells proved to be less than that, // it will simply remove all cells. So now set the cell base to be // in sync with the row base. rcv.cell_base = rcv.rowq[0].base; DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow()); } } else { HLOGC(pflog.Debug, log << "FEC: NOT collecting irrecover from rows: distance=" << CSeqNo::seqoff(rcv.rowq[0].base, seq)); } } return HANG_SUCCESS; } int32_t FECFilterBuiltin::RcvGetLossSeqHoriz(Group& g) { int baseoff = CSeqNo::seqoff(rcv.cell_base, g.base); if (baseoff < 0) { LOGC(pflog.Error, log << "FEC: IPE: negative cell offset, cell_base=%" << rcv.cell_base << " Group's base: %" << g.base << " - NOT ATTEMPTING TO REBUILD"); return -1; } // This is a row, so start from the first cell for this group // and search lineraly for the first loss. int offset = -1; for (size_t cix = baseoff; cix < baseoff + m_number_cols; ++cix) { if (!rcv.CellAt(cix)) { offset = int(cix); #if ENABLE_HEAVY_LOGGING // For heavy logging case, show all cells in the range LOGC(pflog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): MISSING"); #else // Find just one. No more that just one shall be found // because it was checked earlier that we have collected // all but just one packet. break; #endif } #if ENABLE_HEAVY_LOGGING else { LOGC(pflog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): exists"); } #endif } if (offset == -1) { LOGC(pflog.Fatal, log << "FEC/H: IPE: rebuilding attempt, but no lost packet found"); return -1; // sanity, shouldn't happen } // Now that we have an offset towards the first packet in the cells, // translate it to the sequence number of the lost packet. return CSeqNo::incseq(rcv.cell_base, offset); } int32_t FECFilterBuiltin::RcvGetLossSeqVert(Group& g) { int baseoff = CSeqNo::seqoff(rcv.cell_base, g.base); if (baseoff < 0) { LOGC(pflog.Error, log << "FEC: IPE: negative cell offset, cell_base=%" << rcv.cell_base << " Group's base: %" << g.base << " - NOT ATTEMPTING TO REBUILD"); return -1; } // This is a row, so start from the first cell for this group // and search lineraly for the first loss. int offset = -1; for (size_t col = 0; col < sizeCol(); ++col) { size_t cix = baseoff + (col * sizeRow()); if (!rcv.CellAt(cix)) { offset = int(cix); #if ENABLE_HEAVY_LOGGING // For heavy logging case, show all cells in the range LOGC(pflog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): MISSING"); #else // Find just one. No more that just one shall be found // because it was checked earlier that we have collected // all but just one packet. break; #endif } #if ENABLE_HEAVY_LOGGING else { LOGC(pflog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): exists"); } #endif } if (offset == -1) { LOGC(pflog.Fatal, log << "FEC/V: IPE: rebuilding attempt, but no lost packet found"); return -1; // sanity, shouldn't happen } // Now that we have an offset towards the first packet in the cells, // translate it to the sequence number of the lost packet. return CSeqNo::incseq(rcv.cell_base, offset); } void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) { if (seqno == -1) return; uint16_t length_hw = ntohs(g.length_clip); if (length_hw > payloadSize()) { LOGC(pflog.Warn, log << "FEC: DECLIPPED length '" << length_hw << "' exceeds payload size. NOT REBUILDING."); return; } // Rebuild the packet // (length_hw is automatically converted through PrivPacket constructor) rcv.rebuilt.push_back( length_hw ); Receive::PrivPacket& p = rcv.rebuilt.back(); p.hdr[SRT_PH_SEQNO] = seqno; // This is for live mode only, for now, so the message // number will be always 1, PB_SOLO, INORDER, and flags from clip. // The REXMIT flag is set to 1 to fake that the packet was // retransmitted. It is necessary because this packet will // come out of sequence order, and if such a packet has // no rexmit flag set, it's treated as reordered by network, // which isn't true here. p.hdr[SRT_PH_MSGNO] = 1 | MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO) | MSGNO_PACKET_INORDER::wrap(rcv.order_required) | MSGNO_ENCKEYSPEC::wrap(g.flag_clip) | MSGNO_REXMIT::wrap(true) ; p.hdr[SRT_PH_TIMESTAMP] = g.timestamp_clip; p.hdr[SRT_PH_ID] = rcv.id; // Header ready, now we rebuild the contents // First, rebuild the length. // Allocate the buffer and assign to a packet. // This is only temporary, it will be copied to // the target place when needed, with the buffer coming // from the unit queue. // The payload clip may be longer than length_hw, but it // contains only trailing zeros for completion, which are skipped. copy(g.payload_clip.begin(), g.payload_clip.end(), p.buffer); HLOGC(pflog.Debug, log << "FEC: REBUILT: %" << seqno << " msgno=" << MSGNO_SEQ::unwrap(p.hdr[SRT_PH_MSGNO]) << " flags=" << PacketMessageFlagStr(p.hdr[SRT_PH_MSGNO]) << " TS=" << p.hdr[SRT_PH_TIMESTAMP] << " ID=" << dec << p.hdr[SRT_PH_ID] << " size=" << length_hw << " !" << BufferStamp(p.buffer, p.length)); // Mark this packet received MarkCellReceived(seqno); // If this is a single request (filled from row and m_number_cols == 1), // do not attempt recursive rebuilding if (tp == Group::SINGLE) return; // This flips HORIZ/VERT Group::Type crosstype = Group::Type(!tp); EHangStatus stat; if (crosstype == Group::HORIZ) { // Find this packet in the horizontal group const int rowx = RcvGetRowGroupIndex(seqno, (stat)); if (rowx == -1) return; // can't access any group to rebuild RcvGroup& rowg = rcv.rowq[rowx]; // Sanity check. It's impossible that the packet was already // rebuilt and any attempt to rebuild a lacking packet was made. if (rowg.collected > m_number_cols - 1) { return; } // Same as ClipPacket for the incoming packet, just this // is extracting the data directly from the rebuilt one. ClipRebuiltPacket(rowg, p); rowg.collected++; HLOGC(pflog.Debug, log << "FEC/H: REBUILT packet clipped, %" << seqno << ", received " << rowg.collected << "/" << m_number_cols << " FOR base=%" << rowg.base); // Similar as by HangHorizontal, just don't collect irrecoverable packets. // They are already known when the packets were collected. if (rowg.fec && rowg.collected == m_number_cols - 1) { HLOGC(pflog.Debug, log << "FEC/H: with FEC-rebuilt HAVE " << rowg.collected << " collected & FEC; REBUILDING"); // The group will provide the information for rebuilding. // The sequence of the lost packet can be checked in cells. // With the condition of 'collected == m_number_cols - 1', there // should be only one lacking packet, so just rely on first found. // NOTE: RECURSIVE CALL. RcvRebuild(rowg, RcvGetLossSeqHoriz(rowg), crosstype); } } else // crosstype == Group::VERT { // Find this packet in the vertical group const int colx = RcvGetColumnGroupIndex(seqno, (stat)); if (colx == -1) return; // can't access any group to rebuild RcvGroup& colg = rcv.colq[colx]; // Sanity check. It's impossible that the packet was already // rebuilt and any attempt to rebuild a lacking packet was made. if (colg.collected > m_number_rows - 1) { return; } // Same as ClipPacket for the incoming packet, just this // is extracting the data directly from the rebuilt one. ClipRebuiltPacket(colg, p); colg.collected++; HLOGC(pflog.Debug, log << "FEC/V: REBUILT packet clipped, %" << seqno << ", received " << colg.collected << "/" << m_number_rows << " FOR base=%" << colg.base); // Similar as by HangVertical, just don't collect irrecoverable packets. // They are already known when the packets were collected. if (colg.fec && colg.collected == m_number_rows - 1) { HLOGC(pflog.Debug, log << "FEC/V: with FEC-rebuilt HAVE " << colg.collected << " collected & FEC; REBUILDING"); // The group will provide the information for rebuilding. // The sequence of the lost packet can be checked in cells. // With the condition of 'collected == m_number_rows - 1', there // should be only one lacking packet, so just rely on first found. // NOTE: RECURSIVE CALL. RcvRebuild(colg, RcvGetLossSeqVert(colg), crosstype); } } } size_t FECFilterBuiltin::ExtendRows(size_t rowx) { // Check if oversize. Oversize is when the // index is > 2*m_number_cols. If so, shrink // the container first. #if ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: ROW STATS BEFORE: n=" << rcv.rowq.size()); for (size_t i = 0; i < rcv.rowq.size(); ++i) LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats()); #endif const size_t size_in_packets = rowx * numberCols(); const int n_series = int(rowx / numberRows()); if (size_in_packets > rcvBufferSize() && n_series > 2) { HLOGC(pflog.Debug, log << "FEC: Emergency resize, rowx=" << rowx << " series=" << n_series << "npackets=" << size_in_packets << " exceeds buf=" << rcvBufferSize()); EmergencyShrink(n_series); } // Create and configure next groups. size_t old = rcv.rowq.size(); // First, add the number of groups. rcv.rowq.resize(rowx + 1); // Starting from old size for (size_t i = old; i < rcv.rowq.size(); ++i) { // Initialize the base for the row group int32_t ibase = CSeqNo::incseq(rcv.rowq[0].base, int(i*m_number_cols)); ConfigureGroup(rcv.rowq[i], ibase, 1, m_number_cols); } #if ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: ROW STATS AFTER: n=" << rcv.rowq.size()); for (size_t i = 0; i < rcv.rowq.size(); ++i) LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats()); #endif return rowx; } int FECFilterBuiltin::RcvGetRowGroupIndex(int32_t seq, EHangStatus& w_status) { RcvGroup& head = rcv.rowq[0]; const int32_t base = head.base; const int offset = CSeqNo::seqoff(base, seq); // Discard the packet, if older than base. if (offset < 0) { HLOGC(pflog.Debug, log << "FEC/H: Packet %" << seq << " is in the past, ignoring"); w_status = HANG_PAST; return -1; } // Hang in the receiver group first. size_t rowx = offset / m_number_cols; /* Don't. Leaving this code for future if needed, but this check should not be done. The resource management for "crazy" sequence numbers is done in the beginning, so simply TRUST THIS SEQUENCE, no matter what. After the check it won't do any harm. if (rowx > numberRows()*2) // past twice the matrix { LOGC(pflog.Error, log << "FEC/H: Packet %" << seq << " is in the far future, ignoring"); return -1; } */ // The packet might have come completely out of the blue. // The row group container must be prepared to extend // itself in order to give place for the packet. // First, possibly extend the row container if (rowx >= rcv.rowq.size()) { // Never returns -1 rowx = ExtendRows(rowx); } w_status = HANG_SUCCESS; return int(rowx); } void FECFilterBuiltin::MarkCellReceived(int32_t seq, ECellReceived is_received) { // Mark the packet as received. This will allow later to // determine, which exactly packet is lost and needs rebuilding. const int cellsize = int(rcv.cells.size()); const int cell_offset = CSeqNo::seqoff(rcv.cell_base, seq); bool resized SRT_ATR_UNUSED = false; if (cell_offset >= cellsize) { // Expand the cell container with zeros, excluding the 'cell_offset'. // Resize normally up to the required size, just set the lastmost // item to true. resized = true; rcv.cells.resize(cell_offset+1, false); } if (resized || is_received != CELL_EXTEND) { // In both RECEIVED and REMOVE cases, forcefully set the value always. // In EXTEND, only if it was received // Value set should be true only if RECEIVED, false otherwise rcv.cells[cell_offset] = (is_received == CELL_RECEIVED); } #if ENABLE_HEAVY_LOGGING static string const cellop [] = { "RECEIVED", "EXTEND", "REMOVE" }; LOGC(pflog.Debug, log << "FEC: MARK CELL " << cellop[is_received] << "(" << (rcv.cells[cell_offset] ? "SET" : "CLR") << ")" << ": %" << seq << " - cells base=%" << rcv.cell_base << "[" << cell_offset << "]+" << rcv.cells.size() << (resized ? "(resized)":"") << " :"); #endif DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow()); } bool FECFilterBuiltin::IsLost(int32_t seq) const { const int offset = CSeqNo::seqoff(rcv.cell_base, seq); if (offset < 0) { LOGC(pflog.Error, log << "FEC: IsLost: IPE: %" << seq << " is earlier than the cell base %" << rcv.cell_base); return true; // This might be due to emergency shrinking; pretend the packet is lost } if (offset >= int(rcv.cells.size())) { // XXX IPE! LOGC(pflog.Error, log << "FEC: IsLost: IPE: %" << seq << " is past the cells %" << rcv.cell_base << " + " << rcv.cells.size()); return false; // Don't notify it yet } return rcv.cells[offset]; } void FECFilterBuiltin::EmergencyShrink(size_t n_series) { // Shrink is required in order to prepare place for // either vertical or horizontal group in series `n_series`. // The n_series can be calculated as: // n_series = colgx / numberCols() // n_series = rowgx / numberRows() // // The (Column or Row) Group Index value is calculated as // the number of column where the desired sequence number // should be located towards the very first container item // (row/column 0). // The task for this function is to leave only one series // of groups and therefore initialize the containers. Likely // the part that contains the last series should be already // there, so in this case just remove some initial items from // the container so that only those remain that are intended // to remain. However, by various reasons (like e.g. that all // packets from the whole series have been lost) particular // container (colq, rowq, cell) doesn't contain this last // series at all. In that case clear the container completely // and just add an initial configuration for the first part // (which will be then dynamically extended as packets come in). const int32_t oldbase = rcv.colq[0].base; const size_t shift_series = n_series - 1; // This is simply a situation when the size is so excessive // that it couldn't be withstood by the receiver buffer, so // even if this isn't an extremely big size for allocation for // FEC, it doesn't make sense anyway. // // Minimum of 2 series must remain in the group container, // otherwise there's no need to guard the size. // This requires simply resetting all group containers to // the very initial state, just take the calculated base seq // from the value of colgx reset to column 0. // As colgx is calculated by stating that colgx == 0 represents // the very first cell in the column groups, take this, shift // by the number of series. // SHIFT BY: n_series * matrix size // n_series is at least 2 (see condition) const size_t shift = shift_series * numberCols() * numberRows(); // Always positive: colgx, and so n_series, and so shift const int32_t newbase = CSeqNo::incseq(oldbase, int(shift)); const size_t shift_rows = shift_series * numberRows(); bool need_reset = rcv.rowq.size() < shift_rows; if (!need_reset) { // Sanity check - you should have the exact value // of `newbase` at the next series beginning position if (rcv.rowq[numberRows()].base != newbase) { LOGC(pflog.Error, log << "FEC: IPE: row start at %" << rcv.rowq[0].base << " next series %" << rcv.rowq[numberRows()].base << " (expected %" << newbase << "). RESETTING ROWS."); need_reset = true; } } if (need_reset) { rcv.rowq.clear(); // This n_series is the number rounded downwards, // So you just need to prepare place for ONE series. // The procedure below will extend them to the required // size for the received colgx. rcv.rowq.resize(1); HLOGC(pflog.Debug, log << "FEC: Reset recv row %" << oldbase << " -> %" << newbase << ", INIT ROWS:"); ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow()); } else { HLOGC(pflog.Debug, log << "FEC: Shifting rcv row %" << oldbase << " -> %" << newbase); rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.end() + shift_rows); } const size_t shift_cols = shift_series * numberCols(); need_reset = rcv.colq.size() < shift_cols; if (!need_reset) { // Sanity check - you should have the exact value // of `newbase` at the next series beginning position if (rcv.colq[numberCols()].base != newbase) { LOGC(pflog.Error, log << "FEC: IPE: col start at %" << rcv.colq[0].base << " next series %" << rcv.colq[numberCols()].base << " (expected %" << newbase << "). RESETTING ROWS."); need_reset = true; } } if (need_reset) { rcv.colq.clear(); HLOGC(pflog.Debug, log << "FEC: Reset recv row %" << oldbase << " -> %" << newbase << ", INIT first " << numberCols() << ":"); ConfigureColumns(rcv.colq, newbase); } if (rcv.cells.size() > shift) { rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + shift); } else { rcv.cells.clear(); rcv.cells.push_back(false); } rcv.cell_base = newbase; } FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangVertical(const CPacket& rpkt, signed char fec_col, loss_seqs_t& irrecover) { bool fec_ctl = (fec_col != -1); // Now hang the packet in the vertical group const int32_t seq = rpkt.getSeqNo(); // Ok, now we have the column index, we know it exists. // Apply the packet. EHangStatus stat; const int colgx = RcvGetColumnGroupIndex(seq, (stat)); if (colgx == -1) return stat; RcvGroup& colg = rcv.colq[colgx]; if (fec_ctl) { if (!colg.fec) { ClipControlPacket(colg, rpkt); colg.fec = true; HLOGC(pflog.Debug, log << "FEC/V: FEC/CTL packet clipped, %" << seq << " FOR COLUMN " << int(fec_col) << " base=%" << colg.base); } else { HLOGC(pflog.Debug, log << "FEC/V: FEC/CTL at %" << seq << " COLUMN " << int(fec_col) << " DUPLICATED, skipping."); } } else { // Data packet, clip it as data ClipPacket(colg, rpkt); colg.collected++; HLOGC(pflog.Debug, log << "FEC/V: DATA packet clipped, %" << seq << ", received " << colg.collected << "/" << sizeCol() << " base=%" << colg.base); } if (colg.fec && colg.collected == m_number_rows - 1) { HLOGC(pflog.Debug, log << "FEC/V: HAVE " << colg.collected << " collected & FEC; REBUILDING"); RcvRebuild(colg, RcvGetLossSeqVert(colg), Group::VERT); } // Column dismissal takes place under very strictly specified condition, // so simply call it in general here. At least it may happen potentially // at any time of when a packet has been received. RcvCheckDismissColumn(rpkt.getSeqNo(), colgx, irrecover); #if ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: COL STATS ATM: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); #endif return HANG_SUCCESS; } void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t& irrecover) { // The first check we need to do is: // // - get the column number // - get the series for this column // - if series is 0, just return const size_t series = colgx / numberCols(); if (series == 0) return; // - STARTING from the same column series 0: // - unless DISMISSED, collect all irrecoverable packets from this group // - mark this column DISMISSED // - SAME CHECK for previous group, until index 0 set loss; size_t colx SRT_ATR_UNUSED = colgx % numberCols(); HLOGC(pflog.Debug, log << "FEC/V: going to DISMISS cols past %" << seq << " at INDEX=" << colgx << " col=" << colx << " series=" << series << " - looking up candidates..."); // Walk through all column groups in series 0. Collect irrecov's // from every group, for which the incoming 'seq' is in future. for (size_t i = 0; i < numberCols(); ++i) { RcvGroup& pg = rcv.colq[i]; if (pg.dismissed) { HLOGC(pflog.Debug, log << "FEC/V: ... [" << i << "] base=%" << pg.base << " ALREADY DISMISSED, skipping."); continue; } // With multi-staircase it may happen that THIS column contains // sequences that are all in the past, but the PREVIOUS column // has some in the future, because THIS column is the top of // the second staircase, and PREVIOUS is the bottom stair of // the first staircase. When this is confirmed, simply skip // the columns that have the highest sequence in the future // because they can't be dismissed yet. Jump them over, so maybe // they can be dismissed in future. int this_col_offset = CSeqNo::seqoff(pg.base, seq); int last_seq_offset = this_col_offset - int((sizeCol()-1)*sizeRow()); if (last_seq_offset < 0) { HLOGC(pflog.Debug, log << "FEC/V: ... [" << i << "] base=%" << pg.base << " TOO EARLY (last=%" << CSeqNo::incseq(pg.base, (sizeCol()-1)*sizeRow()) << ")"); continue; } // NOTE: If it was standing on the second staircase top, there's // still a chance that it hits the staircase top of the first // staircase and will dismiss it as well. HLOGC(pflog.Debug, log << "FEC/V: ... [" << i << "] base=%" << pg.base << " - PAST last=%" << CSeqNo::incseq(pg.base, (sizeCol()-1)*sizeRow()) << " - collecting losses."); pg.dismissed = true; // mark irrecover already collected for (size_t sof = 0; sof < pg.step * sizeCol(); sof += pg.step) { int32_t lseq = CSeqNo::incseq(pg.base, int(sof)); if (!IsLost(lseq)) { loss.insert(lseq); HLOGC(pflog.Debug, log << "FEC: ... cell +" << sof << " %" << lseq << " lost"); } else { HLOGC(pflog.Debug, log << "FEC: ... cell +" << sof << " %" << lseq << " EXISTS"); } } } // COLUMN DISMISAL: // 1. We can only dismiss ONE SERIES OF COLUMNS - OR NOTHING. // 2. The triggering 'seq' must be past ANY sequence embraced // by any group in the first series of columns. // Useful information: // // 1. It's not known from upside, which column contains a sequence // number that reaches FURTHEST. The safe statement is then: // - For even arrangement, it must be past BASE0 + matrix size // - For staircase arrangement - BASE0 + matrix size * 2. int32_t base0 = rcv.colq[0].base; int this_off = CSeqNo::seqoff(base0, seq); int mindist = int( m_arrangement_staircase ? (numberCols() * numberRows() * 2) : (numberCols() * numberRows())); bool any_dismiss SRT_ATR_UNUSED = false; // Here's a change. // The number of existing column groups is supposed to always cover // at least one full series, whereas the number of row groups are // created always one per necessity, so the number of existing row // groups may be less than required for a full series, whereas here // it is intended to simply dismiss groups for full series. This may // cause that it is aiming for removing more row groups than currently // exist. This is completely ok, as the sequence that triggered removal // is long past these series anyway, so the groups for packets that will // never be received makes no sense. Simply accept this state and delete // all row groups and reinitialize them into the new base, where the base // is the current base for column 0 group. // // Therefore dismissal is triggered whenever you have a cover of one column // series. If the number of row groups doesn't cover it, simply delete all // row groups, that's all. // if (base0 +% mindist) <% seq if (this_off < mindist) // COND 1: minimum remaining { HLOGC(pflog.Debug, log << "FEC/V: NOT dismissing any columns at %" << seq << ", need to pass %" << CSeqNo::incseq(base0, mindist)); } else if (rcv.colq.size() - 1 < numberCols()) // COND 2: full matrix in columns { #if ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC/V: IPE: about to dismiss past %" << seq << " with required %" << CSeqNo::incseq(base0, mindist) << " but col container size still " << rcv.colq.size() << "; COL STATS:"); for (size_t i = 0; i < rcv.colq.size(); ++i) LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); #endif } else { // The condition for dismissal is now. The number of dismissed columns // is numberCols(), regardless of the required 'mindinst'. any_dismiss = true; const int32_t newbase = rcv.colq[numberCols()].base; int32_t newbase_row SRT_ATR_UNUSED; // For logging only, but including FATAL. // Sanity check // If sanity check failed OR if the number of existing row // groups doesn't enclose those that need to be dismissed, // clear row groups completely - these packets are lost and // irrecoverable anyway. bool insane = false; bool undercounted = false; if (rcv.rowq.size() - 1 < numberRows()) // COND 3: full matrix in rows { // Do not reach to index=numberRows() because it doesn't exist. // Take the value from the columns as a good deal - actually // row base and col base shall be always in sync. newbase_row = newbase; undercounted = true; } else { newbase_row = rcv.rowq[numberRows()].base; insane = newbase_row != newbase; } const size_t matrix_size = numberCols() * numberRows(); HLOGC(pflog.Debug, log << "FEC/V: DISMISSING " << numberCols() << " COLS. Base %" << rcv.colq[0].base << " -> %" << newbase << " AND " << numberRows() << " ROWS Base %" << rcv.rowq[0].base << " -> %" << newbase_row << " AND " << matrix_size << " cells"); // ensured existence of the removed range: see COND 2 above. rcv.colq.erase(rcv.colq.begin(), rcv.colq.begin() + numberCols()); #if ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); #endif // Now erase accordingly one matrix of rows. if (insane || undercounted) { if (insane) { LOGC(pflog.Fatal, log << "FEC/V: IPE: DISCREPANCY in new base0 col=%" << newbase << " row=%" << newbase_row << " - DELETING ALL ROWS"); } else { #if ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC/V: about to dismiss past %" << seq << " with required %" << CSeqNo::incseq(base0, mindist) << " but row container size still " << rcv.rowq.size() << " (will clear to %" << newbase << " instead); ROW STATS:"); for (size_t i = 0; i < rcv.rowq.size(); ++i) LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats()); #endif } // Delete all rows and reinitialize them. rcv.rowq.clear(); rcv.rowq.resize(1); ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow()); } else { // Remove "legally" a matrix of rows. // ensured existence of the removed range: see COND 3 above rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + numberRows()); } // And now accordingly remove cells. Exactly one matrix of cells. // Sanity check first. int32_t newbase_cell = CSeqNo::incseq(rcv.cell_base, int32_t(matrix_size)); if (newbase != newbase_cell) { LOGC(pflog.Fatal, log << "FEC/V: IPE: DISCREPANCY in new base0 col=%" << newbase << " cell_base=%" << newbase_cell << " - DELETING ALL CELLS"); // Try to shift it gently first. Find the cell that matches the base. int shift = CSeqNo::seqoff(rcv.cell_base, newbase); if (shift < 0 || size_t(shift) > rcv.cells.size()) rcv.cells.clear(); else rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + shift); } else { if (rcv.cells.size() <= size_t(matrix_size)) rcv.cells.clear(); else rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + matrix_size); } rcv.cell_base = newbase; DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow()); } /* OLD UNUSED CODE, leaving for historical reasons // - check the last sequence of last column in series 0 // - if passed sequence number is earlier than this, just return // - now that seq is newer than the last in the last column, // - dismiss whole series 0 column groups // First, index of the last column size_t lastx = numberCols()-1; if (lastx < rcv.colq.size()) { int32_t lastbase = rcv.colq[lastx].base; // Compare the base sequence with the sequence that caused the update int dist = CSeqNo::seqoff(lastbase, seq); // Shift this distance by the distance between the first and last // sequence managed by a singled column. This counts (sizeCol()-1)*step. dist -= (sizeCol()-1) * rcv.colq[lastx].step; // Now, if this value is in the past (negative), it means that the // 'seq' number is covered by this group or any earlier group. If so, // do nothing. If this value is positive, it means that this // sequence is in future towards the group that is in the last // column of series 0. If so, whole series 0 may be now dismissed. // NOTE: we don't care if lost packets have been collected for // the groups being dismissed. They *SHOULD* be, just as a fallback // SRT - if needed - will simply send LOSSREPORT request for all // packets that are lossreported and all older ones. if (dist > 0 && rcv.colq.size() > numberCols() ) { any_dismiss = true; int32_t newbase = rcv.colq[numberCols()].base; rcv.colq.erase(rcv.colq.begin(), rcv.colq.begin() + numberCols()); // colgx is INVALIDATED after removal int newcolgx SRT_ATR_UNUSED = colgx - numberCols(); // After a column series was dismissed, now dismiss also // the same number of rows. // Do some sanity checks first. size_t nrowrem = 0; int32_t oldrowbase = rcv.rowq[0].base; // before it gets deleted if (rcv.rowq.size() > numberRows()) { int32_t newrowbase = rcv.rowq[numberRows()].base; if (newbase != newrowbase) { LOGC(pflog.Error, log << "FEC: IPE: ROW/COL base DISCREPANCY: Looking up lineraly for the right row."); // Fallback implementation in order not to break everything for (size_t r = 0; r < rcv.rowq.size(); ++r) { if (CSeqNo::seqoff(newbase, rcv.rowq[r].base) >= 0) { rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + r); nrowrem = r; break; } } } else { rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + numberRows()); nrowrem = numberRows(); } } // If rows were removed, so remove also cells if (nrowrem > 0) { int32_t newbase = rcv.rowq[0].base; // This value SHOULD be == nrowrem * sizeRow(), but this // calculation is safe against bugs. Report them, if found, though. int nrem = CSeqNo::seqoff(rcv.cell_base, newbase); if (oldrowbase != rcv.cell_base) { LOGC(pflog.Error, log << "FEC: CELL/ROW base discrepancy, calculating and resynchronizing"); } else { HLOGC(pflog.Debug, log << "FEC: will remove " << nrem << " cells, SHOULD BE = " << (nrowrem * sizeRow())); } if (nrem > 0) { // Now collect losses from all rows about to be dismissed. for (int sof = 0; sof < nrem; sof++) { int32_t lseq = CSeqNo::incseq(rcv.cell_base, sof); if (!IsLost(lseq)) loss.insert(lseq); } HLOGC(pflog.Debug, log << "FEC: ERASING unused cells (" << nrem << "): %" << rcv.cell_base << " - %" << newbase << ", losses collected: " << Printable(loss)); rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + nrem); rcv.cell_base = newbase; DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow()); } else { HLOGC(pflog.Debug, log << "FEC: NOT ERASING cells, base %" << rcv.cell_base << " vs row base %" << rcv.rowq[0].base); } } HLOGC(pflog.Debug, log << "FEC/V: updated g=" << colgx << " -> " << newcolgx << " %" << rcv.colq[newcolgx].base << ", DISMISS up to g=" << numberCols() << " base=%" << lastbase << " ROW=%" << rcv.rowq[0].base << "+" << nrowrem); } } // */ // Now all collected lost packets translate into the range list format TranslateLossRecords(loss, irrecover); HLOGC(pflog.Debug, log << "FEC: ... COLLECTED IRRECOVER: " << Printable(loss) << (any_dismiss ? " CELLS DISMISSED" : " nothing dismissed")); } void FECFilterBuiltin::TranslateLossRecords(const set& loss, loss_seqs_t& irrecover) { if (loss.empty()) return; // size() >= 1 granted set::iterator i = loss.begin(); int32_t fi_start = *i; int32_t fi_end = fi_start; ++i; for (; i != loss.end(); ++i) { int dist = CSeqNo::seqoff(fi_end, *i); if (dist == 1) ++fi_end; else { // Jumped over some sequences, cut the range. irrecover.push_back(make_pair(fi_start, fi_end)); fi_start = fi_end = *i; } } // And ship the last one irrecover.push_back(make_pair(fi_start, fi_end)); } int FECFilterBuiltin::RcvGetColumnGroupIndex(int32_t seqno, EHangStatus& w_status) { // The column is only the column, not yet // exactly the index of the column group in the container. // It's like: // 0 1 2 3 4 // [A] ' ' ' ' // [A] [A] ' ' ' // [A] [A] [A] ' ' // [A] [A] [A] [A] '++ // [A]+[A] [A] [A] [A]+ // [B] [A]+[A] [A] [A]+ // [B] [B] [A]+[A] [A]+ // [B] [B] [B] [A]+[A]+ // [B] [B] [B] [B] [A]++ // [B]+[B] [B] [B] [B] // [B] [B] [B] [B] // [B] [B] [B] // [B] [B] // [B] // // The same groups laid out in the container: // // [A]0 [A]1 [A]2 [A]3 [A]4 [B]0 [B]1 [B]2 [B]3 [B]4 // This means, vert_gx returns the number for the // logical column, but we only know this column, // not the exact group that is assigned to this // packet. That is, in the above picture, if the // vert_gx resulted in 3, we still don't know if // this is A3 or B3. // // To know it, we need to first take the base // sequence number for the very first column group // in the container. Every next group in this // container is shifted by the 'slip' factor, // which in this case is m_number_cols + 1. The base // sequence shifted by m_number_cols*m_number_rows // becomes the sequence number of the next // group in the same column. // // If a single column is dismissed, then the // column 1 will become column 0, but the column // 1 in the next series will also become column 0. // All due to that the very base sequence for // all groups will be the one in the first series // column 1, now 0. // // Therefore, once we have the column, let's extract // the column base sequence. // As we can't count on that packets will surely come to close a group // or just a particular sequence number will be received, we simply have // to check all past groups at the moment when this packet is received: // // 1. Take the sequence number and determine both the GROUP INDEX and the // COLUMN INDEX of this group. // // Important thing here is that the column group base is the base sequence // number of the very first group, which NEED NOT GO HAND IN HAND with the // row group sequence base. The rules are then: // // OFFSET = DISTANCE( colq[0].base TO seq ) // // COLUMN_INDEX = OFFSET % m_number_cols // // COLUMN_BASE = colq[COLUMN_INDEX].base // // COLUMN_OFFSET = DISTANCE(COLUMN_BASE TO seq) // // COLUMN_SERIES = COLUMN_OFFSET / (m_number_cols * m_number_rows) // // GROUP_INDEX = COLUMN_INDEX + (COLUMN_SERIES * m_number_cols) const int offset = CSeqNo::seqoff(rcv.colq[0].base, seqno); if (offset < 0) { HLOGC(pflog.Debug, log << "FEC/V: %" << seqno << " in the past of col ABSOLUTE base %" << rcv.colq[0].base); w_status = HANG_PAST; return -1; } if (offset > CSeqNo::m_iSeqNoTH/2) { LOGC(pflog.Error, log << "FEC/V: IPE/ATTACK: pkt %" << seqno << " has CRAZY OFFSET towards the base %" << rcv.colq[0].base); w_status = HANG_CRAZY; return -1; } const int colx = offset % m_number_cols; const int32_t colbase = rcv.colq[colx].base; const int coloff = CSeqNo::seqoff(colbase, seqno); if (coloff < 0) { HLOGC(pflog.Debug, log << "FEC/V: %" << seqno << " in the past of col #" << colx << " base %" << colbase); // This means that this sequence number predates the earliest // sequence number supported by the very first column. w_status = HANG_PAST; return -1; } const int colseries = coloff / int(m_number_cols * m_number_rows); size_t colgx = colx + int(colseries * m_number_cols); HLOGC(pflog.Debug, log << "FEC/V: Lookup group for %" << seqno << ": cg_base=%" << rcv.colq[0].base << " column=" << colx << " with base %" << colbase << ": SERIES=" << colseries << " INDEX:" << colgx); // Check oversize. Dismiss some earlier items if it exceeds the size. // before you extend the size enormously. if (colgx > m_number_rows * m_number_cols * SRT_FEC_MAX_RCV_HISTORY) { // That's too much LOGC(pflog.Error, log << "FEC/V: IPE or ATTACK: offset " << colgx << " is too crazy, ABORTING lookup"); w_status = HANG_CRAZY; return -1; } if (colgx >= rcv.colq.size()) { colgx = ExtendColumns(colgx); } w_status = HANG_SUCCESS; return int(colgx); // // Even though column groups are arranged in a "staircase", it only means // that DISTANCE(colq[0].base TO colq[1].base) == 1 + m_number_cols, not 1. // But still, this DISTANCE(...) % m_number_cols == 1. So: // // COLUMN_INDEX = GROUP_INDEX % m_number_cols // // What is special here is that, group base sequence numbers with m_number_cols == 5 // is, for example, these column groups in order of how they are arranged in // the container have their [INDEX] BASE (stating the very first has base == 10): // // [0] 10, [1] 16, [2] 22, [3] 28, [4] 34, [5] 40, [6] 46 // // Therefore, if we get the sequence number 51, then: // // OFFSET = 51 - 10 = 41 // // COLUMN_INDEX = 41 % 5[m_number_cols] == 1 // // COLUMN_BASE = colq[1].base == 16 // // COLUMN_OFFSET = DISTANCE(colq[1].base TO 51) = 51 - 16 = 35 // // COLUMN_SERIES = COLUMN_OFFSET / (m_number_cols * m_number_rows) // = 35 / (5*5) = 35/25 = 1 // // GROUP_INDEX = COLUMN_INDEX + (COLUMN_SERIES * m_number_cols) // = 1 + 1*5 = 6 // // --> We have identified column group with index 6, this one // that is designated above as [6] 46. This column group collects // the following sequence numbers: // - 46 // - 51 (the one we were searching) // - 56 // - 61 // - 66 // // We have then column group with index 6. Now we go backward in the column // group container starting from the direct previous column up to either // the start of container or the size == m_number_cols (5), to see if the // group "is closed". So: // // END = min(m_number_cols, colq.size()) <--- should be 5 or less, if there's less in the container // // FOR EACH i = 1 TO END: // g = colq[GROUP_INDEX - i] // gs = SHIFT(seq, -i) // gmax = SHIFT(g.base, m_number_cols * m_number_rows) // IF ( gs %> gmax ) // DISMISS COLUMNS from 0 to GROUP_INDEX - i; break } size_t FECFilterBuiltin::ExtendColumns(size_t colgx) { // This isn't safe to allow the group container to get expanded to any // size, however with some very tolerant settings, such as 10 seconds of // latency and very large receiver buffer, this might be tolerable. // // Therefore put only two conditions here: // // 1. The group containers must keep at most place for so many // packets as it is intended for the receiver buffer. Keeping // group cells for more packets doesn't make sense anyway. // // 2. Existing group containers should contain at least size // for two series. If they don't contain that much, there's no // need to do any emergency shrinking. Unknown whether this is // physically possible, although it may also happen in case when // you have very large FEC matrix size not coordinated with the // receiver buffer size. // colgx is the number of column + NSERIES * numberCols(). // We can state that for every column we should have a number // of packets as many as the number of rows, so simply multiply this. const size_t size_in_packets = colgx * numberRows(); const size_t n_series = colgx / numberCols(); if (size_in_packets > rcvBufferSize()/2 || n_series > SRT_FEC_MAX_RCV_HISTORY) { HLOGC(pflog.Debug, log << "FEC: Emergency resize, colgx=" << colgx << " series=" << n_series << "npackets=" << size_in_packets << " exceeds buf=" << rcvBufferSize()); EmergencyShrink(n_series); } else { HLOGC(pflog.Debug, log << "FEC: Will extend up to colgx=" << colgx << " series=" << n_series << " for npackets=" << size_in_packets); } #if ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); #endif // First, obtain the "series" of columns, possibly fixed. const int series = int(colgx / numberCols()); // Now, the base of the series is the base increased by one matrix size. const int32_t base = rcv.colq[0].base; // This is the base for series 0, but this procedure must be prepared // for that the series will not necessarily be 1, may be greater. // Extension requires to be done in order to achieve this very index // existing in the column, so you need to add whole series in loop // until the series covering this shift is created. // Check, up to which series the columns are initialized. // Start with the series that doesn't exist const int old_series = int(rcv.colq.size() / numberCols()); // Each iteration of this loop adds one series of columns. // One series count numberCols() columns. for (int s = old_series; s <= series; ++s) { // We start with the base in series 0, the calculation of the // sequence number must happen anew for each one anyway, so it // doesn't matter from which start point. // Every base sequence for a series of columns is the series 0 // base increased by one matrix size times series number. // THIS REMAINS TRUE NO MATTER IF WE USE STRAIGNT OR STAIRCASE ARRANGEMENT. const int32_t sbase = CSeqNo::incseq(base, int(numberCols()*numberRows()) * s); HLOGC(pflog.Debug, log << "FEC/V: EXTENDING column groups series " << s << ", size " << rcv.colq.size() << " -> " << (rcv.colq.size() + numberCols()) << ", base=%" << base << " -> %" << sbase); // Every call to this function extends the given container // by 'gsize' number and configures each so added column accordingly. ConfigureColumns(rcv.colq, sbase); } #if ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); #endif return colgx; } } // namespace srt srt-1.4.4/srtcore/fec.h000066400000000000000000000233651412557703600147420ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_FEC_H #define INC_SRT_FEC_H #include #include #include #include #include "packetfilter_api.h" namespace srt { class FECFilterBuiltin: public SrtPacketFilterBase { SrtFilterConfig cfg; size_t m_number_cols; size_t m_number_rows; // Configuration SRT_ARQLevel m_fallback_level; bool m_cols_only; bool m_arrangement_staircase; public: size_t numberCols() const { return m_number_cols; } size_t numberRows() const { return m_number_rows; } size_t sizeCol() const { return m_number_rows; } size_t sizeRow() const { return m_number_cols; } struct Group { int32_t base; //< Sequence of the first packet in the group size_t step; //< by how many packets the sequence should increase to get the next packet size_t drop; //< by how much the sequence should increase to get to the next series size_t collected; //< how many packets were taken to collect the clip Group(): base(CSeqNo::m_iMaxSeqNo), step(0), drop(0), collected(0) { } uint16_t length_clip; uint8_t flag_clip; uint32_t timestamp_clip; std::vector payload_clip; // This is mutable because it's an intermediate buffer for // the purpose of output. //mutable vector output_buffer; enum Type { HORIZ, // Horizontal, recursive VERT, // Vertical, recursive // NOTE: HORIZ/VERT are defined as 0/1 so that not-inversion // can flip between them. SINGLE // Horizontal-only with no recursion }; }; struct RcvGroup: Group { bool fec; bool dismissed; RcvGroup(): fec(false), dismissed(false) {} #if ENABLE_HEAVY_LOGGING std::string DisplayStats() { if (base == CSeqNo::m_iMaxSeqNo) return "UNINITIALIZED!!!"; std::ostringstream os; os << "base=" << base << " step=" << step << " drop=" << drop << " collected=" << collected << " " << (fec ? "+" : "-") << "FEC " << (dismissed ? "DISMISSED" : "active"); return os.str(); } #endif }; private: // Row Groups: every item represents a single row group and collects clips for one row. // Col Groups: every item represents a signel column group and collect clips for packets represented in one column struct Send { // We need only ONE horizontal group. Simply after the group // is closed (last packet supplied), and the FEC packet extracted, // the group is no longer in use. Group row; std::vector cols; } snd; struct Receive { SRTSOCKET id; bool order_required; Receive(std::vector& provided): id(SRT_INVALID_SOCK), order_required(false), rebuilt(provided) { } // In reception we need to keep as many horizontal groups as required // for possible later tracking. A horizontal group should be dismissed // when the size of this container exceeds the `m_number_rows` (size of the column). // // The 'std::deque' type is used here for a trial implementation. A desired solution // would be a kind of a ring buffer where new groups are added and old (exceeding // the size) automatically dismissed. std::deque rowq; // Base index at the oldest column platform determines // the base index of the queue. Meaning, first you need // to determnine the column index, where the index 0 is // the fistmost element of this queue. After determining // the column index, there must be also a second factor // deteremined - which column series it is. So, this can // start by extracting the base sequence of the element // at the index column. This is the series 0. Now, the // distance between these two sequences, divided by // rowsize*colsize should return %index-in-column, // /number-series. The latter multiplied by the row size // is the offset between the firstmost column and the // searched column. std::deque colq; // This keeps the value of "packet received or not". // The sequence number of the first cell is rowq[0].base. // When dropping a row, // - the firstmost element of rowq is removed // - the length of one row is removed from this std::vector int32_t cell_base; std::deque cells; // Note this function will automatically extend the container // with empty cells if the index exceeds the size, HOWEVER // the caller must make sure that this index isn't any "crazy", // that is, it fits somehow in reasonable ranges. bool CellAt(size_t index) { if (index >= cells.size()) { // Cells not prepared for this sequence yet, // so extend in advance. cells.resize(index+1, false); return false; // It wasn't marked, anyway. } return cells[index]; } typedef SrtPacket PrivPacket; std::vector& rebuilt; } rcv; void ConfigureGroup(Group& g, int32_t seqno, size_t gstep, size_t drop); template void ConfigureColumns(Container& which, int32_t isn); void ResetGroup(Group& g); // Universal void ClipData(Group& g, uint16_t length_net, uint8_t kflg, uint32_t timestamp_hw, const char* payload, size_t payload_size); void ClipPacket(Group& g, const CPacket& pkt); // Sending bool CheckGroupClose(Group& g, size_t pos, size_t size); void PackControl(const Group& g, signed char groupix, SrtPacket& pkt, int32_t seqno); // Receiving void CheckLargeDrop(int32_t seqno); size_t ExtendRows(size_t rowx); size_t ExtendColumns(size_t colgx); enum ECellReceived { CELL_RECEIVED, //< mark cell for a received packet (no matter current value) CELL_EXTEND, //< just make sure there's a place for a packet, set false if not CELL_REMOVE //< even if a packet was marked true, remove the cell existence confirmation }; void MarkCellReceived(int32_t seq, ECellReceived recv = CELL_RECEIVED); enum EHangStatus { HANG_NOTDONE, HANG_SUCCESS, HANG_PAST, HANG_CRAZY }; friend bool operator <(FECFilterBuiltin::EHangStatus a, FECFilterBuiltin::EHangStatus b) { return int(a) < int(b); } EHangStatus HangHorizontal(const CPacket& pkt, bool fec_ctl, loss_seqs_t& irrecover); EHangStatus HangVertical(const CPacket& pkt, signed char fec_colx, loss_seqs_t& irrecover); void ClipControlPacket(Group& g, const CPacket& pkt); void ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt); void RcvRebuild(Group& g, int32_t seqno, Group::Type tp); int32_t RcvGetLossSeqHoriz(Group& g); int32_t RcvGetLossSeqVert(Group& g); void EmergencyShrink(size_t n_series); static void TranslateLossRecords(const std::set& loss, loss_seqs_t& irrecover); void RcvCheckDismissColumn(int32_t seqno, int colgx, loss_seqs_t& irrecover); int RcvGetRowGroupIndex(int32_t seq, EHangStatus& w_status); int RcvGetColumnGroupIndex(int32_t seqno, EHangStatus& w_status); void CollectIrrecoverRow(RcvGroup& g, loss_seqs_t& irrecover) const; bool IsLost(int32_t seq) const; public: FECFilterBuiltin(const SrtFilterInitializer& init, std::vector& provided, const std::string& confstr); // Sender side // This function creates and stores the FEC control packet with // a prediction to be immediately sent. This is called in the function // that normally is prepared for extracting a data packet from the sender // buffer and send it over the channel. virtual bool packControlPacket(SrtPacket& r_packet, int32_t seq) ATR_OVERRIDE; // This is called at the moment when the sender queue decided to pick up // a new packet from the scheduled packets. This should be then used to // continue filling the group, possibly followed by final calculating the // FEC control packet ready to send. virtual void feedSource(CPacket& r_packet) ATR_OVERRIDE; // Receiver side // This function is called at the moment when a new data packet has // arrived (no matter if subsequent or recovered). The 'state' value // defines the configured level of loss state required to send the // loss report. virtual bool receive(const CPacket& pkt, loss_seqs_t& loss_seqs) ATR_OVERRIDE; // Configuration // This is the size that is needed extra by packets operated by this corrector. // It should be subtracted from a current maximum value for SRTO_PAYLOADSIZE // The default FEC uses extra space only for FEC/CTL packet. // The timestamp clip is placed in the timestamp field in the header. // The payload contains: // - the length clip // - the flag spec // - the payload clip // The payload clip takes simply the current length of SRTO_PAYLOADSIZE. // So extra 4 bytes are needed, 2 for flags, 2 for length clip. static const size_t EXTRA_SIZE = 4; virtual SRT_ARQLevel arqLevel() ATR_OVERRIDE { return m_fallback_level; } static const char defaultConfig []; static bool verifyConfig(const SrtFilterConfig& config, std::string& w_errormsg); }; } // namespace srt #endif srt-1.4.4/srtcore/filelist.maf000066400000000000000000000016661412557703600163340ustar00rootroot00000000000000 SOURCES api.cpp buffer.cpp cache.cpp channel.cpp common.cpp core.cpp crypto.cpp epoll.cpp fec.cpp handshake.cpp list.cpp logger_default.cpp logger_defs.cpp md5.cpp packet.cpp packetfilter.cpp queue.cpp congctl.cpp socketconfig.cpp srt_c_api.cpp srt_compat.c strerror_defs.cpp sync.cpp tsbpd_time.cpp window.cpp SOURCES - ENABLE_EXPERIMENTAL_BONDING group.cpp group_backup.cpp group_common.cpp SOURCES - !ENABLE_STDCXX_SYNC sync_posix.cpp SOURCES - ENABLE_STDCXX_SYNC sync_cxx11.cpp SOURCES - EXTRA_WIN32_SHARED srt_shared.rc PUBLIC HEADERS srt.h logging_api.h access_control.h PROTECTED HEADERS platform_sys.h udt.h PRIVATE HEADERS api.h buffer.h cache.h channel.h common.h core.h crypto.h epoll.h handshake.h list.h logging.h md5.h netinet_any.h packet.h sync.h queue.h congctl.h socketconfig.h srt_compat.h threadname.h tsbpd_time.h utilities.h window.h PRIVATE HEADERS - ENABLE_EXPERIMENTAL_BONDING group.h group_backup.h group_common.h srt-1.4.4/srtcore/group.cpp000066400000000000000000005240531412557703600156740ustar00rootroot00000000000000#include "platform_sys.h" #include #include "api.h" #include "group.h" using namespace std; using namespace srt::sync; using namespace srt::groups; using namespace srt_logging; // The SRT_DEF_VERSION is defined in core.cpp. extern const int32_t SRT_DEF_VERSION; namespace srt { int32_t CUDTGroup::s_tokenGen = 0; // [[using locked(this->m_GroupLock)]]; bool CUDTGroup::getBufferTimeBase(CUDT* forthesakeof, steady_clock::time_point& w_tb, bool& w_wp, steady_clock::duration& w_dr) { CUDT* master = 0; for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { CUDT* u = &gi->ps->core(); if (gi->laststatus != SRTS_CONNECTED) { HLOGC(gmlog.Debug, log << "getBufferTimeBase: skipping @" << u->m_SocketID << ": not connected, state=" << SockStatusStr(gi->laststatus)); continue; } if (u == forthesakeof) continue; // skip the member if it's the target itself if (!u->m_pRcvBuffer) continue; // Not initialized yet master = u; break; // found } // We don't have any sockets in the group, so can't get // the buffer timebase. This should be then initialized // the usual way. if (!master) return false; master->m_pRcvBuffer->getInternalTimeBase((w_tb), (w_wp), (w_dr)); // Sanity check if (is_zero(w_tb)) { LOGC(gmlog.Error, log << "IPE: existing previously socket has no time base set yet!"); return false; // this will enforce initializing the time base normal way } return true; } // [[using locked(this->m_GroupLock)]]; bool CUDTGroup::applyGroupSequences(SRTSOCKET target, int32_t& w_snd_isn, int32_t& w_rcv_isn) { if (m_bConnected) // You are the first one, no need to change. { IF_HEAVY_LOGGING(string update_reason = "what?"); // Find a socket that is declared connected and is not // the socket that caused the call. for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { if (gi->id == target) continue; CUDT& se = gi->ps->core(); if (!se.m_bConnected) continue; // Found it. Get the following sequences: // For sending, the sequence that is about to be sent next. // For receiving, the sequence of the latest received packet. // SndCurrSeqNo is initially set to ISN-1, this next one is // the sequence that is about to be stamped on the next sent packet // over that socket. Using this field is safer because it is volatile // and its affinity is to the same thread as the sending function. // NOTE: the groupwise scheduling sequence might have been set // already. If so, it means that it was set by either: // - the call of this function on the very first conencted socket (see below) // - the call to `sendBroadcast` or `sendBackup` // In both cases, we want THIS EXACTLY value to be reported if (m_iLastSchedSeqNo != -1) { w_snd_isn = m_iLastSchedSeqNo; IF_HEAVY_LOGGING(update_reason = "GROUPWISE snd-seq"); } else { w_snd_isn = se.m_iSndNextSeqNo; // Write it back to the groupwise scheduling sequence so that // any next connected socket will take this value as well. m_iLastSchedSeqNo = w_snd_isn; IF_HEAVY_LOGGING(update_reason = "existing socket not yet sending"); } // RcvCurrSeqNo is increased by one because it happens that at the // synchronization moment it's already past reading and delivery. // This is redundancy, so the redundant socket is connected at the moment // when the other one is already transmitting, so skipping one packet // even if later transmitted is less troublesome than requesting a // "mistakenly seen as lost" packet. w_rcv_isn = CSeqNo::incseq(se.m_iRcvCurrSeqNo); HLOGC(gmlog.Debug, log << "applyGroupSequences: @" << target << " gets seq from @" << gi->id << " rcv %" << (w_rcv_isn) << " snd %" << (w_rcv_isn) << " as " << update_reason); return false; } } // If the GROUP (!) is not connected, or no running/pending socket has been found. // // That is, given socket is the first one. // The group data should be set up with its own data. They should already be passed here // in the variables. // // Override the schedule sequence of the group in this case because whatever is set now, // it's not valid. HLOGC(gmlog.Debug, log << "applyGroupSequences: no socket found connected and transmitting, @" << target << " not changing sequences, storing snd-seq %" << (w_snd_isn)); set_currentSchedSequence(w_snd_isn); return true; } // NOTE: This function is now for DEBUG PURPOSES ONLY. // Except for presenting the extracted data in the logs, there's no use of it now. void CUDTGroup::debugMasterData(SRTSOCKET slave) { // Find at least one connection, which is running. Note that this function is called // from within a handshake process, so the socket that undergoes this process is at best // currently in SRT_GST_PENDING state and it's going to be in SRT_GST_IDLE state at the // time when the connection process is done, until the first reading/writing happens. ScopedLock cg(m_GroupLock); IF_LOGGING(SRTSOCKET mpeer); IF_LOGGING(steady_clock::time_point start_time); bool found = false; for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { if (gi->sndstate == SRT_GST_RUNNING) { // Found it. Get the socket's peer's ID and this socket's // Start Time. Once it's delivered, this can be used to calculate // the Master-to-Slave start time difference. IF_LOGGING(mpeer = gi->ps->m_PeerID); IF_LOGGING(start_time = gi->ps->core().socketStartTime()); HLOGC(gmlog.Debug, log << "getMasterData: found RUNNING master @" << gi->id << " - reporting master's peer $" << mpeer << " starting at " << FormatTime(start_time)); found = true; break; } } if (!found) { // If no running one found, then take the first socket in any other // state than broken, except the slave. This is for a case when a user // has prepared one link already, but hasn't sent anything through it yet. for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { if (gi->sndstate == SRT_GST_BROKEN) continue; if (gi->id == slave) continue; // Found it. Get the socket's peer's ID and this socket's // Start Time. Once it's delivered, this can be used to calculate // the Master-to-Slave start time difference. IF_LOGGING(mpeer = gi->ps->core().m_PeerID); IF_LOGGING(start_time = gi->ps->core().socketStartTime()); HLOGC(gmlog.Debug, log << "getMasterData: found IDLE/PENDING master @" << gi->id << " - reporting master's peer $" << mpeer << " starting at " << FormatTime(start_time)); found = true; break; } } if (!found) { LOGC(cnlog.Debug, log << CONID() << "NO GROUP MASTER LINK found for group: $" << id()); } else { // The returned master_st is the master's start time. Calculate the // differene time. IF_LOGGING(steady_clock::duration master_tdiff = m_tsStartTime - start_time); LOGC(cnlog.Debug, log << CONID() << "FOUND GROUP MASTER LINK: peer=$" << mpeer << " - start time diff: " << FormatDuration(master_tdiff)); } } // GROUP CUDTGroup::SocketData* CUDTGroup::add(SocketData data) { ScopedLock g(m_GroupLock); // Change the snd/rcv state of the group member to PENDING. // Default for SocketData after creation is BROKEN, which just // after releasing the m_GroupLock could be read and interpreted // as broken connection and removed before the handshake process // is done. data.sndstate = SRT_GST_PENDING; data.rcvstate = SRT_GST_PENDING; HLOGC(gmlog.Debug, log << "CUDTGroup::add: adding new member @" << data.id); m_Group.push_back(data); gli_t end = m_Group.end(); if (m_iMaxPayloadSize == -1) { int plsize = data.ps->core().OPT_PayloadSize(); HLOGC(gmlog.Debug, log << "CUDTGroup::add: taking MAX payload size from socket @" << data.ps->m_SocketID << ": " << plsize << " " << (plsize ? "(explicit)" : "(unspecified = fallback to 1456)")); if (plsize == 0) plsize = SRT_LIVE_MAX_PLSIZE; // It is stated that the payload size // is taken from first, and every next one // will get the same. m_iMaxPayloadSize = plsize; } --end; return &*end; } CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) : m_pGlobal(&CUDT::s_UDTUnited) , m_GroupID(-1) , m_PeerGroupID(-1) , m_selfManaged(true) , m_bSyncOnMsgNo(false) , m_type(gtype) , m_listener() , m_iBusy() , m_iSndOldestMsgNo(SRT_MSGNO_NONE) , m_iSndAckedMsgNo(SRT_MSGNO_NONE) , m_uOPT_StabilityTimeout(CSrtConfig::COMM_DEF_STABILITY_TIMEOUT_US) // -1 = "undefined"; will become defined with first added socket , m_iMaxPayloadSize(-1) , m_bSynRecving(true) , m_bSynSending(true) , m_bTsbPd(true) , m_bTLPktDrop(true) , m_iTsbPdDelay_us(0) // m_*EID and m_*Epolld fields will be initialized // in the constructor body. , m_iSndTimeOut(-1) , m_iRcvTimeOut(-1) , m_tsStartTime() , m_tsRcvPeerStartTime() , m_RcvBaseSeqNo(SRT_SEQNO_NONE) , m_bOpened(false) , m_bConnected(false) , m_bClosing(false) , m_iLastSchedSeqNo(SRT_SEQNO_NONE) , m_iLastSchedMsgNo(SRT_MSGNO_NONE) { setupMutex(m_GroupLock, "Group"); setupMutex(m_RcvDataLock, "RcvData"); setupCond(m_RcvDataCond, "RcvData"); m_RcvEID = m_pGlobal->m_EPoll.create(&m_RcvEpolld); m_SndEID = m_pGlobal->m_EPoll.create(&m_SndEpolld); m_stats.init(); // Set this data immediately during creation before // two or more sockets start arguing about it. m_iLastSchedSeqNo = CUDT::generateISN(); // Configure according to type switch (gtype) { case SRT_GTYPE_BROADCAST: m_selfManaged = true; break; case SRT_GTYPE_BACKUP: m_selfManaged = true; break; case SRT_GTYPE_BALANCING: m_selfManaged = true; m_bSyncOnMsgNo = true; break; case SRT_GTYPE_MULTICAST: m_selfManaged = false; break; default: break; } } CUDTGroup::~CUDTGroup() { srt_epoll_release(m_RcvEID); srt_epoll_release(m_SndEID); releaseMutex(m_GroupLock); releaseMutex(m_RcvDataLock); releaseCond(m_RcvDataCond); } void CUDTGroup::GroupContainer::erase(CUDTGroup::gli_t it) { if (it == m_LastActiveLink) { if (m_List.empty()) { LOGC(gmlog.Error, log << "IPE: GroupContainer is empty and 'erase' is called on it."); m_LastActiveLink = m_List.end(); return; // this avoids any misunderstandings in iterator checks } gli_t bb = m_List.begin(); ++bb; if (bb == m_List.end()) // means: m_List.size() == 1 { // One element, this one being deleted, nothing to point to. m_LastActiveLink = m_List.end(); } else { // Set the link to the previous element IN THE RING. // We have the position pointer. // Reverse iterator is automatically decremented. std::reverse_iterator rt(m_LastActiveLink); if (rt == m_List.rend()) rt = m_List.rbegin(); m_LastActiveLink = rt.base(); // This operation is safe because we know that: // - the size of the container is at least 2 (0 and 1 cases are handled above) // - if m_LastActiveLink == m_List.begin(), `rt` is shifted to the opposite end. --m_LastActiveLink; } } m_List.erase(it); } void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) { HLOGC(gmlog.Debug, log << "GROUP $" << id() << " OPTION: #" << optName << " value:" << FormatBinaryString((uint8_t*)optval, optlen)); switch (optName) { case SRTO_RCVSYN: m_bSynRecving = cast_optval(optval, optlen); return; case SRTO_SNDSYN: m_bSynSending = cast_optval(optval, optlen); return; case SRTO_SNDTIMEO: m_iSndTimeOut = cast_optval(optval, optlen); break; case SRTO_RCVTIMEO: m_iRcvTimeOut = cast_optval(optval, optlen); break; case SRTO_GROUPSTABTIMEO: { const int val = cast_optval(optval, optlen); // Search if you already have SRTO_PEERIDLETIMEO set int idletmo = CSrtConfig::COMM_RESPONSE_TIMEOUT_MS; vector::iterator f = find_if(m_config.begin(), m_config.end(), ConfigItem::OfType(SRTO_PEERIDLETIMEO)); if (f != m_config.end()) { f->get(idletmo); // worst case, it will leave it unchanged. } if (val >= idletmo) { LOGC(qmlog.Error, log << "group option: SRTO_GROUPSTABTIMEO(" << val << ") exceeds SRTO_PEERIDLETIMEO(" << idletmo << ")"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } m_uOPT_StabilityTimeout = val * 1000; } break; // XXX Currently no socket groups allow any other // congestion control mode other than live. case SRTO_CONGESTION: { LOGP(gmlog.Error, "group option: SRTO_CONGESTION is only allowed as 'live' and cannot be changed"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } // Other options to be specifically interpreted by group may follow. default: break; } // All others must be simply stored for setting on a socket. // If the group is already open and any post-option is about // to be modified, it must be allowed and applied on all sockets. if (m_bOpened) { // There's at least one socket in the group, so only // post-options are allowed. if (!binary_search(srt_post_opt_list, srt_post_opt_list + SRT_SOCKOPT_NPOST, optName)) { LOGC(gmlog.Error, log << "setsockopt(group): Group is connected, this option can't be altered"); throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); } HLOGC(gmlog.Debug, log << "... SPREADING to existing sockets."); // This means that there are sockets already, so apply // this option on them. ScopedLock gg(m_GroupLock); for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { gi->ps->core().setOpt(optName, optval, optlen); } } // Store the option regardless if pre or post. This will apply m_config.push_back(ConfigItem(optName, optval, optlen)); } static bool getOptDefault(SRT_SOCKOPT optname, void* optval, int& w_optlen); // unfortunately this is required to properly handle th 'default_opt != opt' // operation in the below importOption. Not required simultaneously operator==. static bool operator!=(const struct linger& l1, const struct linger& l2) { return l1.l_onoff != l2.l_onoff || l1.l_linger != l2.l_linger; } template static void importOption(vector& storage, SRT_SOCKOPT optname, const ValueType& field) { ValueType default_opt = ValueType(); int default_opt_size = sizeof(ValueType); ValueType opt = field; if (!getOptDefault(optname, (&default_opt), (default_opt_size)) || default_opt != opt) { // Store the option when: // - no default for this option is found // - the option value retrieved from the field is different than default storage.push_back(CUDTGroup::ConfigItem(optname, &opt, default_opt_size)); } } // This function is called by the same premises as the CUDT::CUDT(const CUDT&) (copy constructor). // The intention is to rewrite the part that comprises settings from the socket // into the group. Note that some of the settings concern group, some others concern // only target socket, and there are also options that can't be set on a socket. void CUDTGroup::deriveSettings(CUDT* u) { // !!! IMPORTANT !!! // // This function shall ONLY be called on a newly created group // for the sake of the newly accepted socket from the group-enabled listener, // which is lazy-created for the first ever accepted socket. // Once the group is created, it should stay with the options // state as initialized here, and be changeable only in case when // the option is altered on the group. // SRTO_RCVSYN m_bSynRecving = u->m_config.bSynRecving; // SRTO_SNDSYN m_bSynSending = u->m_config.bSynSending; // SRTO_RCVTIMEO m_iRcvTimeOut = u->m_config.iRcvTimeOut; // SRTO_SNDTIMEO m_iSndTimeOut = u->m_config.iSndTimeOut; // Ok, this really is disgusting, but there's only one way // to properly do it. Would be nice to have some more universal // connection between an option symbolic name and the internals // in CUDT class, but until this is done, since now every new // option will have to be handled both in the CUDT::setOpt/getOpt // functions, and here as well. // This is about moving options from listener to the group, // to be potentially replicated on the socket. So both pre // and post options apply. #define IM(option, field) importOption(m_config, option, u->m_config.field) #define IMF(option, field) importOption(m_config, option, u->field) IM(SRTO_MSS, iMSS); IM(SRTO_FC, iFlightFlagSize); // Nonstandard importOption(m_config, SRTO_SNDBUF, u->m_config.iSndBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); importOption(m_config, SRTO_RCVBUF, u->m_config.iRcvBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); IM(SRTO_LINGER, Linger); IM(SRTO_UDP_SNDBUF, iUDPSndBufSize); IM(SRTO_UDP_RCVBUF, iUDPRcvBufSize); // SRTO_RENDEZVOUS: impossible to have it set on a listener socket. // SRTO_SNDTIMEO/RCVTIMEO: groupwise setting IM(SRTO_CONNTIMEO, tdConnTimeOut); IM(SRTO_DRIFTTRACER, bDriftTracer); // Reuseaddr: true by default and should only be true. IM(SRTO_MAXBW, llMaxBW); IM(SRTO_INPUTBW, llInputBW); IM(SRTO_MININPUTBW, llMinInputBW); IM(SRTO_OHEADBW, iOverheadBW); IM(SRTO_IPTOS, iIpToS); IM(SRTO_IPTTL, iIpTTL); IM(SRTO_TSBPDMODE, bTSBPD); IM(SRTO_RCVLATENCY, iRcvLatency); IM(SRTO_PEERLATENCY, iPeerLatency); IM(SRTO_SNDDROPDELAY, iSndDropDelay); IM(SRTO_PAYLOADSIZE, zExpPayloadSize); IMF(SRTO_TLPKTDROP, m_bTLPktDrop); importOption(m_config, SRTO_STREAMID, u->m_config.sStreamName.str()); IM(SRTO_MESSAGEAPI, bMessageAPI); IM(SRTO_NAKREPORT, bRcvNakReport); IM(SRTO_MINVERSION, uMinimumPeerSrtVersion); IM(SRTO_ENFORCEDENCRYPTION, bEnforcedEnc); IM(SRTO_IPV6ONLY, iIpV6Only); IM(SRTO_PEERIDLETIMEO, iPeerIdleTimeout); IM(SRTO_GROUPSTABTIMEO, uStabilityTimeout); importOption(m_config, SRTO_PACKETFILTER, u->m_config.sPacketFilterConfig.str()); importOption(m_config, SRTO_PBKEYLEN, u->m_pCryptoControl->KeyLen()); // Passphrase is empty by default. Decipher the passphrase and // store as passphrase option if (u->m_config.CryptoSecret.len) { string password((const char*)u->m_config.CryptoSecret.str, u->m_config.CryptoSecret.len); m_config.push_back(ConfigItem(SRTO_PASSPHRASE, password.c_str(), password.size())); } IM(SRTO_KMREFRESHRATE, uKmRefreshRatePkt); IM(SRTO_KMPREANNOUNCE, uKmPreAnnouncePkt); string cc = u->m_CongCtl.selected_name(); if (cc != "live") { m_config.push_back(ConfigItem(SRTO_CONGESTION, cc.c_str(), cc.size())); } // NOTE: This is based on information extracted from the "semi-copy-constructor" of CUDT class. // Here should be handled all things that are options that modify the socket, but not all options // are assigned to configurable items. #undef IM #undef IMF } bool CUDTGroup::applyFlags(uint32_t flags, HandshakeSide hsd) { bool synconmsg = IsSet(flags, SRT_GFLAG_SYNCONMSG); if (m_type == SRT_GTYPE_BALANCING) { // We support only TRUE for this flag if (!synconmsg) { HLOGP(gmlog.Debug, "GROUP: Balancing mode implemented only with sync on msgno - overridden request"); return true; // accept, but override } // We have this flag set; change it in yourself, if needed. if (hsd == HSD_INITIATOR && !m_bSyncOnMsgNo) { // With this you can change in future the default value to false. HLOGP(gmlog.Debug, "GROUP: Balancing requrested msgno-sync, OVERRIDING original setting"); m_bSyncOnMsgNo = true; return true; } } else { if (synconmsg) { LOGP(gmlog.Error, "GROUP: non-balancing type requested sync on msgno - IPE/EPE?"); return false; } } // Ignore the flag anyway. This can change in future versions though. return true; } template struct Value { static int fill(void* optval, int, Type value) { // XXX assert size >= sizeof(Type) ? *(Type*)optval = value; return sizeof(Type); } }; template <> inline int Value::fill(void* optval, int len, std::string value) { if (size_t(len) < value.size()) return 0; memcpy(optval, value.c_str(), value.size()); return (int) value.size(); } template inline int fillValue(void* optval, int len, V value) { return Value::fill(optval, len, value); } static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) { static const linger def_linger = {1, CSrtConfig::DEF_LINGER_S}; switch (optname) { default: return false; #define RD(value) \ w_optlen = fillValue((pw_optval), w_optlen, value); \ break case SRTO_KMSTATE: case SRTO_SNDKMSTATE: case SRTO_RCVKMSTATE: RD(SRT_KM_S_UNSECURED); case SRTO_PBKEYLEN: RD(16); case SRTO_MSS: RD(CSrtConfig::DEF_MSS); case SRTO_SNDSYN: RD(true); case SRTO_RCVSYN: RD(true); case SRTO_ISN: RD(SRT_SEQNO_NONE); case SRTO_FC: RD(CSrtConfig::DEF_FLIGHT_SIZE); case SRTO_SNDBUF: case SRTO_RCVBUF: w_optlen = fillValue((pw_optval), w_optlen, CSrtConfig::DEF_BUFFER_SIZE * (CSrtConfig::DEF_MSS - CPacket::UDP_HDR_SIZE)); break; case SRTO_LINGER: RD(def_linger); case SRTO_UDP_SNDBUF: case SRTO_UDP_RCVBUF: RD(CSrtConfig::DEF_UDP_BUFFER_SIZE); case SRTO_RENDEZVOUS: RD(false); case SRTO_SNDTIMEO: RD(-1); case SRTO_RCVTIMEO: RD(-1); case SRTO_REUSEADDR: RD(true); case SRTO_MAXBW: RD(int64_t(-1)); case SRTO_INPUTBW: RD(int64_t(-1)); case SRTO_OHEADBW: RD(0); case SRTO_STATE: RD(SRTS_INIT); case SRTO_EVENT: RD(0); case SRTO_SNDDATA: RD(0); case SRTO_RCVDATA: RD(0); case SRTO_IPTTL: RD(0); case SRTO_IPTOS: RD(0); case SRTO_SENDER: RD(false); case SRTO_TSBPDMODE: RD(false); case SRTO_LATENCY: case SRTO_RCVLATENCY: case SRTO_PEERLATENCY: RD(SRT_LIVE_DEF_LATENCY_MS); case SRTO_TLPKTDROP: RD(true); case SRTO_SNDDROPDELAY: RD(-1); case SRTO_NAKREPORT: RD(true); case SRTO_VERSION: RD(SRT_DEF_VERSION); case SRTO_PEERVERSION: RD(0); case SRTO_CONNTIMEO: RD(-1); case SRTO_DRIFTTRACER: RD(true); case SRTO_MINVERSION: RD(0); case SRTO_STREAMID: RD(std::string()); case SRTO_CONGESTION: RD(std::string()); case SRTO_MESSAGEAPI: RD(true); case SRTO_PAYLOADSIZE: RD(0); } #undef RD return true; } void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) { // Options handled in group switch (optname) { case SRTO_RCVSYN: *(bool*)pw_optval = m_bSynRecving; w_optlen = sizeof(bool); return; case SRTO_SNDSYN: *(bool*)pw_optval = m_bSynSending; w_optlen = sizeof(bool); return; default:; // pass on } // XXX Suspicous: may require locking of GlobControlLock // to prevent from deleting a socket in the meantime. // Deleting a socket requires removing from the group first, // so after GroupLock this will be either already NULL or // a valid socket that will only be closed after time in // the GC, so this is likely safe like all other API functions. CUDTSocket* ps = 0; { // In sockets. All sockets should have all options // set the same and should represent the group state // well enough. If there are no sockets, just use default. // Group lock to protect the container itself. // Once a socket is extracted, we state it cannot be // closed without the group send/recv function or closing // being involved. ScopedLock lg(m_GroupLock); if (m_Group.empty()) { if (!getOptDefault(optname, (pw_optval), (w_optlen))) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); return; } ps = m_Group.begin()->ps; // Release the lock on the group, as it's not necessary, // as well as it might cause a deadlock when combined // with the others. } if (!ps) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); return ps->core().getOpt(optname, (pw_optval), (w_optlen)); } struct HaveState : public unary_function, bool> { SRT_SOCKSTATUS s; HaveState(SRT_SOCKSTATUS ss) : s(ss) { } bool operator()(pair i) const { return i.second == s; } }; SRT_SOCKSTATUS CUDTGroup::getStatus() { typedef vector > states_t; states_t states; { ScopedLock cg(m_GroupLock); for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { switch (gi->sndstate) { // Check only sndstate. If this machine is ONLY receiving, // then rcvstate will turn into SRT_GST_RUNNING, while // sndstate will remain SRT_GST_IDLE, but still this may only // happen if the socket is connected. case SRT_GST_IDLE: case SRT_GST_RUNNING: states.push_back(make_pair(gi->id, SRTS_CONNECTED)); break; case SRT_GST_BROKEN: states.push_back(make_pair(gi->id, SRTS_BROKEN)); break; default: // (pending, or whatever will be added in future) { // TEMPORARY make a node to note a socket to be checked afterwards states.push_back(make_pair(gi->id, SRTS_NONEXIST)); } } } } SRT_SOCKSTATUS pending_state = SRTS_NONEXIST; for (states_t::iterator i = states.begin(); i != states.end(); ++i) { // If at least one socket is connected, the state is connected. if (i->second == SRTS_CONNECTED) return SRTS_CONNECTED; // Second level - pick up the state if (i->second == SRTS_NONEXIST) { // Otherwise find at least one socket, which's state isn't broken. i->second = m_pGlobal->getStatus(i->first); if (pending_state == SRTS_NONEXIST) pending_state = i->second; } } // Return that state as group state if (pending_state != SRTS_NONEXIST) // did call getStatus at least once and it didn't return NOEXIST return pending_state; // If none found, return SRTS_BROKEN. return SRTS_BROKEN; } // [[using locked(m_GroupLock)]]; void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) { if (side == HSD_RESPONDER) { // On the listener side you should synchronize ISN with the incoming // socket, which is done immediately after creating the socket and // adding it to the group. On the caller side the ISN is defined in // the group directly, before any member socket is created. set_currentSchedSequence(core.ISN()); } // XXX // Might need further investigation as to whether this isn't // wrong for some cases. By having this -1 here the value will be // laziliy set from the first reading one. It is believed that // it covers all possible scenarios, that is: // // - no readers - no problem! // - have some readers and a new is attached - this is set already // - connect multiple links, but none has read yet - you'll be the first. // // Previous implementation used setting to: core.m_iPeerISN resetInitialRxSequence(); // Get the latency (possibly fixed against the opposite side) // from the first socket (core.m_iTsbPdDelay_ms), // and set it on the current socket. set_latency(core.m_iTsbPdDelay_ms * int64_t(1000)); } void CUDTGroup::close() { // Close all descriptors, then delete the group. vector ids; { ScopedLock glob(CUDT::s_UDTUnited.m_GlobControlLock); ScopedLock g(m_GroupLock); // A non-managed group may only be closed if there are no // sockets in the group. // XXX Fortunately there are currently no non-self-managed // groups, so this error cannot ever happen, but this error // has the overall code suggesting that it's about the listener, // so either the name should be changed here, or a different code used. if (!m_selfManaged && !m_Group.empty()) throw CUDTException(MJ_NOTSUP, MN_BUSY, 0); m_bClosing = true; // Copy the list of IDs into the array. for (gli_t ig = m_Group.begin(); ig != m_Group.end(); ++ig) { ids.push_back(ig->id); // Immediately cut ties to this group. // Just for a case, redispatch the socket, to stay safe. CUDTSocket* s = CUDT::s_UDTUnited.locateSocket_LOCKED(ig->id); if (!s) { HLOGC(smlog.Debug, log << "group/close: IPE(NF): group member @" << ig->id << " already deleted"); continue; } s->m_GroupOf = NULL; s->m_GroupMemberData = NULL; HLOGC(smlog.Debug, log << "group/close: CUTTING OFF @" << ig->id << " (found as @" << s->m_SocketID << ") from the group"); } // After all sockets that were group members have their ties cut, // the container can be cleared. Note that sockets won't be now // removing themselves from the group when closing because they // are unaware of being group members. m_Group.clear(); m_PeerGroupID = -1; set epollid; { // Global EPOLL lock must be applied to access any socket's epoll set. // This is a set of all epoll ids subscribed to it. ScopedLock elock (CUDT::s_UDTUnited.m_EPoll.m_EPollLock); epollid = m_sPollID; // use move() in C++11 m_sPollID.clear(); } int no_events = 0; for (set::iterator i = epollid.begin(); i != epollid.end(); ++i) { HLOGC(smlog.Debug, log << "close: CLEARING subscription on E" << (*i) << " of $" << id()); try { CUDT::s_UDTUnited.m_EPoll.update_usock(*i, id(), &no_events); } catch (...) { // May catch an API exception, but this isn't an API call to be interrupted. } HLOGC(smlog.Debug, log << "close: removing E" << (*i) << " from back-subscribers of $" << id()); } // NOW, the m_GroupLock is released, then m_GlobControlLock. // The below code should work with no locks and execute socket // closing. } HLOGC(gmlog.Debug, log << "grp/close: closing $" << m_GroupID << ", closing first " << ids.size() << " sockets:"); // Close all sockets with unlocked GroupLock for (vector::iterator i = ids.begin(); i != ids.end(); ++i) { try { CUDT::s_UDTUnited.close(*i); } catch (CUDTException&) { HLOGC(gmlog.Debug, log << "grp/close: socket @" << *i << " is likely closed already, ignoring"); } } HLOGC(gmlog.Debug, log << "grp/close: closing $" << m_GroupID << ": sockets closed, clearing the group:"); // Lock the group again to clear the group data { ScopedLock g(m_GroupLock); if (!m_Group.empty()) { LOGC(gmlog.Error, log << "grp/close: IPE - after requesting to close all members, still " << m_Group.size() << " lingering members!"); m_Group.clear(); } // This takes care of the internal part. // The external part will be done in Global (CUDTUnited) } // Release blocked clients // XXX This looks like a dead code. Group receiver functions // do not use any lock on m_RcvDataLock, it is likely a remainder // of the old, internal impementation. // CSync::lock_signal(m_RcvDataCond, m_RcvDataLock); } // [[using locked(m_pGlobal->m_GlobControlLock)]] // [[using locked(m_GroupLock)]] void CUDTGroup::send_CheckValidSockets() { vector toremove; for (gli_t d = m_Group.begin(), d_next = d; d != m_Group.end(); d = d_next) { ++d_next; // it's now safe to erase d CUDTSocket* revps = m_pGlobal->locateSocket_LOCKED(d->id); if (revps != d->ps) { // Note: the socket might STILL EXIST, just in the trash, so // it can't be found by locateSocket. But it can still be bound // to the group. Just mark it broken from upside so that the // internal sending procedures will skip it. Removal from the // group will happen in GC, which will both remove from // group container and cut backward links to the group. HLOGC(gmlog.Debug, log << "group/send_CheckValidSockets: socket @" << d->id << " is no longer valid, setting BROKEN in $" << id()); d->sndstate = SRT_GST_BROKEN; d->rcvstate = SRT_GST_BROKEN; } } } int CUDTGroup::send(const char* buf, int len, SRT_MSGCTRL& w_mc) { switch (m_type) { default: LOGC(gslog.Error, log << "CUDTGroup::send: not implemented for type #" << m_type); throw CUDTException(MJ_SETUP, MN_INVAL, 0); case SRT_GTYPE_BROADCAST: return sendBroadcast(buf, len, (w_mc)); case SRT_GTYPE_BACKUP: return sendBackup(buf, len, (w_mc)); /* to be implemented case SRT_GTYPE_BALANCING: return sendBalancing(buf, len, (w_mc)); case SRT_GTYPE_MULTICAST: return sendMulticast(buf, len, (w_mc)); */ } } int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) { // Avoid stupid errors in the beginning. if (len <= 0) { throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } // NOTE: This is a "vector of list iterators". Every element here // is an iterator to another container. // Note that "list" is THE ONLY container in standard C++ library, // for which NO ITERATORS ARE INVALIDATED after a node at particular // iterator has been removed, except for that iterator itself. vector wipeme; vector idleLinks; vector pendingSockets; // need sock ids as it will be checked out of lock int32_t curseq = SRT_SEQNO_NONE; int rstat = -1; int stat = 0; SRT_ATR_UNUSED CUDTException cx(MJ_SUCCESS, MN_NONE, 0); vector activeLinks; // First, acquire GlobControlLock to make sure all member sockets still exist enterCS(m_pGlobal->m_GlobControlLock); ScopedLock guard(m_GroupLock); if (m_bClosing) { leaveCS(m_pGlobal->m_GlobControlLock); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // Now, still under lock, check if all sockets still can be dispatched // LOCKED: GlobControlLock, GroupLock (RIGHT ORDER!) send_CheckValidSockets(); leaveCS(m_pGlobal->m_GlobControlLock); // LOCKED: GroupLock (only) // Since this moment GlobControlLock may only be locked if GroupLock is unlocked first. if (m_bClosing) { // No temporary locks here. The group lock is scoped. throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // This simply requires the payload to be sent through every socket in the group for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) { if (d->sndstate != SRT_GST_BROKEN) { // Check the socket state prematurely in order not to uselessly // send over a socket that is broken. CUDT* const pu = (d->ps) ? &d->ps->core() : NULL; if (!pu || pu->m_bBroken) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket @" << d->id << " detected +Broken - transit to BROKEN"); d->sndstate = SRT_GST_BROKEN; d->rcvstate = SRT_GST_BROKEN; } } // Check socket sndstate before sending if (d->sndstate == SRT_GST_BROKEN) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket in BROKEN state: @" << d->id << ", sockstatus=" << SockStatusStr(d->ps ? d->ps->getStatus() : SRTS_NONEXIST)); wipeme.push_back(d->id); continue; } if (d->sndstate == SRT_GST_IDLE) { SRT_SOCKSTATUS st = SRTS_NONEXIST; if (d->ps) st = d->ps->getStatus(); // If the socket is already broken, move it to broken. if (int(st) >= int(SRTS_BROKEN)) { HLOGC(gslog.Debug, log << "CUDTGroup::send.$" << id() << ": @" << d->id << " became " << SockStatusStr(st) << ", WILL BE CLOSED."); wipeme.push_back(d->id); continue; } if (st != SRTS_CONNECTED) { HLOGC(gslog.Debug, log << "CUDTGroup::send. @" << d->id << " is still " << SockStatusStr(st) << ", skipping."); pendingSockets.push_back(d->id); continue; } HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket in IDLE state: @" << d->id << " - will activate it"); // This is idle, we'll take care of them next time // Might be that: // - this socket is idle, while some NEXT socket is running // - we need at least one running socket to work BEFORE activating the idle one. // - if ALL SOCKETS ARE IDLE, then we simply activate the first from the list, // and all others will be activated using the ISN from the first one. idleLinks.push_back(d); continue; } if (d->sndstate == SRT_GST_RUNNING) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket in RUNNING state: @" << d->id << " - will send a payload"); activeLinks.push_back(d); continue; } HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket @" << d->id << " not ready, state: " << StateStr(d->sndstate) << "(" << int(d->sndstate) << ") - NOT sending, SET AS PENDING"); pendingSockets.push_back(d->id); } vector sendstates; if (w_mc.srctime == 0) w_mc.srctime = count_microseconds(steady_clock::now().time_since_epoch()); for (vector::iterator snd = activeLinks.begin(); snd != activeLinks.end(); ++snd) { gli_t d = *snd; int erc = 0; // success // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. try { // This must be wrapped in try-catch because on error it throws an exception. // Possible return values are only 0, in case when len was passed 0, or a positive // >0 value that defines the size of the data that it has sent, that is, in case // of Live mode, equal to 'len'. stat = d->ps->core().sendmsg2(buf, len, (w_mc)); } catch (CUDTException& e) { cx = e; stat = -1; erc = e.getErrorCode(); } if (stat != -1) { curseq = w_mc.pktseq; } const Sendstate cstate = {d->id, &*d, stat, erc}; sendstates.push_back(cstate); d->sndresult = stat; d->laststatus = d->ps->getStatus(); } // Ok, we have attempted to send a payload over all links // that are currently in the RUNNING state. We know that at // least one is successful if we have non-default curseq value. // Here we need to activate all links that are found as IDLE. // Some portion of logical exclusions: // // - sockets that were broken in the beginning are already wiped out // - broken sockets are checked first, so they can't be simultaneously idle // - idle sockets can't get broken because there's no operation done on them // - running sockets are the only one that could change sndstate here // - running sockets can either remain running or turn to broken // In short: Running and Broken sockets can't become idle, // although Running sockets can become Broken. // There's no certainty here as to whether at least one link was // running and it has successfully performed the operation. // Might have even happened that we had 2 running links that // got broken and 3 other links so far in idle sndstate that just connected // at that very moment. In this case we have 3 idle links to activate, // but there is no sequence base to overwrite their ISN with. If this // happens, then the first link that should be activated goes with // whatever ISN it has, whereas every next idle link should use that // exactly ISN. // // If it has additionally happened that the first link got broken at // that very moment of sending, the second one has a chance to succeed // and therefore take over the leading role in setting the ISN. If the // second one fails, too, then the only remaining idle link will simply // go with its own original sequence. // // On the opposite side the reader should know that the link is inactive // so the first received payload activates it. Activation of an idle link // means that the very first packet arriving is TAKEN AS A GOOD DEAL, that is, // no LOSSREPORT is sent even if the sequence looks like a "jumped over". // Only for activated links is the LOSSREPORT sent upon seqhole detection. // Now we can go to the idle links and attempt to send the payload // also over them. // TODO: { sendBroadcast_ActivateIdleLinks for (vector::iterator i = idleLinks.begin(); i != idleLinks.end(); ++i) { gli_t d = *i; if (!d->ps->m_GroupOf) continue; int erc = 0; int lastseq = d->ps->core().schedSeqNo(); if (curseq != SRT_SEQNO_NONE && curseq != lastseq) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket @" << d->id << ": override snd sequence %" << lastseq << " with %" << curseq << " (diff by " << CSeqNo::seqcmp(curseq, lastseq) << "); SENDING PAYLOAD: " << BufferStamp(buf, len)); d->ps->core().overrideSndSeqNo(curseq); } else { HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket @" << d->id << ": sequence remains with original value: %" << lastseq << "; SENDING PAYLOAD " << BufferStamp(buf, len)); } // Now send and check the status // The link could have got broken try { stat = d->ps->core().sendmsg2(buf, len, (w_mc)); } catch (CUDTException& e) { cx = e; stat = -1; erc = e.getErrorCode(); } if (stat != -1) { d->sndstate = SRT_GST_RUNNING; // Note: this will override the sequence number // for all next iterations in this loop. curseq = w_mc.pktseq; HLOGC(gslog.Debug, log << "@" << d->id << ":... sending SUCCESSFUL %" << curseq << " MEMBER STATUS: RUNNING"); } d->sndresult = stat; d->laststatus = d->ps->getStatus(); const Sendstate cstate = {d->id, &*d, stat, erc}; sendstates.push_back(cstate); } if (curseq != SRT_SEQNO_NONE) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: updating current scheduling sequence %" << curseq); m_iLastSchedSeqNo = curseq; } // } // { send_CheckBrokenSockets() if (!pendingSockets.empty()) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: found pending sockets, polling them."); // These sockets if they are in pending state, they should be added to m_SndEID // at the connecting stage. CEPoll::fmap_t sready; if (m_pGlobal->m_EPoll.empty(*m_SndEpolld)) { // Sanity check - weird pending reported. LOGC(gslog.Error, log << "grp/sendBroadcast: IPE: reported pending sockets, but EID is empty - wiping pending!"); copy(pendingSockets.begin(), pendingSockets.end(), back_inserter(wipeme)); } else { { InvertedLock ug(m_GroupLock); THREAD_PAUSED(); m_pGlobal->m_EPoll.swait( *m_SndEpolld, sready, 0, false /*report by retval*/); // Just check if anything happened THREAD_RESUMED(); } if (m_bClosing) { // No temporary locks here. The group lock is scoped. throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } HLOGC(gslog.Debug, log << "grp/sendBroadcast: RDY: " << DisplayEpollResults(sready)); // sockets in EX: should be moved to wipeme. for (vector::iterator i = pendingSockets.begin(); i != pendingSockets.end(); ++i) { if (CEPoll::isready(sready, *i, SRT_EPOLL_ERR)) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: Socket @" << (*i) << " reported FAILURE - moved to wiped."); // Failed socket. Move d to wipeme. Remove from eid. wipeme.push_back(*i); int no_events = 0; m_pGlobal->m_EPoll.update_usock(m_SndEID, *i, &no_events); } } // After that, all sockets that have been reported // as ready to write should be removed from EID. This // will also remove those sockets that have been added // as redundant links at the connecting stage and became // writable (connected) before this function had a chance // to check them. m_pGlobal->m_EPoll.clear_ready_usocks(*m_SndEpolld, SRT_EPOLL_CONNECT); } } // Re-check after the waiting lock has been reacquired if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); send_CloseBrokenSockets(wipeme); // Re-check after the waiting lock has been reacquired if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); // } // { sendBroadcast_CheckBlockedLinks() // Alright, we've made an attempt to send a packet over every link. // Every operation was done through a non-blocking attempt, so // links where sending was blocked have SRT_EASYNCSND error. // Links that were successful, have the len value in state. // First thing then, find out if at least one link was successful. // The first successful link sets the sequence value, // the following links derive it. This might be also the first idle // link with its random-generated ISN, if there were no active links. vector successful, blocked; // This iteration of the state will simply // qualify the remaining sockets into three categories: // // - successful (we only need to know if at least one did) // - blocked - if none succeeded, but some blocked, POLL & RETRY. // - wipeme - sending failed by any other reason than blocking, remove. // Now - sendstates contain directly sockets. // In order to update members, you need to have locked: // - GlobControlLock to prevent sockets from disappearing or being closed // - then GroupLock to latch the validity of m_GroupMemberData field. { { InvertedLock ung (m_GroupLock); enterCS(CUDT::s_UDTUnited.m_GlobControlLock); HLOGC(gslog.Debug, log << "grp/sendBroadcast: Locked GlobControlLock, locking back GroupLock"); } // Under this condition, as an unlock-lock cycle was done on m_GroupLock, // the Sendstate::it field shall not be used here! for (vector::iterator is = sendstates.begin(); is != sendstates.end(); ++is) { CUDTSocket* ps = CUDT::s_UDTUnited.locateSocket_LOCKED(is->id); // Is the socket valid? If not, simply SKIP IT. Nothing to be done with it, // it's already deleted. if (!ps) continue; // Is the socket still group member? If not, SKIP IT. It could only be taken ownership // by being explicitly closed and so it's deleted from the container. if (!ps->m_GroupOf) continue; // Now we are certain that m_GroupMemberData is valid. SocketData* d = ps->m_GroupMemberData; if (is->stat == len) { HLOGC(gslog.Debug, log << "SEND STATE link [" << (is - sendstates.begin()) << "]: SUCCESSFULLY sent " << len << " bytes"); // Successful. successful.push_back(d); rstat = is->stat; continue; } // Remaining are only failed. Check if again. if (is->code == SRT_EASYNCSND) { blocked.push_back(d); continue; } #if ENABLE_HEAVY_LOGGING string errmsg = cx.getErrorString(); LOGC(gslog.Debug, log << "SEND STATE link [" << (is - sendstates.begin()) << "]: FAILURE (result:" << is->stat << "): " << errmsg << ". Setting this socket broken status."); #endif // Turn this link broken d->sndstate = SRT_GST_BROKEN; } // Now you can leave GlobControlLock, while GroupLock is still locked. leaveCS(CUDT::s_UDTUnited.m_GlobControlLock); } // Re-check after the waiting lock has been reacquired if (m_bClosing) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: GROUP CLOSED, ABANDONING"); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // Good, now let's realize the situation. // First, check the most optimistic scenario: at least one link succeeded. bool was_blocked = false; bool none_succeeded = false; if (!successful.empty()) { // Good. All blocked links are now qualified as broken. // You had your chance, but I can't leave you here, // there will be no further chance to reattempt sending. for (vector::iterator b = blocked.begin(); b != blocked.end(); ++b) { (*b)->sndstate = SRT_GST_BROKEN; } blocked.clear(); } else { none_succeeded = true; was_blocked = !blocked.empty(); } int ercode = 0; if (was_blocked) { m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); if (!m_bSynSending) { throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); } HLOGC(gslog.Debug, log << "grp/sendBroadcast: all blocked, trying to common-block on epoll..."); // XXX TO BE REMOVED. Sockets should be subscribed in m_SndEID at connecting time // (both srt_connect and srt_accept). // None was successful, but some were blocked. It means that we // haven't sent the payload over any link so far, so we still have // a chance to retry. int modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; for (vector::iterator b = blocked.begin(); b != blocked.end(); ++b) { HLOGC(gslog.Debug, log << "Will block on blocked socket @" << (*b)->id << " as only blocked socket remained"); CUDT::s_UDTUnited.epoll_add_usock_INTERNAL(m_SndEID, (*b)->ps, &modes); } const int blocklen = blocked.size(); int blst = 0; CEPoll::fmap_t sready; { // Lift the group lock for a while, to avoid possible deadlocks. InvertedLock ug(m_GroupLock); HLOGC(gslog.Debug, log << "grp/sendBroadcast: blocking on any of blocked sockets to allow sending"); // m_iSndTimeOut is -1 by default, which matches the meaning of waiting forever THREAD_PAUSED(); blst = m_pGlobal->m_EPoll.swait(*m_SndEpolld, sready, m_iSndTimeOut); THREAD_RESUMED(); // NOTE EXCEPTIONS: // - EEMPTY: won't happen, we have explicitly added sockets to EID here. // - XTIMEOUT: will be propagated as this what should be reported to API // This is the only reason why here the errors are allowed to be handled // by exceptions. } // Re-check after the waiting lock has been reacquired if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); if (blst == -1) { int rno; ercode = srt_getlasterror(&rno); } else { activeLinks.clear(); sendstates.clear(); // Extract gli's from the whole group that have id found in the array. // LOCKING INFO: // For the moment of lifting m_GroupLock, some sockets could have been closed. // But then, we believe they have been also removed from the group container, // and this requires locking on GroupLock. We can then stafely state that the // group container contains only existing sockets, at worst broken. for (gli_t dd = m_Group.begin(); dd != m_Group.end(); ++dd) { int rdev = CEPoll::ready(sready, dd->id); if (rdev & SRT_EPOLL_ERR) { dd->sndstate = SRT_GST_BROKEN; } else if (rdev & SRT_EPOLL_OUT) activeLinks.push_back(dd); } for (vector::iterator snd = activeLinks.begin(); snd != activeLinks.end(); ++snd) { gli_t d = *snd; int erc = 0; // success // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. try { // This must be wrapped in try-catch because on error it throws an exception. // Possible return values are only 0, in case when len was passed 0, or a positive // >0 value that defines the size of the data that it has sent, that is, in case // of Live mode, equal to 'blocklen'. stat = d->ps->core().sendmsg2(buf, blocklen, (w_mc)); } catch (CUDTException& e) { cx = e; stat = -1; erc = e.getErrorCode(); } if (stat != -1) curseq = w_mc.pktseq; const Sendstate cstate = {d->id, &*d, stat, erc}; sendstates.push_back(cstate); d->sndresult = stat; d->laststatus = d->ps->getStatus(); } // This time only check if any were successful. // All others are wipeme. // NOTE: m_GroupLock is continuously locked - you can safely use Sendstate::it field. for (vector::iterator is = sendstates.begin(); is != sendstates.end(); ++is) { if (is->stat == blocklen) { // Successful. successful.push_back(is->mb); rstat = is->stat; was_blocked = false; none_succeeded = false; continue; } #if ENABLE_HEAVY_LOGGING string errmsg = cx.getErrorString(); HLOGC(gslog.Debug, log << "... (repeat-waited) sending FAILED (" << errmsg << "). Setting this socket broken status."); #endif // Turn this link broken is->mb->sndstate = SRT_GST_BROKEN; } } } // } if (none_succeeded) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: all links broken (none succeeded to send a payload)"); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); // Reparse error code, if set. // It might be set, if the last operation was failed. // If any operation succeeded, this will not be executed anyway. CodeMajor major = CodeMajor(ercode ? ercode / 1000 : MJ_CONNECTION); CodeMinor minor = CodeMinor(ercode ? ercode % 1000 : MN_CONNLOST); throw CUDTException(major, minor, 0); } // Now that at least one link has succeeded, update sending stats. m_stats.sent.Update(len); // Pity that the blocking mode only determines as to whether this function should // block or not, but the epoll flags must be updated regardless of the mode. // Now fill in the socket table. Check if the size is enough, if not, // then set the pointer to NULL and set the correct size. // Note that list::size() is linear time, however this shouldn't matter, // as with the increased number of links in the redundancy group the // impossibility of using that many of them grows exponentally. size_t grpsize = m_Group.size(); if (w_mc.grpdata_size < grpsize) { w_mc.grpdata = NULL; } size_t i = 0; bool ready_again = false; for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d, ++i) { if (w_mc.grpdata) { // Enough space to fill copyGroupData(*d, (w_mc.grpdata[i])); } // We perform this loop anyway because we still need to check if any // socket is writable. Note that the group lock will hold any write ready // updates that are performed just after a single socket update for the // group, so if any socket is actually ready at the moment when this // is performed, and this one will result in none-write-ready, this will // be fixed just after returning from this function. ready_again = ready_again || d->ps->writeReady(); } w_mc.grpdata_size = i; if (!ready_again) { m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); } return rstat; } int CUDTGroup::getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize) { if (!psize) return CUDT::APIError(MJ_NOTSUP, MN_INVAL); ScopedLock gl(m_GroupLock); return getGroupData_LOCKED(pdata, psize); } // [[using locked(this->m_GroupLock)]] int CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) { SRT_ASSERT(psize != NULL); const size_t size = *psize; // Rewrite correct size *psize = m_Group.size(); if (!pdata) { return 0; } if (m_Group.size() > size) { // Not enough space to retrieve the data. return CUDT::APIError(MJ_NOTSUP, MN_XSIZE); } size_t i = 0; for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d, ++i) { copyGroupData(*d, (pdata[i])); } return m_Group.size(); } // [[using locked(this->m_GroupLock)]] void CUDTGroup::copyGroupData(const CUDTGroup::SocketData& source, SRT_SOCKGROUPDATA& w_target) { w_target.id = source.id; memcpy((&w_target.peeraddr), &source.peer, source.peer.size()); w_target.sockstate = source.laststatus; w_target.token = source.token; // In the internal structure the member state // is one per direction. From the user perspective // however it is used either in one direction only, // in which case the one direction that is active // matters, or in both directions, in which case // it will be always either both active or both idle. if (source.sndstate == SRT_GST_RUNNING || source.rcvstate == SRT_GST_RUNNING) { w_target.result = 0; w_target.memberstate = SRT_GST_RUNNING; } // Stats can differ per direction only // when at least in one direction it's ACTIVE. else if (source.sndstate == SRT_GST_BROKEN || source.rcvstate == SRT_GST_BROKEN) { w_target.result = -1; w_target.memberstate = SRT_GST_BROKEN; } else { // IDLE or PENDING w_target.result = 0; w_target.memberstate = source.sndstate; } w_target.weight = source.weight; } void CUDTGroup::getGroupCount(size_t& w_size, bool& w_still_alive) { ScopedLock gg(m_GroupLock); // Note: linear time, but no way to avoid it. // Fortunately the size of the redundancy group is even // in the craziest possible implementation at worst 4 members long. size_t group_list_size = 0; // In managed group, if all sockets made a failure, all // were removed, so the loop won't even run once. In // non-managed, simply no socket found here would have a // connected status. bool still_alive = false; for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { if (gi->laststatus == SRTS_CONNECTED) { still_alive = true; } ++group_list_size; } // If no socket is found connected, don't update any status. w_size = group_list_size; w_still_alive = still_alive; } // [[using locked(m_GroupLock)]] void CUDTGroup::fillGroupData(SRT_MSGCTRL& w_out, // MSGCTRL to be written const SRT_MSGCTRL& in // MSGCTRL read from the data-providing socket ) { // Preserve the data that will be overwritten by assignment SRT_SOCKGROUPDATA* grpdata = w_out.grpdata; size_t grpdata_size = w_out.grpdata_size; w_out = in; // NOTE: This will write NULL to grpdata and 0 to grpdata_size! w_out.grpdata = NULL; // Make sure it's done, for any case w_out.grpdata_size = 0; // User did not wish to read the group data at all. if (!grpdata) { return; } int st = getGroupData_LOCKED((grpdata), (&grpdata_size)); // Always write back the size, no matter if the data were filled. w_out.grpdata_size = grpdata_size; if (st == SRT_ERROR) { // Keep NULL in grpdata return; } // Write back original data w_out.grpdata = grpdata; } // [[using locked(CUDT::s_UDTUnited.m_GlobControLock)]] // [[using locked(m_GroupLock)]] struct FLookupSocketWithEvent_LOCKED { CUDTUnited* glob; int evtype; FLookupSocketWithEvent_LOCKED(CUDTUnited* g, int event_type) : glob(g) , evtype(event_type) { } typedef CUDTSocket* result_type; pair operator()(const pair& es) { CUDTSocket* so = NULL; if ((es.second & evtype) == 0) return make_pair(so, false); so = glob->locateSocket_LOCKED(es.first); return make_pair(so, !!so); } }; void CUDTGroup::recv_CollectAliveAndBroken(vector& alive, set& broken) { #if ENABLE_HEAVY_LOGGING std::ostringstream ds; ds << "E(" << m_RcvEID << ") "; #define HCLOG(expr) expr #else #define HCLOG(x) if (false) {} #endif alive.reserve(m_Group.size()); HLOGC(grlog.Debug, log << "group/recv: Reviewing member sockets for polling"); for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { if (gi->laststatus == SRTS_CONNECTING) { HCLOG(ds << "@" << gi->id << " "); continue; // don't read over a failed or pending socket } if (gi->laststatus >= SRTS_BROKEN) { broken.insert(gi->ps); } if (broken.count(gi->ps)) { HCLOG(ds << "@" << gi->id << " "); continue; } if (gi->laststatus != SRTS_CONNECTED) { HCLOG(ds << "@" << gi->id << "laststatus) << "> "); // Sockets in this state are ignored. We are waiting until it // achieves CONNECTING state, then it's added to write. // Or gets broken and closed in the next step. continue; } // Don't skip packets that are ahead because if we have a situation // that all links are either "elephants" (do not report read readiness) // and "kangaroos" (have already delivered an ahead packet) then // omiting kangaroos will result in only elephants to be polled for // reading. Due to the strict timing requirements and ensurance that // TSBPD on every link will result in exactly the same delivery time // for a packet of given sequence, having an elephant and kangaroo in // one cage means that the elephant is simply a broken or half-broken // link (the data are not delivered, but it will get repaired soon, // enough for SRT to maintain the connection, but it will still drop // packets that didn't arrive in time), in both cases it may // potentially block the reading for an indefinite time, while // simultaneously a kangaroo might be a link that got some packets // dropped, but then it's still capable to deliver packets on time. // Note that gi->id might be a socket that was previously being polled // on write, when it's attempting to connect, but now it's connected. // This will update the socket with the new event set. alive.push_back(gi->ps); HCLOG(ds << "@" << gi->id << "[READ] "); } HLOGC(grlog.Debug, log << "group/recv: " << ds.str() << " --> EPOLL/SWAIT"); #undef HCLOG } vector CUDTGroup::recv_WaitForReadReady(const vector& aliveMembers, set& w_broken) { if (aliveMembers.empty()) { LOGC(grlog.Error, log << "group/recv: all links broken"); throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); } for (vector::const_iterator i = aliveMembers.begin(); i != aliveMembers.end(); ++i) { // NOT using the official srt_epoll_add_usock because this will do socket dispatching, // which requires lock on m_GlobControlLock, while this lock cannot be applied without // first unlocking m_GroupLock. const int read_modes = SRT_EPOLL_IN | SRT_EPOLL_ERR; CUDT::s_UDTUnited.epoll_add_usock_INTERNAL(m_RcvEID, *i, &read_modes); } // Here we need to make an additional check. // There might be a possibility that all sockets that // were added to the reader group, are ahead. At least // surely we don't have a situation that any link contains // an ahead-read subsequent packet, because GroupCheckPacketAhead // already handled that case. // // What we can have is that every link has: // - no known seq position yet (is not registered in the position map yet) // - the position equal to the latest delivered sequence // - the ahead position // Now the situation is that we don't have any packets // waiting for delivery so we need to wait for any to report one. // The non-blocking mode would need to simply check the readiness // with only immediate report, and read-readiness would have to // be done in background. // In blocking mode, use m_iRcvTimeOut, which's default value -1 // means to block indefinitely, also in swait(). // In non-blocking mode use 0, which means to always return immediately. int timeout = m_bSynRecving ? m_iRcvTimeOut : 0; int nready = 0; // Poll on this descriptor until reading is available, indefinitely. CEPoll::fmap_t sready; // GlobControlLock is required for dispatching the sockets. // Therefore it must be applied only when GroupLock is off. { // This call may wait indefinite time, so GroupLock must be unlocked. InvertedLock ung (m_GroupLock); THREAD_PAUSED(); nready = m_pGlobal->m_EPoll.swait(*m_RcvEpolld, sready, timeout, false /*report by retval*/); THREAD_RESUMED(); // HERE GlobControlLock is locked first, then GroupLock is applied back enterCS(CUDT::s_UDTUnited.m_GlobControlLock); } // BOTH m_GlobControlLock AND m_GroupLock are locked here. HLOGC(grlog.Debug, log << "group/recv: " << nready << " RDY: " << DisplayEpollResults(sready)); if (nready == 0) { // GlobControlLock is applied manually, so unlock manually. // GroupLock will be unlocked as per scope. leaveCS(CUDT::s_UDTUnited.m_GlobControlLock); // This can only happen when 0 is passed as timeout and none is ready. // And 0 is passed only in non-blocking mode. So this is none ready in // non-blocking mode. throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } // Handle sockets of pending connection and with errors. // Nice to have something like: // broken = FilterIf(sready, [] (auto s) // { return s.second == SRT_EPOLL_ERR && (auto cs = g->locateSocket(s.first, ERH_RETURN)) // ? {cs, true} // : {nullptr, false} // }); FilterIf( /*FROM*/ sready.begin(), sready.end(), /*TO*/ std::inserter(w_broken, w_broken.begin()), /*VIA*/ FLookupSocketWithEvent_LOCKED(m_pGlobal, SRT_EPOLL_ERR)); // If this set is empty, it won't roll even once, therefore output // will be surely empty. This will be checked then same way as when // reading from every socket resulted in error. vector readReady; readReady.reserve(aliveMembers.size()); for (vector::const_iterator sockiter = aliveMembers.begin(); sockiter != aliveMembers.end(); ++sockiter) { CUDTSocket* sock = *sockiter; const CEPoll::fmap_t::const_iterator ready_iter = sready.find(sock->m_SocketID); if (ready_iter != sready.end()) { if (ready_iter->second & SRT_EPOLL_ERR) continue; // broken already if ((ready_iter->second & SRT_EPOLL_IN) == 0) continue; // not ready for reading readReady.push_back(*sockiter); } else { // No read-readiness reported by epoll, but probably missed or not yet handled // as the receiver buffer is read-ready. ScopedLock lg(sock->core().m_RcvBufferLock); if (sock->core().m_pRcvBuffer && sock->core().m_pRcvBuffer->isRcvDataReady()) readReady.push_back(sock); } } leaveCS(CUDT::s_UDTUnited.m_GlobControlLock); return readReady; } void CUDTGroup::updateReadState(SRTSOCKET /* not sure if needed */, int32_t sequence) { bool ready = false; ScopedLock lg(m_GroupLock); int seqdiff = 0; if (m_RcvBaseSeqNo == SRT_SEQNO_NONE) { // One socket reported readiness, while no reading operation // has ever been done. Whatever the sequence number is, it will // be taken as a good deal and reading will be accepted. ready = true; } else if ((seqdiff = CSeqNo::seqcmp(sequence, m_RcvBaseSeqNo)) > 0) { // Case diff == 1: The very next. Surely read-ready. // Case diff > 1: // We have an ahead packet. There's one strict condition in which // we may believe it needs to be delivered - when KANGAROO->HORSE // transition is allowed. Stating that the time calculation is done // exactly the same way on every link in the redundancy group, when // it came to a situation that a packet from one link is ready for // extraction while it has jumped over some packet, it has surely // happened due to TLPKTDROP, and if it happened on at least one link, // we surely don't have this packet ready on any other link. // This might prove not exactly true, especially when at the moment // when this happens another link may surprisinly receive this lacking // packet, so the situation gets suddenly repaired after this function // is called, the only result of it would be that it will really get // the very next sequence, even though this function doesn't know it // yet, but surely in both cases the situation is the same: the medium // is ready for reading, no matter what packet will turn out to be // returned when reading is done. ready = true; } // When the sequence number is behind the current one, // stating that the readines wasn't checked otherwise, the reading // function will not retrieve anything ready to read just by this premise. // Even though this packet would have to be eventually extracted (and discarded). if (ready) { m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, true); } } int32_t CUDTGroup::getRcvBaseSeqNo() { ScopedLock lg(m_GroupLock); return m_RcvBaseSeqNo; } void CUDTGroup::updateWriteState() { ScopedLock lg(m_GroupLock); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, true); } /// Validate iPktSeqno is in range /// (iBaseSeqno - m_iSeqNoTH/2; iBaseSeqno + m_iSeqNoTH). /// /// EXPECT_EQ(isValidSeqno(125, 124), true); // behind /// EXPECT_EQ(isValidSeqno(125, 125), true); // behind /// EXPECT_EQ(isValidSeqno(125, 126), true); // the next in order /// /// EXPECT_EQ(isValidSeqno(0, 0x3FFFFFFF - 2), true); // ahead, but ok. /// EXPECT_EQ(isValidSeqno(0, 0x3FFFFFFF - 1), false); // too far ahead. /// EXPECT_EQ(isValidSeqno(0x3FFFFFFF + 2, 0x7FFFFFFF), false); // too far ahead. /// EXPECT_EQ(isValidSeqno(0x3FFFFFFF + 3, 0x7FFFFFFF), true); // ahead, but ok. /// EXPECT_EQ(isValidSeqno(0x3FFFFFFF, 0x1FFFFFFF + 2), false); // too far (behind) /// EXPECT_EQ(isValidSeqno(0x3FFFFFFF, 0x1FFFFFFF + 3), true); // behind, but ok /// EXPECT_EQ(isValidSeqno(0x70000000, 0x0FFFFFFF), true); // ahead, but ok /// EXPECT_EQ(isValidSeqno(0x70000000, 0x30000000 - 2), false); // too far ahead. /// EXPECT_EQ(isValidSeqno(0x70000000, 0x30000000 - 3), true); // ahead, but ok /// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0), true); /// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x7FFFFFFF), true); /// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000000), false); /// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000001), false); /// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000002), true); // behind by 536870910 /// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000003), true); /// /// @return false if @a iPktSeqno is not inside the valid range; otherwise true. static bool isValidSeqno(int32_t iBaseSeqno, int32_t iPktSeqno) { const int32_t iLenAhead = CSeqNo::seqlen(iBaseSeqno, iPktSeqno); if (iLenAhead >= 0 && iLenAhead < CSeqNo::m_iSeqNoTH) return true; const int32_t iLenBehind = CSeqNo::seqlen(iPktSeqno, iBaseSeqno); if (iLenBehind >= 0 && iLenBehind < CSeqNo::m_iSeqNoTH / 2) return true; return false; } // The "app reader" version of the reading function. // This reads the packets from every socket treating them as independent // and prepared to work with the application. Then packets are sorted out // by getting the sequence number. int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) { typedef map::iterator pit_t; // Later iteration over it might be less efficient than // by vector, but we'll also often try to check a single id // if it was ever seen broken, so that it's skipped. set broken; size_t output_size = 0; // First, acquire GlobControlLock to make sure all member sockets still exist enterCS(m_pGlobal->m_GlobControlLock); ScopedLock guard(m_GroupLock); if (m_bClosing) { // The group could be set closing in the meantime, but if // this is only about to be set by another thread, this thread // must fist wait for being able to acquire this lock. // The group will not be deleted now because it is added usage counter // by this call, but will be released once it exits. leaveCS(m_pGlobal->m_GlobControlLock); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // Now, still under lock, check if all sockets still can be dispatched send_CheckValidSockets(); leaveCS(m_pGlobal->m_GlobControlLock); if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); for (;;) { if (!m_bOpened || !m_bConnected) { LOGC(grlog.Error, log << boolalpha << "group/recv: ERROR opened=" << m_bOpened << " connected=" << m_bConnected); throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); } // Check first the ahead packets if you have any to deliver. if (m_RcvBaseSeqNo != SRT_SEQNO_NONE && !m_Positions.empty()) { // This function also updates the group sequence pointer. ReadPos* pos = checkPacketAhead(); if (pos) { if (size_t(len) < pos->packet.size()) throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); HLOGC(grlog.Debug, log << "group/recv: delivering AHEAD packet %" << pos->mctrl.pktseq << " #" << pos->mctrl.msgno << ": " << BufferStamp(&pos->packet[0], pos->packet.size())); memcpy(buf, &pos->packet[0], pos->packet.size()); fillGroupData((w_mc), pos->mctrl); m_RcvBaseSeqNo = pos->mctrl.pktseq; len = pos->packet.size(); pos->packet.clear(); // Update stats as per delivery m_stats.recv.Update(len); updateAvgPayloadSize(len); // We predict to have only one packet ahead, others are pending to be reported by tsbpd. // This will be "re-enabled" if the later check puts any new packet into ahead. m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); return len; } } // LINK QUALIFICATION NAMES: // // HORSE: Correct link, which delivers the very next sequence. // Not necessarily this link is currently active. // // KANGAROO: Got some packets dropped and the sequence number // of the packet jumps over the very next sequence and delivers // an ahead packet. // // ELEPHANT: Is not ready to read, while others are, or reading // up to the current latest delivery sequence number does not // reach this sequence and the link becomes non-readable earlier. // The above condition has ruled out one kangaroo and turned it // into a horse. // Below there's a loop that will try to extract packets. Kangaroos // will be among the polled ones because skipping them risks that // the elephants will take over the reading. Links already known as // elephants will be also polled in an attempt to revitalize the // connection that experienced just a short living choking. // // After polling we attempt to read from every link that reported // read-readiness and read at most up to the sequence equal to the // current delivery sequence. // Links that deliver a packet below that sequence will be retried // until they deliver no more packets or deliver the packet of // expected sequence. Links that don't have a record in m_Positions // and report readiness will be always read, at least to know what // sequence they currently stand on. // // Links that are already known as kangaroos will be polled, but // no reading attempt will be done. If after the reading series // it will turn out that we have no more horses, the slowest kangaroo // will be "upgraded to a horse" (the ahead link with a sequence // closest to the current delivery sequence will get its sequence // set as current delivered and its recorded ahead packet returned // as the read packet). // If we find at least one horse, the packet read from that link // will be delivered. All other link will be just ensured update // up to this sequence number, or at worst all available packets // will be read. In this case all kangaroos remain kangaroos, // until the current delivery sequence m_RcvBaseSeqNo will be lifted // to the sequence recorded for these links in m_Positions, // during the next time ahead check, after which they will become // horses. const size_t size = m_Group.size(); // Prepare first the list of sockets to be added as connect-pending // and as read-ready, then unlock the group, and then add them to epoll. vector aliveMembers; recv_CollectAliveAndBroken(aliveMembers, broken); const vector ready_sockets = recv_WaitForReadReady(aliveMembers, broken); // m_GlobControlLock lifted, m_GroupLock still locked. // Now we can safely do this scoped way. // Ok, now we need to have some extra qualifications: // 1. If a socket has no registry yet, we read anyway, just // to notify the current position. We read ONLY ONE PACKET this time, // we'll worry later about adjusting it to the current group sequence // position. // 2. If a socket is already position ahead, DO NOT read from it, even // if it is ready. // The state of things whether we were able to extract the very next // sequence will be simply defined by the fact that `output` is nonempty. int32_t next_seq = m_RcvBaseSeqNo; if (m_bClosing) { HLOGC(gslog.Debug, log << "grp/sendBroadcast: GROUP CLOSED, ABANDONING"); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // // NOTE: Although m_GlobControlLock is lifted here so potentially sockets // colected in ready_sockets could be closed at any time, all of them are member // sockets of this group. Therefore the first socket attempted to be closed will // have to remove the socket from the group, and this will require lock on GroupLock, // which is still applied here. So this will have to wait for this function to finish // (or block on swait, in which case the lock is lifted) anyway. for (vector::const_iterator si = ready_sockets.begin(); si != ready_sockets.end(); ++si) { CUDTSocket* ps = *si; SRTSOCKET id = ps->m_SocketID; ReadPos* p = NULL; pit_t pe = m_Positions.find(id); if (pe != m_Positions.end()) { p = &pe->second; // Possible results of comparison: // x < 0: the sequence is in the past, the socket should be adjusted FIRST // x = 0: the socket should be ready to get the exactly next packet // x = 1: the case is already handled by GroupCheckPacketAhead. // x > 1: AHEAD. DO NOT READ. const int seqdiff = CSeqNo::seqcmp(p->mctrl.pktseq, m_RcvBaseSeqNo); if (seqdiff > 1) { HLOGC(grlog.Debug, log << "group/recv: EPOLL: @" << id << " %" << p->mctrl.pktseq << " AHEAD %" << m_RcvBaseSeqNo << ", not reading."); continue; } } else { // The position is not known, so get the position on which // the socket is currently standing. pair ee = m_Positions.insert(make_pair(id, ReadPos(ps->core().m_iRcvLastSkipAck))); p = &(ee.first->second); HLOGC(grlog.Debug, log << "group/recv: EPOLL: @" << id << " %" << p->mctrl.pktseq << " NEW SOCKET INSERTED"); } // Read from this socket stubbornly, until: // - reading is no longer possible (AGAIN) // - the sequence difference is >= 1 for (;;) { SRT_MSGCTRL mctrl = srt_msgctrl_default; // Read the data into the user's buffer. This is an optimistic // prediction that we'll read the right data. This will be overwritten // by "more correct data" if found more appropriate later. But we have to // copy these data anyway anywhere, even if they need to fall on the floor later. int stat; char extrabuf[SRT_LIVE_MAX_PLSIZE]; char* msgbuf = NULL; if (output_size) { // We already have the target data in `buf`. Now reading extra data potentially redundant (to be ignored) // or AHEAD (to be buffered internally by the group) msgbuf = extrabuf; stat = ps->core().receiveMessage((extrabuf), SRT_LIVE_MAX_PLSIZE, (mctrl), CUDTUnited::ERH_RETURN); HLOGC(grlog.Debug, log << "group/recv: @" << id << " EXTRACTED EXTRA data with %" << mctrl.pktseq << " #" << mctrl.msgno << ": " << (stat <= 0 ? "(NOTHING)" : BufferStamp(extrabuf, stat)) << (CSeqNo::seqcmp(mctrl.pktseq, m_RcvBaseSeqNo) > 1 ? " - TO STORE" : " - TO IGNORE")); } else { msgbuf = buf; stat = ps->core().receiveMessage((buf), len, (mctrl), CUDTUnited::ERH_RETURN); HLOGC(grlog.Debug, log << "group/recv: @" << id << " EXTRACTED data with %" << mctrl.pktseq << " #" << mctrl.msgno << ": " << (stat <= 0 ? "(NOTHING)" : BufferStamp(buf, stat))); } if (stat == 0) { HLOGC(grlog.Debug, log << "group/recv @" << id << ": SPURIOUS epoll, ignoring"); // This is returned in case of "again". In case of errors, we have SRT_ERROR. // Do not treat this as spurious, just stop reading. break; } if (stat == SRT_ERROR) { HLOGC(grlog.Debug, log << "group/recv: @" << id << ": " << srt_getlasterror_str()); broken.insert(ps); break; } // NOTE: checks against m_RcvBaseSeqNo and decisions based on it // must NOT be done if m_RcvBaseSeqNo is SRT_SEQNO_NONE, which // means that we are about to deliver the very first packet and we // take its sequence number as a good deal. // The order must be: // - check discrepancy // - record the sequence // - check ordering. // The second one must be done always, but failed discrepancy // check should exclude the socket from any further checks. // That's why the common check for m_RcvBaseSeqNo != SRT_SEQNO_NONE can't // embrace everything below. // We need to first qualify the sequence, just for a case if (m_RcvBaseSeqNo != SRT_SEQNO_NONE && !isValidSeqno(m_RcvBaseSeqNo, mctrl.pktseq)) { // This error should be returned if the link turns out // to be the only one, or set to the group data. // err = SRT_ESECFAIL; LOGC(grlog.Error, log << "group/recv: @" << id << ": SEQUENCE DISCREPANCY: base=%" << m_RcvBaseSeqNo << " vs pkt=%" << mctrl.pktseq << ", setting ESECFAIL"); broken.insert(ps); break; } // Rewrite it to the state for a case when next reading // would not succeed. Do not insert the buffer here because // this is only required when the sequence is ahead; for that // it will be fixed later. p->mctrl.pktseq = mctrl.pktseq; if (m_RcvBaseSeqNo != SRT_SEQNO_NONE) { // Now we can safely check it. const int seqdiff = CSeqNo::seqcmp(mctrl.pktseq, m_RcvBaseSeqNo); if (seqdiff <= 0) { HLOGC(grlog.Debug, log << "group/recv: @" << id << " %" << mctrl.pktseq << " #" << mctrl.msgno << " BEHIND base=%" << m_RcvBaseSeqNo << " - discarding"); // The sequence is recorded, the packet has to be discarded. m_stats.recvDiscard.Update(stat); continue; } // Now we have only two possibilities: // seqdiff == 1: The very next sequence, we want to read and return the packet. // seqdiff > 1: The packet is ahead - record the ahead packet, but continue with the others. if (seqdiff > 1) { HLOGC(grlog.Debug, log << "@" << id << " %" << mctrl.pktseq << " #" << mctrl.msgno << " AHEAD base=%" << m_RcvBaseSeqNo); p->packet.assign(msgbuf, msgbuf + stat); p->mctrl = mctrl; break; // Don't read from that socket anymore. } } // We have seqdiff = 1, or we simply have the very first packet // which's sequence is taken as a good deal. Update the sequence // and record output. if (output_size) { HLOGC(grlog.Debug, log << "group/recv: @" << id << " %" << mctrl.pktseq << " #" << mctrl.msgno << " REDUNDANT"); break; } HLOGC(grlog.Debug, log << "group/recv: @" << id << " %" << mctrl.pktseq << " #" << mctrl.msgno << " DELIVERING"); output_size = stat; fillGroupData((w_mc), mctrl); // Update stats as per delivery m_stats.recv.Update(output_size); updateAvgPayloadSize(output_size); // Record, but do not update yet, until all sockets are handled. next_seq = mctrl.pktseq; break; } } #if ENABLE_HEAVY_LOGGING if (!broken.empty()) { std::ostringstream brks; for (set::iterator b = broken.begin(); b != broken.end(); ++b) brks << "@" << (*b)->m_SocketID << " "; LOGC(grlog.Debug, log << "group/recv: REMOVING BROKEN: " << brks.str()); } #endif vector brokenid; // Now remove all broken sockets from aheads, if any. // Even if they have already delivered a packet. for (set::iterator di = broken.begin(); di != broken.end(); ++di) { CUDTSocket* ps = *di; m_Positions.erase(ps->m_SocketID); //ps->setBrokenClosed(); } // Force closing { InvertedLock ung (m_GroupLock); for (set::iterator b = broken.begin(); b != broken.end(); ++b) { CUDT::s_UDTUnited.close(*b); } } if (broken.size() >= size) // This > is for sanity check { // All broken HLOGC(grlog.Debug, log << "group/recv: All sockets broken"); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // May be required to be re-read. broken.clear(); if (output_size) { // We have extracted something, meaning that we have the sequence shift. // Update it now and don't do anything else with the sockets. // Sanity check if (next_seq == SRT_SEQNO_NONE) { LOGP(grlog.Error, "IPE: next_seq not set after output extracted!"); // This should never happen, but the only way to keep the code // safe an recoverable is to use the incremented sequence. By // leaving the sequence as is there's a risk of hangup. // Not doing it in case of SRT_SEQNO_NONE as it would make a valid %0. if (m_RcvBaseSeqNo != SRT_SEQNO_NONE) m_RcvBaseSeqNo = CSeqNo::incseq(m_RcvBaseSeqNo); } else { m_RcvBaseSeqNo = next_seq; } const ReadPos* pos = checkPacketAhead(); if (!pos) { // Don't clear the read-readinsess state if you have a packet ahead because // if you have, the next read call will return it. m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); } HLOGC(grlog.Debug, log << "group/recv: successfully extracted packet size=" << output_size << " - returning"); return output_size; } HLOGC(grlog.Debug, log << "group/recv: NOT extracted anything - checking for a need to kick kangaroos"); // Check if we have any sockets left :D // Here we surely don't have any more HORSES, // only ELEPHANTS and KANGAROOS. Qualify them and // attempt to at least take advantage of KANGAROOS. // In this position all links are either: // - updated to the current position // - updated to the newest possible possition available // - not yet ready for extraction (not present in the group) // If we haven't extracted the very next sequence position, // it means that we might only have the ahead packets read, // that is, the next sequence has been dropped by all links. if (!m_Positions.empty()) { // This might notify both lingering links, which didn't // deliver the required sequence yet, and links that have // the sequence ahead. Review them, and if you find at // least one packet behind, just wait for it to be ready. // Use again the waiting function because we don't want // the general waiting procedure to skip others. set elephants; // const because it's `typename decltype(m_Positions)::value_type` pair* slowest_kangaroo = 0; for (pit_t rp = m_Positions.begin(); rp != m_Positions.end(); ++rp) { // NOTE that m_RcvBaseSeqNo in this place wasn't updated // because we haven't successfully extracted anything. int seqdiff = CSeqNo::seqcmp(rp->second.mctrl.pktseq, m_RcvBaseSeqNo); if (seqdiff < 0) { elephants.insert(rp->first); } // If seqdiff == 0, we have a socket ON TRACK. else if (seqdiff > 0) { // If there's already a slowest_kangaroo, seqdiff decides if this one is slower. // Otherwise it is always slower by having no competition. seqdiff = slowest_kangaroo ? CSeqNo::seqcmp(slowest_kangaroo->second.mctrl.pktseq, rp->second.mctrl.pktseq) : 1; if (seqdiff > 0) { slowest_kangaroo = &*rp; } } } // Note that if no "slowest_kangaroo" was found, it means // that we don't have kangaroos. if (slowest_kangaroo) { // We have a slowest kangaroo. Elephants must be ignored. // Best case, they will get revived, worst case they will be // soon broken. // // As we already have the packet delivered by the slowest // kangaroo, we can simply return it. // Check how many were skipped and add them to the stats const int32_t jump = (CSeqNo(slowest_kangaroo->second.mctrl.pktseq) - CSeqNo(m_RcvBaseSeqNo)) - 1; if (jump > 0) { m_stats.recvDrop.UpdateTimes(jump, avgRcvPacketSize()); LOGC(grlog.Warn, log << "@" << m_GroupID << " GROUP RCV-DROPPED " << jump << " packet(s): seqno %" << m_RcvBaseSeqNo << " to %" << slowest_kangaroo->second.mctrl.pktseq); } m_RcvBaseSeqNo = slowest_kangaroo->second.mctrl.pktseq; vector& pkt = slowest_kangaroo->second.packet; if (size_t(len) < pkt.size()) throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); HLOGC(grlog.Debug, log << "@" << slowest_kangaroo->first << " KANGAROO->HORSE %" << slowest_kangaroo->second.mctrl.pktseq << " #" << slowest_kangaroo->second.mctrl.msgno << ": " << BufferStamp(&pkt[0], pkt.size())); memcpy(buf, &pkt[0], pkt.size()); fillGroupData((w_mc), slowest_kangaroo->second.mctrl); len = pkt.size(); pkt.clear(); // Update stats as per delivery m_stats.recv.Update(len); updateAvgPayloadSize(len); // It is unlikely to have a packet ahead because usually having one packet jumped-ahead // clears the possibility of having aheads at all. // XXX Research if this is possible at all; if it isn't, then don't waste time on // looking for it. const ReadPos* pos = checkPacketAhead(); if (!pos) { // Don't clear the read-readinsess state if you have a packet ahead because // if you have, the next read call will return it. m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); } return len; } HLOGC(grlog.Debug, log << "group/recv: " << (elephants.empty() ? "NO LINKS REPORTED ANY FRESHER PACKET." : "ALL LINKS ELEPHANTS.") << " Re-polling."); } else { HLOGC(grlog.Debug, log << "group/recv: POSITIONS EMPTY - Re-polling."); } } } // [[using locked(m_GroupLock)]] CUDTGroup::ReadPos* CUDTGroup::checkPacketAhead() { typedef map::iterator pit_t; ReadPos* out = 0; // This map no longer maps only ahead links. // Here are all links, and whether ahead, it's defined by the sequence. for (pit_t i = m_Positions.begin(); i != m_Positions.end(); ++i) { // i->first: socket ID // i->second: ReadPos { sequence, packet } // We are not interested with the socket ID because we // aren't going to read from it - we have the packet already. ReadPos& a = i->second; const int seqdiff = CSeqNo::seqcmp(a.mctrl.pktseq, m_RcvBaseSeqNo); if (seqdiff == 1) { // The very next packet. Return it. HLOGC(grlog.Debug, log << "group/recv: Base %" << m_RcvBaseSeqNo << " ahead delivery POSSIBLE %" << a.mctrl.pktseq << " #" << a.mctrl.msgno << " from @" << i->first << ")"); out = &a; } else if (seqdiff < 1 && !a.packet.empty()) { HLOGC(grlog.Debug, log << "group/recv: @" << i->first << " dropping collected ahead %" << a.mctrl.pktseq << "#" << a.mctrl.msgno << " with base %" << m_RcvBaseSeqNo); a.packet.clear(); } // In case when it's >1, keep it in ahead } return out; } const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) { static const char* const states[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; static const size_t size = Size(states); static const char* const unknown = "UNKNOWN"; if (size_t(st) < size) return states[st]; return unknown; } void CUDTGroup::synchronizeDrift(const srt::CUDT* srcMember) { SRT_ASSERT(srcMember != NULL); ScopedLock glock(m_GroupLock); if (m_Group.size() <= 1) { HLOGC(grlog.Debug, log << "GROUP: synch uDRIFT NOT DONE, no other links"); return; } steady_clock::time_point timebase; steady_clock::duration udrift(0); bool wrap_period = false; srcMember->m_pRcvBuffer->getInternalTimeBase((timebase), (wrap_period), (udrift)); HLOGC(grlog.Debug, log << "GROUP: synch uDRIFT=" << FormatDuration(udrift) << " TB=" << FormatTime(timebase) << "(" << (wrap_period ? "" : "NO ") << "wrap period)"); // Now that we have the minimum timebase and drift calculated, apply this to every link, // INCLUDING THE REPORTER. for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { // Skip non-connected; these will be synchronized when ready if (gi->laststatus != SRTS_CONNECTED) continue; CUDT& member = gi->ps->core(); if (srcMember == &member) continue; member.m_pRcvBuffer->applyGroupDrift(timebase, wrap_period, udrift); } } void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) { if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); const steady_clock::time_point currtime = steady_clock::now(); memset(perf, 0, sizeof *perf); ScopedLock gg(m_GroupLock); perf->msTimeStamp = count_milliseconds(currtime - m_tsStartTime); perf->pktSentUnique = m_stats.sent.local.pkts; perf->pktRecvUnique = m_stats.recv.local.pkts; perf->pktRcvDrop = m_stats.recvDrop.local.pkts; perf->byteSentUnique = m_stats.sent.local.fullBytes(); perf->byteRecvUnique = m_stats.recv.local.fullBytes(); perf->byteRcvDrop = m_stats.recvDrop.local.fullBytes(); perf->pktSentUniqueTotal = m_stats.sent.total.pkts; perf->pktRecvUniqueTotal = m_stats.recv.total.pkts; perf->pktRcvDropTotal = m_stats.recvDrop.total.pkts; perf->byteSentUniqueTotal = m_stats.sent.total.fullBytes(); perf->byteRecvUniqueTotal = m_stats.recv.total.fullBytes(); perf->byteRcvDropTotal = m_stats.recvDrop.total.fullBytes(); const double interval = static_cast(count_microseconds(currtime - m_stats.tsLastSampleTime)); perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; if (clear) { m_stats.reset(); } } /// @brief Compares group members by their weight (higher weight comes first). struct FCompareByWeight { typedef CUDTGroup::gli_t gli_t; /// @returns true if the first argument is less than (i.e. is ordered before) the second. bool operator()(const gli_t preceding, const gli_t succeeding) { return preceding->weight > succeeding->weight; } }; // [[using maybe_locked(this->m_GroupLock)]] BackupMemberState CUDTGroup::sendBackup_QualifyIfStandBy(const gli_t d) { if (!d->ps) return BKUPST_BROKEN; const SRT_SOCKSTATUS st = d->ps->getStatus(); // If the socket is already broken, move it to broken. if (int(st) >= int(SRTS_BROKEN)) { HLOGC(gslog.Debug, log << "CUDTGroup::send.$" << id() << ": @" << d->id << " became " << SockStatusStr(st) << ", WILL BE CLOSED."); return BKUPST_BROKEN; } if (st != SRTS_CONNECTED) { HLOGC(gslog.Debug, log << "CUDTGroup::send. @" << d->id << " is still " << SockStatusStr(st) << ", skipping."); return BKUPST_PENDING; } return BKUPST_STANDBY; } // [[using maybe_locked(this->m_GroupLock)]] bool CUDTGroup::send_CheckIdle(const gli_t d, vector& w_wipeme, vector& w_pendingSockets) { SRT_SOCKSTATUS st = SRTS_NONEXIST; if (d->ps) st = d->ps->getStatus(); // If the socket is already broken, move it to broken. if (int(st) >= int(SRTS_BROKEN)) { HLOGC(gslog.Debug, log << "CUDTGroup::send.$" << id() << ": @" << d->id << " became " << SockStatusStr(st) << ", WILL BE CLOSED."); w_wipeme.push_back(d->id); return false; } if (st != SRTS_CONNECTED) { HLOGC(gslog.Debug, log << "CUDTGroup::send. @" << d->id << " is still " << SockStatusStr(st) << ", skipping."); w_pendingSockets.push_back(d->id); return false; } return true; } #if SRT_DEBUG_BONDING_STATES class StabilityTracer { public: StabilityTracer() { } ~StabilityTracer() { srt::sync::ScopedLock lck(m_mtx); m_fout.close(); } void trace(const CUDT& u, const srt::sync::steady_clock::time_point& currtime, uint32_t activation_period_us, int64_t stability_tmo_us, const std::string& state, uint16_t weight) { srt::sync::ScopedLock lck(m_mtx); create_file(); m_fout << srt::sync::FormatTime(currtime) << ","; m_fout << u.id() << ","; m_fout << weight << ","; m_fout << u.peerLatency_us() << ","; m_fout << u.SRTT() << ","; m_fout << u.RTTVar() << ","; m_fout << stability_tmo_us << ","; m_fout << count_microseconds(currtime - u.lastRspTime()) << ","; m_fout << state << ","; m_fout << (srt::sync::is_zero(u.freshActivationStart()) ? -1 : (count_microseconds(currtime - u.freshActivationStart()))) << ","; m_fout << activation_period_us << "\n"; m_fout.flush(); } private: void print_header() { //srt::sync::ScopedLock lck(m_mtx); m_fout << "Timepoint,SocketID,weight,usLatency,usRTT,usRTTVar,usStabilityTimeout,usSinceLastResp,State,usSinceActivation,usActivationPeriod\n"; } void create_file() { if (m_fout.is_open()) return; std::string str_tnow = srt::sync::FormatTimeSys(srt::sync::steady_clock::now()); str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part while (str_tnow.find(':') != std::string::npos) { str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); } const std::string fname = "stability_trace_" + str_tnow + ".csv"; m_fout.open(fname, std::ofstream::out); if (!m_fout) std::cerr << "IPE: Failed to open " << fname << "!!!\n"; print_header(); } private: srt::sync::Mutex m_mtx; std::ofstream m_fout; }; StabilityTracer s_stab_trace; #endif void CUDTGroup::sendBackup_QualifyMemberStates(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime) { // First, check status of every link - no matter if idle or active. for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) { if (d->sndstate != SRT_GST_BROKEN) { // Check the socket state prematurely in order not to uselessly // send over a socket that is broken. CUDT* const pu = (d->ps) ? &d->ps->core() : NULL; if (!pu || pu->m_bBroken) { HLOGC(gslog.Debug, log << "grp/sendBackup: socket @" << d->id << " detected +Broken - transit to BROKEN"); d->sndstate = SRT_GST_BROKEN; d->rcvstate = SRT_GST_BROKEN; } } // Check socket sndstate before sending if (d->sndstate == SRT_GST_BROKEN) { HLOGC(gslog.Debug, log << "grp/sendBackup: socket in BROKEN state: @" << d->id << ", sockstatus=" << SockStatusStr(d->ps ? d->ps->getStatus() : SRTS_NONEXIST)); sendBackup_AssignBackupState(d->ps->core(), BKUPST_BROKEN, currtime); w_sendBackupCtx.recordMemberState(&(*d), BKUPST_BROKEN); #if SRT_DEBUG_BONDING_STATES s_stab_trace.trace(d->ps->core(), currtime, 0, 0, stateToStr(BKUPST_BROKEN), d->weight); #endif continue; } if (d->sndstate == SRT_GST_IDLE) { const BackupMemberState idle_state = sendBackup_QualifyIfStandBy(d); sendBackup_AssignBackupState(d->ps->core(), idle_state, currtime); w_sendBackupCtx.recordMemberState(&(*d), idle_state); if (idle_state == BKUPST_STANDBY) { // TODO: Check if this is some abandoned logic. sendBackup_CheckIdleTime(d); } #if SRT_DEBUG_BONDING_STATES s_stab_trace.trace(d->ps->core(), currtime, 0, 0, stateToStr(idle_state), d->weight); #endif continue; } if (d->sndstate == SRT_GST_RUNNING) { const BackupMemberState active_state = sendBackup_QualifyActiveState(d, currtime); sendBackup_AssignBackupState(d->ps->core(), active_state, currtime); w_sendBackupCtx.recordMemberState(&(*d), active_state); #if SRT_DEBUG_BONDING_STATES s_stab_trace.trace(d->ps->core(), currtime, 0, 0, stateToStr(active_state), d->weight); #endif continue; } HLOGC(gslog.Debug, log << "grp/sendBackup: socket @" << d->id << " not ready, state: " << StateStr(d->sndstate) << "(" << int(d->sndstate) << ") - NOT sending, SET AS PENDING"); // Otherwise connection pending sendBackup_AssignBackupState(d->ps->core(), BKUPST_PENDING, currtime); w_sendBackupCtx.recordMemberState(&(*d), BKUPST_PENDING); #if SRT_DEBUG_BONDING_STATES s_stab_trace.trace(d->ps->core(), currtime, 0, 0, stateToStr(BKUPST_PENDING), d->weight); #endif } } void CUDTGroup::sendBackup_AssignBackupState(CUDT& sock, BackupMemberState state, const steady_clock::time_point& currtime) { switch (state) { case BKUPST_PENDING: case BKUPST_STANDBY: case BKUPST_BROKEN: sock.m_tsFreshActivation = steady_clock::time_point(); sock.m_tsUnstableSince = steady_clock::time_point(); sock.m_tsWarySince = steady_clock::time_point(); break; case BKUPST_ACTIVE_FRESH: if (is_zero(sock.freshActivationStart())) { sock.m_tsFreshActivation = currtime; } sock.m_tsUnstableSince = steady_clock::time_point(); sock.m_tsWarySince = steady_clock::time_point();; break; case BKUPST_ACTIVE_STABLE: sock.m_tsFreshActivation = steady_clock::time_point(); sock.m_tsUnstableSince = steady_clock::time_point(); sock.m_tsWarySince = steady_clock::time_point(); break; case BKUPST_ACTIVE_UNSTABLE: if (is_zero(sock.m_tsUnstableSince)) { sock.m_tsUnstableSince = currtime; } sock.m_tsFreshActivation = steady_clock::time_point(); sock.m_tsWarySince = steady_clock::time_point(); break; case BKUPST_ACTIVE_UNSTABLE_WARY: if (is_zero(sock.m_tsWarySince)) { sock.m_tsWarySince = currtime; } break; default: break; } } // [[using locked(this->m_GroupLock)]] void CUDTGroup::sendBackup_CheckIdleTime(gli_t w_d) { // Check if it was fresh set as idle, we had to wait until its sender // buffer gets empty so that we can make sure that KEEPALIVE will be the // really last sent for longer time. CUDT& u = w_d->ps->core(); if (is_zero(u.m_tsFreshActivation)) // TODO: Check if this condition is ever false return; CSndBuffer* b = u.m_pSndBuffer; if (b && b->getCurrBufSize() == 0) { HLOGC(gslog.Debug, log << "grp/sendBackup: FRESH IDLE LINK reached empty buffer - setting permanent and KEEPALIVE"); u.m_tsFreshActivation = steady_clock::time_point(); // Send first immediate keepalive. The link is to be turn to IDLE // now so nothing will be sent to it over time and it will start // getting KEEPALIVES since now. Send the first one now to increase // probability that the link will be recognized as IDLE on the // reception side ASAP. int32_t arg = 1; w_d->ps->core().sendCtrl(UMSG_KEEPALIVE, &arg); } } // [[using locked(this->m_GroupLock)]] CUDTGroup::BackupMemberState CUDTGroup::sendBackup_QualifyActiveState(const gli_t d, const time_point currtime) { const CUDT& u = d->ps->core(); const uint32_t latency_us = u.peerLatency_us(); const int32_t min_stability_us = 60000; // Minimum Link Stability Timeout: 60ms. const int64_t initial_stabtout_us = max(min_stability_us, latency_us); const int64_t probing_period_us = initial_stabtout_us + 5 * CUDT::COMM_SYN_INTERVAL_US; // RTT and RTTVar values are still being refined during the probing period, // therefore the dymanic timeout should not be used during the probing period. const bool is_activation_phase = !is_zero(u.freshActivationStart()) && (count_microseconds(currtime - u.freshActivationStart()) <= probing_period_us); // Initial stability timeout is used only in activation phase. // Otherwise runtime stability is used, including the WARY state. const int64_t stability_tout_us = is_activation_phase ? initial_stabtout_us // activation phase : min(max(min_stability_us, 2 * u.SRTT() + 4 * u.RTTVar()), latency_us); const steady_clock::time_point last_rsp = max(u.freshActivationStart(), u.lastRspTime()); const steady_clock::duration td_response = currtime - last_rsp; // No response for a long time if (count_microseconds(td_response) > stability_tout_us) { return BKUPST_ACTIVE_UNSTABLE; } enterCS(u.m_StatsLock); const int64_t drop_total = u.m_stats.sndDropTotal; leaveCS(u.m_StatsLock); const bool have_new_drops = d->pktSndDropTotal != drop_total; if (have_new_drops) { d->pktSndDropTotal = drop_total; if (!is_activation_phase) return BKUPST_ACTIVE_UNSTABLE; } // Responsive: either stable, wary or still fresh activated. if (is_activation_phase) return BKUPST_ACTIVE_FRESH; const bool is_wary = !is_zero(u.m_tsWarySince); const bool is_wary_probing = is_wary && (count_microseconds(currtime - u.m_tsWarySince) <= 4 * u.peerLatency_us()); const bool is_unstable = !is_zero(u.m_tsUnstableSince); // If unstable and not in wary, become wary. if (is_unstable && !is_wary) return BKUPST_ACTIVE_UNSTABLE_WARY; // Still probing for stability. if (is_wary_probing) return BKUPST_ACTIVE_UNSTABLE_WARY; if (is_wary) { LOGC(gslog.Debug, log << "grp/sendBackup: @" << u.id() << " wary->stable after " << count_milliseconds(currtime - u.m_tsWarySince) << " ms"); } return BKUPST_ACTIVE_STABLE; } // [[using locked(this->m_GroupLock)]] bool CUDTGroup::sendBackup_CheckSendStatus(const steady_clock::time_point& currtime SRT_ATR_UNUSED, const int send_status, const int32_t lastseq, const int32_t pktseq, CUDT& w_u, int32_t& w_curseq, int& w_final_stat) { if (send_status == -1) return false; // Sending failed. bool send_succeeded = false; if (w_curseq == SRT_SEQNO_NONE) { w_curseq = pktseq; } else if (w_curseq != lastseq) { // We believe that all active links use the same seq. // But we can do some sanity check. LOGC(gslog.Error, log << "grp/sendBackup: @" << w_u.m_SocketID << ": IPE: another running link seq discrepancy: %" << lastseq << " vs. previous %" << w_curseq << " - fixing"); // Override must be done with a sequence number greater by one. // Example: // // Link 1 before sending: curr=1114, next=1115 // After sending it reports pktseq=1115 // // Link 2 before sending: curr=1110, next=1111 (->lastseq before sending) // THIS CHECK done after sending: // -- w_curseq(1115) != lastseq(1111) // // NOW: Link 1 after sending is: // curr=1115, next=1116 // // The value of w_curseq here = 1115, while overrideSndSeqNo // calls setInitialSndSeq(seq), which sets: // - curr = seq - 1 // - next = seq // // So, in order to set curr=1115, next=1116 // this must set to 1115+1. w_u.overrideSndSeqNo(CSeqNo::incseq(w_curseq)); } // State it as succeeded, though. We don't know if the link // is broken until we get the connection broken confirmation, // and the instability state may wear off next time. send_succeeded = true; w_final_stat = send_status; return send_succeeded; } // [[using locked(this->m_GroupLock)]] void CUDTGroup::sendBackup_Buffering(const char* buf, const int len, int32_t& w_curseq, SRT_MSGCTRL& w_mc) { // This is required to rewrite into currentSchedSequence() property // as this value will be used as ISN when a new link is connected. int32_t oldest_buffer_seq = SRT_SEQNO_NONE; if (w_curseq != SRT_SEQNO_NONE) { HLOGC(gslog.Debug, log << "grp/sendBackup: successfully sent over running link, ADDING TO BUFFER."); // Note: the sequence number that was used to send this packet should be // recorded here. oldest_buffer_seq = addMessageToBuffer(buf, len, (w_mc)); } else { // We have to predict, which sequence number would have // to be placed on the packet about to be sent now. To // maintain consistency: // 1. If there are any packets in the sender buffer, // get the sequence of the last packet, increase it. // This must be done even if this contradicts the ISN // of all idle links because otherwise packets will get // discrepancy. if (!m_SenderBuffer.empty()) { BufferedMessage& m = m_SenderBuffer.back(); w_curseq = CSeqNo::incseq(m.mc.pktseq); // Set also this sequence to the current w_mc w_mc.pktseq = w_curseq; // XXX may need tighter revision when message mode is allowed w_mc.msgno = ++MsgNo(m.mc.msgno); oldest_buffer_seq = addMessageToBuffer(buf, len, (w_mc)); } // Note that if buffer is empty and w_curseq is (still) SRT_SEQNO_NONE, // it will have to try to send first in order to extract the data. // Note that if w_curseq is still SRT_SEQNO_NONE at this point, it means // that we have the case of the very first packet sending. // Otherwise there would be something in the buffer already. } if (oldest_buffer_seq != SRT_SEQNO_NONE) m_iLastSchedSeqNo = oldest_buffer_seq; } size_t CUDTGroup::sendBackup_TryActivateStandbyIfNeeded( const char* buf, const int len, bool& w_none_succeeded, SRT_MSGCTRL& w_mc, int32_t& w_curseq, int32_t& w_final_stat, SendBackupCtx& w_sendBackupCtx, CUDTException& w_cx, const steady_clock::time_point& currtime) { const unsigned num_standby = w_sendBackupCtx.countMembersByState(BKUPST_STANDBY); if (num_standby == 0) { return 0; } const unsigned num_stable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_FRESH); const unsigned num_fresh = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_STABLE); if (num_stable + num_fresh == 0) { LOGC(gslog.Warn, log << "grp/sendBackup: trying to activate a stand-by link (" << num_standby << " available). " << "Reason: no stable links" ); } else if (w_sendBackupCtx.maxActiveWeight() < w_sendBackupCtx.maxStandbyWeight()) { LOGC(gslog.Warn, log << "grp/sendBackup: trying to activate a stand-by link (" << num_standby << " available). " << "Reason: max active weight " << w_sendBackupCtx.maxActiveWeight() << ", max stand by weight " << w_sendBackupCtx.maxStandbyWeight() ); } else { /*LOGC(gslog.Warn, log << "grp/sendBackup: no need to activate (" << num_standby << " available). " << "Max active weight " << w_sendBackupCtx.maxActiveWeight() << ", max stand by weight " << w_sendBackupCtx.maxStandbyWeight() );*/ return 0; } int stat = -1; size_t num_activated = 0; w_sendBackupCtx.sortByWeightAndState(); typedef vector::const_iterator const_iter_t; for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) { if (member->state != BKUPST_STANDBY) continue; int erc = 0; SocketData* d = member->pSocketData; // Now send and check the status // The link could have got broken try { // TODO: At this point all packets that could be sent // are located in m_SenderBuffer. So maybe just use sendBackupRexmit()? if (w_curseq == SRT_SEQNO_NONE) { // This marks the fact that the given here packet // could not be sent over any link. This includes the // situation of sending the very first packet after connection. HLOGC(gslog.Debug, log << "grp/sendBackup: ... trying @" << d->id << " - sending the VERY FIRST message"); stat = d->ps->core().sendmsg2(buf, len, (w_mc)); if (stat != -1) { // This will be no longer used, but let it stay here. // It's because if this is successful, no other links // will be tried. w_curseq = w_mc.pktseq; addMessageToBuffer(buf, len, (w_mc)); } } else { HLOGC(gslog.Debug, log << "grp/sendBackup: ... trying @" << d->id << " - resending " << m_SenderBuffer.size() << " collected messages..."); // Note: this will set the currently required packet // because it has been just freshly added to the sender buffer stat = sendBackupRexmit(d->ps->core(), (w_mc)); } ++num_activated; } catch (CUDTException& e) { // This will be propagated from internal sendmsg2 call, // but that's ok - we want this sending interrupted even in half. w_cx = e; stat = -1; erc = e.getErrorCode(); } d->sndresult = stat; d->laststatus = d->ps->getStatus(); if (stat != -1) { d->sndstate = SRT_GST_RUNNING; sendBackup_AssignBackupState(d->ps->core(), BKUPST_ACTIVE_FRESH, currtime); w_sendBackupCtx.updateMemberState(d, BKUPST_ACTIVE_FRESH); // Note: this will override the sequence number // for all next iterations in this loop. w_none_succeeded = false; w_final_stat = stat; LOGC(gslog.Warn, log << "@" << d->id << " FRESH-ACTIVATED"); // We've activated the link, so that's enough. break; } // Failure - move to broken those that could not be activated bool isblocked SRT_ATR_UNUSED = true; if (erc != SRT_EASYNCSND) { isblocked = false; sendBackup_AssignBackupState(d->ps->core(), BKUPST_BROKEN, currtime); w_sendBackupCtx.updateMemberState(d, BKUPST_BROKEN); } // If we found a blocked link, leave it alone, however // still try to send something over another link LOGC(gslog.Warn, log << "@" << d->id << " FAILED (" << (isblocked ? "blocked" : "ERROR") << "), trying to activate another link."); } return num_activated; } // [[using locked(this->m_GroupLock)]] void CUDTGroup::sendBackup_CheckPendingSockets(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime) { if (w_sendBackupCtx.countMembersByState(BKUPST_PENDING) == 0) return; HLOGC(gslog.Debug, log << "grp/send*: checking pending sockets."); // These sockets if they are in pending state, should be added to m_SndEID // at the connecting stage. CEPoll::fmap_t sready; if (m_pGlobal->m_EPoll.empty(*m_SndEpolld)) { // Sanity check - weird pending reported. LOGC(gslog.Error, log << "grp/send*: IPE: reported pending sockets, but EID is empty - wiping pending!"); return; } { InvertedLock ug(m_GroupLock); m_pGlobal->m_EPoll.swait( *m_SndEpolld, sready, 0, false /*report by retval*/); // Just check if anything has happened } if (m_bClosing) { HLOGC(gslog.Debug, log << "grp/send...: GROUP CLOSED, ABANDONING"); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // Some sockets could have been closed in the meantime. if (m_pGlobal->m_EPoll.empty(*m_SndEpolld)) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); HLOGC(gslog.Debug, log << "grp/send*: RDY: " << DisplayEpollResults(sready)); typedef vector::const_iterator const_iter_t; for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) { if (member->state != BKUPST_PENDING) continue; const SRTSOCKET sockid = member->pSocketData->id; if (!CEPoll::isready(sready, sockid, SRT_EPOLL_ERR)) continue; HLOGC(gslog.Debug, log << "grp/send*: Socket @" << sockid << " reported FAILURE - qualifying as broken."); w_sendBackupCtx.updateMemberState(member->pSocketData, BKUPST_BROKEN); if (member->pSocketData->ps) sendBackup_AssignBackupState(member->pSocketData->ps->core(), BKUPST_BROKEN, currtime); const int no_events = 0; m_pGlobal->m_EPoll.update_usock(m_SndEID, sockid, &no_events); } // After that, all sockets that have been reported // as ready to write should be removed from EID. This // will also remove those sockets that have been added // as redundant links at the connecting stage and became // writable (connected) before this function had a chance // to check them. m_pGlobal->m_EPoll.clear_ready_usocks(*m_SndEpolld, SRT_EPOLL_OUT); } // [[using locked(this->m_GroupLock)]] void CUDTGroup::sendBackup_CheckUnstableSockets(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime) { const unsigned num_stable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_STABLE); if (num_stable == 0) return; const unsigned num_unstable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE); const unsigned num_wary = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE_WARY); if (num_unstable + num_wary == 0) return; HLOGC(gslog.Debug, log << "grp/send*: checking unstable sockets."); typedef vector::const_iterator const_iter_t; for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) { if (member->state != BKUPST_ACTIVE_UNSTABLE && member->state != BKUPST_ACTIVE_UNSTABLE_WARY) continue; CUDT& sock = member->pSocketData->ps->core(); if (is_zero(sock.m_tsUnstableSince)) { LOGC(gslog.Error, log << "grp/send* IPE: Socket @" << member->socketID << " is qualified as unstable, but does not have the 'unstable since' timestamp. Still marking for closure."); } const int unstable_for_ms = count_milliseconds(currtime - sock.m_tsUnstableSince); if (unstable_for_ms < sock.peerIdleTimeout_ms()) continue; // Requesting this socket to be broken with the next CUDT::checkExpTimer() call. sock.breakAsUnstable(); LOGC(gslog.Warn, log << "grp/send*: Socket @" << member->socketID << " is unstable for " << unstable_for_ms << "ms - requesting breakage."); //w_sendBackupCtx.updateMemberState(member->pSocketData, BKUPST_BROKEN); //if (member->pSocketData->ps) // sendBackup_AssignBackupState(member->pSocketData->ps->core(), BKUPST_BROKEN, currtime); } } // [[using locked(this->m_GroupLock)]] void CUDTGroup::send_CloseBrokenSockets(vector& w_wipeme) { if (!w_wipeme.empty()) { InvertedLock ug(m_GroupLock); // With unlocked GroupLock, we can now lock GlobControlLock. // This is needed prevent any of them be deleted from the container // at the same time. ScopedLock globlock(CUDT::s_UDTUnited.m_GlobControlLock); for (vector::iterator p = w_wipeme.begin(); p != w_wipeme.end(); ++p) { CUDTSocket* s = CUDT::s_UDTUnited.locateSocket_LOCKED(*p); // If the socket has been just moved to ClosedSockets, it means that // the object still exists, but it will be no longer findable. if (!s) continue; HLOGC(gslog.Debug, log << "grp/send...: BROKEN SOCKET @" << (*p) << " - CLOSING, to be removed from group."); // As per sending, make it also broken so that scheduled // packets will be also abandoned. s->setClosed(); } } HLOGC(gslog.Debug, log << "grp/send...: - wiped " << w_wipeme.size() << " broken sockets"); // We'll need you again. w_wipeme.clear(); } // [[using locked(this->m_GroupLock)]] void CUDTGroup::sendBackup_CloseBrokenSockets(SendBackupCtx& w_sendBackupCtx) { if (w_sendBackupCtx.countMembersByState(BKUPST_BROKEN) == 0) return; InvertedLock ug(m_GroupLock); // With unlocked GroupLock, we can now lock GlobControlLock. // This is needed prevent any of them be deleted from the container // at the same time. ScopedLock globlock(CUDT::s_UDTUnited.m_GlobControlLock); typedef vector::const_iterator const_iter_t; for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) { if (member->state != BKUPST_BROKEN) continue; // m_GroupLock is unlocked, therefore member->pSocketData can't be used. const SRTSOCKET sockid = member->socketID; CUDTSocket* s = CUDT::s_UDTUnited.locateSocket_LOCKED(sockid); // If the socket has been just moved to ClosedSockets, it means that // the object still exists, but it will be no longer findable. if (!s) continue; LOGC(gslog.Debug, log << "grp/send...: BROKEN SOCKET @" << sockid << " - CLOSING, to be removed from group."); // As per sending, make it also broken so that scheduled // packets will be also abandoned. s->setBrokenClosed(); } // TODO: all broken members are to be removed from the context now??? } struct FByOldestActive { typedef CUDTGroup::gli_t gli_t; bool operator()(gli_t a, gli_t b) { CUDT& x = a->ps->core(); CUDT& y = b->ps->core(); return x.m_tsFreshActivation < y.m_tsFreshActivation; } }; // [[using locked(this->m_GroupLock)]] void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx, int& w_final_stat, bool& w_none_succeeded, SRT_MSGCTRL& w_mc, CUDTException& w_cx) { // In contradiction to broadcast sending, backup sending must check // the blocking state in total first. We need this information through // epoll because we didn't use all sockets to send the data hence the // blocked socket information would not be complete. // Don't do this check if sending has succeeded over at least one // stable link. This procedure is to wait for at least one write-ready // link. // // If sending succeeded also over at least one unstable link (you only have // unstable links and none other or others just got broken), continue sending // anyway. // This procedure is for a case when the packet could not be sent // over any link (hence "none succeeded"), but there are some unstable // links and no parallel links. We need to WAIT for any of the links // to become available for sending. // Note: A link is added in unstableLinks if sending has failed with SRT_ESYNCSND. const unsigned num_unstable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE); const unsigned num_wary = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE_WARY); if ((num_unstable + num_wary == 0) || !w_none_succeeded) return; HLOGC(gslog.Debug, log << "grp/sendBackup: no successfull sending: " << (num_unstable + num_wary) << " unstable links - waiting to retry sending..."); // Note: GroupLock is set already, skip locks and checks getGroupData_LOCKED((w_mc.grpdata), (&w_mc.grpdata_size)); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); if (m_pGlobal->m_EPoll.empty(*m_SndEpolld)) { // wipeme wiped, pending sockets checked, it can only mean that // all sockets are broken. HLOGC(gslog.Debug, log << "grp/sendBackup: epolld empty - all sockets broken?"); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } if (!m_bSynSending) { HLOGC(gslog.Debug, log << "grp/sendBackup: non-blocking mode - exit with no-write-ready"); throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); } // Here is the situation that the only links left here are: // - those that failed to send (already closed and wiped out) // - those that got blockade on sending // At least, there was so far no socket through which we could // successfully send anything. // As a last resort in this situation, try to wait for any links // remaining in the group to become ready to write. CEPoll::fmap_t sready; int brdy; // This keeps the number of links that existed at the entry. // Simply notify all dead links, regardless as to whether the number // of group members decreases below. If the number of corpses reaches // this number, consider the group connection broken. const size_t nlinks = m_Group.size(); size_t ndead = 0; RetryWaitBlocked: { // Some sockets could have been closed in the meantime. if (m_pGlobal->m_EPoll.empty(*m_SndEpolld)) { HLOGC(gslog.Debug, log << "grp/sendBackup: no more sockets available for sending - group broken"); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } InvertedLock ug(m_GroupLock); HLOGC(gslog.Debug, log << "grp/sendBackup: swait call to get at least one link alive up to " << m_iSndTimeOut << "us"); THREAD_PAUSED(); brdy = m_pGlobal->m_EPoll.swait(*m_SndEpolld, (sready), m_iSndTimeOut); THREAD_RESUMED(); if (brdy == 0) // SND timeout exceeded { throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); } HLOGC(gslog.Debug, log << "grp/sendBackup: swait exited with " << brdy << " ready sockets:"); // Check if there's anything in the "error" section. // This must be cleared here before the lock on group is set again. // (This loop will not fire neither once if no failed sockets found). for (CEPoll::fmap_t::const_iterator i = sready.begin(); i != sready.end(); ++i) { if (i->second & SRT_EPOLL_ERR) { SRTSOCKET id = i->first; CUDTSocket* s = m_pGlobal->locateSocket(id, CUDTUnited::ERH_RETURN); // << LOCKS m_GlobControlLock! if (s) { HLOGC(gslog.Debug, log << "grp/sendBackup: swait/ex on @" << (id) << " while waiting for any writable socket - CLOSING"); CUDT::s_UDTUnited.close(s); // << LOCKS m_GlobControlLock, then GroupLock! } else { HLOGC(gslog.Debug, log << "grp/sendBackup: swait/ex on @" << (id) << " - WAS DELETED IN THE MEANTIME"); } ++ndead; } } HLOGC(gslog.Debug, log << "grp/sendBackup: swait/?close done, re-acquiring GroupLock"); } // GroupLock is locked back // Re-check after the waiting lock has been reacquired if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); if (brdy == -1 || ndead >= nlinks) { LOGC(gslog.Error, log << "grp/sendBackup: swait=>" << brdy << " nlinks=" << nlinks << " ndead=" << ndead << " - looxlike all links broken"); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); // You can safely throw here - nothing to fill in when all sockets down. // (timeout was reported by exception in the swait call). throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // Ok, now check if we have at least one write-ready. // Note that the procedure of activation of a new link in case of // no stable links found embraces also rexmit-sending and status // check as well, including blocked status. // Find which one it was. This is so rare case that we can // suffer linear search. int nwaiting = 0; int nactivated SRT_ATR_UNUSED = 0; int stat = -1; for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) { // We are waiting only for active members if (d->sndstate != SRT_GST_RUNNING) { HLOGC(gslog.Debug, log << "grp/sendBackup: member @" << d->id << " state is not RUNNING - SKIPPING from retry/waiting"); continue; } // Skip if not writable in this run if (!CEPoll::isready(sready, d->id, SRT_EPOLL_OUT)) { ++nwaiting; HLOGC(gslog.Debug, log << "grp/sendBackup: @" << d->id << " NOT ready:OUT, added as waiting"); continue; } try { // Note: this will set the currently required packet // because it has been just freshly added to the sender buffer stat = sendBackupRexmit(d->ps->core(), (w_mc)); ++nactivated; } catch (CUDTException& e) { // This will be propagated from internal sendmsg2 call, // but that's ok - we want this sending interrupted even in half. w_cx = e; stat = -1; } d->sndresult = stat; d->laststatus = d->ps->getStatus(); if (stat == -1) { // This link is no longer waiting. continue; } w_final_stat = stat; d->sndstate = SRT_GST_RUNNING; w_none_succeeded = false; const steady_clock::time_point currtime = steady_clock::now(); sendBackup_AssignBackupState(d->ps->core(), BKUPST_ACTIVE_UNSTABLE_WARY, currtime); w_sendBackupCtx.updateMemberState(&(*d), BKUPST_ACTIVE_UNSTABLE_WARY); HLOGC(gslog.Debug, log << "grp/sendBackup: after waiting, ACTIVATED link @" << d->id); break; } // If we have no links successfully activated, but at least // one link "not ready for writing", continue waiting for at // least one link ready. if (stat == -1 && nwaiting > 0) { HLOGC(gslog.Debug, log << "grp/sendBackup: still have " << nwaiting << " waiting and none succeeded, REPEAT"); goto RetryWaitBlocked; } } // [[using locked(this->m_GroupLock)]] void CUDTGroup::sendBackup_SilenceRedundantLinks(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime) { // The most important principle is to keep the data being sent constantly, // even if it means temporarily full redundancy. // A member can be silenced only if there is at least one stable memebr. const unsigned num_stable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_STABLE); if (num_stable == 0) return; // INPUT NEEDED: // - stable member with maximum weight uint16_t max_weight_stable = 0; SRTSOCKET stableSocketId = SRT_INVALID_SOCK; // SocketID of a stable link with higher weight w_sendBackupCtx.sortByWeightAndState(); //LOGC(gslog.Debug, log << "grp/silenceRedundant: links after sort: " << w_sendBackupCtx.printMembers()); typedef vector::const_iterator const_iter_t; for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) { if (!isStateActive(member->state)) continue; const bool haveHigherWeightStable = stableSocketId != SRT_INVALID_SOCK; const uint16_t weight = member->pSocketData->weight; if (member->state == BKUPST_ACTIVE_STABLE) { // silence stable link if it is not the first stable if (!haveHigherWeightStable) { max_weight_stable = (int) weight; stableSocketId = member->socketID; continue; } else { LOGC(gslog.Note, log << "grp/sendBackup: silencing stable member @" << member->socketID << " (weight " << weight << ") in favor of @" << stableSocketId << " (weight " << max_weight_stable << ")"); } } else if (haveHigherWeightStable && weight <= max_weight_stable) { LOGC(gslog.Note, log << "grp/sendBackup: silencing member @" << member->socketID << " (weight " << weight << " " << stateToStr(member->state) << ") in favor of @" << stableSocketId << " (weight " << max_weight_stable << ")"); } else { continue; } // TODO: Move to a separate function sendBackup_SilenceMember SocketData* d = member->pSocketData; CUDT& u = d->ps->core(); sendBackup_AssignBackupState(u, BKUPST_STANDBY, currtime); w_sendBackupCtx.updateMemberState(d, BKUPST_STANDBY); if (d->sndstate != SRT_GST_RUNNING) { LOGC(gslog.Error, log << "grp/sendBackup: IPE: misidentified a non-running link @" << d->id << " as active"); continue; } d->sndstate = SRT_GST_IDLE; } } int CUDTGroup::sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc) { if (len <= 0) { throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } // Only live streaming is supported if (len > SRT_LIVE_MAX_PLSIZE) { LOGC(gslog.Error, log << "grp/send(backup): buffer size=" << len << " exceeds maximum allowed in live mode"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } // [[using assert(this->m_pSndBuffer != nullptr)]]; // First, acquire GlobControlLock to make sure all member sockets still exist enterCS(m_pGlobal->m_GlobControlLock); ScopedLock guard(m_GroupLock); if (m_bClosing) { leaveCS(m_pGlobal->m_GlobControlLock); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // Now, still under lock, check if all sockets still can be dispatched send_CheckValidSockets(); leaveCS(m_pGlobal->m_GlobControlLock); steady_clock::time_point currtime = steady_clock::now(); SendBackupCtx sendBackupCtx; // default initialized as empty // TODO: reserve? sendBackupCtx.memberStates.reserve(m_Group.size()); sendBackup_QualifyMemberStates((sendBackupCtx), currtime); int32_t curseq = SRT_SEQNO_NONE; size_t nsuccessful = 0; SRT_ATR_UNUSED CUDTException cx(MJ_SUCCESS, MN_NONE, 0); // TODO: Delete then? uint16_t maxActiveWeight = 0; // Maximum weight of active links. // The number of bytes sent or -1 for error will be stored in group_send_result int group_send_result = sendBackup_SendOverActive(buf, len, w_mc, currtime, (curseq), (nsuccessful), (maxActiveWeight), (sendBackupCtx), (cx)); bool none_succeeded = (nsuccessful == 0); // Save current payload in group's sender buffer. sendBackup_Buffering(buf, len, (curseq), (w_mc)); sendBackup_TryActivateStandbyIfNeeded(buf, len, (none_succeeded), (w_mc), (curseq), (group_send_result), (sendBackupCtx), (cx), currtime); sendBackup_CheckPendingSockets((sendBackupCtx), currtime); sendBackup_CheckUnstableSockets((sendBackupCtx), currtime); //LOGC(gslog.Debug, log << "grp/sendBackup: links after all checks: " << sendBackupCtx.printMembers()); // Re-check after the waiting lock has been reacquired if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); sendBackup_CloseBrokenSockets((sendBackupCtx)); // Re-check after the waiting lock has been reacquired if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); // If all links out of the unstable-running links are blocked (SRT_EASYNCSND), // perform epoll wait on them. In this situation we know that // there are no idle blocked links because IDLE LINK CAN'T BE BLOCKED, // no matter what. It's because the link may only be blocked if // the sender buffer of this socket is full, and it can't be // full if it wasn't used so far. // // This means that in case when we have no stable links, we // need to try out any link that can accept the rexmit-load. // We'll check link stability at the next sending attempt. sendBackup_RetryWaitBlocked((sendBackupCtx), (group_send_result), (none_succeeded), (w_mc), (cx)); sendBackup_SilenceRedundantLinks((sendBackupCtx), currtime); // (closing condition checked inside this call) if (none_succeeded) { HLOGC(gslog.Debug, log << "grp/sendBackup: all links broken (none succeeded to send a payload)"); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); // Reparse error code, if set. // It might be set, if the last operation was failed. // If any operation succeeded, this will not be executed anyway. throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } // At least one link has succeeded, update sending stats. m_stats.sent.Update(len); // Now fill in the socket table. Check if the size is enough, if not, // then set the pointer to NULL and set the correct size. // Note that list::size() is linear time, however this shouldn't matter, // as with the increased number of links in the redundancy group the // impossibility of using that many of them grows exponentally. const size_t grpsize = m_Group.size(); if (w_mc.grpdata_size < grpsize) { w_mc.grpdata = NULL; } size_t i = 0; bool ready_again = false; HLOGC(gslog.Debug, log << "grp/sendBackup: copying group data"); for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d, ++i) { if (w_mc.grpdata) { // Enough space to fill copyGroupData(*d, (w_mc.grpdata[i])); } // We perform this loop anyway because we still need to check if any // socket is writable. Note that the group lock will hold any write ready // updates that are performed just after a single socket update for the // group, so if any socket is actually ready at the moment when this // is performed, and this one will result in none-write-ready, this will // be fixed just after returning from this function. ready_again = ready_again || d->ps->writeReady(); } w_mc.grpdata_size = i; if (!ready_again) { m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); } HLOGC(gslog.Debug, log << "grp/sendBackup: successfully sent " << group_send_result << " bytes, " << (ready_again ? "READY for next" : "NOT READY to send next")); return group_send_result; } // [[using locked(this->m_GroupLock)]] int32_t CUDTGroup::addMessageToBuffer(const char* buf, size_t len, SRT_MSGCTRL& w_mc) { if (m_iSndAckedMsgNo == SRT_MSGNO_NONE) { // Very first packet, just set the msgno. m_iSndAckedMsgNo = w_mc.msgno; m_iSndOldestMsgNo = w_mc.msgno; HLOGC(gslog.Debug, log << "addMessageToBuffer: initial message no #" << w_mc.msgno); } else if (m_iSndOldestMsgNo != m_iSndAckedMsgNo) { int offset = MsgNo(m_iSndAckedMsgNo) - MsgNo(m_iSndOldestMsgNo); HLOGC(gslog.Debug, log << "addMessageToBuffer: new ACK-ed messages: #(" << m_iSndOldestMsgNo << "-" << m_iSndAckedMsgNo << ") - going to remove"); if (offset > int(m_SenderBuffer.size())) { LOGC(gslog.Error, log << "addMessageToBuffer: IPE: offset=" << offset << " exceeds buffer size=" << m_SenderBuffer.size() << " - CLEARING"); m_SenderBuffer.clear(); } else { HLOGC(gslog.Debug, log << "addMessageToBuffer: erasing " << offset << "/" << m_SenderBuffer.size() << " group-senderbuffer ACKED messages for #" << m_iSndOldestMsgNo << " - #" << m_iSndAckedMsgNo); m_SenderBuffer.erase(m_SenderBuffer.begin(), m_SenderBuffer.begin() + offset); } // Position at offset is not included m_iSndOldestMsgNo = m_iSndAckedMsgNo; HLOGC(gslog.Debug, log << "addMessageToBuffer: ... after: oldest #" << m_iSndOldestMsgNo); } m_SenderBuffer.resize(m_SenderBuffer.size() + 1); BufferedMessage& bm = m_SenderBuffer.back(); bm.mc = w_mc; bm.copy(buf, len); HLOGC(gslog.Debug, log << "addMessageToBuffer: #" << w_mc.msgno << " size=" << len << " !" << BufferStamp(buf, len)); return m_SenderBuffer.front().mc.pktseq; } int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& w_mc, const steady_clock::time_point& currtime, int32_t& w_curseq, size_t& w_nsuccessful, uint16_t& w_maxActiveWeight, SendBackupCtx& w_sendBackupCtx, CUDTException& w_cx) { if (w_mc.srctime == 0) w_mc.srctime = count_microseconds(currtime.time_since_epoch()); SRT_ASSERT(w_nsuccessful == 0); SRT_ASSERT(w_maxActiveWeight == 0); int group_send_result = SRT_ERROR; // TODO: implement iterator over active links typedef vector::const_iterator const_iter_t; for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) { if (!isStateActive(member->state)) continue; SocketData* d = member->pSocketData; int erc = SRT_SUCCESS; // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. CUDT& u = d->ps->core(); const int32_t lastseq = u.schedSeqNo(); int sndresult = SRT_ERROR; try { // This must be wrapped in try-catch because on error it throws an exception. // Possible return values are only 0, in case when len was passed 0, or a positive // >0 value that defines the size of the data that it has sent, that is, in case // of Live mode, equal to 'len'. sndresult = u.sendmsg2(buf, len, (w_mc)); } catch (CUDTException& e) { w_cx = e; erc = e.getErrorCode(); sndresult = SRT_ERROR; } const bool send_succeeded = sendBackup_CheckSendStatus( currtime, sndresult, lastseq, w_mc.pktseq, (u), (w_curseq), (group_send_result)); if (send_succeeded) { ++w_nsuccessful; w_maxActiveWeight = max(w_maxActiveWeight, d->weight); } else if (erc == SRT_EASYNCSND) { sendBackup_AssignBackupState(u, BKUPST_ACTIVE_UNSTABLE, currtime); w_sendBackupCtx.updateMemberState(d, BKUPST_ACTIVE_UNSTABLE); } d->sndresult = sndresult; d->laststatus = d->ps->getStatus(); } return group_send_result; } // [[using locked(this->m_GroupLock)]] int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) { // This should resend all packets if (m_SenderBuffer.empty()) { LOGC(gslog.Fatal, log << "IPE: sendBackupRexmit: sender buffer empty"); // Although act as if it was successful, otherwise you'll get connection break return 0; } // using [[assert !m_SenderBuffer.empty()]]; // Send everything you currently have in the sender buffer. // The receiver will reject packets that it currently has. // Start from the oldest. CPacket packet; set results; int stat = -1; // Make sure that the link has correctly synchronized sequence numbers. // Note that sequence numbers should be recorded in mc. int32_t curseq = m_SenderBuffer[0].mc.pktseq; size_t skip_initial = 0; if (curseq != core.schedSeqNo()) { const int distance = CSeqNo::seqoff(core.schedSeqNo(), curseq); if (distance < 0) { // This may happen in case when the link to be activated is already running. // Getting sequences backwards is not allowed, as sending them makes no // sense - they are already ACK-ed or are behind the ISN. Instead, skip all // packets that are in the past towards the scheduling sequence. skip_initial = -distance; LOGC(gslog.Warn, log << "sendBackupRexmit: OVERRIDE attempt. Link seqno %" << core.schedSeqNo() << ", trying to send from seqno %" << curseq << " - DENIED; skip " << skip_initial << " pkts, " << m_SenderBuffer.size() << " pkts in buffer"); } else { // In case when the next planned sequence on this link is behind // the firstmost sequence in the backup buffer, synchronize the // sequence with it first so that they go hand-in-hand with // sequences already used by the link from which packets were // copied to the backup buffer. IF_HEAVY_LOGGING(int32_t old = core.schedSeqNo()); const bool su SRT_ATR_UNUSED = core.overrideSndSeqNo(curseq); HLOGC(gslog.Debug, log << "sendBackupRexmit: OVERRIDING seq %" << old << " with %" << curseq << (su ? " - succeeded" : " - FAILED!")); } } if (skip_initial >= m_SenderBuffer.size()) { LOGC(gslog.Warn, log << "sendBackupRexmit: All packets were skipped. Nothing to send %" << core.schedSeqNo() << ", trying to send from seqno %" << curseq << " - DENIED; skip " << skip_initial << " packets"); return 0; // can't return any other state, nothing was sent } senderBuffer_t::iterator i = m_SenderBuffer.begin() + skip_initial; // Send everything - including the packet freshly added to the buffer for (; i != m_SenderBuffer.end(); ++i) { // NOTE: an exception from here will interrupt the loop // and will be caught in the upper level. stat = core.sendmsg2(i->data, i->size, (i->mc)); if (stat == -1) { // Stop sending if one sending ended up with error LOGC(gslog.Warn, log << "sendBackupRexmit: sending from buffer stopped at %" << core.schedSeqNo() << " and FAILED"); return -1; } } // Copy the contents of the last item being updated. w_mc = m_SenderBuffer.back().mc; HLOGC(gslog.Debug, log << "sendBackupRexmit: pre-sent collected %" << curseq << " - %" << w_mc.pktseq); return stat; } // [[using locked(CUDTGroup::m_GroupLock)]]; void CUDTGroup::ackMessage(int32_t msgno) { // The message id could not be identified, skip. if (msgno == SRT_MSGNO_CONTROL) { HLOGC(gslog.Debug, log << "ackMessage: msgno not found in ACK-ed sequence"); return; } // It's impossible to get the exact message position as the // message is allowed also to span for multiple packets. // Search since the oldest packet until you hit the first // packet with this message number. // First, you need to decrease the message number by 1. It's // because the sequence number being ACK-ed can be in the middle // of the message, while it doesn't acknowledge that the whole // message has been received. Decrease the message number so that // partial-message-acknowledgement does not swipe the whole message, // part of which may need to be retransmitted over a backup link. int offset = MsgNo(msgno) - MsgNo(m_iSndAckedMsgNo); if (offset <= 0) { HLOGC(gslog.Debug, log << "ackMessage: already acked up to msgno=" << msgno); return; } HLOGC(gslog.Debug, log << "ackMessage: updated to #" << msgno); // Update last acked. Will be picked up when adding next message. m_iSndAckedMsgNo = msgno; } void CUDTGroup::handleKeepalive(CUDTGroup::SocketData* gli) { // received keepalive for that group member // In backup group it means that the link went IDLE. if (m_type == SRT_GTYPE_BACKUP) { if (gli->rcvstate == SRT_GST_RUNNING) { gli->rcvstate = SRT_GST_IDLE; HLOGC(gslog.Debug, log << "GROUP: received KEEPALIVE in @" << gli->id << " - link turning rcv=IDLE"); } // When received KEEPALIVE, the sending state should be also // turned IDLE, if the link isn't temporarily activated. The // temporarily activated link will not be measured stability anyway, // while this should clear out the problem when the transmission is // stopped and restarted after a while. This will simply set the current // link as IDLE on the sender when the peer sends a keepalive because the // data stopped coming in and it can't send ACKs therefore. // // This also shouldn't be done for the temporary activated links because // stability timeout could be exceeded for them by a reason that, for example, // the packets come with the past sequences (as they are being synchronized // the sequence per being IDLE and empty buffer), so a large portion of initial // series of packets may come with past sequence, delaying this way with ACK, // which may result not only with exceeded stability timeout (which fortunately // isn't being measured in this case), but also with receiveing keepalive // (therefore we also don't reset the link to IDLE in the temporary activation period). if (gli->sndstate == SRT_GST_RUNNING && is_zero(gli->ps->core().m_tsFreshActivation)) { gli->sndstate = SRT_GST_IDLE; HLOGC(gslog.Debug, log << "GROUP: received KEEPALIVE in @" << gli->id << " active=PAST - link turning snd=IDLE"); } } } void CUDTGroup::internalKeepalive(SocketData* gli) { // This is in response to AGENT SENDING keepalive. This means that there's // no transmission in either direction, but the KEEPALIVE packet from the // other party could have been missed. This is to ensure that the IDLE state // is recognized early enough, before any sequence discrepancy can happen. if (m_type == SRT_GTYPE_BACKUP && gli->rcvstate == SRT_GST_RUNNING) { gli->rcvstate = SRT_GST_IDLE; // Prevent sending KEEPALIVE again in group-sending gli->ps->core().m_tsFreshActivation = steady_clock::time_point(); HLOGC(gslog.Debug, log << "GROUP: EXP-requested KEEPALIVE in @" << gli->id << " - link turning IDLE"); } } CUDTGroup::BufferedMessageStorage CUDTGroup::BufferedMessage::storage(SRT_LIVE_MAX_PLSIZE /*, 1000*/); int CUDTGroup::configure(const char* str) { string config = str; switch (type()) { /* TMP review stub case SRT_GTYPE_BALANCING: // config contains the algorithm name if (config == "" || config == "auto") { m_cbSelectLink.set(this, &CUDTGroup::linkSelect_window_fw); HLOGC(gmlog.Debug, log << "group(balancing): WINDOW algorithm selected"); } else if (config == "fixed") { m_cbSelectLink.set(this, &CUDTGroup::linkSelect_fixed_fw); HLOGC(gmlog.Debug, log << "group(balancing): FIXED algorithm selected"); } else { LOGC(gmlog.Error, log << "group(balancing): unknown selection algorithm '" << config << "'"); return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } break;*/ case SRT_GTYPE_BROADCAST: case SRT_GTYPE_BACKUP: default: if (config == "") { // You can always call the config with empty string, // it should set defaults or do nothing, if not supported. return 0; } LOGC(gmlog.Error, log << "this group type doesn't support any configuration"); return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } return 0; } // Forwarder needed due to class definition order int32_t CUDTGroup::generateISN() { return CUDT::generateISN(); } void CUDTGroup::setGroupConnected() { if (!m_bConnected) { // Switch to connected state and give appropriate signal m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_CONNECT, true); m_bConnected = true; } } void CUDTGroup::updateLatestRcv(CUDTSocket* s) { // Currently only Backup groups use connected idle links. if (m_type != SRT_GTYPE_BACKUP) return; HLOGC(grlog.Debug, log << "updateLatestRcv: BACKUP group, updating from active link @" << s->m_SocketID << " with %" << s->core().m_iRcvLastSkipAck); CUDT* source = &s->core(); vector targets; UniqueLock lg(m_GroupLock); // Sanity check for a case when getting a deleted socket if (!s->m_GroupOf) return; // Under a group lock, we block execution of removal of the socket // from the group, so if m_GroupOf is not NULL, we are granted // that m_GroupMemberData is valid. SocketData* current = s->m_GroupMemberData; for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { // Skip the socket that has reported packet reception if (&*gi == current) { HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq on self @" << gi->id); continue; } // Don't update the state if the link is: // - PENDING - because it's not in the connected state, wait for it. // - RUNNING - because in this case it should have its own line of sequences // - BROKEN - because it doesn't make sense anymore, about to be removed if (gi->rcvstate != SRT_GST_IDLE) { HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq on @" << gi->id << " - link state:" << srt_log_grp_state[gi->rcvstate]); continue; } // Sanity check if (!gi->ps->core().m_bConnected) { HLOGC(grlog.Debug, log << "grp: IPE: NOT updating rcv-seq on @" << gi->id << " - IDLE BUT NOT CONNECTED"); continue; } targets.push_back(&gi->ps->core()); } lg.unlock(); // Do this on the unlocked group because this // operation will need receiver lock, so it might // risk a deadlock. for (size_t i = 0; i < targets.size(); ++i) { targets[i]->updateIdleLinkFrom(source); } } void CUDTGroup::activateUpdateEvent(bool still_have_items) { // This function actually reacts on the fact that a socket // was deleted from the group. This might make the group empty. if (!still_have_items) // empty, or removal of unknown socket attempted - set error on group { m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); } else { m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_UPDATE, true); } } void CUDTGroup::addEPoll(int eid) { enterCS(m_pGlobal->m_EPoll.m_EPollLock); m_sPollID.insert(eid); leaveCS(m_pGlobal->m_EPoll.m_EPollLock); bool any_read = false; bool any_write = false; bool any_broken = false; bool any_pending = false; { // Check all member sockets ScopedLock gl(m_GroupLock); // We only need to know if there is any socket that is // ready to get a payload and ready to receive from. for (gli_t i = m_Group.begin(); i != m_Group.end(); ++i) { if (i->sndstate == SRT_GST_IDLE || i->sndstate == SRT_GST_RUNNING) { any_write |= i->ps->writeReady(); } if (i->rcvstate == SRT_GST_IDLE || i->rcvstate == SRT_GST_RUNNING) { any_read |= i->ps->readReady(); } if (i->ps->broken()) any_broken |= true; else any_pending |= true; } } // This is stupid, but we don't have any other interface to epoll // internals. Actually we don't have to check if id() is in m_sPollID // because we know it is, as we just added it. But it's not performance // critical, sockets are not being often added during transmission. if (any_read) m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, true); if (any_write) m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, true); // Set broken if none is non-broken (pending, read-ready or write-ready) if (any_broken && !any_pending) m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); } void CUDTGroup::removeEPollEvents(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); m_pGlobal->m_EPoll.update_events(id(), remove, SRT_EPOLL_IN | SRT_EPOLL_OUT, false); } void CUDTGroup::removeEPollID(const int eid) { enterCS(m_pGlobal->m_EPoll.m_EPollLock); m_sPollID.erase(eid); leaveCS(m_pGlobal->m_EPoll.m_EPollLock); } void CUDTGroup::updateFailedLink() { ScopedLock lg(m_GroupLock); // Check all members if they are in the pending // or connected state. int nhealthy = 0; for (gli_t i = m_Group.begin(); i != m_Group.end(); ++i) { if (i->sndstate < SRT_GST_BROKEN) nhealthy++; } if (!nhealthy) { // No healthy links, set ERR on epoll. HLOGC(gmlog.Debug, log << "group/updateFailedLink: All sockets broken"); m_pGlobal->m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); } else { HLOGC(gmlog.Debug, log << "group/updateFailedLink: Still " << nhealthy << " links in the group"); } } #if ENABLE_HEAVY_LOGGING // [[using maybe_locked(CUDT::s_UDTUnited.m_GlobControlLock)]] void CUDTGroup::debugGroup() { ScopedLock gg(m_GroupLock); HLOGC(gmlog.Debug, log << "GROUP MEMBER STATUS - $" << id()); for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { HLOGC(gmlog.Debug, log << " ... id { agent=@" << gi->id << " peer=@" << gi->ps->m_PeerID << " } address { agent=" << gi->agent.str() << " peer=" << gi->peer.str() << "} " << " state {snd=" << StateStr(gi->sndstate) << " rcv=" << StateStr(gi->rcvstate) << "}"); } } #endif } // namespace srt srt-1.4.4/srtcore/group.h000066400000000000000000000734131412557703600153400ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2020 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_GROUP_H #define INC_SRT_GROUP_H #include "srt.h" #include "common.h" #include "packet.h" #include "group_common.h" #include "group_backup.h" namespace srt { #if ENABLE_HEAVY_LOGGING const char* const srt_log_grp_state[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; #endif class CUDTGroup { friend class CUDTUnited; typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; typedef sync::steady_clock steady_clock; typedef groups::SocketData SocketData; typedef groups::SendBackupCtx SendBackupCtx; typedef groups::BackupMemberState BackupMemberState; public: typedef SRT_MEMBERSTATUS GroupState; // Note that the use of states may differ in particular group types: // // Broadcast: links that are freshly connected become PENDING and then IDLE only // for a short moment to be activated immediately at the nearest sending operation. // // Balancing: like with broadcast, just that the link activation gets its shared percentage // of traffic balancing // // Multicast: The link is never idle. The data are always sent over the UDP multicast link // and the receiver simply gets subscribed and reads packets once it's ready. // // Backup: The link stays idle until it's activated, and the activation can only happen // at the moment when the currently active link is "suspected of being likely broken" // (the current active link fails to receive ACK in a time when two ACKs should already // be received). After a while when the current active link is confirmed broken, it turns // into broken state. static const char* StateStr(GroupState); static int32_t s_tokenGen; static int32_t genToken() { ++s_tokenGen; if (s_tokenGen < 0) s_tokenGen = 0; return s_tokenGen;} struct ConfigItem { SRT_SOCKOPT so; std::vector value; template bool get(T& refr) { if (sizeof(T) > value.size()) return false; refr = *(T*)&value[0]; return true; } ConfigItem(SRT_SOCKOPT o, const void* val, int size) : so(o) { value.resize(size); unsigned char* begin = (unsigned char*)val; std::copy(begin, begin + size, value.begin()); } struct OfType { SRT_SOCKOPT so; OfType(SRT_SOCKOPT soso) : so(soso) { } bool operator()(ConfigItem& ci) { return ci.so == so; } }; }; typedef std::list group_t; typedef group_t::iterator gli_t; typedef std::vector< std::pair > sendable_t; struct Sendstate { SRTSOCKET id; SocketData* mb; int stat; int code; }; CUDTGroup(SRT_GROUP_TYPE); ~CUDTGroup(); SocketData* add(SocketData data); struct HaveID { SRTSOCKET id; HaveID(SRTSOCKET sid) : id(sid) { } bool operator()(const SocketData& s) { return s.id == id; } }; bool contains(SRTSOCKET id, SocketData*& w_f) { srt::sync::ScopedLock g(m_GroupLock); gli_t f = std::find_if(m_Group.begin(), m_Group.end(), HaveID(id)); if (f == m_Group.end()) { w_f = NULL; return false; } w_f = &*f; return true; } // NEED LOCKING gli_t begin() { return m_Group.begin(); } gli_t end() { return m_Group.end(); } /// Remove the socket from the group container. /// REMEMBER: the group spec should be taken from the socket /// (set m_GroupOf and m_GroupMemberData to NULL /// PRIOR TO calling this function. /// @param id Socket ID to look for in the container to remove /// @return true if the container still contains any sockets after the operation bool remove(SRTSOCKET id) { using srt_logging::gmlog; srt::sync::ScopedLock g(m_GroupLock); bool empty = false; HLOGC(gmlog.Debug, log << "group/remove: going to remove @" << id << " from $" << m_GroupID); gli_t f = std::find_if(m_Group.begin(), m_Group.end(), HaveID(id)); if (f != m_Group.end()) { m_Group.erase(f); // Reset sequence numbers on a dead group so that they are // initialized anew with the new alive connection within // the group. // XXX The problem is that this should be done after the // socket is considered DISCONNECTED, not when it's being // closed. After being disconnected, the sequence numbers // are no longer valid, and will be reinitialized when the // socket is connected again. This may stay as is for now // as in SRT it's not predicted to do anything with the socket // that was disconnected other than immediately closing it. if (m_Group.empty()) { // When the group is empty, there's no danger that this // number will collide with any ISN provided by a socket. // Also since now every socket will derive this ISN. m_iLastSchedSeqNo = generateISN(); resetInitialRxSequence(); empty = true; } } else { HLOGC(gmlog.Debug, log << "group/remove: IPE: id @" << id << " NOT FOUND"); empty = true; // not exactly true, but this is to cause error on group in the APP } if (m_Group.empty()) { m_bOpened = false; m_bConnected = false; } // XXX BUGFIX m_Positions.erase(id); return !empty; } bool groupEmpty() { srt::sync::ScopedLock g(m_GroupLock); return m_Group.empty(); } void setGroupConnected(); int send(const char* buf, int len, SRT_MSGCTRL& w_mc); int sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc); int sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc); static int32_t generateISN(); private: // For Backup, sending all previous packet int sendBackupRexmit(srt::CUDT& core, SRT_MSGCTRL& w_mc); // Support functions for sendBackup and sendBroadcast /// Check if group member is idle. /// @param d group member /// @param[in,out] w_wipeme array of sockets to remove from group /// @param[in,out] w_pendingLinks array of sockets pending for connection /// @returns true if d is idle (standby), false otherwise bool send_CheckIdle(const gli_t d, std::vector& w_wipeme, std::vector& w_pendingLinks); /// This function checks if the member has just become idle (check if sender buffer is empty) to send a KEEPALIVE immidiatelly. /// @todo Check it is some abandoned logic. void sendBackup_CheckIdleTime(gli_t w_d); /// Qualify states of member links. /// [[using locked(this->m_GroupLock, m_pGlobal->m_GlobControlLock)]] /// @param[out] w_sendBackupCtx the context will be updated with state qualifications /// @param[in] currtime current timestamp void sendBackup_QualifyMemberStates(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime); void sendBackup_AssignBackupState(srt::CUDT& socket, BackupMemberState state, const steady_clock::time_point& currtime); /// Qualify the state of the active link: fresh, stable, unstable, wary. /// @retval active backup member state: fresh, stable, unstable, wary. BackupMemberState sendBackup_QualifyActiveState(const gli_t d, const time_point currtime); BackupMemberState sendBackup_QualifyIfStandBy(const gli_t d); /// Sends the same payload over all active members. /// @param[in] buf payload /// @param[in] len payload length in bytes /// @param[in,out] w_mc message control /// @param[in] currtime current time /// @param[in] currseq current packet sequence number /// @param[out] w_nsuccessful number of members with successfull sending. /// @param[in,out] maxActiveWeight /// @param[in,out] sendBackupCtx context /// @param[in,out] w_cx error /// @return group send result: -1 if sending over all members has failed; number of bytes sent overwise. int sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& w_mc, const steady_clock::time_point& currtime, int32_t& w_curseq, size_t& w_nsuccessful, uint16_t& w_maxActiveWeight, SendBackupCtx& w_sendBackupCtx, CUDTException& w_cx); /// Check link sending status /// @param[in] currtime Current time (logging only) /// @param[in] send_status Result of sending over the socket /// @param[in] lastseq Last sent sequence number before the current sending operation /// @param[in] pktseq Packet sequence number currently tried to be sent /// @param[out] w_u CUDT unit of the current member (to allow calling overrideSndSeqNo) /// @param[out] w_curseq Group's current sequence number (either -1 or the value used already for other links) /// @param[out] w_final_stat w_final_stat = send_status if sending succeded. /// /// @returns true if the sending operation result (submitted in stat) is a success, false otherwise. bool sendBackup_CheckSendStatus(const time_point& currtime, const int send_status, const int32_t lastseq, const int32_t pktseq, CUDT& w_u, int32_t& w_curseq, int& w_final_stat); void sendBackup_Buffering(const char* buf, const int len, int32_t& curseq, SRT_MSGCTRL& w_mc); size_t sendBackup_TryActivateStandbyIfNeeded( const char* buf, const int len, bool& w_none_succeeded, SRT_MSGCTRL& w_mc, int32_t& w_curseq, int32_t& w_final_stat, SendBackupCtx& w_sendBackupCtx, CUDTException& w_cx, const steady_clock::time_point& currtime); /// Check if pending sockets are to be qualified as broken. /// This qualification later results in removing the socket from a group and closing it. /// @param[in,out] a context with a list of member sockets, some pending might qualified broken void sendBackup_CheckPendingSockets(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime); /// Check if unstable sockets are to be qualified as broken. /// The main reason for such qualification is if a socket is unstable for too long. /// This qualification later results in removing the socket from a group and closing it. /// @param[in,out] a context with a list of member sockets, some pending might qualified broken void sendBackup_CheckUnstableSockets(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime); /// @brief Marks broken sockets as closed. Used in broadcast sending. /// @param w_wipeme a list of sockets to close void send_CloseBrokenSockets(std::vector& w_wipeme); /// @brief Marks broken sockets as closed. Used in backup sending. /// @param w_sendBackupCtx the context with a list of broken sockets void sendBackup_CloseBrokenSockets(SendBackupCtx& w_sendBackupCtx); void sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx, int& w_final_stat, bool& w_none_succeeded, SRT_MSGCTRL& w_mc, CUDTException& w_cx); void sendBackup_SilenceRedundantLinks(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime); void send_CheckValidSockets(); public: int recv(char* buf, int len, SRT_MSGCTRL& w_mc); void close(); void setOpt(SRT_SOCKOPT optname, const void* optval, int optlen); void getOpt(SRT_SOCKOPT optName, void* optval, int& w_optlen); void deriveSettings(srt::CUDT* source); bool applyFlags(uint32_t flags, HandshakeSide); SRT_SOCKSTATUS getStatus(); void debugMasterData(SRTSOCKET slave); bool isGroupReceiver() { // XXX add here also other group types, which // predict group receiving. return m_type == SRT_GTYPE_BROADCAST; } sync::Mutex* exp_groupLock() { return &m_GroupLock; } void addEPoll(int eid); void removeEPollEvents(const int eid); void removeEPollID(const int eid); void updateReadState(SRTSOCKET sock, int32_t sequence); void updateWriteState(); void updateFailedLink(); void activateUpdateEvent(bool still_have_items); int32_t getRcvBaseSeqNo(); /// Update the in-group array of packet providers per sequence number. /// Also basing on the information already provided by possibly other sockets, /// report the real status of packet loss, including packets maybe lost /// by the caller provider, but already received from elsewhere. Note that /// these packets are not ready for extraction until ACK-ed. /// /// @param exp_sequence The previously received sequence at this socket /// @param sequence The sequence of this packet /// @param provider The core of the socket for which the packet was dispatched /// @param time TSBPD time of this packet /// @return The bitmap that marks by 'false' packets lost since next to exp_sequence std::vector providePacket(int32_t exp_sequence, int32_t sequence, srt::CUDT* provider, uint64_t time); /// This is called from the ACK action by particular socket, which /// actually signs off the packet for extraction. /// /// @param core The socket core for which the ACK was sent /// @param ack The past-the-last-received ACK sequence number void readyPackets(srt::CUDT* core, int32_t ack); void syncWithSocket(const srt::CUDT& core, const HandshakeSide side); int getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize); int getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize); int configure(const char* str); /// Predicted to be called from the reading function to fill /// the group data array as requested. void fillGroupData(SRT_MSGCTRL& w_out, //< MSGCTRL to be written const SRT_MSGCTRL& in //< MSGCTRL read from the data-providing socket ); void copyGroupData(const CUDTGroup::SocketData& source, SRT_SOCKGROUPDATA& w_target); #if ENABLE_HEAVY_LOGGING void debugGroup(); #else void debugGroup() {} #endif void ackMessage(int32_t msgno); void handleKeepalive(SocketData*); void internalKeepalive(SocketData*); private: // Check if there's at least one connected socket. // If so, grab the status of all member sockets. void getGroupCount(size_t& w_size, bool& w_still_alive); class srt::CUDTUnited* m_pGlobal; srt::sync::Mutex m_GroupLock; SRTSOCKET m_GroupID; SRTSOCKET m_PeerGroupID; struct GroupContainer { std::list m_List; /// This field is used only by some types of groups that need /// to keep track as to which link was lately used. Note that /// by removal of a node from the m_List container, this link /// must be appropriately reset. gli_t m_LastActiveLink; GroupContainer() : m_LastActiveLink(m_List.end()) { } // Property active = { m_LastActiveLink; } SRTU_PROPERTY_RW(gli_t, active, m_LastActiveLink); gli_t begin() { return m_List.begin(); } gli_t end() { return m_List.end(); } bool empty() { return m_List.empty(); } void push_back(const SocketData& data) { m_List.push_back(data); } void clear() { m_LastActiveLink = end(); m_List.clear(); } size_t size() { return m_List.size(); } void erase(gli_t it); }; GroupContainer m_Group; bool m_selfManaged; bool m_bSyncOnMsgNo; SRT_GROUP_TYPE m_type; CUDTSocket* m_listener; // A "group" can only have one listener. srt::sync::atomic m_iBusy; CallbackHolder m_cbConnectHook; void installConnectHook(srt_connect_callback_fn* hook, void* opaq) { m_cbConnectHook.set(opaq, hook); } public: void apiAcquire() { ++m_iBusy; } void apiRelease() { --m_iBusy; } // A normal cycle of the send/recv functions is the following: // - [Initial API call for a group] // - GroupKeeper - ctor // - LOCK: GlobControlLock // - Find the group ID in the group container (break if not found) // - LOCK: GroupLock of that group // - Set BUSY flag // - UNLOCK GroupLock // - UNLOCK GlobControlLock // - [Call the sending function (sendBroadcast/sendBackup)] // - LOCK GroupLock // - Preparation activities // - Loop over group members // - Send over a single socket // - Check send status and conditions // - Exit, if nothing else to be done // - Check links to send extra // - UNLOCK GroupLock // - Wait for first ready link // - LOCK GroupLock // - Check status and find sendable link // - Send over a single socket // - Check status and update data // - UNLOCK GroupLock, Exit // - GroupKeeper - dtor // - LOCK GroupLock // - Clear BUSY flag // - UNLOCK GroupLock // END. // // The possibility for isStillBusy to go on is only the following: // 1. Before calling the API function. As GlobControlLock is locked, // the nearest lock on GlobControlLock by GroupKeeper can happen: // - before the group is moved to ClosedGroups (this allows it to be found) // - after the group is moved to ClosedGroups (this makes the group not found) // - NOT after the group was deleted, as it could not be found and occupied. // // 2. Before release of GlobControlLock (acquired by GC), but before the // API function locks GroupLock: // - the GC call to isStillBusy locks GroupLock, but BUSY flag is already set // - GC then avoids deletion of the group // // 3. In any further place up to the exit of the API implementation function, // the BUSY flag is still set. // // 4. After exit of GroupKeeper destructor and unlock of GroupLock // - the group is no longer being accessed and can be freely deleted. // - the group also can no longer be found by ID. bool isStillBusy() { sync::ScopedLock glk(m_GroupLock); return m_iBusy || !m_Group.empty(); } struct BufferedMessageStorage { size_t blocksize; size_t maxstorage; std::vector storage; BufferedMessageStorage(size_t blk, size_t max = 0) : blocksize(blk) , maxstorage(max) , storage() { } char* get() { if (storage.empty()) return new char[blocksize]; // Get the element from the end char* block = storage.back(); storage.pop_back(); return block; } void put(char* block) { if (storage.size() >= maxstorage) { // Simply delete delete[] block; return; } // Put the block into the spare buffer storage.push_back(block); } ~BufferedMessageStorage() { for (size_t i = 0; i < storage.size(); ++i) delete[] storage[i]; } }; struct BufferedMessage { static BufferedMessageStorage storage; SRT_MSGCTRL mc; mutable char* data; size_t size; BufferedMessage() : data() , size() { } ~BufferedMessage() { if (data) storage.put(data); } // NOTE: size 's' must be checked against SRT_LIVE_MAX_PLSIZE // before calling void copy(const char* buf, size_t s) { size = s; data = storage.get(); memcpy(data, buf, s); } BufferedMessage(const BufferedMessage& foreign) : mc(foreign.mc) , data(foreign.data) , size(foreign.size) { foreign.data = 0; } BufferedMessage& operator=(const BufferedMessage& foreign) { data = foreign.data; size = foreign.size; mc = foreign.mc; foreign.data = 0; return *this; } private: void swap_with(BufferedMessage& b) { std::swap(this->mc, b.mc); std::swap(this->data, b.data); std::swap(this->size, b.size); } }; typedef std::deque senderBuffer_t; // typedef StaticBuffer senderBuffer_t; private: // Fields required for SRT_GTYPE_BACKUP groups. senderBuffer_t m_SenderBuffer; int32_t m_iSndOldestMsgNo; // oldest position in the sender buffer volatile int32_t m_iSndAckedMsgNo; uint32_t m_uOPT_StabilityTimeout; // THIS function must be called only in a function for a group type // that does use sender buffer. int32_t addMessageToBuffer(const char* buf, size_t len, SRT_MSGCTRL& w_mc); std::set m_sPollID; // set of epoll ID to trigger int m_iMaxPayloadSize; int m_iAvgPayloadSize; bool m_bSynRecving; bool m_bSynSending; bool m_bTsbPd; bool m_bTLPktDrop; int64_t m_iTsbPdDelay_us; int m_RcvEID; class CEPollDesc* m_RcvEpolld; int m_SndEID; class CEPollDesc* m_SndEpolld; int m_iSndTimeOut; // sending timeout in milliseconds int m_iRcvTimeOut; // receiving timeout in milliseconds // Start times for TsbPd. These times shall be synchronized // between all sockets in the group. The first connected one // defines it, others shall derive it. The value 0 decides if // this has been already set. time_point m_tsStartTime; time_point m_tsRcvPeerStartTime; struct ReadPos { std::vector packet; SRT_MSGCTRL mctrl; ReadPos(int32_t s) : mctrl(srt_msgctrl_default) { mctrl.pktseq = s; } }; std::map m_Positions; ReadPos* checkPacketAhead(); void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); /// The function polls alive member sockets and retrieves a list of read-ready. /// [acquires lock for CUDT::s_UDTUnited.m_GlobControlLock] /// [[using locked(m_GroupLock)]] temporally unlocks-locks internally /// /// @returns list of read-ready sockets /// @throws CUDTException(MJ_CONNECTION, MN_NOCONN, 0) /// @throws CUDTException(MJ_AGAIN, MN_RDAVAIL, 0) std::vector recv_WaitForReadReady(const std::vector& aliveMembers, std::set& w_broken); // This is the sequence number of a packet that has been previously // delivered. Initially it should be set to SRT_SEQNO_NONE so that the sequence read // from the first delivering socket will be taken as a good deal. volatile int32_t m_RcvBaseSeqNo; bool m_bOpened; // Set to true when at least one link is at least pending bool m_bConnected; // Set to true on first link confirmed connected bool m_bClosing; // There's no simple way of transforming config // items that are predicted to be used on socket. // Use some options for yourself, store the others // for setting later on a socket. std::vector m_config; // Signal for the blocking user thread that the packet // is ready to deliver. srt::sync::Condition m_RcvDataCond; srt::sync::Mutex m_RcvDataLock; volatile int32_t m_iLastSchedSeqNo; // represetnts the value of CUDT::m_iSndNextSeqNo for each running socket volatile int32_t m_iLastSchedMsgNo; // Statistics struct Stats { // Stats state time_point tsActivateTime; // Time when this group sent or received the first data packet time_point tsLastSampleTime; // Time reset when clearing stats MetricUsage sent; // number of packets sent from the application MetricUsage recv; // number of packets delivered from the group to the application MetricUsage recvDrop; // number of packets dropped by the group receiver (not received from any member) MetricUsage recvDiscard; // number of packets discarded as already delivered void init() { tsActivateTime = srt::sync::steady_clock::time_point(); sent.Init(); recv.Init(); recvDrop.Init(); recvDiscard.Init(); reset(); } void reset() { sent.Clear(); recv.Clear(); recvDrop.Clear(); recvDiscard.Clear(); tsLastSampleTime = srt::sync::steady_clock::now(); } } m_stats; void updateAvgPayloadSize(int size) { if (m_iAvgPayloadSize == -1) m_iAvgPayloadSize = size; else m_iAvgPayloadSize = avg_iir<4>(m_iAvgPayloadSize, size); } int avgRcvPacketSize() { // In case when no packet has been received yet, but already notified // a dropped packet, its size will be SRT_LIVE_DEF_PLSIZE. It will be // the value most matching in the typical uses, although no matter what // value would be used here, each one would be wrong from some points // of view. This one is simply the best choice for typical uses of groups // provided that they are to be ued only for live mode. return m_iAvgPayloadSize == -1 ? SRT_LIVE_DEF_PLSIZE : m_iAvgPayloadSize; } public: void bstatsSocket(CBytePerfMon* perf, bool clear); // Required after the call on newGroup on the listener side. // On the listener side the group is lazily created just before // accepting a new socket and therefore always open. void setOpen() { m_bOpened = true; } std::string CONID() const { #if ENABLE_LOGGING std::ostringstream os; os << "@" << m_GroupID << ":"; return os.str(); #else return ""; #endif } void resetInitialRxSequence() { // The app-reader doesn't care about the real sequence number. // The first provided one will be taken as a good deal; even if // this is going to be past the ISN, at worst it will be caused // by TLPKTDROP. m_RcvBaseSeqNo = SRT_SEQNO_NONE; } bool applyGroupTime(time_point& w_start_time, time_point& w_peer_start_time) { using srt::sync::is_zero; using srt_logging::gmlog; if (is_zero(m_tsStartTime)) { // The first socket, defines the group time for the whole group. m_tsStartTime = w_start_time; m_tsRcvPeerStartTime = w_peer_start_time; return true; } // Sanity check. This should never happen, fix the bug if found! if (is_zero(m_tsRcvPeerStartTime)) { LOGC(gmlog.Error, log << "IPE: only StartTime is set, RcvPeerStartTime still 0!"); // Kinda fallback, but that's not too safe. m_tsRcvPeerStartTime = w_peer_start_time; } // The redundant connection, derive the times w_start_time = m_tsStartTime; w_peer_start_time = m_tsRcvPeerStartTime; return false; } // Live state synchronization bool getBufferTimeBase(srt::CUDT* forthesakeof, time_point& w_tb, bool& w_wp, duration& w_dr); bool applyGroupSequences(SRTSOCKET, int32_t& w_snd_isn, int32_t& w_rcv_isn); /// @brief Synchronize TSBPD base time and clock drift among members using the @a srcMember as a reference. /// @param srcMember a reference for synchronization. void synchronizeDrift(const srt::CUDT* srcMember); void updateLatestRcv(srt::CUDTSocket*); // Property accessors SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, id, m_GroupID); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, peerid, m_PeerGroupID); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, bool, managed, m_selfManaged); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRT_GROUP_TYPE, type, m_type); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int32_t, currentSchedSequence, m_iLastSchedSeqNo); SRTU_PROPERTY_RRW(std::set&, epollset, m_sPollID); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int64_t, latency, m_iTsbPdDelay_us); SRTU_PROPERTY_RO(bool, synconmsgno, m_bSyncOnMsgNo); SRTU_PROPERTY_RO(bool, closing, m_bClosing); }; } // namespace srt #endif // INC_SRT_GROUP_H srt-1.4.4/srtcore/group_backup.cpp000066400000000000000000000105211412557703600172070ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2021 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Written by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include #include #include "group_backup.h" namespace srt { namespace groups { using namespace std; using namespace srt_logging; const char* stateToStr(BackupMemberState state) { switch (state) { case srt::groups::BKUPST_UNKNOWN: return "UNKNOWN"; case srt::groups::BKUPST_PENDING: return "PENDING"; case srt::groups::BKUPST_STANDBY: return "STANDBY"; case srt::groups::BKUPST_ACTIVE_FRESH: return "ACTIVE_FRESH"; case srt::groups::BKUPST_ACTIVE_STABLE: return "ACTIVE_STABLE"; case srt::groups::BKUPST_ACTIVE_UNSTABLE: return "ACTIVE_UNSTABLE"; case srt::groups::BKUPST_ACTIVE_UNSTABLE_WARY: return "ACTIVE_UNSTABLE_WARY"; case srt::groups::BKUPST_BROKEN: return "BROKEN"; default: break; } return "WRONG_STATE"; } /// @brief Compares group members by their weight (higher weight comes first), then state. /// Higher weight comes first, same weight: stable, then fresh active. struct FCompareByWeight { /// @returns true if the first argument is less than (i.e. is ordered before) the second. bool operator()(const BackupMemberStateEntry& a, const BackupMemberStateEntry& b) { if (a.pSocketData != NULL && b.pSocketData != NULL && (a.pSocketData->weight != b.pSocketData->weight)) return a.pSocketData->weight > b.pSocketData->weight; if (a.state != b.state) { SRT_STATIC_ASSERT(BKUPST_ACTIVE_STABLE > BKUPST_ACTIVE_FRESH, "Wrong ordering"); return a.state > b.state; } // the order does not matter, but comparator must return a different value for not equal a and b return a.socketID < b.socketID; } }; void SendBackupCtx::recordMemberState(SocketData* pSockData, BackupMemberState st) { m_memberStates.push_back(BackupMemberStateEntry(pSockData, st)); ++m_stateCounter[st]; if (st == BKUPST_STANDBY) { m_standbyMaxWeight = max(m_standbyMaxWeight, pSockData->weight); } else if (isStateActive(st)) { m_activeMaxWeight = max(m_activeMaxWeight, pSockData->weight); } } void SendBackupCtx::updateMemberState(const SocketData* pSockData, BackupMemberState st) { typedef vector::iterator iter_t; for (iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i) { if (i->pSocketData == NULL) continue; if (i->pSocketData != pSockData) continue; if (i->state == st) return; --m_stateCounter[i->state]; ++m_stateCounter[st]; i->state = st; return; } LOGC(gslog.Error, log << "IPE: SendBackupCtx::updateMemberState failed to locate member"); } void SendBackupCtx::sortByWeightAndState() { sort(m_memberStates.begin(), m_memberStates.end(), FCompareByWeight()); } BackupMemberState SendBackupCtx::getMemberState(const SocketData* pSockData) const { typedef vector::const_iterator const_iter_t; for (const_iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i) { if (i->pSocketData != pSockData) continue; return i->state; } // The entry was not found // TODO: Maybe throw an exception here? return BKUPST_UNKNOWN; } unsigned SendBackupCtx::countMembersByState(BackupMemberState st) const { return m_stateCounter[st]; } std::string SendBackupCtx::printMembers() const { stringstream ss; typedef vector::const_iterator const_iter_t; for (const_iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i) { ss << "@" << i->socketID << " w " << i->pSocketData->weight << " state " << stateToStr(i->state) << ", "; } return ss.str(); } } // namespace groups } // namespace srt srt-1.4.4/srtcore/group_backup.h000066400000000000000000000073021412557703600166570ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2021 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_GROUP_BACKUP_H #define INC_SRT_GROUP_BACKUP_H #include "srt.h" #include "common.h" #include "group_common.h" #include namespace srt { namespace groups { enum BackupMemberState { BKUPST_UNKNOWN = -1, BKUPST_PENDING = 0, BKUPST_STANDBY = 1, BKUPST_BROKEN = 2, BKUPST_ACTIVE_UNSTABLE = 3, BKUPST_ACTIVE_UNSTABLE_WARY = 4, BKUPST_ACTIVE_FRESH = 5, BKUPST_ACTIVE_STABLE = 6, BKUPST_E_SIZE = 7 }; const char* stateToStr(BackupMemberState state); inline bool isStateActive(BackupMemberState state) { if (state == BKUPST_ACTIVE_FRESH || state == BKUPST_ACTIVE_STABLE || state == BKUPST_ACTIVE_UNSTABLE || state == BKUPST_ACTIVE_UNSTABLE_WARY) { return true; } return false; } struct BackupMemberStateEntry { BackupMemberStateEntry(SocketData* psock, BackupMemberState st) : pSocketData(psock) , socketID(psock->id) , state(st) {} SocketData* pSocketData; // accessing pSocketDataIt requires m_GroupLock SRTSOCKET socketID; // therefore socketID is saved separately (needed to close broken sockets) BackupMemberState state; }; /// @brief A context needed for main/backup sending function. /// @todo Using gli_t here does not allow to safely store the context outside of the sendBackup calls. class SendBackupCtx { public: SendBackupCtx() : m_stateCounter() // default init with zeros , m_activeMaxWeight() , m_standbyMaxWeight() { } /// @brief Adds or updates a record of the member socket state. /// @param pSocketDataIt Iterator to a socket /// @param st State of the memmber socket /// @todo Implement updating member state void recordMemberState(SocketData* pSocketDataIt, BackupMemberState st); /// @brief Updates a record of the member socket state. /// @param pSocketDataIt Iterator to a socket /// @param st State of the memmber socket /// @todo To be replaced by recordMemberState /// @todo Update max weights? void updateMemberState(const SocketData* pSocketDataIt, BackupMemberState st); /// @brief sorts members in order /// Higher weight comes first, same weight: stable first, then fresh active. void sortByWeightAndState(); BackupMemberState getMemberState(const SocketData* pSocketDataIt) const; unsigned countMembersByState(BackupMemberState st) const; const std::vector& memberStates() const { return m_memberStates; } uint16_t maxStandbyWeight() const { return m_standbyMaxWeight; } uint16_t maxActiveWeight() const { return m_activeMaxWeight; } std::string printMembers() const; private: std::vector m_memberStates; // TODO: consider std::map here? unsigned m_stateCounter[BKUPST_E_SIZE]; uint16_t m_activeMaxWeight; uint16_t m_standbyMaxWeight; }; } // namespace groups } // namespace srt #endif // INC_SRT_GROUP_BACKUP_H srt-1.4.4/srtcore/group_common.cpp000066400000000000000000000036731412557703600172440ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2021 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Written by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include "group_common.h" #include "api.h" namespace srt { namespace groups { SocketData prepareSocketData(CUDTSocket* s) { // This uses default SRT_GST_BROKEN because when the group operation is done, // then the SRT_GST_IDLE state automatically turns into SRT_GST_RUNNING. This is // recognized as an initial state of the fresh added socket to the group, // so some "initial configuration" must be done on it, after which it's // turned into SRT_GST_RUNNING, that is, it's treated as all others. When // set to SRT_GST_BROKEN, this socket is disregarded. This socket isn't cleaned // up, however, unless the status is simultaneously SRTS_BROKEN. // The order of operations is then: // - add the socket to the group in this "broken" initial state // - connect the socket (or get it extracted from accept) // - update the socket state (should be SRTS_CONNECTED) // - once the connection is established (may take time with connect), set SRT_GST_IDLE // - the next operation of send/recv will automatically turn it into SRT_GST_RUNNING SocketData sd = { s->m_SocketID, s, -1, SRTS_INIT, SRT_GST_BROKEN, SRT_GST_BROKEN, -1, -1, sockaddr_any(), sockaddr_any(), false, false, false, 0, // weight 0 // pktSndDropTotal }; return sd; } } // namespace groups } // namespace srt srt-1.4.4/srtcore/group_common.h000066400000000000000000000027511412557703600167050ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2021 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_GROUP_COMMON_H #define INC_SRT_GROUP_COMMON_H #include "srt.h" #include "common.h" #include "core.h" #include namespace srt { namespace groups { typedef SRT_MEMBERSTATUS GroupState; struct SocketData { SRTSOCKET id; // same as ps->m_SocketID CUDTSocket* ps; int token; SRT_SOCKSTATUS laststatus; GroupState sndstate; GroupState rcvstate; int sndresult; int rcvresult; sockaddr_any agent; sockaddr_any peer; bool ready_read; bool ready_write; bool ready_error; // Configuration uint16_t weight; // Stats int64_t pktSndDropTotal; }; SocketData prepareSocketData(CUDTSocket* s); typedef std::list group_t; typedef group_t::iterator gli_t; } // namespace groups } // namespace srt #endif // INC_SRT_GROUP_COMMON_H srt-1.4.4/srtcore/handshake.cpp000066400000000000000000000166131412557703600164640ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #include "platform_sys.h" #include #include #include #include #include #include "udt.h" #include "api.h" #include "core.h" #include "handshake.h" #include "utilities.h" using namespace std; using namespace srt; CHandShake::CHandShake() : m_iVersion(0) , m_iType(0) // Universal: UDT_UNDEFINED or no flags , m_iISN(0) , m_iMSS(0) , m_iFlightFlagSize(0) , m_iReqType(URQ_WAVEAHAND) , m_iID(0) , m_iCookie(0) , m_extension(false) { for (int i = 0; i < 4; ++ i) m_piPeerIP[i] = 0; } int CHandShake::store_to(char* buf, size_t& w_size) { if (w_size < m_iContentSize) return -1; int32_t* p = reinterpret_cast(buf); *p++ = m_iVersion; *p++ = m_iType; *p++ = m_iISN; *p++ = m_iMSS; *p++ = m_iFlightFlagSize; *p++ = int32_t(m_iReqType); *p++ = m_iID; *p++ = m_iCookie; for (int i = 0; i < 4; ++ i) *p++ = m_piPeerIP[i]; w_size = m_iContentSize; return 0; } int CHandShake::load_from(const char* buf, size_t size) { if (size < m_iContentSize) return -1; const int32_t* p = reinterpret_cast(buf); m_iVersion = *p++; m_iType = *p++; m_iISN = *p++; m_iMSS = *p++; m_iFlightFlagSize = *p++; m_iReqType = UDTRequestType(*p++); m_iID = *p++; m_iCookie = *p++; for (int i = 0; i < 4; ++ i) m_piPeerIP[i] = *p++; return 0; } #ifdef ENABLE_LOGGING const char* srt_rejectreason_name [] = { "UNKNOWN", "SYSTEM", "PEER", "RESOURCE", "ROGUE", "BACKLOG", "IPE", "CLOSE", "VERSION", "RDVCOOKIE", "BADSECRET", "UNSECURE", "MESSAGEAPI", "CONGESTION", "FILTER", }; std::string RequestTypeStr(UDTRequestType rq) { if (rq >= URQ_FAILURE_TYPES) { std::ostringstream rt; rt << "ERROR:"; int id = RejectReasonForURQ(rq); if (id < SRT_REJ_E_SIZE) rt << srt_rejectreason_name[id]; else if (id < SRT_REJC_USERDEFINED) { if (id < SRT_REJC_PREDEFINED) rt << "UNKNOWN:" << id; else rt << "PREDEFINED:" << (id - SRT_REJC_PREDEFINED); } else rt << "USERDEFINED:" << (id - SRT_REJC_USERDEFINED); return rt.str(); } switch ( rq ) { case URQ_INDUCTION: return "induction"; case URQ_WAVEAHAND: return "waveahand"; case URQ_CONCLUSION: return "conclusion"; case URQ_AGREEMENT: return "agreement"; default: return "INVALID"; } } string CHandShake::RdvStateStr(CHandShake::RendezvousState s) { switch (s) { case RDV_WAVING: return "waving"; case RDV_ATTENTION: return "attention"; case RDV_FINE: return "fine"; case RDV_INITIATED: return "initiated"; case RDV_CONNECTED: return "connected"; default: ; } return "invalid"; } #endif bool CHandShake::valid() { if (m_iVersion < CUDT::HS_VERSION_UDT4 || m_iISN < 0 || m_iISN >= CSeqNo::m_iMaxSeqNo || m_iMSS < 32 || m_iFlightFlagSize < 2) return false; return true; } string CHandShake::show() { ostringstream so; so << "version=" << m_iVersion << " type=0x" << hex << m_iType << dec << " ISN=" << m_iISN << " MSS=" << m_iMSS << " FLW=" << m_iFlightFlagSize << " reqtype=" << RequestTypeStr(m_iReqType) << " srcID=" << m_iID << " cookie=" << hex << m_iCookie << dec << " srcIP="; const unsigned char* p = (const unsigned char*)m_piPeerIP; const unsigned char* pe = p + 4 * (sizeof(uint32_t)); copy(p, pe, ostream_iterator(so, ".")); // XXX HS version symbols should be probably declared inside // CHandShake, not CUDT. if ( m_iVersion > CUDT::HS_VERSION_UDT4 ) { const int flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_iType); so << "FLAGS: "; if (flags == SrtHSRequest::SRT_MAGIC_CODE) so << "MAGIC"; else if (m_iType == 0) so << "NONE"; // no flags and no advertised pbkeylen else so << ExtensionFlagStr(m_iType); } return so.str(); } string CHandShake::ExtensionFlagStr(int32_t fl) { std::ostringstream out; if ( fl & HS_EXT_HSREQ ) out << " hsx"; if ( fl & HS_EXT_KMREQ ) out << " kmx"; if ( fl & HS_EXT_CONFIG ) out << " config"; const int kl = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(fl) << 6; if (kl != 0) { out << " AES-" << kl; } else { out << " no-pbklen"; } return out.str(); } // XXX This code isn't currently used. Left here because it can // be used in future, should any refactoring for the "manual word placement" // code be done. bool SrtHSRequest::serialize(char* buf, size_t size) const { if (size < SRT_HS_SIZE) return false; int32_t* p = reinterpret_cast(buf); *p++ = m_iSrtVersion; *p++ = m_iSrtFlags; *p++ = m_iSrtTsbpd; *p++ = 0; // SURPRISE! Seriously, use (something) if this "reserved" is going to be used for something. return true; } bool SrtHSRequest::deserialize(const char* buf, size_t size) { m_iSrtVersion = 0; // just to let users recognize if it succeeded or not. if (size < SRT_HS_SIZE) return false; const int32_t* p = reinterpret_cast(buf); m_iSrtVersion = (*p++); m_iSrtFlags = (*p++); m_iSrtTsbpd = (*p++); m_iSrtReserved = (*p++); return true; } srt-1.4.4/srtcore/handshake.h000066400000000000000000000332461412557703600161320ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #ifndef INC_SRT_HANDSHAKE_H #define INC_SRT_HANDSHAKE_H #include #include "crypto.h" #include "utilities.h" typedef Bits<31, 16> HS_CMDSPEC_CMD; typedef Bits<15, 0> HS_CMDSPEC_SIZE; // NOTE: Some of these flags represent CAPABILITIES, that is, // as long as these flags are defined, they must be always set // (unless they are deprecated). enum SrtOptions { SRT_OPT_TSBPDSND = BIT(0), /* Timestamp-based Packet delivery real-time data sender */ SRT_OPT_TSBPDRCV = BIT(1), /* Timestamp-based Packet delivery real-time data receiver */ SRT_OPT_HAICRYPT = BIT(2), /* CAPABILITY: HaiCrypt AES-128/192/256-CTR */ SRT_OPT_TLPKTDROP = BIT(3), /* Drop real-time data packets too late to be processed in time */ SRT_OPT_NAKREPORT = BIT(4), /* Periodic NAK report */ SRT_OPT_REXMITFLG = BIT(5), // CAPABILITY: One bit in payload packet msgno is "retransmitted" flag // (this flag can be reused for something else, when pre-1.2.0 versions are all abandoned) SRT_OPT_STREAM = BIT(6), // STREAM MODE (not MESSAGE mode) SRT_OPT_FILTERCAP = BIT(7), // CAPABILITY: Packet filter supported }; inline int SrtVersionCapabilities() { // NOTE: SRT_OPT_REXMITFLG is not included here because // SRT is prepared to handle also peers that don't have this // capability, so a listener responding to a peer that doesn't // support it should NOT set this flag. // // This state will remain until this backward compatibility is // decided to be broken, in which case this flag will be always // set, and clients that do not support this capability will be // rejected. return SRT_OPT_HAICRYPT | SRT_OPT_FILTERCAP; } std::string SrtFlagString(int32_t flags); const int SRT_CMD_REJECT = 0, // REJECT is only a symbol for return type SRT_CMD_HSREQ = 1, SRT_CMD_HSRSP = 2, SRT_CMD_KMREQ = 3, SRT_CMD_KMRSP = 4, SRT_CMD_SID = 5, SRT_CMD_CONGESTION = 6, SRT_CMD_FILTER = 7, SRT_CMD_GROUP = 8, SRT_CMD_NONE = -1; // for cases when {no pong for ping is required} | {no extension block found} enum SrtDataStruct { SRT_HS_VERSION = 0, SRT_HS_FLAGS, SRT_HS_LATENCY, // Keep it always last SRT_HS_E_SIZE }; // For HSv5 the lo and hi part is used for particular side's latency typedef Bits<31, 16> SRT_HS_LATENCY_RCV; typedef Bits<15, 0> SRT_HS_LATENCY_SND; // For HSv4 only the lower part is used. typedef Bits<15, 0> SRT_HS_LATENCY_LEG; struct SrtHandshakeExtension { int16_t type; std::vector contents; SrtHandshakeExtension(int16_t cmd): type(cmd) {} }; // Implemented in core.cpp, so far void SrtExtractHandshakeExtensions(const char* bufbegin, size_t size, std::vector& w_output); struct SrtHSRequest: public SrtHandshakeExtension { typedef Bits<31, 16> SRT_HSTYPE_ENCFLAGS; typedef Bits<15, 0> SRT_HSTYPE_HSFLAGS; // For translating PBKEYLEN into crypto flags // This value is 16, 24, 32; after cutting off // the leftmost 3 bits, it is 2, 3, 4. typedef Bits<5, 3> SRT_PBKEYLEN_BITS; // This value fits ins SRT_HSTYPE_HSFLAGS. // .... HAIVISIOn static const int32_t SRT_MAGIC_CODE = 0x4A17; static int32_t wrapFlags(bool withmagic, int crypto_keylen) { int32_t base = withmagic ? SRT_MAGIC_CODE : 0; return base | SRT_HSTYPE_ENCFLAGS::wrap( SRT_PBKEYLEN_BITS::unwrap(crypto_keylen) ); } // Group handshake extension layout // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Group ID | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Group Type | Group's Flags | Group's Weight | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ typedef Bits<31, 24> HS_GROUP_TYPE; typedef Bits<23, 16> HS_GROUP_FLAGS; typedef Bits<15, 0> HS_GROUP_WEIGHT; private: friend class CHandShake; static const size_t SRT_HS_SIZE = 4*sizeof(uint32_t); // 4 existing fields static const size_t SRT_EXT_HS_SIZE = 2*sizeof(uint32_t) + SRT_HS_SIZE; // SRT magic and SRT HS type, used only in UDT HS ext typedef Bits<15, 0> SRT_TSBPD_DELAY; uint32_t m_iSrtVersion; uint32_t m_iSrtFlags; uint32_t m_iSrtTsbpd; uint32_t m_iSrtReserved; public: SrtHSRequest(): SrtHandshakeExtension(SRT_CMD_HSREQ), m_iSrtVersion(), m_iSrtFlags(), m_iSrtTsbpd(), m_iSrtReserved() {} void setVersion(uint32_t v) { m_iSrtVersion = v; } uint32_t version() const { return m_iSrtVersion; } void setFlag(SrtOptions opt) { m_iSrtFlags |= uint32_t(opt); } void clearFlag(SrtOptions opt) { m_iSrtFlags &= ~opt; } uint32_t flags() const { return m_iSrtFlags; } void setTsbPdDelay(uint16_t delay) { m_iSrtTsbpd |= SRT_TSBPD_DELAY::wrap(delay); } // Unknown what the 1-16 bits have to be used for. uint16_t tsbPdDelay() const { return SRT_TSBPD_DELAY::unwrap(m_iSrtTsbpd); } size_t size() const { return SRT_EXT_HS_SIZE; } bool serialize(char* p, size_t size) const; bool deserialize(const char* mem, size_t size); }; struct SrtKMRequest: public SrtHandshakeExtension { uint32_t m_iKmState; char m_aKey[1]; // dynamic size }; //////////////////////////////////////////////////////////////////////////////// enum UDTRequestType { URQ_INDUCTION_TYPES = 0, // XXX used to check in one place. Consdr rm. URQ_INDUCTION = 1, // First part for client-server connection URQ_WAVEAHAND = 0, // First part for rendezvous connection URQ_CONCLUSION = -1, // Second part of handshake negotiation URQ_AGREEMENT = -2, // Extra (last) step for rendezvous only URQ_DONE = -3, // Special value used only in state-switching, to state that nothing should be sent in response // Note: the client-server connection uses: // --> INDUCTION (empty) // <-- INDUCTION (cookie) // --> CONCLUSION (cookie) // <-- CONCLUSION (ok) // The rendezvous HSv4 (legacy): // --> WAVEAHAND (effective only if peer is also connecting) // <-- CONCLUSION (empty) (consider yourself connected upon reception) // --> AGREEMENT (sent as a response for conclusion, requires no response) // The rendezvous HSv5 (using SRT extensions): // --> WAVEAHAND (with cookie) // --- (selecting INITIATOR/RESPONDER by cookie contest - comparing one another's cookie) // <-- CONCLUSION (without extensions, if RESPONDER, with extensions, if INITIATOR) // --> CONCLUSION (with response extensions, if RESPONDER) // <-- AGREEMENT (sent exclusively by INITIATOR upon reception of CONCLUSIOn with response extensions) // This marks the beginning of values that are error codes. URQ_FAILURE_TYPES = 1000, // NOTE: codes above 1000 are reserved for failure codes for // rejection reason, as per `SRT_REJECT_REASON` enum. The // actual rejection code is the value of the request type // minus URQ_FAILURE_TYPES. // This is in order to return standard error codes for server // data retrieval failures. URQ_SERVER_FAILURE_TYPES = URQ_FAILURE_TYPES + SRT_REJC_PREDEFINED, // This is for a completely user-defined reject reasons. URQ_USER_FAILURE_TYPES = URQ_FAILURE_TYPES + SRT_REJC_USERDEFINED }; inline UDTRequestType URQFailure(int reason) { return UDTRequestType(URQ_FAILURE_TYPES + int(reason)); } inline int RejectReasonForURQ(UDTRequestType req) { if (req < URQ_FAILURE_TYPES) return SRT_REJ_UNKNOWN; int reason = req - URQ_FAILURE_TYPES; if (reason < SRT_REJC_PREDEFINED && reason >= SRT_REJ_E_SIZE) return SRT_REJ_UNKNOWN; return reason; } // DEPRECATED values. Use URQFailure(SRT_REJECT_REASON). const UDTRequestType URQ_ERROR_REJECT SRT_ATR_DEPRECATED = (UDTRequestType)1002; // == 1000 + SRT_REJ_PEER const UDTRequestType URQ_ERROR_INVALID SRT_ATR_DEPRECATED = (UDTRequestType)1004; // == 1000 + SRT_REJ_ROGUE // XXX Change all uses of that field to UDTRequestType when possible #if ENABLE_LOGGING std::string RequestTypeStr(UDTRequestType); #else inline std::string RequestTypeStr(UDTRequestType) { return ""; } #endif class CHandShake { public: CHandShake(); int store_to(char* buf, size_t& size); int load_from(const char* buf, size_t size); public: // This is the size of SERIALIZED handshake. // Might be defined as simply sizeof(CHandShake), but the // enum values would have to be forced as int32_t, which is only // available in C++11. Theoretically they are all 32-bit, but // such a statement is not reliable and not portable. static const size_t m_iContentSize = 48; // Size of hand shake data // Extension flags static const int32_t HS_EXT_HSREQ = BIT(0); static const int32_t HS_EXT_KMREQ = BIT(1); static const int32_t HS_EXT_CONFIG = BIT(2); static std::string ExtensionFlagStr(int32_t fl); // Applicable only when m_iVersion == HS_VERSION_SRT1 int32_t flags() { return m_iType; } public: int32_t m_iVersion; // UDT version (HS_VERSION_* symbols) int32_t m_iType; // UDT4: socket type (only UDT_DGRAM is valid); SRT1: extension flags int32_t m_iISN; // random initial sequence number int32_t m_iMSS; // maximum segment size int32_t m_iFlightFlagSize; // flow control window size UDTRequestType m_iReqType; // handshake stage int32_t m_iID; // socket ID int32_t m_iCookie; // cookie uint32_t m_piPeerIP[4]; // The IP address that the peer's UDP port is bound to bool m_extension; bool valid(); std::string show(); // The rendezvous state machine used in HSv5 only (in HSv4 everything is happening the old way). // // The WAVING state is the very initial state of the rendezvous connection and restored after the // connection is closed. // The ATTENTION and FINE are two alternative states that are transited to from WAVING. The possible // situations are: // - "serial arrangement": one party transits to ATTENTION and the other party transits to FINE // - "parallel arrangement" both parties transit to ATTENTION // // Parallel arrangement is a "virtually impossible" case, in which both parties must send the first // URQ_WAVEAHAND message in a perfect time synchronization, when they are started at exactly the same // time, on machines with exactly the same performance and all things preceding the message sending // have taken perfectly identical amount of time. This isn't anyhow possible otherwise because if // the clients have started at different times, the one who started first sends a message and the // system of the receiver buffers this message even before the client binds the port for enough long // time so that it outlasts also the possible second, repeated waveahand. enum RendezvousState { RDV_INVALID, //< This socket wasn't prepared for rendezvous process. Reject any events. RDV_WAVING, //< Initial state for rendezvous. No contact seen from the peer. RDV_ATTENTION, //< When received URQ_WAVEAHAND. [WAVING]:URQ_WAVEAHAND --> [ATTENTION]. RDV_FINE, //< When received URQ_CONCLUSION. [WAVING]:URQ_CONCLUSION --> [FINE]. RDV_INITIATED, //< When received URQ_CONCLUSION+HSREQ extension in ATTENTION state. RDV_CONNECTED //< Final connected state. [ATTENTION]:URQ_CONCLUSION --> [CONNECTED] <-- [FINE]:URQ_AGREEMENT. }; #if ENABLE_LOGGING static std::string RdvStateStr(RendezvousState s); #else static std::string RdvStateStr(RendezvousState) { return ""; } #endif }; #endif srt-1.4.4/srtcore/list.cpp000066400000000000000000000614451412557703600155140ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 01/22/2011 modified by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include "list.h" #include "packet.h" #include "logging.h" // Use "inline namespace" in C++11 namespace srt_logging { extern Logger qrlog; extern Logger qslog; } using srt_logging::qrlog; using srt_logging::qslog; using namespace srt::sync; 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].seqstart = SRT_SEQNO_NONE; m_caSeq[i].seqend = SRT_SEQNO_NONE; } // sender list needs mutex protection setupMutex(m_ListLock, "LossList"); } CSndLossList::~CSndLossList() { delete[] m_caSeq; releaseMutex(m_ListLock); } void CSndLossList::traceState() const { int pos = m_iHead; while (pos != SRT_SEQNO_NONE) { std::cout << pos << ":[" << m_caSeq[pos].seqstart; if (m_caSeq[pos].seqend != SRT_SEQNO_NONE) std::cout << ", " << m_caSeq[pos].seqend; std::cout << "], "; pos = m_caSeq[pos].inext; } std::cout << "\n"; } int CSndLossList::insert(int32_t seqno1, int32_t seqno2) { if (seqno1 < 0 || seqno2 < 0 ) { LOGC(qslog.Error, log << "IPE: Tried to insert negative seqno " << seqno1 << ":" << seqno2 << " into sender's loss list. Ignoring."); return 0; } const int inserted_range = CSeqNo::seqlen(seqno1, seqno2); if (inserted_range <= 0 || inserted_range >= m_iSize) { LOGC(qslog.Error, log << "IPE: Tried to insert too big range of seqno: " << inserted_range << ". Ignoring. " << "seqno " << seqno1 << ":" << seqno2); return 0; } ScopedLock listguard(m_ListLock); if (m_iLength == 0) { insertHead(0, seqno1, seqno2); return m_iLength; } // Find the insert position in the non-empty list const int origlen = m_iLength; const int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqno1); if (offset >= m_iSize) { LOGC(qslog.Error, log << "IPE: New loss record is too far from the first record. Ignoring. " << "First loss seqno " << m_caSeq[m_iHead].seqstart << ", insert seqno " << seqno1 << ":" << seqno2); return 0; } int loc = (m_iHead + offset + m_iSize) % m_iSize; if (loc < 0) { const int offset_seqno2 = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqno2); const int loc_seqno2 = (m_iHead + offset_seqno2 + m_iSize) % m_iSize; if (loc_seqno2 < 0) { // The size of the CSndLossList should be at least the size of the flow window. // It means that all the packets sender has sent should fit within m_iSize. // If the new loss does not fit, there is some error. LOGC(qslog.Error, log << "IPE: New loss record is too old. Ignoring. " << "First loss seqno " << m_caSeq[m_iHead].seqstart << ", insert seqno " << seqno1 << ":" << seqno2); return 0; } loc = loc_seqno2; } if (offset < 0) { insertHead(loc, seqno1, seqno2); } else if (offset > 0) { if (seqno1 == m_caSeq[loc].seqstart) { const bool updated = updateElement(loc, seqno1, seqno2); if (!updated) return 0; } else { // Find the prior node. // It should be the highest sequence number less than seqno1. // 1. Start the search either from m_iHead, or from m_iLastInsertPos int i = m_iHead; if ((m_iLastInsertPos != -1) && (CSeqNo::seqcmp(m_caSeq[m_iLastInsertPos].seqstart, seqno1) < 0)) i = m_iLastInsertPos; // 2. Find the highest sequence number less than seqno1. while (m_caSeq[i].inext != -1 && CSeqNo::seqcmp(m_caSeq[m_caSeq[i].inext].seqstart, seqno1) < 0) i = m_caSeq[i].inext; // 3. Check if seqno1 overlaps with (seqbegin, seqend) const int seqend = m_caSeq[i].seqend == SRT_SEQNO_NONE ? m_caSeq[i].seqstart : m_caSeq[i].seqend; if (CSeqNo::seqcmp(seqend, seqno1) < 0 && CSeqNo::incseq(seqend) != seqno1) { // No overlap // TODO: Here we should actually insert right after i, not at loc. insertAfter(loc, i, seqno1, seqno2); } else { // TODO: Replace with updateElement(i, seqno1, seqno2). // Some changes to updateElement(..) are required. m_iLastInsertPos = i; if (CSeqNo::seqcmp(seqend, seqno2) >= 0) return 0; // overlap, coalesce with prior node, insert(3, 7) to [2, 5], ... becomes [2, 7] m_iLength += CSeqNo::seqlen(seqend, seqno2) - 1; m_caSeq[i].seqend = seqno2; loc = i; } } } else // offset == 0, loc == m_iHead { const bool updated = updateElement(m_iHead, seqno1, seqno2); if (!updated) return 0; } coalesce(loc); return m_iLength - origlen; } void CSndLossList::removeUpTo(int32_t seqno) { ScopedLock 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].seqstart, 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 (SRT_SEQNO_NONE == m_caSeq[m_iHead].seqend) loc = m_caSeq[m_iHead].inext; else { m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); if (CSeqNo::seqcmp(m_caSeq[m_iHead].seqend, CSeqNo::incseq(seqno)) > 0) m_caSeq[loc].seqend = m_caSeq[m_iHead].seqend; m_caSeq[m_iHead].seqend = SRT_SEQNO_NONE; m_caSeq[loc].inext = m_caSeq[m_iHead].inext; } m_caSeq[m_iHead].seqstart = SRT_SEQNO_NONE; 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].seqstart) { // target node is not empty, remove part/all of the seqno in the node. int temp = loc; loc = (loc + 1) % m_iSize; if (SRT_SEQNO_NONE == m_caSeq[temp].seqend) m_iHead = m_caSeq[temp].inext; else { // remove part, e.g., [3, 7] becomes [], [4, 7] after remove(3) m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); if (CSeqNo::seqcmp(m_caSeq[temp].seqend, m_caSeq[loc].seqstart) > 0) m_caSeq[loc].seqend = m_caSeq[temp].seqend; m_iHead = loc; m_caSeq[loc].inext = m_caSeq[temp].inext; m_caSeq[temp].inext = loc; m_caSeq[temp].seqend = SRT_SEQNO_NONE; } } else { // target node is empty, check prior node int i = m_iHead; while ((-1 != m_caSeq[i].inext) && (CSeqNo::seqcmp(m_caSeq[m_caSeq[i].inext].seqstart, seqno) < 0)) i = m_caSeq[i].inext; loc = (loc + 1) % m_iSize; if (SRT_SEQNO_NONE == m_caSeq[i].seqend) m_iHead = m_caSeq[i].inext; else if (CSeqNo::seqcmp(m_caSeq[i].seqend, seqno) > 0) { // remove part/all seqno in the prior node m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); if (CSeqNo::seqcmp(m_caSeq[i].seqend, m_caSeq[loc].seqstart) > 0) m_caSeq[loc].seqend = m_caSeq[i].seqend; m_caSeq[i].seqend = seqno; m_caSeq[loc].inext = m_caSeq[i].inext; m_caSeq[i].inext = loc; m_iHead = loc; } else m_iHead = m_caSeq[i].inext; } // Remove all nodes prior to the new head while (h != m_iHead) { if (m_caSeq[h].seqend != SRT_SEQNO_NONE) { m_iLength -= CSeqNo::seqlen(m_caSeq[h].seqstart, m_caSeq[h].seqend); m_caSeq[h].seqend = SRT_SEQNO_NONE; } else m_iLength--; m_caSeq[h].seqstart = SRT_SEQNO_NONE; if (m_iLastInsertPos == h) m_iLastInsertPos = -1; h = m_caSeq[h].inext; } } } int CSndLossList::getLossLength() const { ScopedLock listguard(m_ListLock); return m_iLength; } int32_t CSndLossList::popLostSeq() { ScopedLock listguard(m_ListLock); if (0 == m_iLength) { SRT_ASSERT(m_iHead == -1); return SRT_SEQNO_NONE; } if (m_iLastInsertPos == m_iHead) m_iLastInsertPos = -1; // return the first loss seq. no. const int32_t seqno = m_caSeq[m_iHead].seqstart; // head moves to the next node if (SRT_SEQNO_NONE == m_caSeq[m_iHead].seqend) { //[3, SRT_SEQNO_NONE] becomes [], and head moves to next node in the list m_caSeq[m_iHead].seqstart = SRT_SEQNO_NONE; m_iHead = m_caSeq[m_iHead].inext; } else { // shift to next node, e.g., [3, 7] becomes [], [4, 7] int loc = (m_iHead + 1) % m_iSize; m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); if (CSeqNo::seqcmp(m_caSeq[m_iHead].seqend, m_caSeq[loc].seqstart) > 0) m_caSeq[loc].seqend = m_caSeq[m_iHead].seqend; m_caSeq[m_iHead].seqstart = SRT_SEQNO_NONE; m_caSeq[m_iHead].seqend = SRT_SEQNO_NONE; m_caSeq[loc].inext = m_caSeq[m_iHead].inext; m_iHead = loc; } m_iLength--; return seqno; } void CSndLossList::insertHead(int pos, int32_t seqno1, int32_t seqno2) { SRT_ASSERT(pos >= 0); m_caSeq[pos].seqstart = seqno1; SRT_ASSERT(m_caSeq[pos].seqend == SRT_SEQNO_NONE); if (seqno2 != seqno1) m_caSeq[pos].seqend = seqno2; // new node becomes head m_caSeq[pos].inext = m_iHead; m_iHead = pos; m_iLastInsertPos = pos; m_iLength += CSeqNo::seqlen(seqno1, seqno2); } void CSndLossList::insertAfter(int pos, int pos_after, int32_t seqno1, int32_t seqno2) { m_caSeq[pos].seqstart = seqno1; SRT_ASSERT(m_caSeq[pos].seqend == SRT_SEQNO_NONE); if (seqno2 != seqno1) m_caSeq[pos].seqend = seqno2; m_caSeq[pos].inext = m_caSeq[pos_after].inext; m_caSeq[pos_after].inext = pos; m_iLastInsertPos = pos; m_iLength += CSeqNo::seqlen(seqno1, seqno2); } void CSndLossList::coalesce(int loc) { // coalesce with next node. E.g., [3, 7], ..., [6, 9] becomes [3, 9] while ((m_caSeq[loc].inext != -1) && (m_caSeq[loc].seqend != SRT_SEQNO_NONE)) { const int i = m_caSeq[loc].inext; if (CSeqNo::seqcmp(m_caSeq[i].seqstart, CSeqNo::incseq(m_caSeq[loc].seqend)) > 0) break; // coalesce if there is overlap if (m_caSeq[i].seqend != SRT_SEQNO_NONE) { if (CSeqNo::seqcmp(m_caSeq[i].seqend, m_caSeq[loc].seqend) > 0) { if (CSeqNo::seqcmp(m_caSeq[loc].seqend, m_caSeq[i].seqstart) >= 0) m_iLength -= CSeqNo::seqlen(m_caSeq[i].seqstart, m_caSeq[loc].seqend); m_caSeq[loc].seqend = m_caSeq[i].seqend; } else m_iLength -= CSeqNo::seqlen(m_caSeq[i].seqstart, m_caSeq[i].seqend); } else { if (m_caSeq[i].seqstart == CSeqNo::incseq(m_caSeq[loc].seqend)) m_caSeq[loc].seqend = m_caSeq[i].seqstart; else m_iLength--; } m_caSeq[i].seqstart = SRT_SEQNO_NONE; m_caSeq[i].seqend = SRT_SEQNO_NONE; m_caSeq[loc].inext = m_caSeq[i].inext; } } bool CSndLossList::updateElement(int pos, int32_t seqno1, int32_t seqno2) { m_iLastInsertPos = pos; if (seqno2 == SRT_SEQNO_NONE || seqno2 == seqno1) return false; if (m_caSeq[pos].seqend == SRT_SEQNO_NONE) { m_iLength += CSeqNo::seqlen(seqno1, seqno2) - 1; m_caSeq[pos].seqend = seqno2; return true; } // seqno2 <= m_caSeq[pos].seqend if (CSeqNo::seqcmp(seqno2, m_caSeq[pos].seqend) <= 0) return false; // seqno2 > m_caSeq[pos].seqend m_iLength += CSeqNo::seqlen(m_caSeq[pos].seqend, seqno2) - 1; m_caSeq[pos].seqend = seqno2; return true; } //////////////////////////////////////////////////////////////////////////////// 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].seqstart = SRT_SEQNO_NONE; m_caSeq[i].seqend = SRT_SEQNO_NONE; } } 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].seqstart = seqno1; if (seqno2 != seqno1) m_caSeq[m_iHead].seqend = seqno2; m_caSeq[m_iHead].inext = -1; m_caSeq[m_iHead].iprior = -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].seqstart, seqno1); if (offset < 0) { LOGC(qrlog.Error, log << "RCV-LOSS/insert: IPE: new LOSS %(" << seqno1 << "-" << seqno2 << ") PREDATES HEAD %" << m_caSeq[m_iHead].seqstart << " -- REJECTING"); return; } int loc = (m_iHead + offset) % m_iSize; if ((SRT_SEQNO_NONE != m_caSeq[m_iTail].seqend) && (CSeqNo::incseq(m_caSeq[m_iTail].seqend) == seqno1)) { // coalesce with prior node, e.g., [2, 5], [6, 7] becomes [2, 7] loc = m_iTail; m_caSeq[loc].seqend = seqno2; } else { // create new node m_caSeq[loc].seqstart = seqno1; if (seqno2 != seqno1) m_caSeq[loc].seqend = seqno2; m_caSeq[m_iTail].inext = loc; m_caSeq[loc].iprior = m_iTail; m_caSeq[loc].inext = -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].seqstart, seqno); if (offset < 0) return false; int loc = (m_iHead + offset) % m_iSize; if (seqno == m_caSeq[loc].seqstart) { // This is a seq. no. that starts the loss sequence if (SRT_SEQNO_NONE == m_caSeq[loc].seqend) { // there is only 1 loss in the sequence, delete it from the node if (m_iHead == loc) { m_iHead = m_caSeq[m_iHead].inext; if (-1 != m_iHead) m_caSeq[m_iHead].iprior = -1; } else { m_caSeq[m_caSeq[loc].iprior].inext = m_caSeq[loc].inext; if (-1 != m_caSeq[loc].inext) m_caSeq[m_caSeq[loc].inext].iprior = m_caSeq[loc].iprior; else m_iTail = m_caSeq[loc].iprior; } m_caSeq[loc].seqstart = SRT_SEQNO_NONE; } 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].seqstart = CSeqNo::incseq(m_caSeq[loc].seqstart); // process the sequence end if (CSeqNo::seqcmp(m_caSeq[loc].seqend, CSeqNo::incseq(m_caSeq[loc].seqstart)) > 0) m_caSeq[i].seqend = m_caSeq[loc].seqend; // remove the current node m_caSeq[loc].seqstart = SRT_SEQNO_NONE; m_caSeq[loc].seqend = SRT_SEQNO_NONE; // update list pointer m_caSeq[i].inext = m_caSeq[loc].inext; m_caSeq[i].iprior = m_caSeq[loc].iprior; if (m_iHead == loc) m_iHead = i; else m_caSeq[m_caSeq[i].iprior].inext = i; if (m_iTail == loc) m_iTail = i; else m_caSeq[m_caSeq[i].inext].iprior = 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 (SRT_SEQNO_NONE == m_caSeq[i].seqstart) i = (i - 1 + m_iSize) % m_iSize; // not contained in this node, return if ((SRT_SEQNO_NONE == m_caSeq[i].seqend) || (CSeqNo::seqcmp(seqno, m_caSeq[i].seqend) > 0)) return false; if (seqno == m_caSeq[i].seqend) { // it is the sequence end if (seqno == CSeqNo::incseq(m_caSeq[i].seqstart)) m_caSeq[i].seqend = SRT_SEQNO_NONE; else m_caSeq[i].seqend = 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].seqstart = CSeqNo::incseq(seqno); if (CSeqNo::seqcmp(m_caSeq[i].seqend, m_caSeq[loc].seqstart) > 0) m_caSeq[loc].seqend = m_caSeq[i].seqend; // the first (original) sequence is between the original sequence start to CSeqNo::decseq(seqno) if (seqno == CSeqNo::incseq(m_caSeq[i].seqstart)) m_caSeq[i].seqend = SRT_SEQNO_NONE; else m_caSeq[i].seqend = CSeqNo::decseq(seqno); // update the list pointer m_caSeq[loc].inext = m_caSeq[i].inext; m_caSeq[i].inext = loc; m_caSeq[loc].iprior = i; if (m_iTail == i) m_iTail = loc; else m_caSeq[m_caSeq[loc].inext].iprior = 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].seqstart, seqno1) == 0) || ((CSeqNo::seqcmp(m_caSeq[p].seqstart, seqno1) > 0) && (CSeqNo::seqcmp(m_caSeq[p].seqstart, seqno2) <= 0)) || ((CSeqNo::seqcmp(m_caSeq[p].seqstart, seqno1) < 0) && (m_caSeq[p].seqend != SRT_SEQNO_NONE) && CSeqNo::seqcmp(m_caSeq[p].seqend, seqno1) >= 0)) return true; p = m_caSeq[p].inext; } return false; } int CRcvLossList::getLossLength() const { return m_iLength; } int32_t CRcvLossList::getFirstLostSeq() const { if (0 == m_iLength) return SRT_SEQNO_NONE; return m_caSeq[m_iHead].seqstart; } 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].seqstart; if (SRT_SEQNO_NONE != m_caSeq[i].seqend) { // there are more than 1 loss in the sequence array[len] |= LOSSDATA_SEQNO_RANGE_FIRST; ++len; array[len] = m_caSeq[i].seqend; } ++len; i = m_caSeq[i].inext; } } CRcvFreshLoss::CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_age) : ttl(initial_age) , timestamp(steady_clock::now()) { 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; } srt-1.4.4/srtcore/list.h000066400000000000000000000213461412557703600151550ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 01/22/2011 modified by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_LIST_H #define INC_SRT_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 the given sequence number and all numbers that precede it. /// @param [in] seqno sequence number. void removeUpTo(int32_t seqno); /// Read the loss length.∠/// @return The length of the list. int getLossLength() const; /// 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 popLostSeq(); void traceState() const; private: struct Seq { int32_t seqstart; // sequence number starts int32_t seqend; // sequence number ends int inext; // index of the next node in the list } * m_caSeq; int m_iHead; // first node int m_iLength; // loss length const int m_iSize; // size of the static array int m_iLastInsertPos; // position of last insert node mutable srt::sync::Mutex m_ListLock; // used to synchronize list operation private: /// Inserts an element to the beginning and updates head pointer. /// No lock. void insertHead(int pos, int32_t seqno1, int32_t seqno2); /// Inserts an element after previous element. /// No lock. void insertAfter(int pos, int pos_after, int32_t seqno1, int32_t seqno2); /// Check if it is possible to coalesce element at loc with further elements. /// @param loc - last changed location void coalesce(int loc); /// Update existing element with the new range (increase only) /// @param pos position of the element being updated /// @param seqno1 first sequence number in range /// @param seqno2 last sequence number in range (SRT_SEQNO_NONE if no range) bool updateElement(int pos, int32_t seqno1, int32_t seqno2); 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. int32_t getFirstLostSeq() const; /// Get a encoded loss array for NAK report. /// @param [out] array the result list of seq. no. to be included in NAK. /// @param [out] len physical length of the result array. /// @param [in] limit maximum length of the array. void getLossArray(int32_t* array, int& len, int limit); private: struct Seq { int32_t seqstart; // sequence number starts int32_t seqend; // sequence number ends int inext; // index of the next node in the list int iprior; // index of the previous 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].inext); } 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].seqstart, seq[head].seqend); } }; iterator begin() { return iterator(m_caSeq, m_iHead); } iterator end() { return iterator(m_caSeq, -1); } }; struct CRcvFreshLoss { int32_t seq[2]; int ttl; srt::sync::steady_clock::time_point timestamp; CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_ttl); // Don't WTF when looking at this. The Windows system headers define // a publicly visible preprocessor macro with that name. REALLY! #ifdef DELETE #undef DELETE #endif 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 srt-1.4.4/srtcore/logger_default.cpp000066400000000000000000000026561412557703600175230ustar00rootroot00000000000000/* WARNING: Generated from ../scripts/generate-logging-defs.tcl DO NOT MODIFY. Copyright applies as per the generator script. */ #include "srt.h" #include "logging.h" #include "logger_defs.h" namespace srt_logging { AllFaOn::AllFaOn() { allfa.set(SRT_LOGFA_GENERAL, true); allfa.set(SRT_LOGFA_SOCKMGMT, true); allfa.set(SRT_LOGFA_CONN, true); allfa.set(SRT_LOGFA_XTIMER, true); allfa.set(SRT_LOGFA_TSBPD, true); allfa.set(SRT_LOGFA_RSRC, true); allfa.set(SRT_LOGFA_CONGEST, true); allfa.set(SRT_LOGFA_PFILTER, true); allfa.set(SRT_LOGFA_API_CTRL, true); allfa.set(SRT_LOGFA_QUE_CTRL, true); allfa.set(SRT_LOGFA_EPOLL_UPD, true); allfa.set(SRT_LOGFA_API_RECV, true); allfa.set(SRT_LOGFA_BUF_RECV, true); allfa.set(SRT_LOGFA_QUE_RECV, true); allfa.set(SRT_LOGFA_CHN_RECV, true); allfa.set(SRT_LOGFA_GRP_RECV, true); allfa.set(SRT_LOGFA_API_SEND, true); allfa.set(SRT_LOGFA_BUF_SEND, true); allfa.set(SRT_LOGFA_QUE_SEND, true); allfa.set(SRT_LOGFA_CHN_SEND, true); allfa.set(SRT_LOGFA_GRP_SEND, true); allfa.set(SRT_LOGFA_INTERNAL, true); allfa.set(SRT_LOGFA_QUE_MGMT, true); allfa.set(SRT_LOGFA_CHN_MGMT, true); allfa.set(SRT_LOGFA_GRP_MGMT, true); allfa.set(SRT_LOGFA_EPOLL_API, true); } } // namespace srt_logging srt-1.4.4/srtcore/logger_defs.cpp000066400000000000000000000043041412557703600170100ustar00rootroot00000000000000/* WARNING: Generated from ../scripts/generate-logging-defs.tcl DO NOT MODIFY. Copyright applies as per the generator script. */ #include "srt.h" #include "logging.h" #include "logger_defs.h" namespace srt_logging { AllFaOn logger_fa_all; } // We need it outside the namespace to preserve the global name. // It's a part of "hidden API" (used by applications) SRT_API srt_logging::LogConfig srt_logger_config(srt_logging::logger_fa_all.allfa); namespace srt_logging { Logger gglog(SRT_LOGFA_GENERAL, srt_logger_config, "SRT.gg"); Logger smlog(SRT_LOGFA_SOCKMGMT, srt_logger_config, "SRT.sm"); Logger cnlog(SRT_LOGFA_CONN, srt_logger_config, "SRT.cn"); Logger xtlog(SRT_LOGFA_XTIMER, srt_logger_config, "SRT.xt"); Logger tslog(SRT_LOGFA_TSBPD, srt_logger_config, "SRT.ts"); Logger rslog(SRT_LOGFA_RSRC, srt_logger_config, "SRT.rs"); Logger cclog(SRT_LOGFA_CONGEST, srt_logger_config, "SRT.cc"); Logger pflog(SRT_LOGFA_PFILTER, srt_logger_config, "SRT.pf"); Logger aclog(SRT_LOGFA_API_CTRL, srt_logger_config, "SRT.ac"); Logger qclog(SRT_LOGFA_QUE_CTRL, srt_logger_config, "SRT.qc"); Logger eilog(SRT_LOGFA_EPOLL_UPD, srt_logger_config, "SRT.ei"); Logger arlog(SRT_LOGFA_API_RECV, srt_logger_config, "SRT.ar"); Logger brlog(SRT_LOGFA_BUF_RECV, srt_logger_config, "SRT.br"); Logger qrlog(SRT_LOGFA_QUE_RECV, srt_logger_config, "SRT.qr"); Logger krlog(SRT_LOGFA_CHN_RECV, srt_logger_config, "SRT.kr"); Logger grlog(SRT_LOGFA_GRP_RECV, srt_logger_config, "SRT.gr"); Logger aslog(SRT_LOGFA_API_SEND, srt_logger_config, "SRT.as"); Logger bslog(SRT_LOGFA_BUF_SEND, srt_logger_config, "SRT.bs"); Logger qslog(SRT_LOGFA_QUE_SEND, srt_logger_config, "SRT.qs"); Logger kslog(SRT_LOGFA_CHN_SEND, srt_logger_config, "SRT.ks"); Logger gslog(SRT_LOGFA_GRP_SEND, srt_logger_config, "SRT.gs"); Logger inlog(SRT_LOGFA_INTERNAL, srt_logger_config, "SRT.in"); Logger qmlog(SRT_LOGFA_QUE_MGMT, srt_logger_config, "SRT.qm"); Logger kmlog(SRT_LOGFA_CHN_MGMT, srt_logger_config, "SRT.km"); Logger gmlog(SRT_LOGFA_GRP_MGMT, srt_logger_config, "SRT.gm"); Logger ealog(SRT_LOGFA_EPOLL_API, srt_logger_config, "SRT.ea"); } // namespace srt_logging srt-1.4.4/srtcore/logger_defs.h000066400000000000000000000020301412557703600164470ustar00rootroot00000000000000/* WARNING: Generated from ../scripts/generate-logging-defs.tcl DO NOT MODIFY. Copyright applies as per the generator script. */ #ifndef INC_SRT_LOGGER_DEFS_H #define INC_SRT_LOGGER_DEFS_H #include "srt.h" #include "logging.h" namespace srt_logging { struct AllFaOn { LogConfig::fa_bitset_t allfa; AllFaOn(); }; extern Logger gglog; extern Logger smlog; extern Logger cnlog; extern Logger xtlog; extern Logger tslog; extern Logger rslog; extern Logger cclog; extern Logger pflog; extern Logger aclog; extern Logger qclog; extern Logger eilog; extern Logger arlog; extern Logger brlog; extern Logger qrlog; extern Logger krlog; extern Logger grlog; extern Logger aslog; extern Logger bslog; extern Logger qslog; extern Logger kslog; extern Logger gslog; extern Logger inlog; extern Logger qmlog; extern Logger kmlog; extern Logger gmlog; extern Logger ealog; } // namespace srt_logging #endif srt-1.4.4/srtcore/logging.h000066400000000000000000000303461412557703600156300ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_LOGGING_H #define INC_SRT_LOGGING_H #include #include #include #include #include #ifdef _WIN32 #include "win/wintime.h" #include #else #include #endif #include "srt.h" #include "utilities.h" #include "threadname.h" #include "logging_api.h" #include "srt_compat.h" #include "sync.h" #ifdef __GNUC__ #define PRINTF_LIKE __attribute__((format(printf,2,3))) #else #define PRINTF_LIKE #endif #if ENABLE_LOGGING // GENERAL NOTE: All logger functions ADD THEIR OWN \n (EOL). Don't add any your own EOL character. // The logging system may not add the EOL character, if appropriate flag was set in log settings. // Anyway, treat the whole contents of eventually formatted message as exactly one line. // LOGC uses an iostream-like syntax, using the special 'log' symbol. // This symbol isn't visible outside the log macro parameters. // Usage: LOGC(gglog.Debug, log << param1 << param2 << param3); #define LOGC(logdes, args) if (logdes.CheckEnabled()) \ { \ srt_logging::LogDispatcher::Proxy log(logdes); \ log.setloc(__FILE__, __LINE__, __FUNCTION__); \ const srt_logging::LogDispatcher::Proxy& log_prox SRT_ATR_UNUSED = args; \ } // LOGF uses printf-like style formatting. // Usage: LOGF(gglog.Debug, "%s: %d", param1.c_str(), int(param2)); #define LOGF(logdes, ...) if (logdes.CheckEnabled()) logdes().setloc(__FILE__, __LINE__, __FUNCTION__).form(__VA_ARGS__) // LOGP is C++11 only OR with only one string argument. // Usage: LOGP(gglog.Debug, param1, param2, param3); #define LOGP(logdes, ...) if (logdes.CheckEnabled()) logdes.printloc(__FILE__, __LINE__, __FUNCTION__,##__VA_ARGS__) #define IF_LOGGING(instr) instr #if ENABLE_HEAVY_LOGGING #define HLOGC LOGC #define HLOGP LOGP #define HLOGF LOGF #define IF_HEAVY_LOGGING(instr) instr #else #define HLOGC(...) #define HLOGF(...) #define HLOGP(...) #define IF_HEAVY_LOGGING(instr) (void)0 #endif #else #define LOGC(...) #define LOGF(...) #define LOGP(...) #define HLOGC(...) #define HLOGF(...) #define HLOGP(...) #define IF_HEAVY_LOGGING(instr) (void)0 #define IF_LOGGING(instr) (void)0 #endif namespace srt_logging { struct LogConfig { typedef std::bitset fa_bitset_t; fa_bitset_t enabled_fa; // NOTE: assumed atomic reading LogLevel::type max_level; // NOTE: assumed atomic reading std::ostream* log_stream; SRT_LOG_HANDLER_FN* loghandler_fn; void* loghandler_opaque; srt::sync::Mutex mutex; int flags; LogConfig(const fa_bitset_t& efa, LogLevel::type l = LogLevel::warning, std::ostream* ls = &std::cerr) : enabled_fa(efa) , max_level(l) , log_stream(ls) , loghandler_fn() , loghandler_opaque() , flags() { } ~LogConfig() { } SRT_ATTR_ACQUIRE(mutex) void lock() { mutex.lock(); } SRT_ATTR_RELEASE(mutex) void unlock() { mutex.unlock(); } }; // The LogDispatcher class represents the object that is responsible for // a decision whether to log something or not, and if so, print the log. struct SRT_API LogDispatcher { private: int fa; LogLevel::type level; static const size_t MAX_PREFIX_SIZE = 32; char prefix[MAX_PREFIX_SIZE+1]; LogConfig* src_config; bool isset(int flg) { return (src_config->flags & flg) != 0; } public: LogDispatcher(int functional_area, LogLevel::type log_level, const char* your_pfx, const char* logger_pfx /*[[nullable]]*/, LogConfig& config): fa(functional_area), level(log_level), src_config(&config) { // XXX stpcpy desired, but not enough portable // Composing the exact prefix is not critical, so simply // cut the prefix, if the length is exceeded // See Logger::Logger; we know this has normally 2 characters, // except !!FATAL!!, which has 9. Still less than 32. strcpy(prefix, your_pfx); // If the size of the FA name together with severity exceeds the size, // just skip the former. if (logger_pfx && strlen(prefix) + strlen(logger_pfx) + 1 < MAX_PREFIX_SIZE) { strcat(prefix, ":"); strcat(prefix, logger_pfx); } } ~LogDispatcher() { } bool CheckEnabled(); 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) { PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", arg1, args...); } template void printloc(const char* file, int line, const std::string& area, Arg1&& arg1, Args&&... args) { PrintLogLine(file, line, area, arg1, args...); } #else template void PrintLogLine(const char* file, int line, const std::string& area, const Arg& arg); // For C++03 (older) standard provide only with one argument. template void operator()(const Arg& arg) { PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", arg); } void printloc(const char* file, int line, const std::string& area, const std::string& arg1) { PrintLogLine(file, line, area, arg1); } #endif #if ENABLE_LOGGING struct Proxy; friend 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; 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); Proxy(LogDispatcher& guy); // 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 = p.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; va_list ap; va_start(ap, fmts); vform(fmts, ap); va_end(ap); return *this; } Proxy& vform(const char* fmts, va_list ap) { char buf[512]; vsprintf(buf, fmts, 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; } }; #endif class Logger { int m_fa; LogConfig& m_config; public: LogDispatcher Debug; LogDispatcher Note; LogDispatcher Warn; LogDispatcher Error; LogDispatcher Fatal; Logger(int functional_area, LogConfig& config, const char* logger_pfx = NULL): m_fa(functional_area), m_config(config), Debug ( m_fa, LogLevel::debug, " D", logger_pfx, m_config ), Note ( m_fa, LogLevel::note, ".N", logger_pfx, m_config ), Warn ( m_fa, LogLevel::warning, "!W", logger_pfx, m_config ), Error ( m_fa, LogLevel::error, "*E", logger_pfx, m_config ), Fatal ( m_fa, LogLevel::fatal, "!!FATAL!!", logger_pfx, m_config ) { } }; inline bool LogDispatcher::CheckEnabled() { // Don't use enabler caching. Check enabled state every time. // These assume to be atomically read, so the lock is not needed // (note that writing to this field is still mutex-protected). // It's also no problem if the level was changed at the moment // when the enabler check is tested here. Worst case, the log // will be printed just a moment after it was turned off. const LogConfig* config = src_config; // to enforce using const operator[] int configured_enabled_fa = config->enabled_fa[fa]; int configured_maxlevel = config->max_level; return configured_enabled_fa && level <= configured_maxlevel; } #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 SRT_ATR_UNUSED, int line SRT_ATR_UNUSED, const std::string& area SRT_ATR_UNUSED, Args&&... args SRT_ATR_UNUSED) { #ifdef ENABLE_LOGGING std::ostringstream serr; CreateLogLinePrefix(serr); PrintArgs(serr, args...); if ( !isset(SRT_LOGF_DISABLE_EOL) ) serr << std::endl; // Not sure, but it wasn't ever used. SendLogLine(file, line, area, serr.str()); #endif } #else template inline void LogDispatcher::PrintLogLine(const char* file SRT_ATR_UNUSED, int line SRT_ATR_UNUSED, const std::string& area SRT_ATR_UNUSED, const Arg& arg SRT_ATR_UNUSED) { #ifdef ENABLE_LOGGING std::ostringstream serr; CreateLogLinePrefix(serr); serr << arg; if ( !isset(SRT_LOGF_DISABLE_EOL) ) serr << std::endl; // Not sure, but it wasn't ever used. SendLogLine(file, line, area, serr.str()); #endif } #endif // SendLogLine can be compiled normally. It's intermediately used by: // - Proxy object, which is replaced by DummyProxy when !ENABLE_LOGGING // - PrintLogLine, which has empty body when !ENABLE_LOGGING 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->log_stream).flush(); } src_config->unlock(); } } #endif srt-1.4.4/srtcore/logging_api.h000066400000000000000000000053711412557703600164610ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** 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 #ifdef _WIN32 #include "win/syslog_defs.h" #else #include #endif // 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 srt_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 srt-1.4.4/srtcore/md5.cpp000066400000000000000000000302351412557703600152170ustar00rootroot00000000000000/* 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)); } srt-1.4.4/srtcore/md5.h000066400000000000000000000065031412557703600146650ustar00rootroot00000000000000/* 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 */ srt-1.4.4/srtcore/netinet_any.h000066400000000000000000000266751412557703600165310ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_NETINET_ANY_H #define INC_SRT_NETINET_ANY_H #include // memcmp #include #include #include "platform_sys.h" // This structure should replace every use of sockaddr and its currently // used specializations, sockaddr_in and sockaddr_in6. This is to simplify // the use of the original BSD API that relies on type-violating type casts. // You can use the instances of sockaddr_any in every place where sockaddr is // required. struct sockaddr_any { union { sockaddr_in sin; sockaddr_in6 sin6; sockaddr sa; }; // The type is intended to be the same as the length // parameter in ::accept, ::bind and ::connect functions. // This is the type used by SRT. typedef int len_t; // This is the type used by system functions #ifdef _WIN32 typedef int syslen_t; #else typedef socklen_t syslen_t; #endif // Note: by having `len_t` type here the usage in // API functions is here limited to SRT. For system // functions you can pass the address here as (socklen_t*)&sa.len, // but just do it on your own risk, as there's no guarantee // that sizes of `int` and `socklen_t` do not differ. The safest // way seems to be using an intermediate proxy to be written // back here from the value of `syslen_t`. len_t len; struct SysLenWrapper { syslen_t syslen; len_t& backwriter; syslen_t* operator&() { return &syslen; } SysLenWrapper(len_t& source): syslen(source), backwriter(source) { } ~SysLenWrapper() { backwriter = syslen; } }; // Usage: // ::accept(lsn_sock, sa.get(), &sa.syslen()); SysLenWrapper syslen() { return SysLenWrapper(len); } static size_t storage_size() { typedef union { sockaddr_in sin; sockaddr_in6 sin6; sockaddr sa; } ucopy; return sizeof (ucopy); } void reset() { // sin6 is the largest field memset((&sin6), 0, sizeof sin6); len = 0; } // Default domain is unspecified, and // in this case the size is 0. // Note that AF_* (and alias PF_*) types have // many various values, of which only // AF_INET and AF_INET6 are handled here. // Others make the same effect as unspecified. explicit sockaddr_any(int domain = AF_UNSPEC) { // Default domain is "unspecified", 0 reset(); // Overriding family as required in the parameters // and the size then accordingly. sa.sa_family = domain == AF_INET || domain == AF_INET6 ? domain : AF_UNSPEC; switch (domain) { case AF_INET: len = len_t(sizeof (sockaddr_in)); break; // Use size of sin6 as the default size // len must be properly set so that the // family-less sockaddr is passed to bind/accept default: len = len_t(sizeof (sockaddr_in6)); break; } } sockaddr_any(const sockaddr_storage& stor) { // Here the length isn't passed, so just rely on family. set((const sockaddr*)&stor); } sockaddr_any(const sockaddr* source, len_t namelen = 0) { if (namelen == 0) set(source); else set(source, namelen); } void set(const sockaddr* source) { // Less safe version, simply trust the caller that the // memory at 'source' is also large enough to contain // all data required for particular family. if (source->sa_family == AF_INET) { memcpy((&sin), source, sizeof sin); len = sizeof sin; } else if (source->sa_family == AF_INET6) { memcpy((&sin6), source, sizeof sin6); len = sizeof sin6; } else { // Error fallback: no other families than IP are regarded. // Note: socket set up this way isn't intended to be used // for bind/accept. sa.sa_family = AF_UNSPEC; len = 0; } } void set(const sockaddr* source, syslen_t namelen) { // It's not safe to copy it directly, so check. if (source->sa_family == AF_INET && namelen >= syslen_t(sizeof sin)) { memcpy((&sin), source, sizeof sin); len = sizeof sin; } else if (source->sa_family == AF_INET6 && namelen >= syslen_t(sizeof sin6)) { // Note: this isn't too safe, may crash for stupid values // of source->sa_family or any other data // in the source structure, so make sure it's correct first. memcpy((&sin6), source, sizeof sin6); len = sizeof sin6; } else { reset(); } } void set(const sockaddr_in& in4) { memcpy((&sin), &in4, sizeof in4); len = sizeof in4; } void set(const sockaddr_in6& in6) { memcpy((&sin6), &in6, sizeof in6); len = sizeof in6; } sockaddr_any(const in_addr& i4_adr, uint16_t port) { // Some cases require separately IPv4 address passed as in_addr, // so port is given separately. sa.sa_family = AF_INET; sin.sin_addr = i4_adr; sin.sin_port = htons(port); len = sizeof sin; } sockaddr_any(const in6_addr& i6_adr, uint16_t port) { sa.sa_family = AF_INET6; sin6.sin6_addr = i6_adr; sin6.sin6_port = htons(port); len = sizeof sin6; } static len_t size(int family) { switch (family) { case AF_INET: return len_t(sizeof (sockaddr_in)); case AF_INET6: return len_t(sizeof (sockaddr_in6)); default: return 0; // fallback } } bool empty() const { bool isempty = true; // unspec-family address is always empty if (sa.sa_family == AF_INET) { isempty = (sin.sin_port == 0 && sin.sin_addr.s_addr == 0); } else if (sa.sa_family == AF_INET6) { isempty = (sin6.sin6_port == 0 && memcmp(&sin6.sin6_addr, &in6addr_any, sizeof in6addr_any) == 0); } // otherwise isempty stays with default false return isempty; } len_t size() const { return size(sa.sa_family); } int family() const { return sa.sa_family; } void family(int val) { sa.sa_family = val; len = size(); } // 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* get() { return &sa; } const sockaddr* get() const { return &sa; } // Sometimes you need to get the address // the way suitable for e.g. inet_ntop. const void* get_addr() const { if (sa.sa_family == AF_INET) return &sin.sin_addr.s_addr; if (sa.sa_family == AF_INET6) return &sin6.sin6_addr; return NULL; } void* get_addr() { const sockaddr_any* that = this; return (void*)that->get_addr(); } template struct TypeMap; template typename TypeMap::type& get(); struct Equal { bool operator()(const sockaddr_any& c1, const sockaddr_any& c2) { if (c1.family() != c2.family()) return false; // Cannot use memcmp due to having in some systems // another field like sockaddr_in::sin_len. This exists // in some BSD-derived systems, but is not required by POSIX. // Therefore sockaddr_any class cannot operate with it, // as in this situation it would be safest to state that // particular implementations may have additional fields // of different purpose beside those required by POSIX. // // The only reliable way to compare two underlying sockaddr // object is then to compare the port value and the address // value. // // Fortunately the port is 16-bit and located at the same // offset in both sockaddr_in and sockaddr_in6. return c1.sin.sin_port == c2.sin.sin_port && c1.equal_address(c2); } }; 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; } }; // Tests if the current address is the "any" wildcard. bool isany() const { if (sa.sa_family == AF_INET) return sin.sin_addr.s_addr == INADDR_ANY; if (sa.sa_family == AF_INET6) return memcmp(&sin6.sin6_addr, &in6addr_any, sizeof in6addr_any) == 0; return false; } // Debug support std::string str() const { if (family() != AF_INET && family() != AF_INET6) return "unknown:0"; std::ostringstream output; char hostbuf[1024]; int flags; #if ENABLE_GETNAMEINFO flags = NI_NAMEREQD; #else flags = NI_NUMERICHOST | NI_NUMERICSERV; #endif if (!getnameinfo(get(), size(), hostbuf, 1024, NULL, 0, flags)) { output << hostbuf; } output << ":" << hport(); return output.str(); } bool operator==(const sockaddr_any& other) const { return Equal()(*this, other); } bool operator!=(const sockaddr_any& other) const { return !(*this == other); } }; 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 srt-1.4.4/srtcore/packet.cpp000066400000000000000000000472711412557703600160110ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 02/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 "platform_sys.h" #include #include "packet.h" #include "handshake.h" #include "logging.h" #include "handshake.h" namespace srt_logging { extern Logger inlog; } using namespace srt_logging; // Set up the aliases in the constructure srt::CPacket::CPacket(): m_extra_pad(), m_data_owned(false), m_iSeqNo((int32_t&)(m_nHeader[SRT_PH_SEQNO])), m_iMsgNo((int32_t&)(m_nHeader[SRT_PH_MSGNO])), m_iTimeStamp((int32_t&)(m_nHeader[SRT_PH_TIMESTAMP])), m_iID((int32_t&)(m_nHeader[SRT_PH_ID])), m_pcData((char*&)(m_PacketVector[PV_DATA].dataRef())) { m_nHeader.clear(); // The part at PV_HEADER will be always set to a builtin buffer // containing SRT header. m_PacketVector[PV_HEADER].set(m_nHeader.raw(), 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].set(NULL, 0); } char* srt::CPacket::getData() { return (char*)m_PacketVector[PV_DATA].dataRef(); } void srt::CPacket::allocate(size_t alloc_buffer_size) { if (m_data_owned) { if (getLength() == alloc_buffer_size) return; // already allocated // Would be nice to reallocate; for now just allocate again. delete [] m_pcData; } m_PacketVector[PV_DATA].set(new char[alloc_buffer_size], alloc_buffer_size); m_data_owned = true; } void srt::CPacket::deallocate() { if (m_data_owned) delete [] (char*)m_PacketVector[PV_DATA].data(); m_PacketVector[PV_DATA].set(NULL, 0); } char* srt::CPacket::release() { // When not owned, release returns NULL. char* buffer = NULL; if (m_data_owned) { buffer = getData(); m_data_owned = false; } deallocate(); // won't delete because m_data_owned == false return buffer; } srt::CPacket::~CPacket() { // PV_HEADER is always owned, PV_DATA may use a "borrowed" buffer. // Delete the internal buffer only if it was declared as owned. if (m_data_owned) delete[](char*)m_PacketVector[PV_DATA].data(); } size_t srt::CPacket::getLength() const { return m_PacketVector[PV_DATA].size(); } void srt::CPacket::setLength(size_t len) { m_PacketVector[PV_DATA].setLength(len); } void srt::CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rparam, size_t size) { // Set (bit-0 = 1) and (bit-1~15 = type) setControl(pkttype); HLOGC(inlog.Debug, log << "pack: type=" << MessageTypeStr(pkttype) << " ARG=" << (lparam ? Sprint(*lparam) : std::string("NULL")) << " [ " << (rparam ? Sprint(*(int32_t*)rparam) : std::string()) << " ]"); // Set additional information and control information field switch (pkttype) { case UMSG_ACK: //0010 - Acknowledgement (ACK) // ACK packet seq. no. if (NULL != lparam) m_nHeader[SRT_PH_MSGNO] = *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].set(rparam, size); break; case UMSG_ACKACK: //0110 - Acknowledgement of Acknowledgement (ACK-2) // ACK packet seq. no. m_nHeader[SRT_PH_MSGNO] = *lparam; // control info field should be none // but "writev" does not allow this m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4); break; case UMSG_LOSSREPORT: //0011 - Loss Report (NAK) // loss list m_PacketVector[PV_DATA].set(rparam, size); break; case UMSG_CGWARNING: //0100 - Congestion Warning // control info field should be none // but "writev" does not allow this m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4); break; case UMSG_KEEPALIVE: //0001 - Keep-alive if (lparam) { // XXX EXPERIMENTAL. Pass the 32-bit integer here. m_nHeader[SRT_PH_MSGNO] = *lparam; } // control info field should be none // but "writev" does not allow this m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4); break; case UMSG_HANDSHAKE: //0000 - Handshake // control info filed is handshake info m_PacketVector[PV_DATA].set(rparam, size); break; case UMSG_SHUTDOWN: //0101 - Shutdown // control info field should be none // but "writev" does not allow this m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4); break; case UMSG_DROPREQ: //0111 - Message Drop Request // msg id m_nHeader[SRT_PH_MSGNO] = *lparam; //first seq no, last seq no m_PacketVector[PV_DATA].set(rparam, size); break; case UMSG_PEERERROR: //1000 - Error Signal from the Peer Side // Error type m_nHeader[SRT_PH_MSGNO] = *lparam; // control info field should be none // but "writev" does not allow this m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4); 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[SRT_PH_SEQNO] |= *lparam; if (NULL != rparam) { m_PacketVector[PV_DATA].set(rparam, size); } else { m_PacketVector[PV_DATA].set((void *)&m_extra_pad, 4); } break; default: break; } } void srt::CPacket::toNL() { // XXX USE HtoNLA! if (isControl()) { for (ptrdiff_t i = 0, n = getLength() / 4; i < n; ++i) *((uint32_t*)m_pcData + i) = htonl(*((uint32_t*)m_pcData + i)); } // convert packet header into network order uint32_t* p = m_nHeader; for (int j = 0; j < 4; ++j) { *p = htonl(*p); ++p; } } void srt::CPacket::toHL() { // convert back into local host order uint32_t* p = m_nHeader; for (int k = 0; k < 4; ++k) { *p = ntohl(*p); ++p; } if (isControl()) { for (ptrdiff_t l = 0, n = getLength() / 4; l < n; ++l) *((uint32_t*) m_pcData + l) = ntohl(*((uint32_t*) m_pcData + l)); } } IOVector* srt::CPacket::getPacketVector() { return m_PacketVector; } UDTMessageType srt::CPacket::getType() const { return UDTMessageType(SEQNO_MSGTYPE::unwrap(m_nHeader[SRT_PH_SEQNO])); } int srt::CPacket::getExtendedType() const { return SEQNO_EXTTYPE::unwrap(m_nHeader[SRT_PH_SEQNO]); } int32_t srt::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[SRT_PH_MSGNO]; } uint16_t srt::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[SRT_PH_SEQNO]); } PacketBoundary srt::CPacket::getMsgBoundary() const { return PacketBoundary(MSGNO_PACKET_BOUNDARY::unwrap(m_nHeader[SRT_PH_MSGNO])); } bool srt::CPacket::getMsgOrderFlag() const { return 0!= MSGNO_PACKET_INORDER::unwrap(m_nHeader[SRT_PH_MSGNO]); } int32_t srt::CPacket::getMsgSeq(bool has_rexmit) const { if ( has_rexmit ) { return MSGNO_SEQ::unwrap(m_nHeader[SRT_PH_MSGNO]); } else { return MSGNO_SEQ_OLD::unwrap(m_nHeader[SRT_PH_MSGNO]); } } bool srt::CPacket::getRexmitFlag() const { // return false; // return 0 != MSGNO_REXMIT::unwrap(m_nHeader[SRT_PH_MSGNO]); } EncryptionKeySpec srt::CPacket::getMsgCryptoFlags() const { return EncryptionKeySpec(MSGNO_ENCKEYSPEC::unwrap(m_nHeader[SRT_PH_MSGNO])); } // This is required as the encryption/decryption happens in place. // This is required to clear off the flags after decryption or set // crypto flags after encrypting a packet. void srt::CPacket::setMsgCryptoFlags(EncryptionKeySpec spec) { int32_t clr_msgno = m_nHeader[SRT_PH_MSGNO] & ~MSGNO_ENCKEYSPEC::mask; m_nHeader[SRT_PH_MSGNO] = clr_msgno | EncryptionKeyBits(spec); } uint32_t srt::CPacket::getMsgTimeStamp() const { // SRT_DEBUG_TSBPD_WRAP may enable smaller timestamp for faster wraparoud handling tests return (uint32_t)m_nHeader[SRT_PH_TIMESTAMP] & TIMESTAMP_MASK; } srt::CPacket* srt::CPacket::clone() const { CPacket* pkt = new CPacket; memcpy((pkt->m_nHeader), m_nHeader, HDR_SIZE); pkt->m_pcData = new char[m_PacketVector[PV_DATA].size()]; memcpy((pkt->m_pcData), m_pcData, m_PacketVector[PV_DATA].size()); pkt->m_PacketVector[PV_DATA].setLength(m_PacketVector[PV_DATA].size()); return pkt; } namespace srt { // Useful for debugging std::string PacketMessageFlagStr(uint32_t msgno_field) { using namespace std; stringstream out; static const char* const boundary [] = { "PB_SUBSEQUENT", "PB_LAST", "PB_FIRST", "PB_SOLO" }; static const char* const order [] = { "ORD_RELAXED", "ORD_REQUIRED" }; static const char* const crypto [] = { "EK_NOENC", "EK_EVEN", "EK_ODD", "EK*ERROR" }; static const char* const rexmit [] = { "SN_ORIGINAL", "SN_REXMIT" }; out << boundary[MSGNO_PACKET_BOUNDARY::unwrap(msgno_field)] << " "; out << order[MSGNO_PACKET_INORDER::unwrap(msgno_field)] << " "; out << crypto[MSGNO_ENCKEYSPEC::unwrap(msgno_field)] << " "; out << rexmit[MSGNO_REXMIT::unwrap(msgno_field)]; return out.str(); } inline void SprintSpecialWord(std::ostream& os, int32_t val) { if (val & LOSSDATA_SEQNO_RANGE_FIRST) os << "<" << (val & (~LOSSDATA_SEQNO_RANGE_FIRST)) << ">"; else os << val; } } // namespace srt #if ENABLE_LOGGING std::string srt::CPacket::Info() { std::ostringstream os; os << "TARGET=@" << m_iID << " "; if (isControl()) { os << "CONTROL: size=" << getLength() << " type=" << MessageTypeStr(getType(), getExtendedType()); if (getType() == UMSG_HANDSHAKE) { os << " HS: "; // For handshake we already have a parsing method CHandShake hs; hs.load_from(m_pcData, getLength()); os << hs.show(); } else { // This is a value that some messages use for some purposes. // The "ack seq no" is one of the purposes, used by UMSG_ACK and UMSG_ACKACK. // This is simply the SRT_PH_MSGNO field used as a message number in data packets. os << " ARG: 0x"; os << std::hex << getAckSeqNo() << " "; os << std::dec << getAckSeqNo(); // It would be nice to see the extended packet data, but this // requires strictly a message-dependent interpreter. So let's simply // display all numbers in the array with the following restrictions: // - all data contained in the buffer are considered 32-bit integer // - sign flag will be cleared before displaying, with additional mark size_t wordlen = getLength()/4; // drop any remainder if present int32_t* array = (int32_t*)m_pcData; os << " [ "; for (size_t i = 0; i < wordlen; ++i) { SprintSpecialWord(os, array[i]); os << " "; } os << "]"; } } else { // It's hard to extract the information about peer's supported rexmit flag. // This is only a log, nothing crucial, so we can risk displaying incorrect message number. // Declaring that the peer supports rexmit flag cuts off the highest bit from // the displayed number. os << "DATA: size=" << getLength() << " " << BufferStamp(m_pcData, getLength()) << " #" << getMsgSeq(true) << " %" << getSeqNo() << " " << MessageFlagStr(); } return os.str(); } #endif srt-1.4.4/srtcore/packet.h000066400000000000000000000313761412557703600154550ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 01/02/2011 modified by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_PACKET_H #define INC_SRT_PACKET_H #include "udt.h" #include "common.h" #include "utilities.h" #include "netinet_any.h" #include "packetfilter_api.h" ////////////////////////////////////////////////////////////////////////////// // The purpose of the IOVector class is to proide a platform-independet interface // to the WSABUF on Windows and iovec on Linux, that can be easilly converted // to the native structure for use in WSARecvFrom() and recvmsg(...) functions class IOVector #ifdef _WIN32 : public WSABUF #else : public iovec #endif { public: inline void set(void *buffer, size_t length) { #ifdef _WIN32 len = (ULONG)length; buf = (CHAR*)buffer; #else iov_base = (void*)buffer; iov_len = length; #endif } inline char*& dataRef() { #ifdef _WIN32 return buf; #else return (char*&) iov_base; #endif } inline char* data() { #ifdef _WIN32 return buf; #else return (char*)iov_base; #endif } inline size_t size() const { #ifdef _WIN32 return (size_t) len; #else return iov_len; #endif } inline void setLength(size_t length) { #ifdef _WIN32 len = (ULONG) length; #else iov_len = length; #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|R|M M M M M M M M M M...M typedef Bits<31, 30> MSGNO_PACKET_BOUNDARY; typedef Bits<29> MSGNO_PACKET_INORDER; typedef Bits<28, 27> MSGNO_ENCKEYSPEC; #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; const int MSGNO_SEQ_MAX = MSGNO_SEQ::mask; #else // Old bit breakdown - no rexmit flag typedef Bits<26, 0> MSGNO_SEQ; #endif typedef RollNumber MsgNo; // 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; namespace srt { std::string PacketMessageFlagStr(uint32_t msgno_field); class CPacket { friend class CChannel; friend class CSndQueue; friend class CRcvQueue; public: CPacket(); ~CPacket(); void allocate(size_t size); void deallocate(); /// Get the payload or the control information field length. /// @return the payload or the control information field length. size_t getLength() const; /// Set the payload or the control information field length. /// @param len [in] the payload or the control information field length. void setLength(size_t 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, const int32_t* lparam = NULL, void* rparam = NULL, size_t size = 0); /// Read the packet vector. /// @return Pointer to the packet vector. IOVector* 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 0!= SEQNO_CONTROL::unwrap(m_nHeader[SRT_PH_SEQNO]); } void setControl(UDTMessageType type) { m_nHeader[srt::SRT_PH_SEQNO] = SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(type); } /// 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 "singular" value, if the packet // contains the control message int32_t getSeqNo() const { return m_nHeader[SRT_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 = true) const; /// Read the message crypto key bits. /// @return packet header field [1] (bit 3~4). EncryptionKeySpec getMsgCryptoFlags() const; void setMsgCryptoFlags(EncryptionKeySpec spec); /// 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: /// Clone this packet. /// @return Pointer to the new packet. CPacket* clone() const; enum PacketVectorFields { PV_HEADER = 0, PV_DATA = 1, PV_SIZE = 2 }; public: void toNL(); void toHL(); 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 // XXX NOTE: iovec here is not portable. On Windows there's a different // (although similar) structure defined, which means that this way the // Windows function that is an equivalent of `recvmsg` cannot be used. // For example, something like that: // class IoVector: public iovec { public: size_t size() { return iov_len; } char* data() { return iov_base; } }; // class IoVector: public WSAMSG { public: size_t size() { return len; } char* data() { return buf; } }; IOVector m_PacketVector[PV_SIZE]; //< The 2-demension vector of UDT packet [header, data] int32_t m_extra_pad; bool m_data_owned; protected: CPacket& operator=(const CPacket&); CPacket (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 // Experimental: sometimes these references don't work! char* getData(); char* release(); //static const int m_iPktHdrSize; // packet header size static const size_t HDR_SIZE = sizeof(HEADER_TYPE); // packet header size = SRT_PH_E_SIZE * sizeof(uint32_t) // Used in many computations // Actually this can be also calculated as: sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr). static const size_t UDP_HDR_SIZE = 28; // 20 bytes IPv4 + 8 bytes of UDP { u16 sport, dport, len, csum }. static const size_t SRT_DATA_HDR_SIZE = UDP_HDR_SIZE + HDR_SIZE; // Some well known data static const size_t ETH_MAX_MTU_SIZE = 1500; // And derived static const size_t SRT_MAX_PAYLOAD_SIZE = ETH_MAX_MTU_SIZE - SRT_DATA_HDR_SIZE; // Packet interface char* data() { return m_pcData; } const char* data() const { return m_pcData; } size_t size() const { return getLength(); } uint32_t header(SrtPktHeaderFields field) const { return m_nHeader[field]; } #if ENABLE_LOGGING std::string MessageFlagStr() { return PacketMessageFlagStr(m_nHeader[SRT_PH_MSGNO]); } std::string Info(); #else std::string MessageFlagStr() { return std::string(); } std::string Info() { return std::string(); } #endif }; } // namespace srt #endif srt-1.4.4/srtcore/packetfilter.cpp000066400000000000000000000256731412557703600172210ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include "platform_sys.h" #include #include #include #include #include #include "packetfilter.h" #include "packetfilter_builtin.h" #include "core.h" #include "packet.h" #include "logging.h" using namespace std; using namespace srt_logging; using namespace srt::sync; bool srt::ParseFilterConfig(string s, SrtFilterConfig& w_config, PacketFilter::Factory** ppf) { if (!SrtParseConfig(s, (w_config))) return false; PacketFilter::Factory* fac = PacketFilter::find(w_config.type); if (!fac) return false; if (ppf) *ppf = fac; // Extract characteristic data w_config.extra_size = fac->ExtraSize(); return true; } bool srt::ParseFilterConfig(string s, SrtFilterConfig& w_config) { return ParseFilterConfig(s, (w_config), NULL); } // Parameters are passed by value because they need to be potentially modicied inside. bool srt::CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer) { PacketFilter::Factory* fac = PacketFilter::find(w_agent.type); if (!fac) return false; SrtFilterConfig defaults; if (!ParseFilterConfig(fac->defaultConfig(), (defaults))) { return false; } set keys; // Extract all keys to identify also unspecified parameters on both sides // Note that theoretically for FEC it could simply check for the "cols" parameter // that is the only mandatory one, but this is a procedure for packet filters in // general and every filter may define its own set of parameters and mandatory rules. for (map::iterator x = w_agent.parameters.begin(); x != w_agent.parameters.end(); ++x) { keys.insert(x->first); if (peer.parameters.count(x->first) == 0) peer.parameters[x->first] = x->second; } for (map::iterator x = peer.parameters.begin(); x != peer.parameters.end(); ++x) { keys.insert(x->first); if (w_agent.parameters.count(x->first) == 0) w_agent.parameters[x->first] = x->second; } HLOGC(cnlog.Debug, log << "CheckFilterCompat: re-filled: AGENT:" << Printable(w_agent.parameters) << " PEER:" << Printable(peer.parameters)); // Complete nonexistent keys with default values for (map::iterator x = defaults.parameters.begin(); x != defaults.parameters.end(); ++x) { if (!w_agent.parameters.count(x->first)) w_agent.parameters[x->first] = x->second; if (!peer.parameters.count(x->first)) peer.parameters[x->first] = x->second; } for (set::iterator x = keys.begin(); x != keys.end(); ++x) { // Note: operator[] will insert an element with default value // if it doesn't exist. This will inject the empty string as value, // which is acceptable. if (w_agent.parameters[*x] != peer.parameters[*x]) { LOGC(cnlog.Error, log << "Packet Filter (" << defaults.type << "): collision on '" << (*x) << "' parameter (agent:" << w_agent.parameters[*x] << " peer:" << (peer.parameters[*x]) << ")"); return false; } } // Mandatory parameters will be checked when trying to create the filter object. return true; } namespace srt { struct SortBySequence { bool operator()(const CUnit* u1, const CUnit* u2) { int32_t s1 = u1->m_Packet.getSeqNo(); int32_t s2 = u2->m_Packet.getSeqNo(); return CSeqNo::seqcmp(s1, s2) < 0; } }; } // namespace srt void srt::PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs) { const CPacket& rpkt = unit->m_Packet; if (m_filter->receive(rpkt, w_loss_seqs)) { // For the sake of rebuilding MARK THIS UNIT GOOD, otherwise the // unit factory will supply it from getNextAvailUnit() as if it were not in use. unit->m_iFlag = CUnit::GOOD; HLOGC(pflog.Debug, log << "FILTER: PASSTHRU current packet %" << unit->m_Packet.getSeqNo()); w_incoming.push_back(unit); } else { // Packet not to be passthru, update stats ScopedLock lg(m_parent->m_StatsLock); ++m_parent->m_stats.rcvFilterExtra; ++m_parent->m_stats.rcvFilterExtraTotal; } // w_loss_seqs enters empty into this function and can be only filled here. XXX ASSERT? for (loss_seqs_t::iterator i = w_loss_seqs.begin(); i != w_loss_seqs.end(); ++i) { // Sequences here are low-high, if there happens any negative distance // here, simply skip and report IPE. int dist = CSeqNo::seqoff(i->first, i->second) + 1; if (dist > 0) { ScopedLock lg(m_parent->m_StatsLock); m_parent->m_stats.rcvFilterLoss += dist; m_parent->m_stats.rcvFilterLossTotal += dist; } else { LOGC(pflog.Error, log << "FILTER: IPE: loss record: invalid loss: %" << i->first << " - %" << i->second); } } // Pack first recovered packets, if any. if (!m_provided.empty()) { HLOGC(pflog.Debug, log << "FILTER: inserting REBUILT packets (" << m_provided.size() << "):"); size_t nsupply = m_provided.size(); InsertRebuilt(w_incoming, m_unitq); ScopedLock lg(m_parent->m_StatsLock); m_parent->m_stats.rcvFilterSupply += nsupply; m_parent->m_stats.rcvFilterSupplyTotal += nsupply; } // Now that all units have been filled as they should be, // SET THEM ALL FREE. This is because now it's up to the // buffer to decide as to whether it wants them or not. // Wanted units will be set GOOD flag, unwanted will remain // with FREE and therefore will be returned at the next // call to getNextAvailUnit(). unit->m_iFlag = CUnit::FREE; for (vector::iterator i = w_incoming.begin(); i != w_incoming.end(); ++i) { CUnit* u = *i; u->m_iFlag = CUnit::FREE; } // Packets must be sorted by sequence number, ascending, in order // not to challenge the SRT's contiguity checker. sort(w_incoming.begin(), w_incoming.end(), SortBySequence()); // For now, report immediately the irrecoverable packets // from the row. // Later, the `irrecover_row` or `irrecover_col` will be // reported only, depending on level settings. For example, // with default LATELY level, packets will be reported as // irrecoverable only when they are irrecoverable in the // vertical group. // With "always", do not report any losses, SRT will simply check // them itself. return; } bool srt::PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet) { bool have = m_filter->packControlPacket(m_sndctlpkt, seq); if (!have) return false; // Now this should be repacked back to CPacket. // The header must be copied, it's always part of CPacket. uint32_t* hdr = w_packet.getHeader(); memcpy((hdr), m_sndctlpkt.hdr, SRT_PH_E_SIZE * sizeof(*hdr)); // The buffer can be assigned. w_packet.m_pcData = m_sndctlpkt.buffer; w_packet.setLength(m_sndctlpkt.length); // This sets only the Packet Boundary flags, while all other things: // - Order // - Rexmit // - Crypto // - Message Number // will be set to 0/false w_packet.m_iMsgNo = SRT_MSGNO_CONTROL | MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO); // ... and then fix only the Crypto flags w_packet.setMsgCryptoFlags(EncryptionKeySpec(kflg)); // Don't set the ID, it will be later set for any kind of packet. // Write the timestamp clip into the timestamp field. return true; } void srt::PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) { if (m_provided.empty()) return; for (vector::iterator i = m_provided.begin(); i != m_provided.end(); ++i) { CUnit* u = uq->getNextAvailUnit(); if (!u) { LOGC(pflog.Error, log << "FILTER: LOCAL STORAGE DEPLETED. Can't return rebuilt packets."); break; } // LOCK the unit as GOOD because otherwise the next // call to getNextAvailUnit will return THE SAME UNIT. u->m_iFlag = CUnit::GOOD; // After returning from this function, all units will be // set back to FREE so that the buffer can decide whether // it wants them or not. CPacket& packet = u->m_Packet; memcpy((packet.getHeader()), i->hdr, CPacket::HDR_SIZE); memcpy((packet.m_pcData), i->buffer, i->length); packet.setLength(i->length); HLOGC(pflog.Debug, log << "FILTER: PROVIDING rebuilt packet %" << packet.getSeqNo()); incoming.push_back(u); } m_provided.clear(); } bool srt::PacketFilter::IsBuiltin(const string& s) { return builtin_filters.count(s); } namespace srt { std::set PacketFilter::builtin_filters; PacketFilter::filters_map_t PacketFilter::filters; } srt::PacketFilter::Factory::~Factory() { } void srt::PacketFilter::globalInit() { // Add here builtin packet filters and mark them // as builtin. This will disallow users to register // external filters with the same name. filters["fec"] = new Creator; builtin_filters.insert("fec"); } bool srt::PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr) { m_parent = parent; SrtFilterConfig cfg; if (!ParseFilterConfig(confstr, (cfg))) return false; // Extract the "type" key from parameters, or use // builtin if lacking. filters_map_t::iterator selector = filters.find(cfg.type); if (selector == filters.end()) return false; SrtFilterInitializer init; init.socket_id = parent->socketID(); init.snd_isn = parent->sndSeqNo(); init.rcv_isn = parent->rcvSeqNo(); init.payload_size = parent->OPT_PayloadSize(); init.rcvbuf_size = parent->m_config.iRcvBufSize; // Found a filter, so call the creation function m_filter = selector->second->Create(init, m_provided, confstr); if (!m_filter) return false; m_unitq = uq; // The filter should have pinned in all events // that are of its interest. It's stated that // it's ready after creation. return true; } bool srt::PacketFilter::correctConfig(const SrtFilterConfig& conf) { const string* pname = map_getp(conf.parameters, "type"); if (!pname) return true; // default, parameters ignored if (*pname == "adaptive") return true; filters_map_t::iterator x = filters.find(*pname); if (x == filters.end()) return false; return true; } srt::PacketFilter::~PacketFilter() { delete m_filter; } srt-1.4.4/srtcore/packetfilter.h000066400000000000000000000150471412557703600166600ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_PACKETFILTER_H #define INC_SRT_PACKETFILTER_H #include #include #include #include "packet.h" #include "utilities.h" #include "packetfilter_api.h" namespace srt { class CUnitQueue; struct CUnit; class CUDT; class PacketFilter { friend class SrtPacketFilterBase; public: typedef std::vector< std::pair > loss_seqs_t; typedef SrtPacketFilterBase* filter_create_t(const SrtFilterInitializer& init, std::vector&, const std::string& config); class Factory { public: virtual SrtPacketFilterBase* Create(const SrtFilterInitializer& init, std::vector& provided, const std::string& confstr) = 0; // Characteristic data virtual size_t ExtraSize() const = 0; // Represent default parameters. This is for completing and comparing // filter configurations from both parties. Possible values to return: // - an empty string (all parameters are mandatory) // - a form of: ",:,..." virtual std::string defaultConfig() const = 0; virtual bool verifyConfig(const SrtFilterConfig& config, std::string& w_errormsg) const = 0; virtual ~Factory(); }; private: friend bool ParseFilterConfig(std::string s, SrtFilterConfig& out, PacketFilter::Factory** ppf); template class Creator: public Factory { virtual SrtPacketFilterBase* Create(const SrtFilterInitializer& init, std::vector& provided, const std::string& confstr) ATR_OVERRIDE { return new Target(init, provided, confstr); } // Import the extra size data virtual size_t ExtraSize() const ATR_OVERRIDE { return Target::EXTRA_SIZE; } virtual std::string defaultConfig() const ATR_OVERRIDE { return Target::defaultConfig; } virtual bool verifyConfig(const SrtFilterConfig& config, std::string& w_errormsg) const ATR_OVERRIDE { return Target::verifyConfig(config, (w_errormsg)); } public: Creator() {} virtual ~Creator() {} }; // We need a private wrapper for the auto-pointer, can't use // std::unique_ptr here due to no C++11. struct ManagedPtr { Factory* f; mutable bool owns; // Accept whatever ManagedPtr(Factory* ff): f(ff), owns(true) {} ManagedPtr(): f(NULL), owns(false) {} ~ManagedPtr() { if (owns) delete f; } void copy_internal(const ManagedPtr& other) { other.owns = false; f = other.f; owns = true; } ManagedPtr(const ManagedPtr& other) { copy_internal(other); } void operator=(const ManagedPtr& other) { if (owns) delete f; copy_internal(other); } Factory* operator->() { return f; } Factory* get() { return f; } }; // The list of builtin names that are reserved. static std::set builtin_filters; // Temporarily changed to linear searching, until this is exposed // for a user-defined filter. typedef std::map filters_map_t; static filters_map_t filters; // This is a filter container. SrtPacketFilterBase* m_filter; void Check() { #if ENABLE_DEBUG if (!m_filter) abort(); #endif // Don't do any check for now. } public: static void globalInit(); static bool IsBuiltin(const std::string&); template static bool add(const std::string& name) { if (IsBuiltin(name)) return false; filters[name] = new Creator; return true; } static Factory* find(const std::string& type) { filters_map_t::iterator i = filters.find(type); if (i == filters.end()) return NULL; // No matter what to return - this is "undefined behavior" to be prevented return i->second.get(); } // Filter is optional, so this check should be done always // manually. bool installed() const { return m_filter; } operator bool() const { return installed(); } SrtPacketFilterBase* operator->() { Check(); return m_filter; } // In the beginning it's initialized as first, builtin default. // Still, it will be created only when requested. PacketFilter(): m_filter(), m_parent(), m_sndctlpkt(0), m_unitq() {} // Copy constructor - important when listener-spawning // Things being done: // 1. The filter is individual, so don't copy it. Set NULL. // 2. This will be configued anyway basing on possibly a new rule set. PacketFilter(const PacketFilter& source SRT_ATR_UNUSED): m_filter(), m_sndctlpkt(0), m_unitq() {} // This function will be called by the parent CUDT // in appropriate time. It should select appropriate // filter basing on the value in selector, then // pin oneself in into CUDT for receiving event signals. bool configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr); static bool correctConfig(const SrtFilterConfig& c); // Will delete the pinned in filter object. // This must be defined in *.cpp file due to virtual // destruction. ~PacketFilter(); // Simple wrappers void feedSource(CPacket& w_packet); SRT_ARQLevel arqLevel(); bool packControlPacket(int32_t seq, int kflg, CPacket& w_packet); void receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs); protected: PacketFilter& operator=(const PacketFilter& p); void InsertRebuilt(std::vector& incoming, CUnitQueue* uq); CUDT* m_parent; // Sender part SrtPacket m_sndctlpkt; // Receiver part CUnitQueue* m_unitq; std::vector m_provided; }; bool CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer); inline void PacketFilter::feedSource(CPacket& w_packet) { SRT_ASSERT(m_filter); return m_filter->feedSource((w_packet)); } inline SRT_ARQLevel PacketFilter::arqLevel() { SRT_ASSERT(m_filter); return m_filter->arqLevel(); } bool ParseFilterConfig(std::string s, SrtFilterConfig& out, PacketFilter::Factory** ppf); } // namespace srt #endif srt-1.4.4/srtcore/packetfilter_api.h000066400000000000000000000110561412557703600175050ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_PACKETFILTER_API_H #define INC_SRT_PACKETFILTER_API_H #include "platform_sys.h" #include #include #include #include #include namespace srt { class CPacket; enum SrtPktHeaderFields { SRT_PH_SEQNO = 0, //< sequence number SRT_PH_MSGNO = 1, //< message number SRT_PH_TIMESTAMP = 2, //< time stamp SRT_PH_ID = 3, //< socket ID // Must be the last value - this is size of all, not a field id SRT_PH_E_SIZE }; enum SRT_ARQLevel { SRT_ARQ_NEVER, //< Never send LOSSREPORT SRT_ARQ_ONREQ, //< Only record the loss, but report only those that are returned in receive() SRT_ARQ_ALWAYS, //< always send LOSSREPORT immediately after detecting a loss }; struct SrtConfig { std::string type; typedef std::map par_t; par_t parameters; }; struct SrtFilterConfig: SrtConfig { size_t extra_size; // needed for filter option check against payload size }; struct SrtFilterInitializer { SRTSOCKET socket_id; int32_t snd_isn; int32_t rcv_isn; size_t payload_size; size_t rcvbuf_size; }; struct SrtPacket { uint32_t hdr[SRT_PH_E_SIZE]; char buffer[SRT_LIVE_MAX_PLSIZE]; size_t length; SrtPacket(size_t size): length(size) { memset(hdr, 0, sizeof(hdr)); } uint32_t header(SrtPktHeaderFields field) { return hdr[field]; } char* data() { return buffer; } const char* data() const { return buffer; } size_t size() const { return length; } }; bool ParseFilterConfig(std::string s, SrtFilterConfig& w_config); class SrtPacketFilterBase { SrtFilterInitializer initParams; protected: SRTSOCKET socketID() const { return initParams.socket_id; } int32_t sndISN() const { return initParams.snd_isn; } int32_t rcvISN() const { return initParams.rcv_isn; } size_t payloadSize() const { return initParams.payload_size; } size_t rcvBufferSize() const { return initParams.rcvbuf_size; } friend class PacketFilter; // Beside the size of the rows, special values: // 0: if you have 0 specified for rows, there are only columns // -1: Only during the handshake, use the value specified by peer. // -N: The N value still specifies the size, but in particular // dimension there is no filter control packet formed nor expected. public: typedef std::vector< std::pair > loss_seqs_t; protected: SrtPacketFilterBase(const SrtFilterInitializer& i): initParams(i) { } // Sender side /// This function creates and stores the filter control packet with /// a prediction to be immediately sent. This is called in the function /// that normally is prepared for extracting a data packet from the sender /// buffer and send it over the channel. The returned value informs the /// caller whether the control packet was available and therefore provided. /// @param [OUT] packet Target place where the packet should be stored /// @param [IN] seq Sequence number of the packet last requested for sending /// @return true if the control packet has been provided virtual bool packControlPacket(SrtPacket& packet, int32_t seq) = 0; /// This is called at the moment when the sender queue decided to pick up /// a new packet from the scheduled packets. This should be then used to /// continue filling the group, possibly followed by final calculating the /// control packet ready to send. The packet received by this function is /// potentially allowed to be modified. /// @param [INOUT] packet The packet about to send virtual void feedSource(CPacket& packet) = 0; // Receiver side // This function is called at the moment when a new data packet has // arrived (no matter if subsequent or recovered). The 'state' value // defines the configured level of loss state required to send the // loss report. virtual bool receive(const CPacket& pkt, loss_seqs_t& loss_seqs) = 0; // Backward configuration. // This should have some stable value after the configuration is parsed, // and it should be a stable value set ONCE, after the filter module is ready. virtual SRT_ARQLevel arqLevel() = 0; virtual ~SrtPacketFilterBase() { } }; } // namespace srt #endif srt-1.4.4/srtcore/packetfilter_builtin.h000066400000000000000000000006531412557703600204030ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_PACKETFILTER_BUILTIN_H #define INC_SRT_PACKETFILTER_BUILTIN_H // Integration header #include "fec.h" #endif srt-1.4.4/srtcore/platform_sys.h000066400000000000000000000055301412557703600167210ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_PLATFORM_SYS_H #define INC_SRT_PLATFORM_SYS_H // INFORMATION // // This file collects all required platform-specific declarations // required to provide everything that the SRT library needs from system. // // There's also semi-modular system implemented using SRT_IMPORT_* macros. // To require a module to be imported, #define SRT_IMPORT_* where * is // the module name. Currently handled module macros: // // SRT_IMPORT_TIME (mach time on Mac, portability gettimeofday on WIN32) // SRT_IMPORT_EVENT (includes kevent on Mac) #ifdef _WIN32 #define _CRT_SECURE_NO_WARNINGS 1 // silences windows complaints for sscanf #include #include #include #include #ifndef __MINGW32__ #include #endif #ifdef SRT_IMPORT_TIME #include #endif #include #include #if defined(_MSC_VER) #pragma warning(disable: 4251 26812) #endif #else #if defined(__APPLE__) && __APPLE__ // Warning: please keep this test as it is, do not make it // "#if __APPLE__" or "#ifdef __APPLE__". In applications with // a strict "no warning policy", "#if __APPLE__" triggers an "undef" // error. With GCC, an old & never fixed bug prevents muting this // warning (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431). // Before this fix, the solution was to "#define __APPLE__ 0" before // including srt.h. So, don't use "#ifdef __APPLE__" either. // XXX Check if this condition doesn't require checking of // also other macros, like TARGET_OS_IOS etc. #include "TargetConditionals.h" #define __APPLE_USE_RFC_3542 /* IPV6_PKTINFO */ #ifdef SRT_IMPORT_TIME #include #endif #ifdef SRT_IMPORT_EVENT #include #include #include #include #endif #endif #ifdef BSD #ifdef SRT_IMPORT_EVENT #include #include #include #include #endif #endif #ifdef LINUX #ifdef SRT_IMPORT_EVENT #include #include #endif #endif #ifdef __ANDROID__ #ifdef SRT_IMPORT_EVENT #include #endif #endif #include #include #include #include #include #include #include #include #include #ifdef __cplusplus // Headers for errno, string and stdlib are // included indirectly correct C++ way. #else #include #include #include #endif #endif #endif srt-1.4.4/srtcore/queue.cpp000066400000000000000000001602461412557703600156640ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 05/05/2011 modified by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include #include "common.h" #include "api.h" #include "netinet_any.h" #include "threadname.h" #include "logging.h" #include "queue.h" using namespace std; using namespace srt::sync; using namespace srt_logging; srt::CUnitQueue::CUnitQueue() : m_pQEntry(NULL) , m_pCurrQueue(NULL) , m_pLastQueue(NULL) , m_iSize(0) , m_iCount(0) , m_iMSS() , m_iIPversion() { } srt::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 srt::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; } // XXX Lots of common code with CUnitQueue:init. // Consider merging. int srt::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 const 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; LOGC(rslog.Error, log << "CUnitQueue:increase: failed to allocate " << size << " new units." << " Current size=" << m_iSize); 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 srt::CUnitQueue::shrink() { // currently queue cannot be shrunk. return -1; } srt::CUnit* srt::CUnitQueue::getNextAvailUnit() { if (m_iCount * 10 > m_iSize * 9) increase(); if (m_iCount >= m_iSize) return NULL; int units_checked = 0; do { const CUnit* end = m_pCurrQueue->m_pUnit + m_pCurrQueue->m_iSize; for (; m_pAvailUnit != end; ++m_pAvailUnit, ++units_checked) { if (m_pAvailUnit->m_iFlag == CUnit::FREE) { return m_pAvailUnit; } } m_pCurrQueue = m_pCurrQueue->m_pNext; m_pAvailUnit = m_pCurrQueue->m_pUnit; } while (units_checked < m_iSize); increase(); return NULL; } void srt::CUnitQueue::makeUnitFree(CUnit* unit) { SRT_ASSERT(unit != NULL); SRT_ASSERT(unit->m_iFlag != CUnit::FREE); unit->m_iFlag = CUnit::FREE; --m_iCount; } void srt::CUnitQueue::makeUnitGood(CUnit* unit) { ++m_iCount; SRT_ASSERT(unit != NULL); SRT_ASSERT(unit->m_iFlag == CUnit::FREE); unit->m_iFlag = CUnit::GOOD; } srt::CSndUList::CSndUList(sync::CTimer* pTimer) : m_pHeap(NULL) , m_iArrayLength(512) , m_iLastEntry(-1) , m_ListLock() , m_pTimer(pTimer) { setupCond(m_ListCond, "CSndUListCond"); m_pHeap = new CSNode*[m_iArrayLength]; } srt::CSndUList::~CSndUList() { releaseCond(m_ListCond); delete[] m_pHeap; } void srt::CSndUList::update(const CUDT* u, EReschedule reschedule, sync::steady_clock::time_point ts) { ScopedLock listguard(m_ListLock); CSNode* n = u->m_pSNode; if (n->m_iHeapLoc >= 0) { if (reschedule == DONT_RESCHEDULE) return; if (n->m_tsTimeStamp <= ts) return; if (n->m_iHeapLoc == 0) { n->m_tsTimeStamp = ts; m_pTimer->interrupt(); return; } remove_(u); insert_norealloc_(ts, u); return; } insert_(ts, u); } srt::CUDT* srt::CSndUList::pop() { ScopedLock listguard(m_ListLock); if (-1 == m_iLastEntry) return NULL; // no pop until the next scheduled time if (m_pHeap[0]->m_tsTimeStamp > steady_clock::now()) return NULL; CUDT* u = m_pHeap[0]->m_pUDT; remove_(u); return u; } void srt::CSndUList::remove(const CUDT* u) { ScopedLock listguard(m_ListLock); remove_(u); } steady_clock::time_point srt::CSndUList::getNextProcTime() { ScopedLock listguard(m_ListLock); if (-1 == m_iLastEntry) return steady_clock::time_point(); return m_pHeap[0]->m_tsTimeStamp; } void srt::CSndUList::waitNonEmpty() const { UniqueLock listguard(m_ListLock); if (m_iLastEntry >= 0) return; m_ListCond.wait(listguard); } void srt::CSndUList::signalInterrupt() const { ScopedLock listguard(m_ListLock); m_ListCond.notify_all(); } void srt::CSndUList::realloc_() { CSNode** temp = NULL; try { temp = new CSNode*[2 * m_iArrayLength]; } catch (...) { throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } memcpy((temp), m_pHeap, sizeof(CSNode*) * m_iArrayLength); m_iArrayLength *= 2; delete[] m_pHeap; m_pHeap = temp; } void srt::CSndUList::insert_(const steady_clock::time_point& ts, const CUDT* u) { // increase the heap array size if necessary if (m_iLastEntry == m_iArrayLength - 1) realloc_(); insert_norealloc_(ts, u); } void srt::CSndUList::insert_norealloc_(const steady_clock::time_point& ts, const CUDT* u) { CSNode* n = u->m_pSNode; // do not insert repeated node if (n->m_iHeapLoc >= 0) return; SRT_ASSERT(m_iLastEntry < m_iArrayLength); m_iLastEntry++; m_pHeap[m_iLastEntry] = n; n->m_tsTimeStamp = ts; int q = m_iLastEntry; int p = q; while (p != 0) { p = (q - 1) >> 1; if (m_pHeap[p]->m_tsTimeStamp <= m_pHeap[q]->m_tsTimeStamp) break; swap(m_pHeap[p], m_pHeap[q]); m_pHeap[q]->m_iHeapLoc = q; q = p; } 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) { // m_ListLock is assumed to be locked. m_ListCond.notify_all(); } } void srt::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.load(); int q = n->m_iHeapLoc; int p = q * 2 + 1; while (p <= m_iLastEntry) { if ((p + 1 <= m_iLastEntry) && (m_pHeap[p]->m_tsTimeStamp > m_pHeap[p + 1]->m_tsTimeStamp)) p++; if (m_pHeap[q]->m_tsTimeStamp > m_pHeap[p]->m_tsTimeStamp) { swap(m_pHeap[p], m_pHeap[q]); m_pHeap[p]->m_iHeapLoc = p; 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(); } // srt::CSndQueue::CSndQueue() : m_pSndUList(NULL) , m_pChannel(NULL) , m_pTimer(NULL) , m_bClosing(false) { } srt::CSndQueue::~CSndQueue() { m_bClosing = true; if (m_pTimer != NULL) { m_pTimer->interrupt(); } // Unblock CSndQueue worker thread if it is waiting. m_pSndUList->signalInterrupt(); if (m_WorkerThread.joinable()) { HLOGC(rslog.Debug, log << "SndQueue: EXIT"); m_WorkerThread.join(); } delete m_pSndUList; } int srt::CSndQueue::ioctlQuery(int type) const { return m_pChannel->ioctlQuery(type); } int srt::CSndQueue::sockoptQuery(int level, int type) const { return m_pChannel->sockoptQuery(level, type); } #if ENABLE_LOGGING int srt::CSndQueue::m_counter = 0; #endif void srt::CSndQueue::init(CChannel* c, CTimer* t) { m_pChannel = c; m_pTimer = t; m_pSndUList = new CSndUList(t); #if ENABLE_LOGGING ++m_counter; const std::string thrname = "SRT:SndQ:w" + Sprint(m_counter); const char* thname = thrname.c_str(); #else const char* thname = "SRT:SndQ"; #endif if (!StartThread(m_WorkerThread, CSndQueue::worker, this, thname)) throw CUDTException(MJ_SYSTEMRES, MN_THREAD); } int srt::CSndQueue::getIpTTL() const { return m_pChannel ? m_pChannel->getIpTTL() : -1; } int srt::CSndQueue::getIpToS() const { return m_pChannel ? m_pChannel->getIpToS() : -1; } #ifdef SRT_ENABLE_BINDTODEVICE bool srt::CSndQueue::getBind(char* dst, size_t len) const { return m_pChannel ? m_pChannel->getBind(dst, len) : false; } #endif void* srt::CSndQueue::worker(void* param) { CSndQueue* self = (CSndQueue*)param; #if ENABLE_LOGGING THREAD_STATE_INIT(("SRT:SndQ:w" + Sprint(m_counter)).c_str()); #else THREAD_STATE_INIT("SRT:SndQ:worker"); #endif #if defined(SRT_DEBUG_SNDQ_HIGHRATE) CTimer::rdtsc(self->m_ullDbgTime); self->m_ullDbgPeriod = uint64_t(5000000) * CTimer::getCPUFrequency(); self->m_ullDbgTime += self->m_ullDbgPeriod; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ while (!self->m_bClosing) { const steady_clock::time_point next_time = self->m_pSndUList->getNextProcTime(); #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lIteration++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ if (is_zero(next_time)) { #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(); if (!self->m_bClosing) { self->m_pSndUList->waitNonEmpty(); #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lCondWait++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ } THREAD_RESUMED(); continue; } // wait until next processing time of the first socket on the list const steady_clock::time_point currtime = steady_clock::now(); #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 < next_time) { self->m_pTimer->sleep_until(next_time); #if defined(HAI_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lSleepTo++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ } THREAD_RESUMED(); // Get a socket with a send request if any. CUDT* u = self->m_pSndUList->pop(); if (u == NULL) { #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lNotReadyPop++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ continue; } #define UST(field) ((u->m_b##field) ? "+" : "-") << #field << " " HLOGC(qslog.Debug, log << "CSndQueue: requesting packet from @" << u->socketID() << " STATUS: " << UST(Listening) << UST(Connecting) << UST(Connected) << UST(Closing) << UST(Shutdown) << UST(Broken) << UST(PeerHealth) << UST(Opened)); #undef UST if (!u->m_bConnected || u->m_bBroken) { #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lNotReadyPop++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ continue; } // pack a packet from the socket CPacket pkt; const std::pair res_time = u->packData((pkt)); // Check if payload size is invalid. if (res_time.first <= 0) { #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lNotReadyPop++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ continue; } const sockaddr_any addr = u->m_PeerAddr; const steady_clock::time_point next_send_time = res_time.second; if (!is_zero(next_send_time)) self->m_pSndUList->update(u, CSndUList::DO_RESCHEDULE, next_send_time); HLOGC(qslog.Debug, log << self->CONID() << "chn:SENDING: " << pkt.Info()); self->m_pChannel->sendto(addr, pkt); #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lSendTo++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ } THREAD_EXIT(); return NULL; } int srt::CSndQueue::sendto(const sockaddr_any& w_addr, CPacket& w_packet) { // send out the packet immediately (high priority), this is a control packet m_pChannel->sendto(w_addr, w_packet); return (int)w_packet.getLength(); } // srt::CRcvUList::CRcvUList() : m_pUList(NULL) , m_pLast(NULL) { } srt::CRcvUList::~CRcvUList() {} void srt::CRcvUList::insert(const CUDT* u) { CRNode* n = u->m_pRNode; n->m_tsTimeStamp = steady_clock::now(); 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 srt::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 srt::CRcvUList::update(const CUDT* u) { CRNode* n = u->m_pRNode; if (!n->m_bOnList) return; n->m_tsTimeStamp = steady_clock::now(); // 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; } // srt::CHash::CHash() : m_pBucket(NULL) , m_iHashSize(0) { } srt::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 srt::CHash::init(int size) { m_pBucket = new CBucket*[size]; for (int i = 0; i < size; ++i) m_pBucket[i] = NULL; m_iHashSize = size; } srt::CUDT* srt::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 srt::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 srt::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; } } // srt::CRendezvousQueue::CRendezvousQueue() : m_lRendezvousID() , m_RIDListLock() { } srt::CRendezvousQueue::~CRendezvousQueue() { m_lRendezvousID.clear(); } void srt::CRendezvousQueue::insert(const SRTSOCKET& id, CUDT* u, const sockaddr_any& addr, const steady_clock::time_point& ttl) { ScopedLock vg(m_RIDListLock); CRL r; r.m_iID = id; r.m_pUDT = u; r.m_PeerAddr = addr; r.m_tsTTL = ttl; m_lRendezvousID.push_back(r); HLOGC(cnlog.Debug, log << "RID: adding socket @" << id << " for address: " << addr.str() << " expires: " << FormatTime(ttl) << " (total connectors: " << m_lRendezvousID.size() << ")"); } void srt::CRendezvousQueue::remove(const SRTSOCKET& id) { ScopedLock lkv(m_RIDListLock); for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { if (i->m_iID == id) { m_lRendezvousID.erase(i); break; } } } srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& w_id) const { ScopedLock vg(m_RIDListLock); // TODO: optimize search for (list::const_iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { if (i->m_PeerAddr == addr && ((w_id == 0) || (w_id == i->m_iID))) { HLOGC(cnlog.Debug, log << "RID: found id @" << i->m_iID << " while looking for " << (w_id ? "THIS ID FROM " : "A NEW CONNECTION FROM ") << i->m_PeerAddr.str()); w_id = i->m_iID; return i->m_pUDT; } } #if ENABLE_HEAVY_LOGGING std::ostringstream spec; if (w_id == 0) spec << "A NEW CONNECTION REQUEST"; else spec << " AGENT @" << w_id; HLOGC(cnlog.Debug, log << "RID: NO CONNECTOR FOR ADR:" << addr.str() << " while looking for " << spec.str() << " (" << m_lRendezvousID.size() << " connectors total)"); #endif return NULL; } void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst, CUnit* unit) { vector toRemove, toProcess; const CPacket* pkt = unit ? &unit->m_Packet : NULL; // Need a stub value for a case when there's no unit provided ("storage depleted" case). // It should be normally NOT IN USE because in case of "storage depleted", rst != RST_OK. const SRTSOCKET dest_id = pkt ? pkt->m_iID : 0; // If no socket were qualified for further handling, finish here. // Otherwise toRemove and toProcess contain items to handle. if (!qualifyToHandle(rst, cst, dest_id, (toRemove), (toProcess))) return; HLOGC(cnlog.Debug, log << "updateConnStatus: collected " << toProcess.size() << " for processing, " << toRemove.size() << " to close"); // Repeat (resend) connection request. for (vector::iterator i = toProcess.begin(); i != toProcess.end(); ++i) { // IMPORTANT INFORMATION concerning changes towards UDT legacy. // In the UDT code there was no attempt to interpret any incoming data. // All data from the incoming packet were considered to be already deployed into // m_ConnRes field, and m_ConnReq field was considered at this time accordingly updated. // Therefore this procedure did only one thing: craft a new handshake packet and send it. // In SRT this may also interpret extra data (extensions in case when Agent is Responder) // and the `pktIn` packet may sometimes contain no data. Therefore the passed `rst` // must be checked to distinguish the call by periodic update (RST_AGAIN) from a call // due to have received the packet (RST_OK). // // In the below call, only the underlying `processRendezvous` function will be attempting // to interpret these data (for caller-listener this was already done by `processConnectRequest` // before calling this function), and it checks for the data presence. EReadStatus read_st = rst; EConnectStatus conn_st = cst; if (i->id != dest_id) { read_st = RST_AGAIN; conn_st = CONN_AGAIN; } HLOGC(cnlog.Debug, log << "updateConnStatus: processing async conn for @" << i->id << " FROM " << i->peeraddr.str()); if (!i->u->processAsyncConnectRequest(read_st, conn_st, pkt, i->peeraddr)) { // cst == CONN_REJECT can only be result of worker_ProcessAddressedPacket and // its already set in this case. LinkStatusInfo fi = *i; fi.errorcode = SRT_ECONNREJ; toRemove.push_back(fi); i->u->sendCtrl(UMSG_SHUTDOWN); } } // NOTE: it is "believed" here that all CUDT objects will not be // deleted in the meantime. This is based on a statement that at worst // they have been "just" declared failed and it will pass at least 1s until // they are moved to ClosedSockets and it is believed that this function will // not be held on mutexes that long. for (vector::iterator i = toRemove.begin(); i != toRemove.end(); ++i) { HLOGC(cnlog.Debug, log << "updateConnStatus: COMPLETING dep objects update on failed @" << i->id); // // Setting m_bConnecting to false, and need to remove the socket from the rendezvous queue // because the next CUDT::close will not remove it from the queue when m_bConnecting = false, // and may crash on next pass. // // TODO: maybe lock i->u->m_ConnectionLock? i->u->m_bConnecting = false; remove(i->u->m_SocketID); // DO NOT close the socket here because in this case it might be // unable to get status from at the right moment. Also only member // sockets should be taken care of internally - single sockets should // be normally closed by the application, after it is done with them. // app can call any UDT API to learn the connection_broken error CUDT::s_UDTUnited.m_EPoll.update_events( i->u->m_SocketID, i->u->m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); i->u->completeBrokenConnectionDependencies(i->errorcode); } { // Now, additionally for every failed link reset the TTL so that // they are set expired right now. ScopedLock vg(m_RIDListLock); for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { if (find_if(toRemove.begin(), toRemove.end(), LinkStatusInfo::HasID(i->m_iID)) != toRemove.end()) { LOGC(cnlog.Error, log << "updateConnStatus: processAsyncConnectRequest FAILED on @" << i->m_iID << ". Setting TTL as EXPIRED."); i->m_tsTTL = steady_clock::time_point(); // Make it expire right now, will be picked up at the next iteration } } } } bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, EConnectStatus cst SRT_ATR_UNUSED, int iDstSockID, vector& toRemove, vector& toProcess) { ScopedLock vg(m_RIDListLock); if (m_lRendezvousID.empty()) return false; // nothing to process. HLOGC(cnlog.Debug, log << "updateConnStatus: updating after getting pkt with DST socket ID @" << iDstSockID << " status: " << ConnectStatusStr(cst)); for (list::iterator i = m_lRendezvousID.begin(), i_next = i; i != m_lRendezvousID.end(); i = i_next) { // Safe iterator to the next element. If the current element is erased, the iterator is updated again. ++i_next; const steady_clock::time_point tsNow = steady_clock::now(); if (tsNow >= i->m_tsTTL) { HLOGC(cnlog.Debug, log << "RID: socket @" << i->m_iID << " removed - EXPIRED (" // The "enforced on FAILURE" is below when processAsyncConnectRequest failed. << (is_zero(i->m_tsTTL) ? "enforced on FAILURE" : "passed TTL") << "). WILL REMOVE from queue."); // Set appropriate error information, but do not update yet. // Exit the lock first. Collect objects to update them later. int ccerror = SRT_ECONNREJ; if (i->m_pUDT->m_RejectReason == SRT_REJ_UNKNOWN) { if (!is_zero(i->m_tsTTL)) { // Timer expired, set TIMEOUT forcefully i->m_pUDT->m_RejectReason = SRT_REJ_TIMEOUT; ccerror = SRT_ENOSERVER; } else { // In case of unknown reason, rejection should at least // suggest error on the peer i->m_pUDT->m_RejectReason = SRT_REJ_PEER; } } // The call to completeBrokenConnectionDependencies() cannot happen here // under the lock of m_RIDListLock as it risks a deadlock. // Collect in 'toRemove' to update later. LinkStatusInfo fi = {i->m_pUDT, i->m_iID, ccerror, i->m_PeerAddr, -1}; toRemove.push_back(fi); // i_next was preincremented, but this is guaranteed to point to // the element next to erased one. i_next = m_lRendezvousID.erase(i); continue; } else { HLOGC(cnlog.Debug, log << "RID: socket @" << i->m_iID << " still active (remaining " << std::fixed << (count_microseconds(i->m_tsTTL - tsNow) / 1000000.0) << "s of TTL)..."); } const steady_clock::time_point tsLastReq = i->m_pUDT->m_tsLastReqTime; const steady_clock::time_point tsRepeat = tsLastReq + milliseconds_from(250); // Repeat connection request (send HS). // A connection request is repeated every 250 ms if there was no response from the peer: // - RST_AGAIN means no packet was received over UDP. // - a packet was received, but not for THIS socket. if ((rst == RST_AGAIN || i->m_iID != iDstSockID) && tsNow <= tsRepeat) { HLOGC(cnlog.Debug, log << "RID:@" << i->m_iID << std::fixed << count_microseconds(tsNow - tsLastReq) / 1000.0 << " ms passed since last connection request."); continue; } HLOGC(cnlog.Debug, log << "RID:@" << i->m_iID << " cst=" << ConnectStatusStr(cst) << " -- repeating connection request."); // This queue is used only in case of Async mode (rendezvous or caller-listener). // Synchronous connection requests are handled in startConnect() completely. if (!i->m_pUDT->m_config.bSynRecving) { // Collect them so that they can be updated out of m_RIDListLock. LinkStatusInfo fi = {i->m_pUDT, i->m_iID, SRT_SUCCESS, i->m_PeerAddr, -1}; toProcess.push_back(fi); } else { HLOGC(cnlog.Debug, log << "RID: socket @" << i->m_iID << " is SYNCHRONOUS, NOT UPDATING"); } } return !toRemove.empty() || !toProcess.empty(); } // srt::CRcvQueue::CRcvQueue() : m_WorkerThread() , m_UnitQueue() , m_pRcvUList(NULL) , m_pHash(NULL) , m_pChannel(NULL) , m_pTimer(NULL) , m_szPayloadSize() , m_bClosing(false) , m_LSLock() , m_pListener(NULL) , m_pRendezvousQueue(NULL) , m_vNewEntry() , m_IDLock() , m_mBuffer() , m_BufferCond() { setupCond(m_BufferCond, "QueueBuffer"); } srt::CRcvQueue::~CRcvQueue() { m_bClosing = true; if (m_WorkerThread.joinable()) { HLOGC(rslog.Debug, log << "RcvQueue: EXIT"); m_WorkerThread.join(); } releaseCond(m_BufferCond); 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(); } } } #if ENABLE_LOGGING int srt::CRcvQueue::m_counter = 0; #endif void srt::CRcvQueue::init(int qsize, size_t payload, int version, int hsize, CChannel* cc, CTimer* t) { m_szPayloadSize = payload; m_UnitQueue.init(qsize, (int)payload, version); m_pHash = new CHash; m_pHash->init(hsize); m_pChannel = cc; m_pTimer = t; m_pRcvUList = new CRcvUList; m_pRendezvousQueue = new CRendezvousQueue; #if ENABLE_LOGGING ++m_counter; const std::string thrname = "SRT:RcvQ:w" + Sprint(m_counter); #else const std::string thrname = "SRT:RcvQ:w"; #endif if (!StartThread(m_WorkerThread, CRcvQueue::worker, this, thrname.c_str())) { throw CUDTException(MJ_SYSTEMRES, MN_THREAD); } } void* srt::CRcvQueue::worker(void* param) { CRcvQueue* self = (CRcvQueue*)param; sockaddr_any sa(self->m_UnitQueue.getIPversion()); int32_t id = 0; #if ENABLE_LOGGING THREAD_STATE_INIT(("SRT:RcvQ:w" + Sprint(m_counter)).c_str()); #else THREAD_STATE_INIT("SRT:RcvQ:worker"); #endif CUnit* unit = 0; EConnectStatus cst = CONN_AGAIN; while (!self->m_bClosing) { bool have_received = false; EReadStatus rst = self->worker_RetrieveUnit((id), (unit), (sa)); if (rst == RST_OK) { 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". HLOGC(qrlog.Debug, log << self->CONID() << "RECEIVED negative socket id '" << id << "', rejecting (POSSIBLE ATTACK)"); continue; } // NOTE: cst state is being changed here. // This state should be maintained through any next failed calls to worker_RetrieveUnit. // Any error switches this to rejection, just for a case. // Note to rendezvous connection. This can accept: // - ID == 0 - take the first waiting rendezvous socket // - ID > 0 - find the rendezvous socket that has this ID. if (id == 0) { // ID 0 is for connection request, which should be passed to the listening socket or rendezvous sockets cst = self->worker_ProcessConnectionRequest(unit, sa); } else { // Otherwise ID is expected to be associated with: // - an enqueued rendezvous socket // - a socket connected to a peer cst = self->worker_ProcessAddressedPacket(id, unit, sa); // CAN RETURN CONN_REJECT, but m_RejectReason is already set } HLOGC(qrlog.Debug, log << self->CONID() << "worker: result for the unit: " << ConnectStatusStr(cst)); if (cst == CONN_AGAIN) { HLOGC(qrlog.Debug, log << self->CONID() << "worker: packet not dispatched, continuing reading."); continue; } have_received = true; } else if (rst == RST_ERROR) { // According to the description by CChannel::recvfrom, this can be either of: // - IPE: all errors except EBADF // - socket was closed in the meantime by another thread: EBADF // If EBADF, then it's expected that the "closing" state is also set. // Check that just to report possible errors, but interrupt the loop anyway. if (self->m_bClosing) { HLOGC(qrlog.Debug, log << self->CONID() << "CChannel reported error, but Queue is closing - INTERRUPTING worker."); } else { LOGC(qrlog.Fatal, log << self->CONID() << "CChannel reported ERROR DURING TRANSMISSION - IPE. INTERRUPTING worker anyway."); } cst = CONN_REJECT; break; } // OTHERWISE: this is an "AGAIN" situation. No data was read, but the process should continue. // take care of the timing event for all UDT sockets const steady_clock::time_point curtime_minus_syn = steady_clock::now() - microseconds_from(CUDT::COMM_SYN_INTERVAL_US); CRNode* ul = self->m_pRcvUList->m_pUList; while ((NULL != ul) && (ul->m_tsTimeStamp < curtime_minus_syn)) { CUDT* u = ul->m_pUDT; if (u->m_bConnected && !u->m_bBroken && !u->m_bClosing) { u->checkTimers(); self->m_pRcvUList->update(u); } else { HLOGC(qrlog.Debug, log << CUDTUnited::CONID(u->m_SocketID) << " SOCKET broken, REMOVING FROM RCV QUEUE/MAP."); // the socket must be removed from Hash table first, then RcvUList self->m_pHash->remove(u->m_SocketID); self->m_pRcvUList->remove(u); u->m_pRNode->m_bOnList = false; } ul = self->m_pRcvUList->m_pUList; } if (have_received) { HLOGC(qrlog.Debug, log << "worker: RECEIVED PACKET --> updateConnStatus. cst=" << ConnectStatusStr(cst) << " id=" << id << " pkt-payload-size=" << unit->m_Packet.getLength()); } // Check connection requests status for all sockets in the RendezvousQueue. // Pass the connection status from the last call of: // worker_ProcessAddressedPacket ---> // worker_TryAsyncRend_OrStore ---> // CUDT::processAsyncConnectResponse ---> // CUDT::processConnectResponse self->m_pRendezvousQueue->updateConnStatus(rst, cst, unit); // XXX updateConnStatus may have removed the connector from the list, // however there's still m_mBuffer in CRcvQueue for that socket to care about. } HLOGC(qrlog.Debug, log << "worker: EXIT"); THREAD_EXIT(); return NULL; } EReadStatus srt::CRcvQueue::worker_RetrieveUnit(int32_t& w_id, CUnit*& w_unit, sockaddr_any& w_addr) { #if !USE_BUSY_WAITING // This might be not really necessary, and probably // not good for extensive bidirectional communication. m_pTimer->tick(); #endif // check waiting list, if new socket, insert it to the list while (ifNewEntry()) { CUDT* ne = getNewEntry(); if (ne) { HLOGC(qrlog.Debug, log << CUDTUnited::CONID(ne->m_SocketID) << " SOCKET pending for connection - ADDING TO RCV QUEUE/MAP"); m_pRcvUList->insert(ne); m_pHash->insert(ne->m_SocketID, ne); } } // find next available slot for incoming packet w_unit = m_UnitQueue.getNextAvailUnit(); if (!w_unit) { // no space, skip this packet CPacket temp; temp.m_pcData = new char[m_szPayloadSize]; temp.setLength(m_szPayloadSize); THREAD_PAUSED(); EReadStatus rst = m_pChannel->recvfrom((w_addr), (temp)); THREAD_RESUMED(); // Note: this will print nothing about the packet details unless heavy logging is on. LOGC(qrlog.Error, log << CONID() << "LOCAL STORAGE DEPLETED. Dropping 1 packet: " << temp.Info()); delete[] temp.m_pcData; // Be transparent for RST_ERROR, but ignore the correct // data read and fake that the packet was dropped. return rst == RST_ERROR ? RST_ERROR : RST_AGAIN; } w_unit->m_Packet.setLength(m_szPayloadSize); // reading next incoming packet, recvfrom returns -1 is nothing has been received THREAD_PAUSED(); EReadStatus rst = m_pChannel->recvfrom((w_addr), (w_unit->m_Packet)); THREAD_RESUMED(); if (rst == RST_OK) { w_id = w_unit->m_Packet.m_iID; HLOGC(qrlog.Debug, log << "INCOMING PACKET: FROM=" << w_addr.str() << " BOUND=" << m_pChannel->bindAddressAny().str() << " " << w_unit->m_Packet.Info()); } return rst; } EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, const sockaddr_any& addr) { HLOGC(cnlog.Debug, log << "Got sockID=0 from " << addr.str() << " - trying to resolve it as a connection request..."); // Introduced protection because it may potentially happen // that another thread could have closed the socket at // the same time and inject a bug between checking the // pointer for NULL and using it. int listener_ret = SRT_REJ_UNKNOWN; bool have_listener = false; { ScopedLock cg(m_LSLock); if (m_pListener) { LOGC(cnlog.Note, log << "PASSING request from: " << addr.str() << " to agent:" << m_pListener->socketID()); listener_ret = m_pListener->processConnectRequest(addr, unit->m_Packet); // This function does return a code, but it's hard to say as to whether // anything can be done about it. In case when it's stated possible, the // listener will try to send some rejection response to the caller, but // that's already done inside this function. So it's only used for // displaying the error in logs. have_listener = true; } } // NOTE: Rendezvous sockets do bind(), but not listen(). It means that the socket is // ready to accept connection requests, but they are not being redirected to the listener // socket, as this is not a listener socket at all. This goes then HERE. if (have_listener) // That is, the above block with m_pListener->processConnectRequest was executed { LOGC(cnlog.Note, log << CONID() << "Listener managed the connection request from: " << addr.str() << " result:" << RequestTypeStr(UDTRequestType(listener_ret))); return listener_ret == SRT_REJ_UNKNOWN ? CONN_CONTINUE : CONN_REJECT; } // If there's no listener waiting for the packet, just store it into the queue. return worker_TryAsyncRend_OrStore(0, unit, addr); // 0 id because the packet came in with that very ID. } EConnectStatus srt::CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr_any& addr) { CUDT* u = m_pHash->lookup(id); if (!u) { // Pass this to either async rendezvous connection, // or store the packet in the queue. HLOGC(cnlog.Debug, log << "worker_ProcessAddressedPacket: resending to QUEUED socket @" << id); return worker_TryAsyncRend_OrStore(id, unit, addr); } // Found associated CUDT - process this as control or data packet // addressed to an associated socket. if (addr != u->m_PeerAddr) { HLOGC(cnlog.Debug, log << CONID() << "Packet for SID=" << id << " asoc with " << u->m_PeerAddr.str() << " received from " << addr.str() << " (CONSIDERED ATTACK ATTEMPT)"); // This came not from the address that is the peer associated // with the socket. Ignore it. return CONN_AGAIN; } if (!u->m_bConnected || u->m_bBroken || u->m_bClosing) { u->m_RejectReason = SRT_REJ_CLOSE; // 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 CONN_REJECT; } if (unit->m_Packet.isControl()) u->processCtrl(unit->m_Packet); else u->processData(unit); u->checkTimers(); m_pRcvUList->update(u); return CONN_RUNNING; } // This function responds to the fact that a packet has come // for a socket that does not expect to receive a normal connection // request. This can be then: // - a normal packet of whatever kind, just to be processed by the message loop // - a rendezvous connection // This function then tries to manage the packet as a rendezvous connection // request in ASYNC mode; when this is not applicable, it stores the packet // in the "receiving queue" so that it will be picked up in the "main" thread. EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr_any& addr) { // This 'retrieve' requires that 'id' be either one of those // stored in the rendezvous queue (see CRcvQueue::registerConnector) // or simply 0, but then at least the address must match one of these. // If the id was 0, it will be set to the actual socket ID of the returned CUDT. CUDT* u = m_pRendezvousQueue->retrieve(addr, (id)); if (!u) { // this socket is then completely unknown to the system. // Note that this situation may also happen at a very unfortunate // coincidence that the socket is already bound, but the registerConnector() // has not yet started. In case of rendezvous this may mean that the other // side just started sending its handshake packets, the local side has already // run the CRcvQueue::worker thread, and this worker thread is trying to dispatch // the handshake packet too early, before the dispatcher has a chance to see // this socket registerred in the RendezvousQueue, which causes the packet unable // to be dispatched. Therefore simply treat every "out of band" packet (with socket // not belonging to the connection and not registered as rendezvous) as "possible // attack" and ignore it. This also should better protect the rendezvous socket // against a rogue connector. if (id == 0) { HLOGC(cnlog.Debug, log << CONID() << "AsyncOrRND: no sockets expect connection from " << addr.str() << " - POSSIBLE ATTACK, ignore packet"); } else { HLOGC(cnlog.Debug, log << CONID() << "AsyncOrRND: no sockets expect socket " << id << " from " << addr.str() << " - POSSIBLE ATTACK, ignore packet"); } return CONN_AGAIN; // This means that the packet should be ignored. } // asynchronous connect: call connect here // otherwise wait for the UDT socket to retrieve this packet if (!u->m_config.bSynRecving) { HLOGC(cnlog.Debug, log << "AsyncOrRND: packet RESOLVED TO @" << id << " -- continuing as ASYNC CONNECT"); // This is practically same as processConnectResponse, just this applies // appropriate mutex lock - which can't be done here because it's intentionally private. // OTOH it can't be applied to processConnectResponse because the synchronous // call to this method applies the lock by itself, and same-thread-double-locking is nonportable (crashable). EConnectStatus cst = u->processAsyncConnectResponse(unit->m_Packet); if (cst == CONN_CONFUSED) { LOGC(cnlog.Warn, log << "AsyncOrRND: PACKET NOT HANDSHAKE - re-requesting handshake from peer"); storePkt(id, unit->m_Packet.clone()); if (!u->processAsyncConnectRequest(RST_AGAIN, CONN_CONTINUE, &unit->m_Packet, u->m_PeerAddr)) { // Reuse previous behavior to reject a packet cst = CONN_REJECT; } else { cst = CONN_CONTINUE; } } // It might be that this is a data packet, which has turned the connection // into "connected" state, removed the connector (so since now every next packet // will land directly in the queue), but this data packet shall still be delivered. if (cst == CONN_ACCEPT && !unit->m_Packet.isControl()) { // The process as called through processAsyncConnectResponse() should have put the // socket into the pending queue for pending connection (don't ask me, this is so). // This pending queue is being purged every time in the beginning of this loop, so // currently the socket is in the pending queue, but not yet in the connection queue. // It will be done at the next iteration of the reading loop, but it will be too late, // we have a pending data packet now and we must either dispatch it to an already connected // socket or disregard it, and rather prefer the former. So do this transformation now // that we KNOW (by the cst == CONN_ACCEPT result) that the socket should be inserted // into the pending anteroom. CUDT* ne = getNewEntry(); // This function actuall removes the entry and returns it. // This **should** now always return a non-null value, but check it first // because if this accidentally isn't true, the call to worker_ProcessAddressedPacket will // result in redirecting it to here and so on until the call stack overflow. In case of // this "accident" simply disregard the packet from any further processing, it will be later // loss-recovered. // XXX (Probably the old contents of UDT's CRcvQueue::worker should be shaped a little bit // differently throughout the functions). if (ne) { HLOGC(cnlog.Debug, log << CUDTUnited::CONID(ne->m_SocketID) << " SOCKET pending for connection - ADDING TO RCV QUEUE/MAP"); m_pRcvUList->insert(ne); m_pHash->insert(ne->m_SocketID, ne); // The current situation is that this has passed processAsyncConnectResponse, but actually // this packet *SHOULD HAVE BEEN* handled by worker_ProcessAddressedPacket, however the // connection state wasn't completed at the moment when dispatching this packet. This has // been now completed inside the call to processAsyncConnectResponse, but this is still a // data packet that should have expected the connection to be already established. Therefore // redirect it once again into worker_ProcessAddressedPacket here. HLOGC(cnlog.Debug, log << "AsyncOrRND: packet SWITCHED TO CONNECTED with ID=" << id << " -- passing to worker_ProcessAddressedPacket"); // Theoretically we should check if m_pHash->lookup(ne->m_SocketID) returns 'ne', but this // has been just added to m_pHash, so the check would be extremely paranoid here. cst = worker_ProcessAddressedPacket(id, unit, addr); if (cst == CONN_REJECT) return cst; return CONN_ACCEPT; // this function usually will return CONN_CONTINUE, which doesn't represent current // situation. } else { LOGC(cnlog.Error, log << "IPE: AsyncOrRND: packet SWITCHED TO CONNECTED, but ID=" << id << " is still not present in the socket ID dispatch hash - DISREGARDING"); } } return cst; } HLOGC(cnlog.Debug, log << "AsyncOrRND: packet RESOLVED TO ID=" << id << " -- continuing through CENTRAL PACKET QUEUE"); // This is where also the packets for rendezvous connection will be landing, // in case of a synchronous connection. storePkt(id, unit->m_Packet.clone()); return CONN_CONTINUE; } void srt::CRcvQueue::stopWorker() { // We use the decent way, so we say to the thread "please exit". m_bClosing = true; // Sanity check of the function's affinity. if (srt::sync::this_thread::get_id() == m_WorkerThread.get_id()) { LOGC(rslog.Error, log << "IPE: RcvQ:WORKER TRIES TO CLOSE ITSELF!"); return; // do nothing else, this would cause a hangup or crash. } HLOGC(rslog.Debug, log << "RcvQueue: EXIT (forced)"); // And we trust the thread that it does. m_WorkerThread.join(); } int srt::CRcvQueue::recvfrom(int32_t id, CPacket& w_packet) { UniqueLock bufferlock(m_BufferLock); CSync buffercond(m_BufferCond, bufferlock); map >::iterator i = m_mBuffer.find(id); if (i == m_mBuffer.end()) { THREAD_PAUSED(); buffercond.wait_for(seconds_from(1)); THREAD_RESUMED(); i = m_mBuffer.find(id); if (i == m_mBuffer.end()) { w_packet.setLength(-1); return -1; } } // retrieve the earliest packet CPacket* newpkt = i->second.front(); if (w_packet.getLength() < newpkt->getLength()) { w_packet.setLength(-1); return -1; } // copy packet content // XXX Check if this wouldn't be better done by providing // copy constructor for DynamicStruct. // XXX Another thing: this looks wasteful. This expects an already // allocated memory on the packet, this thing gets the packet, // copies it into the passed packet and then the source packet // gets deleted. Why not simply return the originally stored packet, // without copying, allocation and deallocation? memcpy((w_packet.m_nHeader), newpkt->m_nHeader, CPacket::HDR_SIZE); memcpy((w_packet.m_pcData), newpkt->m_pcData, newpkt->getLength()); w_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 (int)w_packet.getLength(); } int srt::CRcvQueue::setListener(CUDT* u) { ScopedLock lslock(m_LSLock); if (NULL != m_pListener) return -1; m_pListener = u; return 0; } void srt::CRcvQueue::removeListener(const CUDT* u) { ScopedLock lslock(m_LSLock); if (u == m_pListener) m_pListener = NULL; } void srt::CRcvQueue::registerConnector(const SRTSOCKET& id, CUDT* u, const sockaddr_any& addr, const steady_clock::time_point& ttl) { HLOGC(cnlog.Debug, log << "registerConnector: adding @" << id << " addr=" << addr.str() << " TTL=" << FormatTime(ttl)); m_pRendezvousQueue->insert(id, u, addr, ttl); } void srt::CRcvQueue::removeConnector(const SRTSOCKET& id) { HLOGC(cnlog.Debug, log << "removeConnector: removing @" << id); m_pRendezvousQueue->remove(id); ScopedLock bufferlock(m_BufferLock); map >::iterator i = m_mBuffer.find(id); if (i != m_mBuffer.end()) { HLOGC(cnlog.Debug, log << "removeConnector: ... and its packet queue with " << i->second.size() << " packets collected"); while (!i->second.empty()) { delete[] i->second.front()->m_pcData; delete i->second.front(); i->second.pop(); } m_mBuffer.erase(i); } } void srt::CRcvQueue::setNewEntry(CUDT* u) { HLOGC(cnlog.Debug, log << CUDTUnited::CONID(u->m_SocketID) << "setting socket PENDING FOR CONNECTION"); ScopedLock listguard(m_IDLock); m_vNewEntry.push_back(u); } bool srt::CRcvQueue::ifNewEntry() { return !(m_vNewEntry.empty()); } srt::CUDT* srt::CRcvQueue::getNewEntry() { ScopedLock listguard(m_IDLock); if (m_vNewEntry.empty()) return NULL; CUDT* u = (CUDT*)*(m_vNewEntry.begin()); m_vNewEntry.erase(m_vNewEntry.begin()); return u; } void srt::CRcvQueue::storePkt(int32_t id, CPacket* pkt) { UniqueLock bufferlock(m_BufferLock); CSync passcond(m_BufferCond, bufferlock); map >::iterator i = m_mBuffer.find(id); if (i == m_mBuffer.end()) { m_mBuffer[id].push(pkt); passcond.signal_locked(bufferlock); } else { // avoid storing too many packets, in case of malfunction or attack if (i->second.size() > 16) return; i->second.push(pkt); } } void srt::CMultiplexer::destroy() { // Reverse order of the assigned delete m_pRcvQueue; delete m_pSndQueue; delete m_pTimer; if (m_pChannel) { m_pChannel->close(); delete m_pChannel; } } srt-1.4.4/srtcore/queue.h000066400000000000000000000460021412557703600153220ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 01/12/2011 modified by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_QUEUE_H #define INC_SRT_QUEUE_H #include "common.h" #include "packet.h" #include "socketconfig.h" #include "netinet_any.h" #include "utilities.h" #include #include #include #include namespace srt { class CChannel; 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 { public: CUnitQueue(); ~CUnitQueue(); public: // Storage size operations /// 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(); public: int size() const { return m_iSize - m_iCount; } int capacity() const { return m_iSize; } public: // Operations on units /// find an available unit for incoming packet. /// @return Pointer to the available unit, NULL if not found. CUnit* getNextAvailUnit(); void makeUnitFree(CUnit* unit); void makeUnitGood(CUnit* unit); public: inline int getIPversion() const { return m_iIPversion; } 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 srt::sync::atomic m_iCount; // total number of valid (occupied) 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 sync::steady_clock::time_point m_tsTimeStamp; srt::sync::atomic m_iHeapLoc; // location on the heap, -1 means not on the heap }; class CSndUList { public: CSndUList(sync::CTimer* pTimer); ~CSndUList(); public: enum EReschedule { DONT_RESCHEDULE = 0, DO_RESCHEDULE = 1 }; static EReschedule rescheduleIf(bool cond) { return cond ? DO_RESCHEDULE : DONT_RESCHEDULE; } /// Update the timestamp of the UDT instance on the list. /// @param [in] u pointer to the UDT instance /// @param [in] reschedule if the timestamp should be rescheduled /// @param [in] ts the next time to trigger sending logic on the CUDT void update(const CUDT* u, EReschedule reschedule, sync::steady_clock::time_point ts = sync::steady_clock::now()); /// Retrieve the next (in time) socket from the heap to process its sending request. /// @return a pointer to CUDT instance to process next. CUDT* pop(); /// Remove UDT instance from the list. /// @param [in] u pointer to the UDT instance void remove(const CUDT* u);// EXCLUDES(m_ListLock); /// Retrieve the next scheduled processing time. /// @return Scheduled processing time of the first UDT socket in the list. sync::steady_clock::time_point getNextProcTime(); /// Wait for the list to become non empty. void waitNonEmpty() const; /// Signal to stop waiting in waitNonEmpty(). void signalInterrupt() const; private: /// Doubles the size of the list. /// void realloc_();// REQUIRES(m_ListLock); /// Insert a new UDT instance into the list with realloc if required. /// /// @param [in] ts time stamp: next processing time /// @param [in] u pointer to the UDT instance void insert_(const sync::steady_clock::time_point& ts, const CUDT* u); /// Insert a new UDT instance into the list without realloc. /// Should be called if there is a gauranteed space for the element. /// /// @param [in] ts time stamp: next processing time /// @param [in] u pointer to the UDT instance void insert_norealloc_(const sync::steady_clock::time_point& ts, const CUDT* u);// REQUIRES(m_ListLock); /// Removes CUDT entry from the list. /// If the last entry is removed, calls sync::CTimer::interrupt(). 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 or -1 if empty. mutable sync::Mutex m_ListLock; // Protects the list (m_pHeap, m_iArrayLength, m_iLastEntry). mutable sync::Condition m_ListCond; sync::CTimer* const m_pTimer; private: CSndUList(const CSndUList&); CSndUList& operator=(const CSndUList&); }; struct CRNode { CUDT* m_pUDT; // Pointer to the instance of CUDT socket sync::steady_clock::time_point m_tsTimeStamp; // Time Stamp CRNode* m_pPrev; // previous link CRNode* m_pNext; // next link srt::sync::atomic 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&); }; /// @brief A queue of sockets pending for connection. /// It can be either a caller socket in a non-blocking mode /// (the connection has to be handled in background), /// or a socket in rendezvous connection mode. class CRendezvousQueue { public: CRendezvousQueue(); ~CRendezvousQueue(); public: /// @brief Insert a new socket pending for connection (non-blocking caller or rendezvous). /// @param id socket ID. /// @param u pointer to a corresponding CUDT instance. /// @param addr remote address to connect to. /// @param ttl timepoint for connection attempt to expire. void insert(const SRTSOCKET& id, CUDT* u, const sockaddr_any& addr, const srt::sync::steady_clock::time_point& ttl); /// @brief Remove a socket from the connection pending list. /// @param id socket ID. void remove(const SRTSOCKET& id); /// @brief Locate a socket in the connection pending queue. /// @param addr source address of the packet received over UDP (peer address). /// @param id socket ID. /// @return a pointer to CUDT instance retrieved, or NULL if nothing was found. CUDT* retrieve(const sockaddr_any& addr, SRTSOCKET& id) const; /// @brief Update status of connections in the pending queue. /// Stop connecting if TTL expires. Resend handshake request every 250 ms if no response from the peer. /// @param rst result of reading from a UDP socket: received packet / nothin read / read error. /// @param cst target status for pending connection: reject or proceed. /// @param pktIn packet received from the UDP socket. void updateConnStatus(EReadStatus rst, EConnectStatus cst, CUnit* unit); private: struct LinkStatusInfo { CUDT* u; SRTSOCKET id; int errorcode; sockaddr_any peeraddr; int token; struct HasID { SRTSOCKET id; HasID(SRTSOCKET p) : id(p) { } bool operator()(const LinkStatusInfo& i) { return i.id == id; } }; }; /// @brief Qualify pending connections: /// - Sockets with expired TTL go to the 'to_remove' list and removed from the queue straight away. /// - If HS request is to be resent (resend 250 ms if no response from the peer) go to the 'to_process' list. /// /// @param rst result of reading from a UDP socket: received packet / nothin read / read error. /// @param cst target status for pending connection: reject or proceed. /// @param iDstSockID destination socket ID of the received packet. /// @param[in,out] toRemove stores sockets with expired TTL. /// @param[in,out] toProcess stores sockets which should repeat (resend) HS connection request. bool qualifyToHandle(EReadStatus rst, EConnectStatus cst, int iDstSockID, std::vector& toRemove, std::vector& toProcess); private: struct CRL { SRTSOCKET m_iID; // SRT socket ID (self) CUDT* m_pUDT; // CUDT instance sockaddr_any m_PeerAddr; // SRT sonnection peer address srt::sync::steady_clock::time_point m_tsTTL; // the time that this request expires }; std::list m_lRendezvousID; // The sockets currently in rendezvous mode mutable sync::Mutex m_RIDListLock; }; 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, srt::sync::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_any& addr, CPacket& packet); /// 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; #ifdef SRT_ENABLE_BINDTODEVICE bool getBind(char* dst, size_t len) const; #endif int ioctlQuery(int type) const; int sockoptQuery(int level, int type) const; void setClosing() { m_bClosing = true; } private: static void* worker(void* param); srt::sync::CThread m_WorkerThread; private: CSndUList* m_pSndUList; // List of UDT instances for data sending CChannel* m_pChannel; // The UDP channel for data sending srt::sync::CTimer* m_pTimer; // Timing facility srt::sync::atomic m_bClosing; // closing the worker #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 */ #if ENABLE_LOGGING static int m_counter; #endif 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. 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, size_t payload, int version, int hsize, CChannel* c, sync::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& to_packet); void stopWorker(); void setClosing() { m_bClosing = true; } private: static void* worker(void* param); sync::CThread m_WorkerThread; // Subroutines of worker EReadStatus worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr_any& sa); EConnectStatus worker_ProcessConnectionRequest(CUnit* unit, const sockaddr_any& sa); EConnectStatus worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr_any& sa); EConnectStatus worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr_any& 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 sync::CTimer* m_pTimer; // shared timer with the snd queue size_t m_szPayloadSize; // packet payload size srt::sync::atomic m_bClosing; // closing the worker #if ENABLE_LOGGING static int m_counter; #endif private: int setListener(CUDT* u); void removeListener(const CUDT* u); void registerConnector(const SRTSOCKET& id, CUDT* u, const sockaddr_any& addr, const sync::steady_clock::time_point& ttl); void removeConnector(const SRTSOCKET& id); void setNewEntry(CUDT* u); bool ifNewEntry(); CUDT* getNewEntry(); void storePkt(int32_t id, CPacket* pkt); private: sync::Mutex 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 sync::Mutex m_IDLock; std::map > m_mBuffer; // temporary buffer for rendezvous connection request sync::Mutex m_BufferLock; sync::Condition m_BufferCond; 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 sync::CTimer* m_pTimer; // The timer int m_iPort; // The UDP port number of this multiplexer int m_iIPversion; // Address family (AF_INET or AF_INET6) int m_iRefCount; // number of UDT instances that are associated with this multiplexer CSrtMuxerConfig m_mcfg; int m_iID; // multiplexer ID // Constructor should reset all pointers to NULL // to prevent dangling pointer when checking for memory alloc fails CMultiplexer() : m_pSndQueue(NULL) , m_pRcvQueue(NULL) , m_pChannel(NULL) , m_pTimer(NULL) { } void destroy(); }; } // namespace srt #endif srt-1.4.4/srtcore/socketconfig.cpp000066400000000000000000000761151412557703600172170ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #include #include "srt.h" #include "socketconfig.h" extern const int32_t SRT_DEF_VERSION = SrtParseVersion(SRT_VERSION); namespace { typedef void setter_function(CSrtConfig& co, const void* optval, int optlen); template struct CSrtConfigSetter { static setter_function set; }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { int ival = cast_optval(optval, optlen); if (ival < int(srt::CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iMSS = ival; // Packet size cannot be greater than UDP buffer size if (co.iMSS > co.iUDPSndBufSize) co.iMSS = co.iUDPSndBufSize; if (co.iMSS > co.iUDPRcvBufSize) co.iMSS = co.iUDPRcvBufSize; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { using namespace srt_logging; const int fc = cast_optval(optval, optlen); if (fc < co.DEF_MIN_FLIGHT_PKT) { LOGC(kmlog.Error, log << "SRTO_FC: minimum allowed value is 32 (provided: " << fc << ")"); throw CUDTException(MJ_NOTSUP, MN_INVAL); } co.iFlightFlagSize = fc; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { int bs = cast_optval(optval, optlen); if (bs <= 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iSndBufSize = bs / (co.iMSS - srt::CPacket::UDP_HDR_SIZE); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val <= 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); // Mimimum recv buffer size is 32 packets const int mssin_size = co.iMSS - srt::CPacket::UDP_HDR_SIZE; if (val > mssin_size * co.DEF_MIN_FLIGHT_PKT) co.iRcvBufSize = val / mssin_size; else co.iRcvBufSize = co.DEF_MIN_FLIGHT_PKT; // recv buffer MUST not be greater than FC size if (co.iRcvBufSize > co.iFlightFlagSize) co.iRcvBufSize = co.iFlightFlagSize; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.Linger = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.iUDPSndBufSize = std::max(co.iMSS, cast_optval(optval, optlen)); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.iUDPRcvBufSize = std::max(co.iMSS, cast_optval(optval, optlen)); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bRendezvous = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val < -1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iSndTimeOut = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val < -1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iRcvTimeOut = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bSynSending = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bSynRecving = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bReuseAddr = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int64_t val = cast_optval(optval, optlen); if (val < -1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.llMaxBW = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { int val = cast_optval(optval, optlen); if (!(val == -1) && !((val >= 1) && (val <= 255))) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iIpTTL = cast_optval(optval); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.iIpToS = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { using namespace srt_logging; #ifdef SRT_ENABLE_BINDTODEVICE using namespace std; using namespace srt_logging; string val; if (optlen == -1) val = (const char *)optval; else val.assign((const char *)optval, optlen); if (val.size() >= IFNAMSIZ) { LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE: device name too long (max: IFNAMSIZ=" << IFNAMSIZ << ")"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } co.sBindToDevice = val; #else (void)co; // prevent warning (void)optval; (void)optlen; LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE is not supported on that platform"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #endif } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int64_t val = cast_optval(optval, optlen); if (val < 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.llInputBW = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int64_t val = cast_optval(optval, optlen); if (val < 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.llMinInputBW = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int32_t val = cast_optval(optval, optlen); if (val < 5 || val > 100) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iOverheadBW = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bDataSender = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bTSBPD = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val < 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iRcvLatency = val; co.iPeerLatency = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val < 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iRcvLatency = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val < 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iPeerLatency = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bTLPktDrop = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val < -1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iSndDropDelay = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { using namespace srt_logging; #ifdef SRT_ENABLE_ENCRYPTION // Password must be 10-80 characters. // Or it can be empty to clear the password. if ((optlen != 0) && (optlen < 10 || optlen > HAICRYPT_SECRET_MAX_SZ)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); memset(&co.CryptoSecret, 0, sizeof(co.CryptoSecret)); co.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; co.CryptoSecret.len = (optlen <= (int)sizeof(co.CryptoSecret.str) ? optlen : (int)sizeof(co.CryptoSecret.str)); memcpy((co.CryptoSecret.str), optval, co.CryptoSecret.len); #else (void)co; // prevent warning (void)optval; if (optlen == 0) return; // Allow to set empty passphrase if no encryption supported. LOGC(aclog.Error, log << "SRTO_PASSPHRASE: encryption not enabled at compile time"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #endif } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { using namespace srt_logging; #ifdef SRT_ENABLE_ENCRYPTION const int v = cast_optval(optval, optlen); int const allowed[4] = { 0, // Default value, if this results for initiator, defaults to 16. See below. 16, // AES-128 24, // AES-192 32 // AES-256 }; const int *const allowed_end = allowed + 4; if (std::find(allowed, allowed_end, v) == allowed_end) { LOGC(aclog.Error, log << "Invalid value for option SRTO_PBKEYLEN: " << v << "; allowed are: 0, 16, 24, 32"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } // Note: This works a little different in HSv4 and HSv5. // HSv4: // The party that is set SRTO_SENDER will send KMREQ, and it will // use default value 16, if SRTO_PBKEYLEN is the default value 0. // The responder that receives KMRSP has nothing to say about // PBKEYLEN anyway and it will take the length of the key from // the initiator (sender) as a good deal. // // HSv5: // The initiator (independently on the sender) will send KMREQ, // and as it should be the sender to decide about the PBKEYLEN. // Your application should do the following then: // 1. The sender should set PBKEYLEN to the required value. // 2. If the sender is initiator, it will create the key using // its preset PBKEYLEN (or default 16, if not set) and the // receiver-responder will take it as a good deal. // 3. Leave the PBKEYLEN value on the receiver as default 0. // 4. If sender is responder, it should then advertise the PBKEYLEN // value in the initial handshake messages (URQ_INDUCTION if // listener, and both URQ_WAVEAHAND and URQ_CONCLUSION in case // of rendezvous, as it is the matter of luck who of them will // eventually become the initiator). This way the receiver // being an initiator will set iSndCryptoKeyLen before setting // up KMREQ for sending to the sender-responder. // // Note that in HSv5 if both sides set PBKEYLEN, the responder // wins, unless the initiator is a sender (the effective PBKEYLEN // will be the one advertised by the responder). If none sets, // PBKEYLEN will default to 16. co.iSndCryptoKeyLen = v; #else (void)co; // prevent warning (void)optval; (void)optlen; LOGC(aclog.Error, log << "SRTO_PBKEYLEN: encryption not enabled at compile time"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #endif } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bRcvNakReport = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val < 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); using namespace srt::sync; co.tdConnTimeOut = milliseconds_from(val); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bDriftTracer = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.iMaxReorderTolerance = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.uSrtVersion = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.uMinimumPeerSrtVersion = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { if (size_t(optlen) > CSrtConfig::MAX_SID_LENGTH) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.sStreamName.set((const char*)optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { std::string val; if (optlen == -1) val = (const char*)optval; else val.assign((const char*)optval, optlen); // Translate alias if (val == "vod") val = "file"; bool res = srt::SrtCongestion::exists(val); if (!res) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.sCongestion.set(val); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bMessageAPI = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { using namespace srt_logging; const int val = cast_optval(optval, optlen); if (val < 0) { throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } if (val > SRT_LIVE_MAX_PLSIZE) { LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE, maximum payload per MTU."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } if (!co.sPacketFilterConfig.empty()) { // This means that the filter might have been installed before, // and the fix to the maximum payload size was already applied. // This needs to be checked now. srt::SrtFilterConfig fc; if (!srt::ParseFilterConfig(co.sPacketFilterConfig.str(), fc)) { // Break silently. This should not happen LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } const size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; if (size_t(val) > efc_max_payload_size) { LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE decreased by " << fc.extra_size << " required for packet filter header"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } } co.zExpPayloadSize = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { // XXX Note that here the configuration for SRTT_LIVE // is the same as DEFAULT VALUES for these fields set // in CUDT::CUDT. switch (cast_optval(optval, optlen)) { case SRTT_LIVE: // Default live options: // - tsbpd: on // - latency: 120ms // - linger: off // - congctl: live // - extraction method: message (reading call extracts one message) co.bTSBPD = true; co.iRcvLatency = SRT_LIVE_DEF_LATENCY_MS; co.iPeerLatency = 0; co.bTLPktDrop = true; co.iSndDropDelay = 0; co.bMessageAPI = true; co.bRcvNakReport = true; co.iRetransmitAlgo = 1; co.zExpPayloadSize = SRT_LIVE_DEF_PLSIZE; co.Linger.l_onoff = 0; co.Linger.l_linger = 0; co.sCongestion.set("live", 4); break; case SRTT_FILE: // File transfer mode: // - tsbpd: off // - latency: 0 // - linger: on // - congctl: file (original UDT congestion control) // - extraction method: stream (reading call extracts as many bytes as available and fits in buffer) co.bTSBPD = false; co.iRcvLatency = 0; co.iPeerLatency = 0; co.bTLPktDrop = false; co.iSndDropDelay = -1; co.bMessageAPI = false; co.bRcvNakReport = false; co.iRetransmitAlgo = 0; co.zExpPayloadSize = 0; // use maximum co.Linger.l_onoff = 1; co.Linger.l_linger = CSrtConfig::DEF_LINGER_S; co.sCongestion.set("file", 4); break; default: throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } } }; #if ENABLE_EXPERIMENTAL_BONDING template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.iGroupConnect = cast_optval(optval, optlen); } }; #endif template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { using namespace srt_logging; const int val = cast_optval(optval, optlen); if (val < 0) { LOGC(aclog.Error, log << "SRTO_KMREFRESHRATE=" << val << " can't be negative"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } // Changing the KMREFRESHRATE sets KMPREANNOUNCE to the maximum allowed value co.uKmRefreshRatePkt = (unsigned) val; if (co.uKmPreAnnouncePkt == 0 && co.uKmRefreshRatePkt == 0) return; // Both values are default const unsigned km_preanno = co.uKmPreAnnouncePkt == 0 ? HAICRYPT_DEF_KM_PRE_ANNOUNCE : co.uKmPreAnnouncePkt; const unsigned km_refresh = co.uKmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : co.uKmRefreshRatePkt; if (co.uKmPreAnnouncePkt == 0 || km_preanno > (km_refresh - 1) / 2) { co.uKmPreAnnouncePkt = (km_refresh - 1) / 2; LOGC(aclog.Warn, log << "SRTO_KMREFRESHRATE=0x" << std::hex << km_refresh << ": setting SRTO_KMPREANNOUNCE=0x" << std::hex << co.uKmPreAnnouncePkt); } } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { using namespace srt_logging; const int val = cast_optval(optval, optlen); if (val < 0) { LOGC(aclog.Error, log << "SRTO_KMPREANNOUNCE=" << val << " can't be negative"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } const unsigned km_preanno = val == 0 ? HAICRYPT_DEF_KM_PRE_ANNOUNCE : val; const unsigned kmref = co.uKmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : co.uKmRefreshRatePkt; if (km_preanno > (kmref - 1) / 2) { LOGC(aclog.Error, log << "SRTO_KMPREANNOUNCE=0x" << std::hex << km_preanno << " exceeds KmRefresh/2, 0x" << ((kmref - 1) / 2) << " - OPTION REJECTED."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } co.uKmPreAnnouncePkt = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.bEnforcedEnc = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val < 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iPeerIdleTimeout = val; } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { co.iIpV6Only = cast_optval(optval, optlen); } }; template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { using namespace srt_logging; std::string arg((const char*)optval, optlen); // Parse the configuration string prematurely srt::SrtFilterConfig fc; srt::PacketFilter::Factory* fax = 0; if (!srt::ParseFilterConfig(arg, (fc), (&fax))) { LOGC(aclog.Error, log << "SRTO_PACKETFILTER: Incorrect syntax. Use: FILTERTYPE[,KEY:VALUE...]. " "FILTERTYPE (" << fc.type << ") must be installed (or builtin)"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } std::string error; if (!fax->verifyConfig(fc, (error))) { LOGC(aclog.Error, log << "SRTO_PACKETFILTER: Incorrect config: " << error); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; if (co.zExpPayloadSize > efc_max_payload_size) { LOGC(aclog.Warn, log << "Due to filter-required extra " << fc.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to " << efc_max_payload_size << " bytes"); co.zExpPayloadSize = efc_max_payload_size; } co.sPacketFilterConfig.set(arg); } }; #if ENABLE_EXPERIMENTAL_BONDING template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { using namespace srt_logging; // This option is meaningless for the socket itself. // It's set here just for the sake of setting it on a listener // socket so that it is then applied on the group when a // group connection is configuired. const int val = cast_optval(optval, optlen); // Search if you already have SRTO_PEERIDLETIMEO set const int idletmo = co.iPeerIdleTimeout; // Both are in milliseconds. // This option is RECORDED in microseconds, while // idletmo is recorded in milliseconds, only translated to // microseconds directly before use. if (val >= idletmo) { LOGC(aclog.Error, log << "group option: SRTO_GROUPSTABTIMEO(" << val << ") exceeds SRTO_PEERIDLETIMEO(" << idletmo << ")"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } co.uStabilityTimeout = val * 1000; } }; #endif template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { const int val = cast_optval(optval, optlen); if (val < 0 || val > 1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iRetransmitAlgo = val; } }; int dispatchSet(SRT_SOCKOPT optName, CSrtConfig& co, const void* optval, int optlen) { switch (optName) { #define DISPATCH(optname) case optname: CSrtConfigSetter::set(co, optval, optlen); return 0; DISPATCH(SRTO_MSS); DISPATCH(SRTO_FC); DISPATCH(SRTO_SNDBUF); DISPATCH(SRTO_RCVBUF); DISPATCH(SRTO_LINGER); DISPATCH(SRTO_UDP_SNDBUF); DISPATCH(SRTO_UDP_RCVBUF); DISPATCH(SRTO_RENDEZVOUS); DISPATCH(SRTO_SNDTIMEO); DISPATCH(SRTO_RCVTIMEO); DISPATCH(SRTO_SNDSYN); DISPATCH(SRTO_RCVSYN); DISPATCH(SRTO_REUSEADDR); DISPATCH(SRTO_MAXBW); DISPATCH(SRTO_IPTTL); DISPATCH(SRTO_IPTOS); DISPATCH(SRTO_BINDTODEVICE); DISPATCH(SRTO_INPUTBW); DISPATCH(SRTO_MININPUTBW); DISPATCH(SRTO_OHEADBW); DISPATCH(SRTO_SENDER); DISPATCH(SRTO_TSBPDMODE); DISPATCH(SRTO_LATENCY); DISPATCH(SRTO_RCVLATENCY); DISPATCH(SRTO_PEERLATENCY); DISPATCH(SRTO_TLPKTDROP); DISPATCH(SRTO_SNDDROPDELAY); DISPATCH(SRTO_PASSPHRASE); DISPATCH(SRTO_PBKEYLEN); DISPATCH(SRTO_NAKREPORT); DISPATCH(SRTO_CONNTIMEO); DISPATCH(SRTO_DRIFTTRACER); DISPATCH(SRTO_LOSSMAXTTL); DISPATCH(SRTO_VERSION); DISPATCH(SRTO_MINVERSION); DISPATCH(SRTO_STREAMID); DISPATCH(SRTO_CONGESTION); DISPATCH(SRTO_MESSAGEAPI); DISPATCH(SRTO_PAYLOADSIZE); DISPATCH(SRTO_TRANSTYPE); #if ENABLE_EXPERIMENTAL_BONDING DISPATCH(SRTO_GROUPCONNECT); #endif DISPATCH(SRTO_KMREFRESHRATE); DISPATCH(SRTO_KMPREANNOUNCE); DISPATCH(SRTO_ENFORCEDENCRYPTION); DISPATCH(SRTO_PEERIDLETIMEO); DISPATCH(SRTO_IPV6ONLY); DISPATCH(SRTO_PACKETFILTER); #if ENABLE_EXPERIMENTAL_BONDING DISPATCH(SRTO_GROUPSTABTIMEO); #endif DISPATCH(SRTO_RETRANSMITALGO); #undef DISPATCH default: return -1; } } } // anonymous namespace int CSrtConfig::set(SRT_SOCKOPT optName, const void* optval, int optlen) { return dispatchSet(optName, *this, optval, optlen); } #if ENABLE_EXPERIMENTAL_BONDING bool SRT_SocketOptionObject::add(SRT_SOCKOPT optname, const void* optval, size_t optlen) { // Check first if this option is allowed to be set // as on a member socket. switch (optname) { case SRTO_BINDTODEVICE: case SRTO_CONNTIMEO: case SRTO_DRIFTTRACER: //SRTO_FC - not allowed to be different among group members case SRTO_GROUPSTABTIMEO: //SRTO_INPUTBW - per transmission setting case SRTO_IPTOS: case SRTO_IPTTL: case SRTO_KMREFRESHRATE: case SRTO_KMPREANNOUNCE: //SRTO_LATENCY - per transmission setting //SRTO_LINGER - not for managed sockets case SRTO_LOSSMAXTTL: //SRTO_MAXBW - per transmission setting //SRTO_MESSAGEAPI - groups are live mode only //SRTO_MINVERSION - per group connection setting case SRTO_NAKREPORT: //SRTO_OHEADBW - per transmission setting //SRTO_PACKETFILTER - per transmission setting //SRTO_PASSPHRASE - per group connection setting //SRTO_PASSPHRASE - per transmission setting //SRTO_PBKEYLEN - per group connection setting case SRTO_PEERIDLETIMEO: case SRTO_RCVBUF: //SRTO_RCVSYN - must be always false in groups //SRTO_RCVTIMEO - must be alwyas -1 in groups case SRTO_SNDBUF: case SRTO_SNDDROPDELAY: //SRTO_TLPKTDROP - per transmission setting //SRTO_TSBPDMODE - per transmission setting case SRTO_UDP_RCVBUF: case SRTO_UDP_SNDBUF: break; default: // Other options are not allowed return false; } // Header size will get the size likely aligned, but it won't // hurt if the memory size will be up to 4 bytes more than // needed - and it's better to not risk that alighment rules // will make these calculations result in less space than needed. const size_t headersize = sizeof(SingleOption); const size_t payload = std::min(sizeof(uint32_t), optlen); unsigned char* mem = new unsigned char[headersize + payload]; SingleOption* option = reinterpret_cast(mem); option->option = optname; option->length = (uint16_t) optlen; memcpy(option->storage, optval, optlen); options.push_back(option); return true; } #endif srt-1.4.4/srtcore/socketconfig.h000066400000000000000000000275641412557703600166700ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_SOCKETCONFIG_H #define INC_SRT_SOCKETCONFIG_H #include "platform_sys.h" #ifdef SRT_ENABLE_BINDTODEVICE #include #endif #include #include "haicrypt.h" #include "congctl.h" #include "packet.h" #include "handshake.h" #include "logger_defs.h" #include "packetfilter.h" // SRT Version constants #define SRT_VERSION_UNK 0 #define SRT_VERSION_MAJ1 0x010000 /* Version 1 major */ #define SRT_VERSION_MAJ(v) (0xFF0000 & (v)) /* Major number ensuring backward compatibility */ #define SRT_VERSION_MIN(v) (0x00FF00 & (v)) #define SRT_VERSION_PCH(v) (0x0000FF & (v)) // NOTE: SRT_VERSION is primarily defined in the build file. extern const int32_t SRT_DEF_VERSION; struct CSrtMuxerConfig { static const int DEF_UDP_BUFFER_SIZE = 65536; int iIpTTL; int iIpToS; int iIpV6Only; // IPV6_V6ONLY option (-1 if not set) bool bReuseAddr; // reuse an exiting port or not, for UDP multiplexer #ifdef SRT_ENABLE_BINDTODEVICE std::string sBindToDevice; #endif int iUDPSndBufSize; // UDP sending buffer size int iUDPRcvBufSize; // UDP receiving buffer size bool operator==(const CSrtMuxerConfig& other) const { #define CEQUAL(field) (field == other.field) return CEQUAL(iIpTTL) && CEQUAL(iIpToS) && CEQUAL(iIpV6Only) && CEQUAL(bReuseAddr) #ifdef SRT_ENABLE_BINDTODEVICE && CEQUAL(sBindToDevice) #endif && CEQUAL(iUDPSndBufSize) && CEQUAL(iUDPRcvBufSize); #undef CEQUAL } CSrtMuxerConfig() : iIpTTL(-1) /* IPv4 TTL or IPv6 HOPs [1..255] (-1:undefined) */ , iIpToS(-1) /* IPv4 Type of Service or IPv6 Traffic Class [0x00..0xff] (-1:undefined) */ , iIpV6Only(-1) , bReuseAddr(true) // This is default in SRT , iUDPSndBufSize(DEF_UDP_BUFFER_SIZE) , iUDPRcvBufSize(DEF_UDP_BUFFER_SIZE) { } }; struct CSrtConfig; template class StringStorage { char stor[SIZE + 1]; uint16_t len; // NOTE: default copying allowed. public: StringStorage() : len(0) { memset(stor, 0, sizeof stor); } bool set(const char* s, size_t length) { if (length > SIZE) return false; memcpy(stor, s, length); stor[length] = 0; len = (int) length; return true; } bool set(const std::string& s) { return set(s.c_str(), s.size()); } std::string str() const { return len == 0 ? std::string() : std::string(stor); } const char* c_str() const { return stor; } size_t size() const { return size_t(len); } bool empty() const { return len == 0; } }; struct CSrtConfig: CSrtMuxerConfig { typedef srt::sync::steady_clock::time_point time_point; typedef srt::sync::steady_clock::duration duration; static const int DEF_MSS = 1500, DEF_FLIGHT_SIZE = 25600, DEF_BUFFER_SIZE = 8192, //Rcv buffer MUST NOT be bigger than Flight Flag size DEF_LINGER_S = 3*60, // 3 minutes DEF_CONNTIMEO_S = 3; // 3 seconds static const int COMM_RESPONSE_TIMEOUT_MS = 5 * 1000; // 5 seconds static const uint32_t COMM_DEF_STABILITY_TIMEOUT_US = 80 * 1000; // Mimimum recv flight flag size is 32 packets static const int DEF_MIN_FLIGHT_PKT = 32; static const size_t MAX_SID_LENGTH = 512; static const size_t MAX_PFILTER_LENGTH = 64; static const size_t MAX_CONG_LENGTH = 16; int iMSS; // Maximum Segment Size, in bytes size_t zExpPayloadSize; // Expected average payload size (user option) // Options bool bSynSending; // Sending syncronization mode bool bSynRecving; // Receiving syncronization mode int iFlightFlagSize; // Maximum number of packets in flight from the peer side int iSndBufSize; // Maximum UDT sender buffer size int iRcvBufSize; // Maximum UDT receiver buffer size linger Linger; // Linger information on close bool bRendezvous; // Rendezvous connection mode duration tdConnTimeOut; // connect timeout in milliseconds bool bDriftTracer; int iSndTimeOut; // sending timeout in milliseconds int iRcvTimeOut; // receiving timeout in milliseconds int64_t llMaxBW; // maximum data transfer rate (threshold) // These fields keep the options for encryption // (SRTO_PASSPHRASE, SRTO_PBKEYLEN). Crypto object is // created later and takes values from these. HaiCrypt_Secret CryptoSecret; int iSndCryptoKeyLen; // XXX Consider removing. The bDataSender stays here // in order to maintain the HS side selection in HSv4. bool bDataSender; bool bMessageAPI; bool bTSBPD; // Whether AGENT will do TSBPD Rx (whether peer does, is not agent's problem) int iRcvLatency; // Agent's Rx latency int iPeerLatency; // Peer's Rx latency for the traffic made by Agent's Tx. bool bTLPktDrop; // Whether Agent WILL DO TLPKTDROP on Rx. int iSndDropDelay; // Extra delay when deciding to snd-drop for TLPKTDROP, -1 to off bool bEnforcedEnc; // Off by default. When on, any connection other than nopw-nopw & pw1-pw1 is rejected. int iGroupConnect; // 1 - allow group connections int iPeerIdleTimeout; // Timeout for hearing anything from the peer. uint32_t uStabilityTimeout; int iRetransmitAlgo; int64_t llInputBW; // Input stream rate (bytes/sec). 0: use internally estimated input bandwidth int64_t llMinInputBW; // Minimum input stream rate estimate (bytes/sec) int iOverheadBW; // Percent above input stream rate (applies if llMaxBW == 0) bool bRcvNakReport; // Enable Receiver Periodic NAK Reports int iMaxReorderTolerance; //< Maximum allowed value for dynamic reorder tolerance // For the use of CCryptoControl // HaiCrypt configuration unsigned int uKmRefreshRatePkt; unsigned int uKmPreAnnouncePkt; uint32_t uSrtVersion; uint32_t uMinimumPeerSrtVersion; StringStorage sCongestion; StringStorage sPacketFilterConfig; StringStorage sStreamName; // Shortcuts and utilities int32_t flightCapacity() { return std::min(iRcvBufSize, iFlightFlagSize); } CSrtConfig() : iMSS(DEF_MSS) , zExpPayloadSize(SRT_LIVE_DEF_PLSIZE) , bSynSending(true) , bSynRecving(true) , iFlightFlagSize(DEF_FLIGHT_SIZE) , iSndBufSize(DEF_BUFFER_SIZE) , iRcvBufSize(DEF_BUFFER_SIZE) , bRendezvous(false) , tdConnTimeOut(srt::sync::seconds_from(DEF_CONNTIMEO_S)) , bDriftTracer(true) , iSndTimeOut(-1) , iRcvTimeOut(-1) , llMaxBW(-1) , bDataSender(false) , bMessageAPI(true) , bTSBPD(true) , iRcvLatency(SRT_LIVE_DEF_LATENCY_MS) , iPeerLatency(0) , bTLPktDrop(true) , iSndDropDelay(0) , bEnforcedEnc(true) , iGroupConnect(0) , iPeerIdleTimeout(COMM_RESPONSE_TIMEOUT_MS) , uStabilityTimeout(COMM_DEF_STABILITY_TIMEOUT_US) , iRetransmitAlgo(1) , llInputBW(0) , llMinInputBW(0) , iOverheadBW(25) , bRcvNakReport(true) , iMaxReorderTolerance(0) // Sensible optimal value is 10, 0 preserves old behavior , uKmRefreshRatePkt(0) , uKmPreAnnouncePkt(0) , uSrtVersion(SRT_DEF_VERSION) , uMinimumPeerSrtVersion(SRT_VERSION_MAJ1) { // Default UDT configurations iUDPRcvBufSize = iRcvBufSize * iMSS; // Linger: LIVE mode defaults, please refer to `SRTO_TRANSTYPE` option // for other modes. Linger.l_onoff = 0; Linger.l_linger = 0; CryptoSecret.len = 0; iSndCryptoKeyLen = 0; // Default congestion is "live". // Available builtin congestions: "file". // Others can be registerred. sCongestion.set("live", 4); } ~CSrtConfig() { // Wipeout critical data memset(&CryptoSecret, 0, sizeof(CryptoSecret)); } int set(SRT_SOCKOPT optName, const void* val, int size); }; #if ENABLE_EXPERIMENTAL_BONDING struct SRT_SocketOptionObject { struct SingleOption { uint16_t option; uint16_t length; unsigned char storage[1]; // NOTE: Variable length object! }; std::vector options; SRT_SocketOptionObject() {} ~SRT_SocketOptionObject() { for (size_t i = 0; i < options.size(); ++i) { // Convert back unsigned char* mem = reinterpret_cast(options[i]); delete[] mem; } } bool add(SRT_SOCKOPT optname, const void* optval, size_t optlen); }; #endif template inline T cast_optval(const void* optval) { return *reinterpret_cast(optval); } template inline T cast_optval(const void* optval, int optlen) { if (optlen > 0 && optlen != sizeof(T)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); return cast_optval(optval); } // 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). template <> inline bool cast_optval(const void* optval, int optlen) { if (optlen == sizeof(bool)) { return *reinterpret_cast(optval); } if (optlen == sizeof(int)) { // 0!= is a windows warning-killer int-to-bool conversion return 0 != *reinterpret_cast(optval); } return false; } #endif srt-1.4.4/srtcore/srt.h000066400000000000000000001252041412557703600150100ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRTC_H #define INC_SRTC_H #include "version.h" #include "platform_sys.h" #include #include #include "logging_api.h" //////////////////////////////////////////////////////////////////////////////// //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 __MINGW32__ // 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 SRT_DYNAMIC #ifdef SRT_EXPORTS #define SRT_API __declspec(dllexport) #else #define SRT_API __declspec(dllimport) #endif #else #define SRT_API #endif #else // __MINGW32__ #define SRT_API #endif #else #define SRT_API __attribute__ ((visibility("default"))) #endif // For feature tests if you need. // You can use these constants with SRTO_MINVERSION option. #define SRT_VERSION_FEAT_HSv5 0x010300 #if defined(__cplusplus) && __cplusplus > 201406 #define SRT_HAVE_CXX17 1 #else #define SRT_HAVE_CXX17 0 #endif // Stadnard attributes // When compiling in C++17 mode, use the standard C++17 attributes // (out of these, only [[deprecated]] is supported in C++14, so // for all lesser standard use compiler-specific attributes). #if SRT_HAVE_CXX17 // Unused: DO NOT issue a warning if this entity is unused. #define SRT_ATR_UNUSED [[maybe_unused]] // Nodiscard: issue a warning if the return value was discarded. #define SRT_ATR_NODISCARD [[nodiscard]] // GNUG is GNU C/C++; this syntax is also supported by Clang #elif defined(__GNUC__) #define SRT_ATR_UNUSED __attribute__((unused)) #define SRT_ATR_NODISCARD __attribute__((warn_unused_result)) #elif defined(_MSC_VER) #define SRT_ATR_UNUSED __pragma(warning(suppress: 4100 4101)) #define SRT_ATR_NODISCARD _Check_return_ #else #define SRT_ATR_UNUSED #define SRT_ATR_NODISCARD #endif // DEPRECATED attributes // There's needed DEPRECATED and DEPRECATED_PX, as some compilers require them // before the entity, others after the entity. // The *_PX version is the prefix attribute, which applies only // to functions (Microsoft compilers). // When deprecating a function, mark it: // // SRT_ATR_DEPRECATED_PX retval function(arguments) SRT_ATR_DEPRECATED; // // When SRT_NO_DEPRECATED defined, do not issue any deprecation warnings. // Regardless of the compiler type. #if defined(SRT_NO_DEPRECATED) #define SRT_ATR_DEPRECATED #define SRT_ATR_DEPRECATED_PX #elif SRT_HAVE_CXX17 #define SRT_ATR_DEPRECATED #define SRT_ATR_DEPRECATED_PX [[deprecated]] // GNUG is GNU C/C++; this syntax is also supported by Clang #elif defined(__GNUC__) #define SRT_ATR_DEPRECATED_PX #define SRT_ATR_DEPRECATED __attribute__((deprecated)) #elif defined(_MSC_VER) #define SRT_ATR_DEPRECATED_PX __declspec(deprecated) #define SRT_ATR_DEPRECATED // no postfix-type modifier #else #define SRT_ATR_DEPRECATED_PX #define SRT_ATR_DEPRECATED #endif #ifdef __cplusplus extern "C" { #endif typedef int32_t SRTSOCKET; // The most significant bit 31 (sign bit actually) is left unused, // so that all people who check the value for < 0 instead of -1 // still get what they want. The bit 30 is reserved for marking // the "socket group". Most of the API functions should work // transparently with the socket descriptor designating a single // socket or a socket group. static const int32_t SRTGROUP_MASK = (1 << 30); #ifdef _WIN32 #ifndef __MINGW32__ typedef SOCKET SYSSOCKET; #else typedef int SYSSOCKET; #endif #else typedef int SYSSOCKET; #endif #ifndef ENABLE_EXPERIMENTAL_BONDING #define ENABLE_EXPERIMENTAL_BONDING 0 #endif typedef SYSSOCKET UDPSOCKET; // 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 = 0, // the Maximum Transfer Unit SRTO_SNDSYN = 1, // if sending is blocking SRTO_RCVSYN = 2, // if receiving is blocking SRTO_ISN = 3, // Initial Sequence Number (valid only after srt_connect or srt_accept-ed sockets) SRTO_FC = 4, // Flight flag size (window size) SRTO_SNDBUF = 5, // maximum buffer in sending queue SRTO_RCVBUF = 6, // UDT receiving buffer size SRTO_LINGER = 7, // waiting for unsent data when closing SRTO_UDP_SNDBUF = 8, // UDP sending buffer size SRTO_UDP_RCVBUF = 9, // UDP receiving buffer size // (some space left) SRTO_RENDEZVOUS = 12, // rendezvous connection mode SRTO_SNDTIMEO = 13, // send() timeout SRTO_RCVTIMEO = 14, // recv() timeout SRTO_REUSEADDR = 15, // reuse an existing port or create a new one SRTO_MAXBW = 16, // maximum bandwidth (bytes per second) that the connection can use SRTO_STATE = 17, // current socket state, see UDTSTATUS, read only SRTO_EVENT = 18, // current available events associated with the socket SRTO_SNDDATA = 19, // size of data in the sending buffer SRTO_RCVDATA = 20, // size of data available for recv 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_LATENCY = 23, // NOT RECOMMENDED. SET: to both SRTO_RCVLATENCY and SRTO_PEERLATENCY. GET: same as SRTO_RCVLATENCY. 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 (must be 10..79 characters, or empty to disable encryption) SRTO_PBKEYLEN, // Crypto key len in bytes {16,24,32} Default: 16 (AES-128) SRTO_KMSTATE, // Key Material exchange status (UDT_SRTKmState) SRTO_IPTTL = 29, // IP Time To Live (passthru for system sockopt IPPROTO_IP/IP_TTL) SRTO_IPTOS, // IP Type of Service (passthru for system sockopt IPPROTO_IP/IP_TOS) SRTO_TLPKTDROP = 31, // Enable receiver pkt drop SRTO_SNDDROPDELAY = 32, // Extra delay towards latency for sender TLPKTDROP decision (-1 to off) 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. Caller default: 3000, rendezvous (x 10) SRTO_DRIFTTRACER = 37, // Enable or disable drift tracer SRTO_MININPUTBW = 38, // Minimum estimate of input stream rate. // (some space left) SRTO_SNDKMSTATE = 40, // (GET) the current state of the encryption at the peer side SRTO_RCVKMSTATE, // (GET) the current state of the encryption at the agent side SRTO_LOSSMAXTTL, // Maximum possible packet reorder tolerance (number of packets to receive after loss to send lossreport) SRTO_RCVLATENCY, // TsbPd receiver delay (mSec) to absorb burst of missed packet retransmission SRTO_PEERLATENCY, // Minimum value of the TsbPd receiver delay (mSec) for the opposite side (peer) SRTO_MINVERSION, // Minimum SRT version needed for the peer (peers with less version will get connection reject) SRTO_STREAMID, // A string set to a socket and passed to the listener's accepted socket SRTO_CONGESTION, // Congestion controller type selection SRTO_MESSAGEAPI, // In File mode, use message API (portions of data with boundaries) SRTO_PAYLOADSIZE, // Maximum payload size sent in one UDP packet (0 if unlimited) SRTO_TRANSTYPE = 50, // Transmission type (set of options required for given transmission type) SRTO_KMREFRESHRATE, // After sending how many packets the encryption key should be flipped to the new key SRTO_KMPREANNOUNCE, // How many packets before key flip the new key is annnounced and after key flip the old one decommissioned SRTO_ENFORCEDENCRYPTION, // Connection to be rejected or quickly broken when one side encryption set or bad password SRTO_IPV6ONLY, // IPV6_V6ONLY mode SRTO_PEERIDLETIMEO, // Peer-idle timeout (max time of silence heard from peer) in [ms] SRTO_BINDTODEVICE, // Forward the SOL_SOCKET/SO_BINDTODEVICE option on socket (pass packets only from that device) #if ENABLE_EXPERIMENTAL_BONDING SRTO_GROUPCONNECT, // Set on a listener to allow group connection SRTO_GROUPSTABTIMEO, // Stability timeout (backup groups) in [us] SRTO_GROUPTYPE, // Group type to which an accepted socket is about to be added, available in the handshake #endif SRTO_PACKETFILTER = 60, // Add and configure a packet filter SRTO_RETRANSMITALGO = 61, // An option to select packet retransmission algorithm SRTO_E_SIZE // Always last element, not a valid option. } SRT_SOCKOPT; #ifdef __cplusplus #if __cplusplus > 199711L // C++11 // Newer compilers report error when [[deprecated]] is applied to types, // and C++11 and higher uses this. // Note that this doesn't exactly use the 'deprecated' attribute, // as it's introduced in C++14. What is actually used here is the // fact that unknown attributes are ignored, but still warned about. // This should only catch an eye - and that's what it does. #define SRT_DEPRECATED_OPTION(value) ((SRT_SOCKOPT [[deprecated]])value) #else // Older (pre-C++11) compilers use gcc deprecated applied to a typedef typedef SRT_ATR_DEPRECATED_PX SRT_SOCKOPT SRT_SOCKOPT_DEPRECATED SRT_ATR_DEPRECATED; #define SRT_DEPRECATED_OPTION(value) ((SRT_SOCKOPT_DEPRECATED)value) #endif #else // deprecated enum labels are supported only since gcc 6, so in C there // will be a whole deprecated enum type, as it's not an error in C to mix // enum types enum SRT_ATR_DEPRECATED SRT_SOCKOPT_DEPRECATED { // Dummy last option, as every entry ends with a comma SRTO_DEPRECATED_END = 0 }; #define SRT_DEPRECATED_OPTION(value) ((enum SRT_SOCKOPT_DEPRECATED)value) #endif // Note that there are no deprecated options at the moment, but the mechanism // stays so that it can be used in future. Example: // #define SRTO_STRICTENC SRT_DEPRECATED_OPTION(53) typedef enum SRT_TRANSTYPE { SRTT_LIVE, SRTT_FILE, SRTT_INVALID } SRT_TRANSTYPE; // These sizes should be used for Live mode. In Live mode you should not // exceed the size that fits in a single MTU. // This is for MPEG TS and it's a default SRTO_PAYLOADSIZE for SRTT_LIVE. static const int SRT_LIVE_DEF_PLSIZE = 1316; // = 188*7, recommended for MPEG TS // This is the maximum payload size for Live mode, should you have a different // payload type than MPEG TS. static const int SRT_LIVE_MAX_PLSIZE = 1456; // MTU(1500) - UDP.hdr(28) - SRT.hdr(16) // Latency for Live transmission: default is 120 static const int SRT_LIVE_DEF_LATENCY_MS = 120; // Importrant note: please add new fields to this structure to the end and don't remove any existing fields 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 uint64_t byteRcvLossTotal; // total number of lost bytes 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 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 //>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 uint64_t byteRcvLoss; // number of retransmitted bytes 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 int pktSndFilterExtraTotal; // number of control packets supplied by packet filter int pktRcvFilterExtraTotal; // number of control packets received and not supplied back int pktRcvFilterSupplyTotal; // number of packets that the filter supplied extra (e.g. FEC rebuilt) int pktRcvFilterLossTotal; // number of packet loss not coverable by filter int pktSndFilterExtra; // number of control packets supplied by packet filter int pktRcvFilterExtra; // number of control packets received and not supplied back int pktRcvFilterSupply; // number of packets that the filter supplied extra (e.g. FEC rebuilt) int pktRcvFilterLoss; // number of packet loss not coverable by filter int pktReorderTolerance; // packet reorder tolerance value //< // New stats in 1.5.0 // Total int64_t pktSentUniqueTotal; // total number of data packets sent by the application int64_t pktRecvUniqueTotal; // total number of packets to be received by the application uint64_t byteSentUniqueTotal; // total number of data bytes, sent by the application uint64_t byteRecvUniqueTotal; // total number of data bytes to be received by the application // Local int64_t pktSentUnique; // number of data packets sent by the application int64_t pktRecvUnique; // number of packets to be received by the application uint64_t byteSentUnique; // number of data bytes, sent by the application uint64_t byteRecvUnique; // number of data bytes to be received by the application }; //////////////////////////////////////////////////////////////////////////////// // 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, MN_CLOSED = 5, // MJ_CONNECTION MN_CONNLOST = 1, MN_NOCONN = 2, // MJ_SYSTEMRES MN_THREAD = 1, MN_MEMORY = 2, MN_OBJECT = 3, // 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_INVALMSGAPI = 9, MN_INVALBUFFERAPI = 10, MN_BUSY = 11, MN_XSIZE = 12, MN_EIDINVAL = 13, MN_EEMPTY = 14, MN_BUSYPORT = 15, // MJ_AGAIN MN_WRAVAIL = 1, MN_RDAVAIL = 2, MN_XMTIMEOUT = 3, MN_CONGESTION = 4 }; // 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. typedef enum SRT_ERRNO { SRT_EUNKNOWN = -1, SRT_SUCCESS = MJ_SUCCESS, SRT_ECONNSETUP = MJ(SETUP), SRT_ENOSERVER = MN(SETUP, TIMEOUT), SRT_ECONNREJ = MN(SETUP, REJECTED), SRT_ESOCKFAIL = MN(SETUP, NORES), SRT_ESECFAIL = MN(SETUP, SECURITY), SRT_ESCLOSED = MN(SETUP, CLOSED), SRT_ECONNFAIL = MJ(CONNECTION), SRT_ECONNLOST = MN(CONNECTION, CONNLOST), SRT_ENOCONN = MN(CONNECTION, NOCONN), SRT_ERESOURCE = MJ(SYSTEMRES), SRT_ETHREAD = MN(SYSTEMRES, THREAD), SRT_ENOBUF = MN(SYSTEMRES, MEMORY), SRT_ESYSOBJ = MN(SYSTEMRES, OBJECT), SRT_EFILE = MJ(FILESYSTEM), SRT_EINVRDOFF = MN(FILESYSTEM, SEEKGFAIL), SRT_ERDPERM = MN(FILESYSTEM, READFAIL), SRT_EINVWROFF = MN(FILESYSTEM, SEEKPFAIL), SRT_EWRPERM = MN(FILESYSTEM, WRITEFAIL), SRT_EINVOP = MJ(NOTSUP), SRT_EBOUNDSOCK = MN(NOTSUP, ISBOUND), SRT_ECONNSOCK = MN(NOTSUP, ISCONNECTED), SRT_EINVPARAM = MN(NOTSUP, INVAL), SRT_EINVSOCK = MN(NOTSUP, SIDINVAL), SRT_EUNBOUNDSOCK = MN(NOTSUP, ISUNBOUND), SRT_ENOLISTEN = MN(NOTSUP, NOLISTEN), SRT_ERDVNOSERV = MN(NOTSUP, ISRENDEZVOUS), SRT_ERDVUNBOUND = MN(NOTSUP, ISRENDUNBOUND), SRT_EINVALMSGAPI = MN(NOTSUP, INVALMSGAPI), SRT_EINVALBUFFERAPI = MN(NOTSUP, INVALBUFFERAPI), SRT_EDUPLISTEN = MN(NOTSUP, BUSY), SRT_ELARGEMSG = MN(NOTSUP, XSIZE), SRT_EINVPOLLID = MN(NOTSUP, EIDINVAL), SRT_EPOLLEMPTY = MN(NOTSUP, EEMPTY), SRT_EBINDCONFLICT = MN(NOTSUP, BUSYPORT), SRT_EASYNCFAIL = MJ(AGAIN), SRT_EASYNCSND = MN(AGAIN, WRAVAIL), SRT_EASYNCRCV = MN(AGAIN, RDAVAIL), SRT_ETIMEOUT = MN(AGAIN, XMTIMEOUT), SRT_ECONGEST = MN(AGAIN, CONGESTION), SRT_EPEERERR = MJ(PEERERROR) } SRT_ERRNO; #undef MJ #undef MN enum SRT_REJECT_REASON { SRT_REJ_UNKNOWN, // initial set when in progress SRT_REJ_SYSTEM, // broken due to system function error SRT_REJ_PEER, // connection was rejected by peer SRT_REJ_RESOURCE, // internal problem with resource allocation SRT_REJ_ROGUE, // incorrect data in handshake messages SRT_REJ_BACKLOG, // listener's backlog exceeded SRT_REJ_IPE, // internal program error SRT_REJ_CLOSE, // socket is closing SRT_REJ_VERSION, // peer is older version than agent's minimum set SRT_REJ_RDVCOOKIE, // rendezvous cookie collision SRT_REJ_BADSECRET, // wrong password SRT_REJ_UNSECURE, // password required or unexpected SRT_REJ_MESSAGEAPI, // streamapi/messageapi collision SRT_REJ_CONGESTION, // incompatible congestion-controller type SRT_REJ_FILTER, // incompatible packet filter SRT_REJ_GROUP, // incompatible group SRT_REJ_TIMEOUT, // connection timeout SRT_REJ_E_SIZE, }; // XXX This value remains for some time, but it's deprecated // Planned deprecation removal: rel1.6.0. #define SRT_REJ__SIZE SRT_REJ_E_SIZE // Reject category codes: #define SRT_REJC_VALUE(code) (1000 * (code/1000)) #define SRT_REJC_INTERNAL 0 // Codes from above SRT_REJECT_REASON enum #define SRT_REJC_PREDEFINED 1000 // Standard server error codes #define SRT_REJC_USERDEFINED 2000 // User defined error codes // Logging API - specialization for SRT. // WARNING: This part is generated. // Logger Functional Areas // Note that 0 is "general". // Values 0* - general, unqualified // Values 1* - control // Values 2* - receiving // Values 3* - sending // Values 4* - management // Made by #define so that it's available also for C API. // Use ../scripts/generate-logging-defs.tcl to regenerate. // SRT_LOGFA BEGIN GENERATED SECTION { #define SRT_LOGFA_GENERAL 0 // gglog: General uncategorized log, for serious issues only #define SRT_LOGFA_SOCKMGMT 1 // smlog: Socket create/open/close/configure activities #define SRT_LOGFA_CONN 2 // cnlog: Connection establishment and handshake #define SRT_LOGFA_XTIMER 3 // xtlog: The checkTimer and around activities #define SRT_LOGFA_TSBPD 4 // tslog: The TsBPD thread #define SRT_LOGFA_RSRC 5 // rslog: System resource allocation and management #define SRT_LOGFA_CONGEST 7 // cclog: Congestion control module #define SRT_LOGFA_PFILTER 8 // pflog: Packet filter module #define SRT_LOGFA_API_CTRL 11 // aclog: API part for socket and library managmenet #define SRT_LOGFA_QUE_CTRL 13 // qclog: Queue control activities #define SRT_LOGFA_EPOLL_UPD 16 // eilog: EPoll, internal update activities #define SRT_LOGFA_API_RECV 21 // arlog: API part for receiving #define SRT_LOGFA_BUF_RECV 22 // brlog: Buffer, receiving side #define SRT_LOGFA_QUE_RECV 23 // qrlog: Queue, receiving side #define SRT_LOGFA_CHN_RECV 24 // krlog: CChannel, receiving side #define SRT_LOGFA_GRP_RECV 25 // grlog: Group, receiving side #define SRT_LOGFA_API_SEND 31 // aslog: API part for sending #define SRT_LOGFA_BUF_SEND 32 // bslog: Buffer, sending side #define SRT_LOGFA_QUE_SEND 33 // qslog: Queue, sending side #define SRT_LOGFA_CHN_SEND 34 // kslog: CChannel, sending side #define SRT_LOGFA_GRP_SEND 35 // gslog: Group, sending side #define SRT_LOGFA_INTERNAL 41 // inlog: Internal activities not connected directly to a socket #define SRT_LOGFA_QUE_MGMT 43 // qmlog: Queue, management part #define SRT_LOGFA_CHN_MGMT 44 // kmlog: CChannel, management part #define SRT_LOGFA_GRP_MGMT 45 // gmlog: Group, management part #define SRT_LOGFA_EPOLL_API 46 // ealog: EPoll, API part #define SRT_LOGFA_HAICRYPT 6 // hclog: Haicrypt module area #define SRT_LOGFA_APPLOG 10 // aplog: Applications // } SRT_LOGFA END GENERATED SECTION // To make a typical int64_t size, although still use std::bitset. // C API will carry it over. #define SRT_LOGFA_LASTNONE 63 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 SRT_EPOLL_OPT { SRT_EPOLL_OPT_NONE = 0x0, // fallback // Values intended to be the same as in ``. // so that if system values are used by mistake, they should have the same effect // This applies to: IN, OUT, ERR and ET. /// Ready for 'recv' operation: /// /// - For stream mode it means that at least 1 byte is available. /// In this mode the buffer may extract only a part of the packet, /// leaving next data possible for extraction later. /// /// - For message mode it means that there is at least one packet /// available (this may change in future, as it is desired that /// one full message should only wake up, not single packet of a /// not yet extractable message). /// /// - For live mode it means that there's at least one packet /// ready to play. /// /// - For listener sockets, this means that there is a new connection /// waiting for pickup through the `srt_accept()` call, that is, /// the next call to `srt_accept()` will succeed without blocking /// (see an alias SRT_EPOLL_ACCEPT below). SRT_EPOLL_IN = 0x1, /// Ready for 'send' operation. /// /// - For stream mode it means that there's a free space in the /// sender buffer for at least 1 byte of data. The next send /// operation will only allow to send as much data as it is free /// space in the buffer. /// /// - For message mode it means that there's a free space for at /// least one UDP packet. The edge-triggered mode can be used to /// pick up updates as the free space in the sender buffer grows. /// /// - For live mode it means that there's a free space for at least /// one UDP packet. On the other hand, no readiness for OUT usually /// means an extraordinary congestion on the link, meaning also that /// you should immediately slow down the sending rate or you may get /// a connection break soon. /// /// - For non-blocking sockets used with `srt_connect*` operation, /// this flag simply means that the connection was established. SRT_EPOLL_OUT = 0x4, /// The socket has encountered an error in the last operation /// and the next operation on that socket will end up with error. /// You can retry the operation, but getting the error from it /// is certain, so you may as well close the socket. SRT_EPOLL_ERR = 0x8, // To avoid confusion in the internal code, the following // duplicates are introduced to improve clarity. SRT_EPOLL_CONNECT = SRT_EPOLL_OUT, SRT_EPOLL_ACCEPT = SRT_EPOLL_IN, SRT_EPOLL_UPDATE = 0x10, SRT_EPOLL_ET = 1u << 31 }; // These are actually flags - use a bit container: typedef int32_t SRT_EPOLL_T; // Define which epoll flags determine events. All others are special flags. #define SRT_EPOLL_EVENTTYPES (SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_UPDATE | SRT_EPOLL_ERR) #define SRT_EPOLL_ETONLY (SRT_EPOLL_UPDATE) enum SRT_EPOLL_FLAGS { /// This allows the EID container to be empty when calling the waiting /// function with infinite time. This means an infinite hangup, although /// a socket can be added to this EID from a separate thread. SRT_EPOLL_ENABLE_EMPTY = 1, /// This makes the waiting function check if there is output container /// passed to it, and report an error if it isn't. By default it is allowed /// that the output container is 0 size or NULL and therefore the readiness /// state is reported only as a number of ready sockets from return value. SRT_EPOLL_ENABLE_OUTPUTCHECK = 2 }; #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 typedef struct CBytePerfMon SRT_TRACEBSTATS; static const SRTSOCKET SRT_INVALID_SOCK = -1; static const int SRT_ERROR = -1; // library initialization SRT_API int srt_startup(void); SRT_API int srt_cleanup(void); // // Socket operations // // DEPRECATED: srt_socket with 3 arguments. All these arguments are ignored // and socket creation doesn't need any arguments. Use srt_create_socket(). // Planned deprecation removal: rel1.6.0 SRT_ATR_DEPRECATED_PX SRT_API SRTSOCKET srt_socket(int, int, int) SRT_ATR_DEPRECATED; SRT_API SRTSOCKET srt_create_socket(void); // Group management // Stubs when off typedef struct SRT_SocketGroupData_ SRT_SOCKGROUPDATA; #if ENABLE_EXPERIMENTAL_BONDING typedef enum SRT_GROUP_TYPE { SRT_GTYPE_UNDEFINED, SRT_GTYPE_BROADCAST, SRT_GTYPE_BACKUP, SRT_GTYPE_BALANCING, SRT_GTYPE_MULTICAST, // ... SRT_GTYPE_E_END } SRT_GROUP_TYPE; // Free-form flags for groups // Flags may be type-specific! static const uint32_t SRT_GFLAG_SYNCONMSG = 1; typedef enum SRT_MemberStatus { SRT_GST_PENDING, // The socket is created correctly, but not yet ready for getting data. SRT_GST_IDLE, // The socket is ready to be activated SRT_GST_RUNNING, // The socket was already activated and is in use SRT_GST_BROKEN // The last operation broke the socket, it should be closed. } SRT_MEMBERSTATUS; struct SRT_SocketGroupData_ { SRTSOCKET id; struct sockaddr_storage peeraddr; // Don't want to expose sockaddr_any to public API SRT_SOCKSTATUS sockstate; uint16_t weight; SRT_MEMBERSTATUS memberstate; int result; int token; }; typedef struct SRT_SocketOptionObject SRT_SOCKOPT_CONFIG; typedef struct SRT_GroupMemberConfig_ { SRTSOCKET id; struct sockaddr_storage srcaddr; struct sockaddr_storage peeraddr; // Don't want to expose sockaddr_any to public API uint16_t weight; SRT_SOCKOPT_CONFIG* config; int errorcode; int token; } SRT_SOCKGROUPCONFIG; SRT_API SRTSOCKET srt_create_group (SRT_GROUP_TYPE); SRT_API int srt_include (SRTSOCKET socket, SRTSOCKET group); SRT_API int srt_exclude (SRTSOCKET socket); SRT_API SRTSOCKET srt_groupof (SRTSOCKET socket); SRT_API int srt_group_data (SRTSOCKET socketgroup, SRT_SOCKGROUPDATA* output, size_t* inoutlen); SRT_API int srt_group_configure(SRTSOCKET socketgroup, const char* str); SRT_API SRT_SOCKOPT_CONFIG* srt_create_config(void); SRT_API void srt_delete_config(SRT_SOCKOPT_CONFIG* config /*nullable*/); SRT_API int srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len); SRT_API SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src /*nullable*/, const struct sockaddr* adr, int namelen); SRT_API int srt_connect_group(SRTSOCKET group, SRT_SOCKGROUPCONFIG name [], int arraysize); #endif // ENABLE_EXPERIMENTAL_BONDING SRT_API int srt_bind (SRTSOCKET u, const struct sockaddr* name, int namelen); SRT_API int srt_bind_acquire (SRTSOCKET u, UDPSOCKET sys_udp_sock); // Old name of srt_bind_acquire(), please don't use // Planned deprecation removal: rel1.6.0 SRT_ATR_DEPRECATED_PX static inline int srt_bind_peerof(SRTSOCKET u, UDPSOCKET sys_udp_sock) SRT_ATR_DEPRECATED; static inline int srt_bind_peerof (SRTSOCKET u, UDPSOCKET sys_udp_sock) { return srt_bind_acquire(u, sys_udp_sock); } SRT_API int srt_listen (SRTSOCKET u, int backlog); SRT_API SRTSOCKET srt_accept (SRTSOCKET u, struct sockaddr* addr, int* addrlen); SRT_API SRTSOCKET srt_accept_bond (const SRTSOCKET listeners[], int lsize, int64_t msTimeOut); typedef int srt_listen_callback_fn (void* opaq, SRTSOCKET ns, int hsversion, const struct sockaddr* peeraddr, const char* streamid); SRT_API int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook_fn, void* hook_opaque); typedef void srt_connect_callback_fn (void* opaq, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token); SRT_API int srt_connect_callback(SRTSOCKET clr, srt_connect_callback_fn* hook_fn, void* hook_opaque); SRT_API int srt_connect (SRTSOCKET u, const struct sockaddr* name, int namelen); SRT_API int srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); SRT_API int srt_connect_bind (SRTSOCKET u, const struct sockaddr* source, const struct sockaddr* target, int len); SRT_API int srt_rendezvous (SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen); SRT_API int srt_close (SRTSOCKET u); SRT_API int srt_getpeername (SRTSOCKET u, struct sockaddr* name, int* namelen); SRT_API int srt_getsockname (SRTSOCKET u, struct sockaddr* name, int* namelen); SRT_API int srt_getsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, void* optval, int* optlen); SRT_API int srt_setsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, const void* optval, int optlen); SRT_API int srt_getsockflag (SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); SRT_API int srt_setsockflag (SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); typedef struct SRT_MsgCtrl_ { int flags; // Left for future int msgttl; // TTL for a message (millisec), default -1 (no TTL limitation) int inorder; // Whether a message is allowed to supersede partially lost one. Unused in stream and live mode. int boundary; // 0:mid pkt, 1(01b):end of frame, 2(11b):complete frame, 3(10b): start of frame int64_t srctime; // source time since epoch (usec), 0: use internal time (sender) int32_t pktseq; // sequence number of the first packet in received message (unused for sending) int32_t msgno; // message number (output value for both sending and receiving) SRT_SOCKGROUPDATA* grpdata; size_t grpdata_size; } SRT_MSGCTRL; // Trap representation for sequence and message numbers // This value means that this is "unset", and it's never // a result of an operation made on this number. static const int32_t SRT_SEQNO_NONE = -1; // -1: no seq (0 is a valid seqno!) static const int32_t SRT_MSGNO_NONE = -1; // -1: unset static const int32_t SRT_MSGNO_CONTROL = 0; // 0: control (used by packet filter) static const int SRT_MSGTTL_INF = -1; // unlimited TTL specification for message TTL // XXX Might be useful also other special uses of -1: // - -1 as infinity for srt_epoll_wait // - -1 as a trap index value used in list.cpp // You are free to use either of these two methods to set SRT_MSGCTRL object // to default values: either call srt_msgctrl_init(&obj) or obj = srt_msgctrl_default. SRT_API void srt_msgctrl_init(SRT_MSGCTRL* mctrl); SRT_API extern const SRT_MSGCTRL srt_msgctrl_default; // The send/receive functions. // These functions have different names due to different sets of parameters // to be supplied. Not all of them are needed or make sense in all modes: // Plain: supply only the buffer and its size. // Msg: supply additionally // - TTL (message is not delivered when exceeded) and // - INORDER (when false, the message is allowed to be delivered in different // order than when it was sent, when the later message is earlier ready to // deliver) // Msg2: Supply extra parameters in SRT_MSGCTRL. When receiving, these // parameters will be filled, as needed. NULL is acceptable, in which case // the defaults are used. // // Sending functions // SRT_API int srt_send (SRTSOCKET u, const char* buf, int len); SRT_API int srt_sendmsg (SRTSOCKET u, const char* buf, int len, int ttl/* = -1*/, int inorder/* = false*/); SRT_API int srt_sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL *mctrl); // // Receiving functions // SRT_API int srt_recv (SRTSOCKET u, char* buf, int len); // srt_recvmsg is actually an alias to srt_recv, it stays under the old name for compat reasons. SRT_API int srt_recvmsg (SRTSOCKET u, char* buf, int len); SRT_API int srt_recvmsg2(SRTSOCKET u, char *buf, int len, SRT_MSGCTRL *mctrl); // Special send/receive functions for files only. #define SRT_DEFAULT_SENDFILE_BLOCK 364000 #define SRT_DEFAULT_RECVFILE_BLOCK 7280000 SRT_API int64_t srt_sendfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block); SRT_API int64_t srt_recvfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block); // last error detection SRT_API const char* srt_getlasterror_str(void); SRT_API int srt_getlasterror(int* errno_loc); SRT_API const char* srt_strerror(int code, int errnoval); SRT_API void srt_clearlasterror(void); // Performance tracking // Performance monitor with Byte counters for better bitrate estimation. SRT_API int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); // Performance monitor with Byte counters and instantaneous stats instead of moving averages for Snd/Rcvbuffer sizes. SRT_API int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous); // Socket Status (for problem tracking) SRT_API SRT_SOCKSTATUS srt_getsockstate(SRTSOCKET u); SRT_API int srt_epoll_create(void); SRT_API int srt_epoll_clear_usocks(int eid); SRT_API int srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); SRT_API int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); SRT_API int srt_epoll_remove_usock(int eid, SRTSOCKET u); SRT_API int srt_epoll_remove_ssock(int eid, SYSSOCKET s); SRT_API int srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); SRT_API int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); SRT_API 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); typedef struct SRT_EPOLL_EVENT_STR { SRTSOCKET fd; int events; // SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR #ifdef __cplusplus SRT_EPOLL_EVENT_STR(SRTSOCKET s, int ev): fd(s), events(ev) {} SRT_EPOLL_EVENT_STR(): fd(-1), events(0) {} // NOTE: allows singular values, no init. #endif } SRT_EPOLL_EVENT; SRT_API int srt_epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); SRT_API int32_t srt_epoll_set(int eid, int32_t flags); SRT_API 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); SRT_API int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes); SRT_API int srt_getrejectreason(SRTSOCKET sock); SRT_API int srt_setrejectreason(SRTSOCKET sock, int value); SRT_API extern const char* const srt_rejectreason_msg []; SRT_API const char* srt_rejectreason_str(int id); SRT_API uint32_t srt_getversion(void); SRT_API int64_t srt_time_now(void); SRT_API int64_t srt_connection_time(SRTSOCKET sock); // Possible internal clock types #define SRT_SYNC_CLOCK_STDCXX_STEADY 0 // C++11 std::chrono::steady_clock #define SRT_SYNC_CLOCK_GETTIME_MONOTONIC 1 // clock_gettime with CLOCK_MONOTONIC #define SRT_SYNC_CLOCK_WINQPC 2 #define SRT_SYNC_CLOCK_MACH_ABSTIME 3 #define SRT_SYNC_CLOCK_POSIX_GETTIMEOFDAY 4 #define SRT_SYNC_CLOCK_AMD64_RDTSC 5 #define SRT_SYNC_CLOCK_IA32_RDTSC 6 #define SRT_SYNC_CLOCK_IA64_ITC 7 SRT_API int srt_clock_type(void); #ifdef __cplusplus } #endif #endif srt-1.4.4/srtcore/srt_attr_defs.h000066400000000000000000000147411412557703600170460ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v.2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** The file contains various planform and compiler dependent attribute definitions used by SRT library internally. *****************************************************************************/ #ifndef INC_SRT_ATTR_DEFS_H #define INC_SRT_ATTR_DEFS_H // ATTRIBUTES: // // SRT_ATR_UNUSED: declare an entity ALLOWED to be unused (prevents warnings) // ATR_DEPRECATED: declare an entity deprecated (compiler should warn when used) // ATR_NOEXCEPT: The true `noexcept` from C++11, or nothing if compiling in pre-C++11 mode // ATR_NOTHROW: In C++11: `noexcept`. In pre-C++11: `throw()`. Required for GNU libstdc++. // ATR_CONSTEXPR: In C++11: `constexpr`. Otherwise empty. // ATR_OVERRIDE: In C++11: `override`. Otherwise empty. // ATR_FINAL: In C++11: `final`. Otherwise empty. #ifdef __GNUG__ #define ATR_DEPRECATED __attribute__((deprecated)) #else #define ATR_DEPRECATED #endif #if defined(__cplusplus) && __cplusplus > 199711L #define HAVE_CXX11 1 // For gcc 4.7, claim C++11 is supported, as long as experimental C++0x is on, // however it's only the "most required C++11 support". #if defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 7 // 4.7 only! #define ATR_NOEXCEPT #define ATR_NOTHROW throw() #define ATR_CONSTEXPR #define ATR_OVERRIDE #define ATR_FINAL #else #define HAVE_FULL_CXX11 1 #define ATR_NOEXCEPT noexcept #define ATR_NOTHROW noexcept #define ATR_CONSTEXPR constexpr #define ATR_OVERRIDE override #define ATR_FINAL final #endif #elif defined(_MSC_VER) && _MSC_VER >= 1800 // Microsoft Visual Studio supports C++11, but not fully, // and still did not change the value of __cplusplus. Treat // this special way. // _MSC_VER == 1800 means Microsoft Visual Studio 2013. #define HAVE_CXX11 1 #if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026 #define HAVE_FULL_CXX11 1 #define ATR_NOEXCEPT noexcept #define ATR_NOTHROW noexcept #define ATR_CONSTEXPR constexpr #define ATR_OVERRIDE override #define ATR_FINAL final #else #define ATR_NOEXCEPT #define ATR_NOTHROW throw() #define ATR_CONSTEXPR #define ATR_OVERRIDE #define ATR_FINAL #endif #else #define HAVE_CXX11 0 #define ATR_NOEXCEPT #define ATR_NOTHROW throw() #define ATR_CONSTEXPR #define ATR_OVERRIDE #define ATR_FINAL #endif // __cplusplus #if !HAVE_CXX11 && defined(REQUIRE_CXX11) && REQUIRE_CXX11 == 1 #error "The currently compiled application required C++11, but your compiler doesn't support it." #endif /////////////////////////////////////////////////////////////////////////////// // Attributes for thread safety analysis // - Clang TSA (https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#mutexheader). // - MSVC SAL (partially). // - Other compilers: none. /////////////////////////////////////////////////////////////////////////////// #if _MSC_VER >= 1920 // In case of MSVC these attributes have to preceed the attributed objects (variable, function). // E.g. SRT_ATTR_GUARDED_BY(mtx) int object; // It is tricky to annotate e.g. the following function, as clang complaints it does not know 'm'. // SRT_ATTR_EXCLUDES(m) SRT_ATTR_ACQUIRE(m) // inline void enterCS(Mutex& m) { m.lock(); } #define SRT_ATTR_CAPABILITY(expr) #define SRT_ATTR_SCOPED_CAPABILITY #define SRT_ATTR_GUARDED_BY(expr) _Guarded_by_(expr) #define SRT_ATTR_PT_GUARDED_BY(expr) #define SRT_ATTR_ACQUIRED_BEFORE(...) #define SRT_ATTR_ACQUIRED_AFTER(...) #define SRT_ATTR_REQUIRES(expr) _Requires_lock_held_(expr) #define SRT_ATTR_REQUIRES_SHARED(...) #define SRT_ATTR_ACQUIRE(expr) _Acquires_nonreentrant_lock_(expr) #define SRT_ATTR_ACQUIRE_SHARED(...) #define SRT_ATTR_RELEASE(expr) _Releases_lock_(expr) #define SRT_ATTR_RELEASE_SHARED(...) #define SRT_ATTR_RELEASE_GENERIC(...) #define SRT_ATTR_TRY_ACQUIRE(...) _Acquires_nonreentrant_lock_(expr) #define SRT_ATTR_TRY_ACQUIRE_SHARED(...) #define SRT_ATTR_EXCLUDES(...) #define SRT_ATTR_ASSERT_CAPABILITY(expr) #define SRT_ATTR_ASSERT_SHARED_CAPABILITY(x) #define SRT_ATTR_RETURN_CAPABILITY(x) #define SRT_ATTR_NO_THREAD_SAFETY_ANALYSIS #else #if defined(__clang__) && defined(__clang_major__) && (__clang_major__ > 5) #define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) #else #define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op #endif #define SRT_ATTR_CAPABILITY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) #define SRT_ATTR_SCOPED_CAPABILITY \ THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) #define SRT_ATTR_GUARDED_BY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) #define SRT_ATTR_PT_GUARDED_BY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) #define SRT_ATTR_ACQUIRED_BEFORE(...) \ THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) #define SRT_ATTR_ACQUIRED_AFTER(...) \ THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) #define SRT_ATTR_REQUIRES(...) \ THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) #define SRT_ATTR_REQUIRES_SHARED(...) \ THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) #define SRT_ATTR_ACQUIRE(...) \ THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) #define SRT_ATTR_ACQUIRE_SHARED(...) \ THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) #define SRT_ATTR_RELEASE(...) \ THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) #define SRT_ATTR_RELEASE_SHARED(...) \ THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) #define SRT_ATTR_RELEASE_GENERIC(...) \ THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) #define SRT_ATTR_TRY_ACQUIRE(...) \ THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) #define SRT_ATTR_TRY_ACQUIRE_SHARED(...) \ THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) #define SRT_ATTR_EXCLUDES(...) \ THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) #define SRT_ATTR_ASSERT_CAPABILITY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) #define SRT_ATTR_ASSERT_SHARED_CAPABILITY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) #define SRT_ATTR_RETURN_CAPABILITY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) #define SRT_ATTR_NO_THREAD_SAFETY_ANALYSIS \ THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) #endif // not _MSC_VER #endif // INC_SRT_ATTR_DEFS_H srt-1.4.4/srtcore/srt_c_api.cpp000066400000000000000000000305171412557703600165000ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include #include #include "srt.h" #include "common.h" #include "packet.h" #include "core.h" #include "utilities.h" using namespace std; using namespace srt; extern "C" { int srt_startup() { return CUDT::startup(); } int srt_cleanup() { return CUDT::cleanup(); } // Socket creation. SRTSOCKET srt_socket(int , int , int ) { return CUDT::socket(); } SRTSOCKET srt_create_socket() { return CUDT::socket(); } #if ENABLE_EXPERIMENTAL_BONDING // Group management. SRTSOCKET srt_create_group(SRT_GROUP_TYPE gt) { return CUDT::createGroup(gt); } int srt_include(SRTSOCKET socket, SRTSOCKET group) { return CUDT::addSocketToGroup(socket, group); } int srt_exclude(SRTSOCKET socket) { return CUDT::removeSocketFromGroup(socket); } SRTSOCKET srt_groupof(SRTSOCKET socket) { return CUDT::getGroupOfSocket(socket); } int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA* output, size_t* inoutlen) { return CUDT::getGroupData(socketgroup, output, inoutlen); } int srt_group_configure(SRTSOCKET socketgroup, const char* str) { return CUDT::configureGroup(socketgroup, str); } SRT_SOCKOPT_CONFIG* srt_create_config() { return new SRT_SocketOptionObject; } void srt_delete_config(SRT_SOCKOPT_CONFIG* in) { delete in; } int srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len) { if (!config) return -1; if (!config->add(option, contents, len)) return -1; return 0; } #endif // int srt_bind_multicast() // Binding and connection management int srt_bind(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::bind(u, name, namelen); } int srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock) { return CUDT::bind(u, udpsock); } int srt_listen(SRTSOCKET u, int backlog) { return CUDT::listen(u, backlog); } SRTSOCKET srt_accept(SRTSOCKET u, struct sockaddr * addr, int * addrlen) { return CUDT::accept(u, addr, addrlen); } SRTSOCKET srt_accept_bond(const SRTSOCKET lsns[], int lsize, int64_t msTimeOut) { return CUDT::accept_bond(lsns, lsize, msTimeOut); } int srt_connect(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); } int srt_connect_debug(SRTSOCKET u, const struct sockaddr * name, int namelen, int forced_isn) { return CUDT::connect(u, name, namelen, forced_isn); } int srt_connect_bind(SRTSOCKET u, const struct sockaddr* source, const struct sockaddr* target, int target_len) { return CUDT::connect(u, source, target, target_len); } #if ENABLE_EXPERIMENTAL_BONDING SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src, const struct sockaddr* adr, int namelen) { SRT_SOCKGROUPCONFIG data; data.errorcode = SRT_SUCCESS; data.id = -1; data.token = -1; data.weight = 0; data.config = NULL; if (src) memcpy(&data.srcaddr, src, namelen); else { memset(&data.srcaddr, 0, sizeof data.srcaddr); // Still set the family according to the target address data.srcaddr.ss_family = adr->sa_family; } memcpy(&data.peeraddr, adr, namelen); return data; } int srt_connect_group(SRTSOCKET group, SRT_SOCKGROUPCONFIG name [], int arraysize) { return CUDT::connectLinks(group, name, arraysize); } #endif int srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen) { bool yes = 1; CUDT::setsockopt(u, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); // Note: PORT is 16-bit and at the same location in both sockaddr_in and sockaddr_in6. // Just as a safety precaution, check the structs. if ( (local_name->sa_family != AF_INET && local_name->sa_family != AF_INET6) || local_name->sa_family != remote_name->sa_family) return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); const int st = srt_bind(u, local_name, local_namelen); if (st != 0) return st; return srt_connect(u, remote_name, remote_namelen); } int srt_close(SRTSOCKET 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 CUDT::close(u); } int srt_getpeername(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getpeername(u, name, namelen); } int srt_getsockname(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getsockname(u, name, namelen); } int srt_getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void * optval, int * optlen) { return CUDT::getsockopt(u, level, optname, optval, optlen); } int srt_setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void * optval, int optlen) { return CUDT::setsockopt(u, level, optname, optval, optlen); } int srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen) { return CUDT::getsockopt(u, 0, opt, optval, optlen); } int srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen) { return CUDT::setsockopt(u, 0, opt, optval, optlen); } int srt_send(SRTSOCKET u, const char * buf, int len) { return CUDT::send(u, buf, len, 0); } int srt_recv(SRTSOCKET u, char * buf, int len) { return CUDT::recv(u, buf, len, 0); } int srt_sendmsg(SRTSOCKET u, const char * buf, int len, int ttl, int inorder) { return CUDT::sendmsg(u, buf, len, ttl, 0!= inorder); } int srt_recvmsg(SRTSOCKET u, char * buf, int len) { int64_t ign_srctime; return CUDT::recvmsg(u, buf, len, ign_srctime); } int64_t srt_sendfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block) { if (!path || !offset ) { return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } fstream ifs(path, ios::binary | ios::in); if (!ifs) { return CUDT::APIError(MJ_FILESYSTEM, MN_READFAIL, 0); } int64_t ret = CUDT::sendfile(u, ifs, *offset, size, block); ifs.close(); return ret; } int64_t srt_recvfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block) { if (!path || !offset ) { return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } fstream ofs(path, ios::binary | ios::out); if (!ofs) { return CUDT::APIError(MJ_FILESYSTEM, MN_WRAVAIL, 0); } int64_t ret = CUDT::recvfile(u, ofs, *offset, size, block); ofs.close(); return ret; } extern const SRT_MSGCTRL srt_msgctrl_default = { 0, // no flags set SRT_MSGTTL_INF, false, // not in order (matters for msg mode only) PB_SUBSEQUENT, 0, // srctime: take "now" time SRT_SEQNO_NONE, SRT_MSGNO_NONE, NULL, // grpdata not supplied 0 // idem }; void srt_msgctrl_init(SRT_MSGCTRL* mctrl) { *mctrl = srt_msgctrl_default; } int srt_sendmsg2(SRTSOCKET u, const char * buf, int len, SRT_MSGCTRL *mctrl) { // Allow NULL mctrl in the API, but not internally. if (mctrl) return CUDT::sendmsg2(u, buf, len, (*mctrl)); SRT_MSGCTRL mignore = srt_msgctrl_default; return CUDT::sendmsg2(u, buf, len, (mignore)); } int srt_recvmsg2(SRTSOCKET u, char * buf, int len, SRT_MSGCTRL *mctrl) { if (mctrl) return CUDT::recvmsg2(u, buf, len, (*mctrl)); SRT_MSGCTRL mignore = srt_msgctrl_default; return CUDT::recvmsg2(u, buf, len, (mignore)); } const char* srt_getlasterror_str() { return UDT::getlasterror().getErrorMessage(); } int srt_getlasterror(int* loc_errno) { if ( loc_errno ) *loc_errno = UDT::getlasterror().getErrno(); return CUDT::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_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear) { return CUDT::bstats(u, perf, 0!= clear); } int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous) { return CUDT::bstats(u, perf, 0!= clear, 0!= instantaneous); } SRT_SOCKSTATUS srt_getsockstate(SRTSOCKET u) { return SRT_SOCKSTATUS((int)CUDT::getsockstate(u)); } // event mechanism int srt_epoll_create() { return CUDT::epoll_create(); } int srt_epoll_clear_usocks(int eit) { return CUDT::epoll_clear_usocks(eit); } // 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, SRTSOCKET u, const int * events) { return CUDT::epoll_add_usock(eid, u, events); } int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int * events) { int flag = 0; if (events) { flag = *events; } else { flag = SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR; } // call UDT native function return CUDT::epoll_add_ssock(eid, s, &flag); } int srt_epoll_remove_usock(int eid, SRTSOCKET u) { return CUDT::epoll_remove_usock(eid, u); } int srt_epoll_remove_ssock(int eid, SYSSOCKET s) { return CUDT::epoll_remove_ssock(eid, s); } int srt_epoll_update_usock(int eid, SRTSOCKET u, const int * events) { return CUDT::epoll_update_usock(eid, u, events); } int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int * events) { int flag = 0; if (events) { flag = *events; } else { flag = SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR; } // call UDT native function return CUDT::epoll_update_ssock(eid, s, &flag); } 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) { return UDT::epoll_wait2( eid, readfds, rnum, writefds, wnum, msTimeOut, lrfds, lrnum, lwfds, lwnum); } int srt_epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { return UDT::epoll_uwait( eid, fdsSet, fdsSize, msTimeOut); } // use this function to set flags. Default flags are always "everything unset". // Pass 0 here to clear everything, or nonzero to set a desired flag. // Pass -1 to not change anything (but still get the current flag value). int32_t srt_epoll_set(int eid, int32_t flags) { return CUDT::epoll_set(eid, flags); } int srt_epoll_release(int eid) { return CUDT::epoll_release(eid); } void srt_setloglevel(int ll) { UDT::setloglevel(srt_logging::LogLevel::type(ll)); } void srt_addlogfa(int fa) { UDT::addlogfa(srt_logging::LogFA(fa)); } void srt_dellogfa(int fa) { UDT::dellogfa(srt_logging::LogFA(fa)); } void srt_resetlogfa(const int* fara, size_t fara_size) { UDT::resetlogfa(fara, fara_size); } void srt_setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler) { UDT::setloghandler(opaque, handler); } void srt_setlogflags(int flags) { UDT::setlogflags(flags); } int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes) { return CUDT::getsndbuffer(sock, blocks, bytes); } int srt_getrejectreason(SRTSOCKET sock) { return CUDT::rejectReason(sock); } int srt_setrejectreason(SRTSOCKET sock, int value) { return CUDT::rejectReason(sock, value); } int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { if (!hook) return CUDT::APIError(MJ_NOTSUP, MN_INVAL); return CUDT::installAcceptHook(lsn, hook, opaq); } int srt_connect_callback(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) { if (!hook) return CUDT::APIError(MJ_NOTSUP, MN_INVAL); return CUDT::installConnectHook(lsn, hook, opaq); } uint32_t srt_getversion() { return SrtVersion(SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH); } int64_t srt_time_now() { return srt::sync::count_microseconds(srt::sync::steady_clock::now().time_since_epoch()); } int64_t srt_connection_time(SRTSOCKET sock) { return CUDT::socketStartTime(sock); } int srt_clock_type() { return SRT_SYNC_CLOCK; } } srt-1.4.4/srtcore/srt_compat.c000066400000000000000000000115451412557703600163500ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ // Implementation file for srt_compat.h /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ // Prevents from misconfiguration through preprocessor. #include "platform_sys.h" #include #include #include #include #if defined(__unix__) && !defined(BSD) && !defined(SUNOS) #include #endif #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include #endif static const char* SysStrError_Fallback(int errnum, char* buf, size_t buflen) { #if defined(_MSC_VER) && _MSC_VER < 1900 _snprintf(buf, buflen - 1, "ERROR CODE %d", errnum); buf[buflen - 1] = '\0'; #else snprintf(buf, buflen, "ERROR CODE %d", errnum); #endif return buf; } // This function is a portable and thread-safe version of `strerror`. // It requires a user-supplied buffer to store the message. The returned // value is always equal to the given buffer pointer. If the system // error message is longer than the given buflen, it will be trimmed. // When the error code is incorrect for the given error message function, // a fallback message will be returned, either as returned by the underlying // function, or crafted by this function as a response to error in an // underlying function. extern const char * SysStrError(int errnum, char * buf, size_t buflen) { if (buf == NULL || buflen < 4) // Required to put ??? into it as a fallback { errno = EFAULT; return buf; } buf[0] = '\0'; #if defined(_WIN32) const char* lpMsgBuf; // Note: Intentionally the "fixed char size" types are used despite using // character size dependent FormatMessage (instead of FormatMessageA) so that // your compilation fails when you use wide characters. // The problem is that when TCHAR != char, then the buffer written this way // would have to be converted to ASCII, not just copied by strncpy. FormatMessage(0 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, // no lpSource errnum, // dwMessageId (as controlled by FORMAT_MESSAGE_FROM_SYSTEM) MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // This below parameter normally should contain a pointer to an allocated buffer, // and this way it's LPTSTR. But when FORMAT_MESSAGE_ALLOCATE_BUFFER, then it is // expected to be a the value of LPTSTR* type, converted to LPTSTR, that designates // a pointer to a variable of type LPTSTR, to which the newly allocated buffer is // assigned. This buffer should be freed afterwards using LocalFree(). (LPSTR)&lpMsgBuf, 0, NULL); if (lpMsgBuf) { strncpy(buf, lpMsgBuf, buflen-1); buf[buflen-1] = 0; LocalFree((HLOCAL)lpMsgBuf); } else { SysStrError_Fallback(errnum, buf, buflen); } return buf; #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: // - possibly -1 and the error code is in ::errno // - possibly the error code itself // The details of the errror are not interesting; simply // craft a fallback message in this case. if (strerror_r(errnum, buf, buflen) != 0) { return SysStrError_Fallback(errnum, buf, buflen); } 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 (buf) // or some system-wide (constant) storage. To maintain // stability of the API, this overall function shall // always return the local buffer and the message in // this buffer - so these cases should be distinguished // and the internal storage copied to the buffer. char * gnu_buffer = strerror_r(errnum, buf, buflen); if (!gnu_buffer) { // This should never happen, so just a paranoid check return SysStrError_Fallback(errnum, buf, buflen); } // If they are the same, the message is already copied // (and it's usually a "fallback message" for an error case). if (gnu_buffer != buf) { strncpy(buf, gnu_buffer, buflen-1); buf[buflen-1] = 0; // guarantee what strncpy doesn't } return buf; #endif } srt-1.4.4/srtcore/srt_compat.h000066400000000000000000000044461412557703600163570ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_COMPAT_H #define INC_SRT_COMPAT_H #include #include #ifndef SRT_API #ifdef _WIN32 #ifndef __MINGW32__ #ifdef SRT_DYNAMIC #ifdef SRT_EXPORTS #define SRT_API __declspec(dllexport) #else #define SRT_API __declspec(dllimport) #endif #else #define SRT_API #endif #else #define SRT_API #endif #else #define SRT_API __attribute__ ((visibility("default"))) #endif #endif #ifdef _WIN32 // https://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx // printf() Format for ssize_t #if !defined(PRIzd) #define PRIzd "Id" #endif // printf() Format for size_t #if !defined(PRIzu) #define PRIzu "Iu" #endif #else // http://www.gnu.org/software/libc/manual/html_node/Integer-Conversions.html // printf() Format for ssize_t #if !defined(PRIzd) #define PRIzd "zd" #endif // printf() Format for size_t #if !defined(PRIzu) #define PRIzu "zu" #endif #endif #ifdef __cplusplus extern "C" { #endif /* Ensures that we store the error in the buffer and return the bufer. */ SRT_API const char * SysStrError(int errnum, char * buf, size_t buflen); #ifdef __cplusplus } // extern C // Extra C++ stuff. Included only in C++ mode. #include #include inline std::string SysStrError(int errnum) { char buf[1024]; return SysStrError(errnum, buf, 1024); } inline struct tm SysLocalTime(time_t tt) { struct tm tms; memset(&tms, 0, sizeof tms); #ifdef _WIN32 errno_t rr = localtime_s(&tms, &tt); if (rr == 0) return tms; #else // Ignore the error, state that if something // happened, you simply have a pre-cleared tms. localtime_r(&tt, &tms); #endif return tms; } #endif // defined C++ #endif // INC_SRT_COMPAT_H srt-1.4.4/srtcore/srt_shared.rc000066400000000000000000000020471412557703600165120ustar00rootroot00000000000000// Microsoft Visual C++ generated resource script. // #include "version.h" #include "winres.h" ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO #ifdef SRT_VERSION_BUILD FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH, SRT_VERSION_BUILD #else FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH #endif FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080904b0" BEGIN VALUE "CompanyName", "SRT Alliance" VALUE "FileDescription", "SRT Local Build" VALUE "InternalName", "srt.dll" VALUE "LegalCopyright", "" VALUE "OriginalFilename", "srt.dll" VALUE "ProductName", "SRT" VALUE "ProductVersion", SRT_VERSION_STRING END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1200 END END srt-1.4.4/srtcore/strerror_defs.cpp000066400000000000000000000125521412557703600174170ustar00rootroot00000000000000 /* WARNING: Generated from ../scripts/generate-error-types.tcl DO NOT MODIFY. Copyright applies as per the generator script. */ #include namespace srt { // MJ_SUCCESS 'Success' const char* strerror_msgs_success [] = { "Success", // MN_NONE = 0 "" }; // MJ_SETUP 'Connection setup failure' const char* strerror_msgs_setup [] = { "Connection setup failure", // MN_NONE = 0 "Connection setup failure: connection timed out", // MN_TIMEOUT = 1 "Connection setup failure: connection rejected", // MN_REJECTED = 2 "Connection setup failure: unable to create/configure SRT socket", // MN_NORES = 3 "Connection setup failure: aborted for security reasons", // MN_SECURITY = 4 "Connection setup failure: socket closed during operation", // MN_CLOSED = 5 "" }; // MJ_CONNECTION '' const char* strerror_msgs_connection [] = { "", // MN_NONE = 0 "Connection was broken", // MN_CONNLOST = 1 "Connection does not exist", // MN_NOCONN = 2 "" }; // MJ_SYSTEMRES 'System resource failure' const char* strerror_msgs_systemres [] = { "System resource failure", // MN_NONE = 0 "System resource failure: unable to create new threads", // MN_THREAD = 1 "System resource failure: unable to allocate buffers", // MN_MEMORY = 2 "System resource failure: unable to allocate a system object", // MN_OBJECT = 3 "" }; // MJ_FILESYSTEM 'File system failure' const char* strerror_msgs_filesystem [] = { "File system failure", // MN_NONE = 0 "File system failure: cannot seek read position", // MN_SEEKGFAIL = 1 "File system failure: failure in read", // MN_READFAIL = 2 "File system failure: cannot seek write position", // MN_SEEKPFAIL = 3 "File system failure: failure in write", // MN_WRITEFAIL = 4 "" }; // MJ_NOTSUP 'Operation not supported' const char* strerror_msgs_notsup [] = { "Operation not supported", // MN_NONE = 0 "Operation not supported: Cannot do this operation on a BOUND socket", // MN_ISBOUND = 1 "Operation not supported: Cannot do this operation on a CONNECTED socket", // MN_ISCONNECTED = 2 "Operation not supported: Bad parameters", // MN_INVAL = 3 "Operation not supported: Invalid socket ID", // MN_SIDINVAL = 4 "Operation not supported: Cannot do this operation on an UNBOUND socket", // MN_ISUNBOUND = 5 "Operation not supported: Socket is not in listening state", // MN_NOLISTEN = 6 "Operation not supported: Listen/accept is not supported in rendezous connection setup", // MN_ISRENDEZVOUS = 7 "Operation not supported: Cannot call connect on UNBOUND socket in rendezvous connection setup", // MN_ISRENDUNBOUND = 8 "Operation not supported: Incorrect use of Message API (sendmsg/recvmsg).", // MN_INVALMSGAPI = 9 "Operation not supported: Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile).", // MN_INVALBUFFERAPI = 10 "Operation not supported: Another socket is already listening on the same port", // MN_BUSY = 11 "Operation not supported: Message is too large to send (it must be less than the SRT send buffer size)", // MN_XSIZE = 12 "Operation not supported: Invalid epoll ID", // MN_EIDINVAL = 13 "Operation not supported: All sockets removed from epoll, waiting would deadlock", // MN_EEMPTY = 14 "Operation not supported: Another socket is bound to that port and is not reusable for requested settings", // MN_BUSYPORT = 15 "" }; // MJ_AGAIN 'Non-blocking call failure' const char* strerror_msgs_again [] = { "Non-blocking call failure", // MN_NONE = 0 "Non-blocking call failure: no buffer available for sending", // MN_WRAVAIL = 1 "Non-blocking call failure: no data available for reading", // MN_RDAVAIL = 2 "Non-blocking call failure: transmission timed out", // MN_XMTIMEOUT = 3 "Non-blocking call failure: early congestion notification", // MN_CONGESTION = 4 "" }; // MJ_PEERERROR 'The peer side has signaled an error' const char* strerror_msgs_peererror [] = { "The peer side has signaled an error", // MN_NONE = 0 "" }; const char** strerror_array_major [] = { strerror_msgs_success, // MJ_SUCCESS = 0 strerror_msgs_setup, // MJ_SETUP = 1 strerror_msgs_connection, // MJ_CONNECTION = 2 strerror_msgs_systemres, // MJ_SYSTEMRES = 3 strerror_msgs_filesystem, // MJ_FILESYSTEM = 4 strerror_msgs_notsup, // MJ_NOTSUP = 5 strerror_msgs_again, // MJ_AGAIN = 6 strerror_msgs_peererror, // MJ_PEERERROR = 7 NULL }; #define SRT_ARRAY_SIZE(ARR) sizeof(ARR) / sizeof(ARR[0]) const size_t strerror_array_sizes [] = { SRT_ARRAY_SIZE(strerror_msgs_success) - 1, SRT_ARRAY_SIZE(strerror_msgs_setup) - 1, SRT_ARRAY_SIZE(strerror_msgs_connection) - 1, SRT_ARRAY_SIZE(strerror_msgs_systemres) - 1, SRT_ARRAY_SIZE(strerror_msgs_filesystem) - 1, SRT_ARRAY_SIZE(strerror_msgs_notsup) - 1, SRT_ARRAY_SIZE(strerror_msgs_again) - 1, SRT_ARRAY_SIZE(strerror_msgs_peererror) - 1, 0 }; const char* strerror_get_message(size_t major, size_t minor) { static const char* const undefined = "UNDEFINED ERROR"; // Extract the major array if (major >= sizeof(strerror_array_major)/sizeof(const char**)) { return undefined; } const char** array = strerror_array_major[major]; size_t size = strerror_array_sizes[major]; if (minor >= size) { return undefined; } return array[minor]; } } // namespace srt srt-1.4.4/srtcore/sync.cpp000066400000000000000000000220361412557703600155060ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include "platform_sys.h" #include #include #include #include "sync.h" #include "srt.h" #include "srt_compat.h" #include "logging.h" #include "common.h" // HAVE_CXX11 is defined in utilities.h, included with common.h. // The following conditional inclusion must go after common.h. #if HAVE_CXX11 #include #endif namespace srt_logging { extern Logger inlog; } using namespace srt_logging; using namespace std; namespace srt { namespace sync { std::string FormatTime(const steady_clock::time_point& timestamp) { if (is_zero(timestamp)) { // Use special string for 0 return "00:00:00.000000 [STDY]"; } const int decimals = clockSubsecondPrecision(); const uint64_t total_sec = count_seconds(timestamp.time_since_epoch()); const uint64_t days = total_sec / (60 * 60 * 24); const uint64_t hours = total_sec / (60 * 60) - days * 24; const uint64_t minutes = total_sec / 60 - (days * 24 * 60) - hours * 60; const uint64_t seconds = total_sec - (days * 24 * 60 * 60) - hours * 60 * 60 - minutes * 60; ostringstream out; if (days) out << days << "D "; out << setfill('0') << setw(2) << hours << ":" << setfill('0') << setw(2) << minutes << ":" << setfill('0') << setw(2) << seconds << "." << setfill('0') << setw(decimals) << (timestamp - seconds_from(total_sec)).time_since_epoch().count() << " [STDY]"; return out.str(); } std::string FormatTimeSys(const steady_clock::time_point& timestamp) { const time_t now_s = ::time(NULL); // get current time in seconds const steady_clock::time_point now_timestamp = steady_clock::now(); const int64_t delta_us = count_microseconds(timestamp - now_timestamp); const int64_t delta_s = floor((static_cast(count_microseconds(now_timestamp.time_since_epoch()) % 1000000) + delta_us) / 1000000.0); const time_t tt = now_s + delta_s; struct tm tm = SysLocalTime(tt); // in seconds char tmp_buf[512]; strftime(tmp_buf, 512, "%X.", &tm); ostringstream out; out << tmp_buf << setfill('0') << setw(6) << (count_microseconds(timestamp.time_since_epoch()) % 1000000) << " [SYST]"; return out.str(); } #ifdef ENABLE_STDCXX_SYNC bool StartThread(CThread& th, ThreadFunc&& f, void* args, const string& name) #else bool StartThread(CThread& th, void* (*f) (void*), void* args, const string& name) #endif { ThreadName tn(name); try { #if HAVE_FULL_CXX11 || defined(ENABLE_STDCXX_SYNC) th = CThread(f, args); #else // No move semantics in C++03, therefore using a dedicated function th.create_thread(f, args); #endif } catch (const CThreadException& e) { HLOGC(inlog.Debug, log << name << ": failed to start thread. " << e.what()); return false; } return true; } } // namespace sync } // namespace srt //////////////////////////////////////////////////////////////////////////////// // // CEvent class // //////////////////////////////////////////////////////////////////////////////// srt::sync::CEvent::CEvent() { #ifndef _WIN32 m_cond.init(); #endif } srt::sync::CEvent::~CEvent() { #ifndef _WIN32 m_cond.destroy(); #endif } bool srt::sync::CEvent::lock_wait_until(const TimePoint& tp) { UniqueLock lock(m_lock); return m_cond.wait_until(lock, tp); } void srt::sync::CEvent::notify_one() { return m_cond.notify_one(); } void srt::sync::CEvent::notify_all() { return m_cond.notify_all(); } bool srt::sync::CEvent::lock_wait_for(const steady_clock::duration& rel_time) { UniqueLock lock(m_lock); return m_cond.wait_for(lock, rel_time); } bool srt::sync::CEvent::wait_for(UniqueLock& lock, const steady_clock::duration& rel_time) { return m_cond.wait_for(lock, rel_time); } void srt::sync::CEvent::lock_wait() { UniqueLock lock(m_lock); return wait(lock); } void srt::sync::CEvent::wait(UniqueLock& lock) { return m_cond.wait(lock); } namespace srt { namespace sync { srt::sync::CEvent g_Sync; } // namespace sync } // namespace srt //////////////////////////////////////////////////////////////////////////////// // // Timer // //////////////////////////////////////////////////////////////////////////////// srt::sync::CTimer::CTimer() { } srt::sync::CTimer::~CTimer() { } bool srt::sync::CTimer::sleep_until(TimePoint tp) { // The class member m_sched_time can be used to interrupt the sleep. // Refer to Timer::interrupt(). enterCS(m_event.mutex()); m_tsSchedTime = tp; leaveCS(m_event.mutex()); #if USE_BUSY_WAITING #if defined(_WIN32) // 10 ms on Windows: bad accuracy of timers const steady_clock::duration td_threshold = milliseconds_from(10); #else // 1 ms on non-Windows platforms const steady_clock::duration td_threshold = milliseconds_from(1); #endif #endif // USE_BUSY_WAITING TimePoint cur_tp = steady_clock::now(); while (cur_tp < m_tsSchedTime) { #if USE_BUSY_WAITING steady_clock::duration td_wait = m_tsSchedTime - cur_tp; if (td_wait <= 2 * td_threshold) break; td_wait -= td_threshold; m_event.lock_wait_for(td_wait); #else m_event.lock_wait_until(m_tsSchedTime); #endif // USE_BUSY_WAITING cur_tp = steady_clock::now(); } #if USE_BUSY_WAITING while (cur_tp < m_tsSchedTime) { #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;"); #elif defined(_WIN32) && !defined(__MINGW32__) __nop(); __nop(); __nop(); __nop(); __nop(); #endif cur_tp = steady_clock::now(); } #endif // USE_BUSY_WAITING return cur_tp >= m_tsSchedTime; } void srt::sync::CTimer::interrupt() { UniqueLock lck(m_event.mutex()); m_tsSchedTime = steady_clock::now(); m_event.notify_all(); } void srt::sync::CTimer::tick() { m_event.notify_one(); } void srt::sync::CGlobEvent::triggerEvent() { return g_Sync.notify_one(); } bool srt::sync::CGlobEvent::waitForEvent() { return g_Sync.lock_wait_for(milliseconds_from(10)); } //////////////////////////////////////////////////////////////////////////////// // // Random // //////////////////////////////////////////////////////////////////////////////// namespace srt { #if HAVE_CXX11 static std::random_device& randomDevice() { static std::random_device s_RandomDevice; return s_RandomDevice; } #elif defined(_WIN32) && defined(__MINGW32__) static void initRandSeed() { const int64_t seed = sync::steady_clock::now().time_since_epoch().count(); srand((unsigned int) seed); } static pthread_once_t s_InitRandSeedOnce = PTHREAD_ONCE_INIT; #else static unsigned int genRandSeed() { // Duration::count() does not depend on any global objects, // therefore it is preferred over count)microseconds(..). const int64_t seed = sync::steady_clock::now().time_since_epoch().count(); return (unsigned int) seed; } static unsigned int* getRandSeed() { static unsigned int s_uRandSeed = genRandSeed(); return &s_uRandSeed; } #endif } int srt::sync::genRandomInt(int minVal, int maxVal) { // This Meyers singleton initialization is thread-safe since C++11, but is not thread-safe in C++03. // A mutex to protect simulteneout access to the random device. // Thread-local storage could be used here instead to store the seed / random device. // However the generator is not used often (Initial Socket ID, Initial sequence number, FileCC), // so sharing a single seed among threads should not impact the performance. static sync::Mutex s_mtxRandomDevice; sync::ScopedLock lck(s_mtxRandomDevice); #if HAVE_CXX11 uniform_int_distribution<> dis(minVal, maxVal); return dis(randomDevice()); #else #if defined(__MINGW32__) // No rand_r(..) for MinGW. pthread_once(&s_InitRandSeedOnce, initRandSeed); // rand() returns a pseudo-random integer in the range 0 to RAND_MAX inclusive // (i.e., the mathematical range [0, RAND_MAX]). // Therefore, rand_0_1 belongs to [0.0, 1.0]. const double rand_0_1 = double(rand()) / RAND_MAX; #else // not __MINGW32__ // rand_r(..) returns a pseudo-random integer in the range 0 to RAND_MAX inclusive // (i.e., the mathematical range [0, RAND_MAX]). // Therefore, rand_0_1 belongs to [0.0, 1.0]. const double rand_0_1 = double(rand_r(getRandSeed())) / RAND_MAX; #endif // Map onto [minVal, maxVal]. // Note. The probablity to get maxVal as the result is minuscule. const int res = minVal + static_cast((maxVal - minVal) * rand_0_1); return res; #endif // HAVE_CXX11 } srt-1.4.4/srtcore/sync.h000066400000000000000000000654211412557703600151600ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #pragma once #ifndef INC_SRT_SYNC_H #define INC_SRT_SYNC_H #include #include #ifdef ENABLE_STDCXX_SYNC #include #include #include #include #include #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_STDCXX_STEADY #define SRT_SYNC_CLOCK_STR "STDCXX_STEADY" #else #include // Defile clock type to use #ifdef IA32 #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_IA32_RDTSC #define SRT_SYNC_CLOCK_STR "IA32_RDTSC" #elif defined(IA64) #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_IA64_ITC #define SRT_SYNC_CLOCK_STR "IA64_ITC" #elif defined(AMD64) #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_AMD64_RDTSC #define SRT_SYNC_CLOCK_STR "AMD64_RDTSC" #elif defined(_WIN32) #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_WINQPC #define SRT_SYNC_CLOCK_STR "WINQPC" #elif TARGET_OS_MAC #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_MACH_ABSTIME #define SRT_SYNC_CLOCK_STR "MACH_ABSTIME" #elif defined(ENABLE_MONOTONIC_CLOCK) #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_GETTIME_MONOTONIC #define SRT_SYNC_CLOCK_STR "GETTIME_MONOTONIC" #else #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_POSIX_GETTIMEOFDAY #define SRT_SYNC_CLOCK_STR "POSIX_GETTIMEOFDAY" #endif #endif // ENABLE_STDCXX_SYNC #include "srt.h" #include "utilities.h" #include "srt_attr_defs.h" class CUDTException; // defined in common.h namespace srt { namespace sync { /////////////////////////////////////////////////////////////////////////////// // // Duration class // /////////////////////////////////////////////////////////////////////////////// #if ENABLE_STDCXX_SYNC template using Duration = std::chrono::duration; #else /// Class template srt::sync::Duration represents a time interval. /// It consists of a count of ticks of _Clock. /// It is a wrapper of system timers in case of non-C++11 chrono build. template class Duration { public: Duration() : m_duration(0) { } explicit Duration(int64_t d) : m_duration(d) { } public: inline int64_t count() const { return m_duration; } static Duration zero() { return Duration(); } public: // Relational operators inline bool operator>=(const Duration& rhs) const { return m_duration >= rhs.m_duration; } inline bool operator>(const Duration& rhs) const { return m_duration > rhs.m_duration; } inline bool operator==(const Duration& rhs) const { return m_duration == rhs.m_duration; } inline bool operator!=(const Duration& rhs) const { return m_duration != rhs.m_duration; } inline bool operator<=(const Duration& rhs) const { return m_duration <= rhs.m_duration; } inline bool operator<(const Duration& rhs) const { return m_duration < rhs.m_duration; } public: // Assignment operators inline void operator*=(const int64_t mult) { m_duration = static_cast(m_duration * mult); } inline void operator+=(const Duration& rhs) { m_duration += rhs.m_duration; } inline void operator-=(const Duration& rhs) { m_duration -= rhs.m_duration; } inline Duration operator+(const Duration& rhs) const { return Duration(m_duration + rhs.m_duration); } inline Duration operator-(const Duration& rhs) const { return Duration(m_duration - rhs.m_duration); } inline Duration operator*(const int64_t& rhs) const { return Duration(m_duration * rhs); } inline Duration operator/(const int64_t& rhs) const { return Duration(m_duration / rhs); } private: // int64_t range is from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 int64_t m_duration; }; #endif // ENABLE_STDCXX_SYNC /////////////////////////////////////////////////////////////////////////////// // // TimePoint and steadt_clock classes // /////////////////////////////////////////////////////////////////////////////// #if ENABLE_STDCXX_SYNC using steady_clock = std::chrono::steady_clock; template using time_point = std::chrono::time_point; template using TimePoint = std::chrono::time_point; template inline bool is_zero(const time_point &tp) { return tp.time_since_epoch() == Clock::duration::zero(); } inline bool is_zero(const steady_clock::time_point& t) { return t == steady_clock::time_point(); } #else template class TimePoint; class steady_clock { public: typedef Duration duration; typedef TimePoint time_point; public: static time_point now(); }; /// Represents a point in time template class TimePoint { public: TimePoint() : m_timestamp(0) { } explicit TimePoint(uint64_t tp) : m_timestamp(tp) { } TimePoint(const TimePoint& other) : m_timestamp(other.m_timestamp) { } TimePoint(const Duration& duration_since_epoch) : m_timestamp(duration_since_epoch.count()) { } ~TimePoint() {} public: // Relational operators inline bool operator<(const TimePoint& rhs) const { return m_timestamp < rhs.m_timestamp; } inline bool operator<=(const TimePoint& rhs) const { return m_timestamp <= rhs.m_timestamp; } inline bool operator==(const TimePoint& rhs) const { return m_timestamp == rhs.m_timestamp; } inline bool operator!=(const TimePoint& rhs) const { return m_timestamp != rhs.m_timestamp; } inline bool operator>=(const TimePoint& rhs) const { return m_timestamp >= rhs.m_timestamp; } inline bool operator>(const TimePoint& rhs) const { return m_timestamp > rhs.m_timestamp; } public: // Arithmetic operators inline Duration operator-(const TimePoint& rhs) const { return Duration(m_timestamp - rhs.m_timestamp); } inline TimePoint operator+(const Duration& rhs) const { return TimePoint(m_timestamp + rhs.count()); } inline TimePoint operator-(const Duration& rhs) const { return TimePoint(m_timestamp - rhs.count()); } public: // Assignment operators inline void operator=(const TimePoint& rhs) { m_timestamp = rhs.m_timestamp; } inline void operator+=(const Duration& rhs) { m_timestamp += rhs.count(); } inline void operator-=(const Duration& rhs) { m_timestamp -= rhs.count(); } public: // static inline ATR_CONSTEXPR TimePoint min() { return TimePoint(std::numeric_limits::min()); } static inline ATR_CONSTEXPR TimePoint max() { return TimePoint(std::numeric_limits::max()); } public: Duration time_since_epoch() const; private: uint64_t m_timestamp; }; template <> srt::sync::Duration srt::sync::TimePoint::time_since_epoch() const; inline Duration operator*(const int& lhs, const Duration& rhs) { return rhs * lhs; } #endif // ENABLE_STDCXX_SYNC // NOTE: Moved the following class definitons to "atomic_clock.h" // template // class AtomicDuration; // template // class AtomicClock; /////////////////////////////////////////////////////////////////////////////// // // Duration and timepoint conversions // /////////////////////////////////////////////////////////////////////////////// /// Function return number of decimals in a subsecond precision. /// E.g. for a microsecond accuracy of steady_clock the return would be 6. /// For a nanosecond accuracy of the steady_clock the return value would be 9. int clockSubsecondPrecision(); #if ENABLE_STDCXX_SYNC inline long long count_microseconds(const steady_clock::duration &t) { return std::chrono::duration_cast(t).count(); } inline long long count_microseconds(const steady_clock::time_point tp) { return std::chrono::duration_cast(tp.time_since_epoch()).count(); } inline long long count_milliseconds(const steady_clock::duration &t) { return std::chrono::duration_cast(t).count(); } inline long long count_seconds(const steady_clock::duration &t) { return std::chrono::duration_cast(t).count(); } inline steady_clock::duration microseconds_from(int64_t t_us) { return std::chrono::microseconds(t_us); } inline steady_clock::duration milliseconds_from(int64_t t_ms) { return std::chrono::milliseconds(t_ms); } inline steady_clock::duration seconds_from(int64_t t_s) { return std::chrono::seconds(t_s); } #else int64_t count_microseconds(const steady_clock::duration& t); int64_t count_milliseconds(const steady_clock::duration& t); int64_t count_seconds(const steady_clock::duration& t); Duration microseconds_from(int64_t t_us); Duration milliseconds_from(int64_t t_ms); Duration seconds_from(int64_t t_s); inline bool is_zero(const TimePoint& t) { return t == TimePoint(); } #endif // ENABLE_STDCXX_SYNC /////////////////////////////////////////////////////////////////////////////// // // Mutex section // /////////////////////////////////////////////////////////////////////////////// #if ENABLE_STDCXX_SYNC using Mutex = std::mutex; using UniqueLock = std::unique_lock; using ScopedLock = std::lock_guard; #else /// Mutex is a class wrapper, that should mimic the std::chrono::mutex class. /// At the moment the extra function ref() is temporally added to allow calls /// to pthread_cond_timedwait(). Will be removed by introducing CEvent. class SRT_ATTR_CAPABILITY("mutex") Mutex { friend class SyncEvent; public: Mutex(); ~Mutex(); public: int lock() SRT_ATTR_ACQUIRE(); int unlock() SRT_ATTR_RELEASE(); /// @return true if the lock was acquired successfully, otherwise false bool try_lock() SRT_ATTR_TRY_ACQUIRE(true); // TODO: To be removed with introduction of the CEvent. pthread_mutex_t& ref() { return m_mutex; } private: pthread_mutex_t m_mutex; }; /// A pthread version of std::chrono::scoped_lock (or lock_guard for C++11) class SRT_ATTR_SCOPED_CAPABILITY ScopedLock { public: SRT_ATTR_ACQUIRE(m) ScopedLock(Mutex& m); SRT_ATTR_RELEASE() ~ScopedLock(); private: Mutex& m_mutex; }; /// A pthread version of std::chrono::unique_lock class SRT_ATTR_SCOPED_CAPABILITY UniqueLock { friend class SyncEvent; public: SRT_ATTR_ACQUIRE(m_Mutex) UniqueLock(Mutex &m); SRT_ATTR_RELEASE(m_Mutex) ~UniqueLock(); public: SRT_ATTR_ACQUIRE(m_Mutex) void lock(); SRT_ATTR_RELEASE(m_Mutex) void unlock(); SRT_ATTR_RETURN_CAPABILITY(m_Mutex) Mutex* mutex(); // reflects C++11 unique_lock::mutex() private: int m_iLocked; Mutex& m_Mutex; }; #endif // ENABLE_STDCXX_SYNC inline void enterCS(Mutex& m) SRT_ATTR_EXCLUDES(m) SRT_ATTR_ACQUIRE(m) { m.lock(); } inline bool tryEnterCS(Mutex& m) SRT_ATTR_EXCLUDES(m) SRT_ATTR_TRY_ACQUIRE(true, m) { return m.try_lock(); } inline void leaveCS(Mutex& m) SRT_ATTR_REQUIRES(m) SRT_ATTR_RELEASE(m) { m.unlock(); } class InvertedLock { Mutex& m_mtx; public: SRT_ATTR_REQUIRES(m) SRT_ATTR_RELEASE(m) InvertedLock(Mutex& m) : m_mtx(m) { m_mtx.unlock(); } SRT_ATTR_ACQUIRE(m_mtx) ~InvertedLock() { m_mtx.lock(); } }; inline void setupMutex(Mutex&, const char*) {} inline void releaseMutex(Mutex&) {} //////////////////////////////////////////////////////////////////////////////// // // Condition section // //////////////////////////////////////////////////////////////////////////////// class Condition { public: Condition(); ~Condition(); public: /// These functions do not align with C++11 version. They are here hopefully as a temporal solution /// to avoud issues with static initialization of CV on windows. void init(); void destroy(); public: /// Causes the current thread to block until the condition variable is notified /// or a spurious wakeup occurs. /// /// @param lock Corresponding mutex locked by UniqueLock void wait(UniqueLock& lock); /// Atomically releases lock, blocks the current executing thread, /// and adds it to the list of threads waiting on *this. /// The thread will be unblocked when notify_all() or notify_one() is executed, /// or when the relative timeout rel_time expires. /// It may also be unblocked spuriously. When unblocked, regardless of the reason, /// lock is reacquired and wait_for() exits. /// /// @returns false if the relative timeout specified by rel_time expired, /// true otherwise (signal or spurious wake up). /// /// @note Calling this function if lock.mutex() /// is not locked by the current thread is undefined behavior. /// Calling this function if lock.mutex() is not the same mutex as the one /// used by all other threads that are currently waiting on the same /// condition variable is undefined behavior. bool wait_for(UniqueLock& lock, const steady_clock::duration& rel_time); /// Causes the current thread to block until the condition variable is notified, /// a specific time is reached, or a spurious wakeup occurs. /// /// @param[in] lock an object of type UniqueLock, which must be locked by the current thread /// @param[in] timeout_time an object of type time_point representing the time when to stop waiting /// /// @returns false if the relative timeout specified by timeout_time expired, /// true otherwise (signal or spurious wake up). bool wait_until(UniqueLock& lock, const steady_clock::time_point& timeout_time); /// Calling notify_one() unblocks one of the waiting threads, /// if any threads are waiting on this CV. void notify_one(); /// Unblocks all threads currently waiting for this CV. void notify_all(); private: #if ENABLE_STDCXX_SYNC std::condition_variable m_cv; #else pthread_cond_t m_cv; #endif }; inline void setupCond(Condition& cv, const char*) { cv.init(); } inline void releaseCond(Condition& cv) { cv.destroy(); } /////////////////////////////////////////////////////////////////////////////// // // Event (CV) section // /////////////////////////////////////////////////////////////////////////////// // This class is used for condition variable combined with mutex by different ways. // This should provide a cleaner API around locking with debug-logging inside. class CSync { Condition* m_cond; UniqueLock* m_locker; public: // Locked version: must be declared only after the declaration of UniqueLock, // which has locked the mutex. On this delegate you should call only // signal_locked() and pass the UniqueLock variable that should remain locked. // Also wait() and wait_for() can be used only with this socket. CSync(Condition& cond, UniqueLock& g) : m_cond(&cond), m_locker(&g) { // XXX it would be nice to check whether the owner is also current thread // but this can't be done portable way. // When constructed by this constructor, the user is expected // to only call signal_locked() function. You should pass the same guard // variable that you have used for construction as its argument. } // COPY CONSTRUCTOR: DEFAULT! // Wait indefinitely, until getting a signal on CV. void wait() { m_cond->wait(*m_locker); } /// Block the call until either @a timestamp time achieved /// or the conditional is signaled. /// @param [in] delay Maximum time to wait since the moment of the call /// @retval false if the relative timeout specified by rel_time expired, /// @retval true if condition is signaled or spurious wake up. bool wait_for(const steady_clock::duration& delay) { return m_cond->wait_for(*m_locker, delay); } // Wait until the given time is achieved. /// @param [in] exptime The target time to wait until. /// @retval false if the target wait time is reached. /// @retval true if condition is signal or spurious wake up. bool wait_until(const steady_clock::time_point& exptime) { return m_cond->wait_until(*m_locker, exptime); } // Static ad-hoc version static void lock_signal(Condition& cond, Mutex& m) { ScopedLock lk(m); // XXX with thread logging, don't use ScopedLock directly! cond.notify_one(); } static void lock_broadcast(Condition& cond, Mutex& m) { ScopedLock lk(m); // XXX with thread logging, don't use ScopedLock directly! cond.notify_all(); } void signal_locked(UniqueLock& lk SRT_ATR_UNUSED) { // EXPECTED: lk.mutex() is LOCKED. m_cond->notify_one(); } // The signal_relaxed and broadcast_relaxed functions are to be used in case // when you don't care whether the associated mutex is locked or not (you // accept the case that a mutex isn't locked and the signal gets effectively // missed), or you somehow know that the mutex is locked, but you don't have // access to the associated UniqueLock object. This function, although it does // the same thing as signal_locked() and broadcast_locked(), is here for // the user to declare explicitly that the signal/broadcast is done without // being prematurely certain that the associated mutex is locked. // // It is then expected that whenever these functions are used, an extra // comment is provided to explain, why the use of the relaxed signaling is // correctly used. void signal_relaxed() { signal_relaxed(*m_cond); } static void signal_relaxed(Condition& cond) { cond.notify_one(); } static void broadcast_relaxed(Condition& cond) { cond.notify_all(); } }; //////////////////////////////////////////////////////////////////////////////// // // CEvent class // //////////////////////////////////////////////////////////////////////////////// class CEvent { public: CEvent(); ~CEvent(); public: Mutex& mutex() { return m_lock; } public: /// Causes the current thread to block until /// a specific time is reached. /// /// @return true if condition occured or spuriously woken up /// false on timeout bool lock_wait_until(const steady_clock::time_point& tp); /// Blocks the current executing thread, /// and adds it to the list of threads waiting on* this. /// The thread will be unblocked when notify_all() or notify_one() is executed, /// or when the relative timeout rel_time expires. /// It may also be unblocked spuriously. /// Uses internal mutex to lock. /// /// @return true if condition occured or spuriously woken up /// false on timeout bool lock_wait_for(const steady_clock::duration& rel_time); /// Atomically releases lock, blocks the current executing thread, /// and adds it to the list of threads waiting on* this. /// The thread will be unblocked when notify_all() or notify_one() is executed, /// or when the relative timeout rel_time expires. /// It may also be unblocked spuriously. /// When unblocked, regardless of the reason, lock is reacquiredand wait_for() exits. /// /// @return true if condition occured or spuriously woken up /// false on timeout bool wait_for(UniqueLock& lk, const steady_clock::duration& rel_time); void lock_wait(); void wait(UniqueLock& lk); void notify_one(); void notify_all(); private: Mutex m_lock; Condition m_cond; }; class CTimer { public: CTimer(); ~CTimer(); public: /// Causes the current thread to block until /// the specified time is reached. /// Sleep can be interrupted by calling interrupt() /// or woken up to recheck the scheduled time by tick() /// @param tp target time to sleep until /// /// @return true if the specified time was reached /// false should never happen bool sleep_until(steady_clock::time_point tp); /// Resets target wait time and interrupts waiting /// in sleep_until(..) void interrupt(); /// Wakes up waiting thread (sleep_until(..)) without /// changing the target waiting time to force a recheck /// of the current time in comparisson to the target time. void tick(); private: CEvent m_event; steady_clock::time_point m_tsSchedTime; }; /// Print steady clock timepoint in a human readable way. /// days HH:MM:SS.us [STD] /// Example: 1D 02:12:56.123456 /// /// @param [in] steady clock timepoint /// @returns a string with a formatted time representation std::string FormatTime(const steady_clock::time_point& time); /// Print steady clock timepoint relative to the current system time /// Date HH:MM:SS.us [SYS] /// @param [in] steady clock timepoint /// @returns a string with a formatted time representation std::string FormatTimeSys(const steady_clock::time_point& time); enum eDurationUnit {DUNIT_S, DUNIT_MS, DUNIT_US}; template struct DurationUnitName; template<> struct DurationUnitName { static const char* name() { return "us"; } static double count(const steady_clock::duration& dur) { return static_cast(count_microseconds(dur)); } }; template<> struct DurationUnitName { static const char* name() { return "ms"; } static double count(const steady_clock::duration& dur) { return static_cast(count_microseconds(dur))/1000.0; } }; template<> struct DurationUnitName { static const char* name() { return "s"; } static double count(const steady_clock::duration& dur) { return static_cast(count_microseconds(dur))/1000000.0; } }; template inline std::string FormatDuration(const steady_clock::duration& dur) { return Sprint(DurationUnitName::count(dur)) + DurationUnitName::name(); } inline std::string FormatDuration(const steady_clock::duration& dur) { return FormatDuration(dur); } //////////////////////////////////////////////////////////////////////////////// // // CGlobEvent class // //////////////////////////////////////////////////////////////////////////////// class CGlobEvent { public: /// Triggers the event and notifies waiting threads. /// Simply calls notify_one(). static void triggerEvent(); /// Waits for the event to be triggered with 10ms timeout. /// Simply calls wait_for(). static bool waitForEvent(); }; //////////////////////////////////////////////////////////////////////////////// // // CThread class // //////////////////////////////////////////////////////////////////////////////// #ifdef ENABLE_STDCXX_SYNC typedef std::system_error CThreadException; using CThread = std::thread; namespace this_thread = std::this_thread; #else // pthreads wrapper version typedef ::CUDTException CThreadException; class CThread { public: CThread(); /// @throws std::system_error if the thread could not be started. CThread(void *(*start_routine) (void *), void *arg); #if HAVE_FULL_CXX11 CThread& operator=(CThread &other) = delete; CThread& operator=(CThread &&other); #else CThread& operator=(CThread &other); /// To be used only in StartThread function. /// Creates a new stread and assigns to this. /// @throw CThreadException void create_thread(void *(*start_routine) (void *), void *arg); #endif public: // Observers /// Checks if the CThread object identifies an active thread of execution. /// A default constructed thread is not joinable. /// A thread that has finished executing code, but has not yet been joined /// is still considered an active thread of execution and is therefore joinable. bool joinable() const; struct id { explicit id(const pthread_t t) : value(t) {} const pthread_t value; inline bool operator==(const id& second) const { return pthread_equal(value, second.value) != 0; } }; /// Returns the id of the current thread. /// In this implementation the ID is the pthread_t. const id get_id() const { return id(m_thread); } public: /// Blocks the current thread until the thread identified by *this finishes its execution. /// If that thread has already terminated, then join() returns immediately. /// /// @throws std::system_error if an error occurs void join(); public: // Internal /// Calls pthread_create, throws exception on failure. /// @throw CThreadException void create(void *(*start_routine) (void *), void *arg); private: pthread_t m_thread; }; template inline Stream& operator<<(Stream& str, const CThread::id& cid) { #if defined(_WIN32) && (defined(PTW32_VERSION) || defined (__PTW32_VERSION)) // This is a version specific for pthread-win32 implementation // Here pthread_t type is a structure that is not convertible // to a number at all. return str << pthread_getw32threadid_np(cid.value); #else return str << cid.value; #endif } namespace this_thread { const inline CThread::id get_id() { return CThread::id (pthread_self()); } inline void sleep_for(const steady_clock::duration& t) { #if !defined(_WIN32) usleep(count_microseconds(t)); // microseconds #else Sleep((DWORD) count_milliseconds(t)); #endif } } #endif /// StartThread function should be used to do CThread assignments: /// @code /// CThread a(); /// a = CThread(func, args); /// @endcode /// /// @returns true if thread was started successfully, /// false on failure /// #ifdef ENABLE_STDCXX_SYNC typedef void* (&ThreadFunc) (void*); bool StartThread(CThread& th, ThreadFunc&& f, void* args, const std::string& name); #else bool StartThread(CThread& th, void* (*f) (void*), void* args, const std::string& name); #endif //////////////////////////////////////////////////////////////////////////////// // // CThreadError class - thread local storage wrapper // //////////////////////////////////////////////////////////////////////////////// /// Set thread local error /// @param e new CUDTException void SetThreadLocalError(const CUDTException& e); /// Get thread local error /// @returns CUDTException pointer CUDTException& GetThreadLocalError(); //////////////////////////////////////////////////////////////////////////////// // // Random distribution functions. // //////////////////////////////////////////////////////////////////////////////// /// Generate a uniform-distributed random integer from [minVal; maxVal]. /// If HAVE_CXX11, uses std::uniform_distribution(std::random_device). /// @param[in] minVal minimum allowed value of the resulting random number. /// @param[in] maxVal maximum allowed value of the resulting random number. int genRandomInt(int minVal, int maxVal); } // namespace sync } // namespace srt #include "atomic_clock.h" #endif // INC_SRT_SYNC_H srt-1.4.4/srtcore/sync_cxx11.cpp000066400000000000000000000050311412557703600165260ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2020 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include "platform_sys.h" #include #include #include #include "sync.h" #include "srt_compat.h" #include "common.h" //////////////////////////////////////////////////////////////////////////////// // // Clock frequency helpers // //////////////////////////////////////////////////////////////////////////////// namespace { template int pow10(); template <> int pow10<10>() { return 1; } template int pow10() { return 1 + pow10(); } } int srt::sync::clockSubsecondPrecision() { const int64_t ticks_per_sec = (srt::sync::steady_clock::period::den / srt::sync::steady_clock::period::num); const int decimals = pow10(); return decimals; } //////////////////////////////////////////////////////////////////////////////// // // SyncCond (based on stl chrono C++11) // //////////////////////////////////////////////////////////////////////////////// srt::sync::Condition::Condition() {} srt::sync::Condition::~Condition() {} void srt::sync::Condition::init() {} void srt::sync::Condition::destroy() {} void srt::sync::Condition::wait(UniqueLock& lock) { m_cv.wait(lock); } bool srt::sync::Condition::wait_for(UniqueLock& lock, const steady_clock::duration& rel_time) { // Another possible implementation is wait_until(steady_clock::now() + timeout); return m_cv.wait_for(lock, rel_time) != std::cv_status::timeout; } bool srt::sync::Condition::wait_until(UniqueLock& lock, const steady_clock::time_point& timeout_time) { return m_cv.wait_until(lock, timeout_time) != std::cv_status::timeout; } void srt::sync::Condition::notify_one() { m_cv.notify_one(); } void srt::sync::Condition::notify_all() { m_cv.notify_all(); } //////////////////////////////////////////////////////////////////////////////// // // CThreadError class - thread local storage error wrapper // //////////////////////////////////////////////////////////////////////////////// // Threal local error will be used by CUDTUnited // with a static scope, therefore static thread_local static thread_local CUDTException s_thErr; void srt::sync::SetThreadLocalError(const CUDTException& e) { s_thErr = e; } CUDTException& srt::sync::GetThreadLocalError() { return s_thErr; } srt-1.4.4/srtcore/sync_posix.cpp000066400000000000000000000367331412557703600167410ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include "platform_sys.h" #include #include #include #include "sync.h" #include "utilities.h" #include "udt.h" #include "srt.h" #include "srt_compat.h" #include "logging.h" #include "common.h" #if defined(_WIN32) #include "win/wintime.h" #include #elif TARGET_OS_MAC #include #endif namespace srt_logging { extern Logger inlog; } using namespace srt_logging; namespace srt { namespace sync { static void rdtsc(uint64_t& x) { #if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_IA32_RDTSC 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 SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_IA64_ITC asm("mov %0=ar.itc" : "=r"(x)::"memory"); #elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_AMD64_RDTSC uint32_t lval, hval; asm("rdtsc" : "=a"(lval), "=d"(hval)); x = hval; x = (x << 32) | lval; #elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_WINQPC // This function should not fail, because we checked the QPC // when calling to QueryPerformanceFrequency. If it failed, // the m_bUseMicroSecond was set to true. QueryPerformanceCounter((LARGE_INTEGER*)&x); #elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_MACH_ABSTIME x = mach_absolute_time(); #elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC // get_cpu_frequency() returns 1 us accuracy in this case timespec tm; clock_gettime(CLOCK_MONOTONIC, &tm); x = tm.tv_sec * uint64_t(1000000) + (tm.tv_nsec / 1000); #elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_POSIX_GETTIMEOFDAY // use system call to read time clock for other archs timeval t; gettimeofday(&t, 0); x = t.tv_sec * uint64_t(1000000) + t.tv_usec; #else #error Wrong SRT_SYNC_CLOCK #endif } static int64_t get_cpu_frequency() { int64_t frequency = 1; // 1 tick per microsecond. #if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_WINQPC LARGE_INTEGER ccf; // in counts per second if (QueryPerformanceFrequency(&ccf)) { frequency = ccf.QuadPart / 1000000; // counts per microsecond if (frequency == 0) { LOGC(inlog.Warn, log << "Win QPC frequency of " << ccf.QuadPart << " counts/s is below the required 1 us accuracy. Please consider using C++11 timing (-DENABLE_STDCXX_SYNC=ON) instead."); frequency = 1; // set back to 1 to avoid division by zero. } } else { // Can't throw an exception, it won't be handled. LOGC(inlog.Error, log << "IPE: QueryPerformanceFrequency failed with " << GetLastError()); } #elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_MACH_ABSTIME mach_timebase_info_data_t info; mach_timebase_info(&info); frequency = info.denom * int64_t(1000) / info.numer; #elif SRT_SYNC_CLOCK >= SRT_SYNC_CLOCK_AMD64_RDTSC && SRT_SYNC_CLOCK <= SRT_SYNC_CLOCK_IA64_ITC // SRT_SYNC_CLOCK_AMD64_RDTSC or SRT_SYNC_CLOCK_IA32_RDTSC or SRT_SYNC_CLOCK_IA64_ITC 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 = int64_t(t2 - t1) / 100000; #endif return frequency; } static int count_subsecond_precision(int64_t ticks_per_us) { int signs = 6; // starting from 1 us while (ticks_per_us /= 10) ++signs; return signs; } const int64_t s_clock_ticks_per_us = get_cpu_frequency(); const int s_clock_subsecond_precision = count_subsecond_precision(s_clock_ticks_per_us); int clockSubsecondPrecision() { return s_clock_subsecond_precision; } } // namespace sync } // namespace srt //////////////////////////////////////////////////////////////////////////////// // // Sync utilities section // //////////////////////////////////////////////////////////////////////////////// static timespec us_to_timespec(const uint64_t time_us) { timespec timeout; timeout.tv_sec = time_us / 1000000; timeout.tv_nsec = (time_us % 1000000) * 1000; return timeout; } //////////////////////////////////////////////////////////////////////////////// // // TimePoint section // //////////////////////////////////////////////////////////////////////////////// template <> srt::sync::Duration srt::sync::TimePoint::time_since_epoch() const { return srt::sync::Duration(m_timestamp); } srt::sync::TimePoint srt::sync::steady_clock::now() { uint64_t x = 0; rdtsc(x); return TimePoint(x); } int64_t srt::sync::count_microseconds(const steady_clock::duration& t) { return t.count() / s_clock_ticks_per_us; } int64_t srt::sync::count_milliseconds(const steady_clock::duration& t) { return t.count() / s_clock_ticks_per_us / 1000; } int64_t srt::sync::count_seconds(const steady_clock::duration& t) { return t.count() / s_clock_ticks_per_us / 1000000; } srt::sync::steady_clock::duration srt::sync::microseconds_from(int64_t t_us) { return steady_clock::duration(t_us * s_clock_ticks_per_us); } srt::sync::steady_clock::duration srt::sync::milliseconds_from(int64_t t_ms) { return steady_clock::duration((1000 * t_ms) * s_clock_ticks_per_us); } srt::sync::steady_clock::duration srt::sync::seconds_from(int64_t t_s) { return steady_clock::duration((1000000 * t_s) * s_clock_ticks_per_us); } srt::sync::Mutex::Mutex() { const int err = pthread_mutex_init(&m_mutex, 0); if (err) { throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } } srt::sync::Mutex::~Mutex() { pthread_mutex_destroy(&m_mutex); } int srt::sync::Mutex::lock() { return pthread_mutex_lock(&m_mutex); } int srt::sync::Mutex::unlock() { return pthread_mutex_unlock(&m_mutex); } bool srt::sync::Mutex::try_lock() { return (pthread_mutex_trylock(&m_mutex) == 0); } srt::sync::ScopedLock::ScopedLock(Mutex& m) : m_mutex(m) { m_mutex.lock(); } srt::sync::ScopedLock::~ScopedLock() { m_mutex.unlock(); } srt::sync::UniqueLock::UniqueLock(Mutex& m) : m_Mutex(m) { m_iLocked = m_Mutex.lock(); } srt::sync::UniqueLock::~UniqueLock() { if (m_iLocked == 0) { unlock(); } } void srt::sync::UniqueLock::lock() { if (m_iLocked != -1) throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0); m_iLocked = m_Mutex.lock(); } void srt::sync::UniqueLock::unlock() { if (m_iLocked != 0) throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0); m_Mutex.unlock(); m_iLocked = -1; } srt::sync::Mutex* srt::sync::UniqueLock::mutex() { return &m_Mutex; } //////////////////////////////////////////////////////////////////////////////// // // Condition section (based on pthreads) // //////////////////////////////////////////////////////////////////////////////// namespace srt { namespace sync { Condition::Condition() #ifdef _WIN32 : m_cv(PTHREAD_COND_INITIALIZER) #endif {} Condition::~Condition() {} void Condition::init() { pthread_condattr_t* attr = NULL; #if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC pthread_condattr_t CondAttribs; pthread_condattr_init(&CondAttribs); pthread_condattr_setclock(&CondAttribs, CLOCK_MONOTONIC); attr = &CondAttribs; #endif const int res = pthread_cond_init(&m_cv, attr); if (res != 0) throw std::runtime_error("pthread_cond_init monotonic failed"); } void Condition::destroy() { pthread_cond_destroy(&m_cv); } void Condition::wait(UniqueLock& lock) { pthread_cond_wait(&m_cv, &lock.mutex()->ref()); } bool Condition::wait_for(UniqueLock& lock, const steady_clock::duration& rel_time) { timespec timeout; #if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &timeout); const uint64_t now_us = timeout.tv_sec * uint64_t(1000000) + (timeout.tv_nsec / 1000); #else timeval now; gettimeofday(&now, 0); const uint64_t now_us = now.tv_sec * uint64_t(1000000) + now.tv_usec; #endif timeout = us_to_timespec(now_us + count_microseconds(rel_time)); return pthread_cond_timedwait(&m_cv, &lock.mutex()->ref(), &timeout) != ETIMEDOUT; } bool Condition::wait_until(UniqueLock& lock, const steady_clock::time_point& timeout_time) { // This will work regardless as to which clock is in use. The time // should be specified as steady_clock::time_point, so there's no // question of the timer base. const steady_clock::time_point now = steady_clock::now(); if (now >= timeout_time) return false; // timeout // wait_for() is used because it will be converted to pthread-frienly timeout_time inside. return wait_for(lock, timeout_time - now); } void Condition::notify_one() { pthread_cond_signal(&m_cv); } void Condition::notify_all() { pthread_cond_broadcast(&m_cv); } }; // namespace sync }; // namespace srt //////////////////////////////////////////////////////////////////////////////// // // CThread class // //////////////////////////////////////////////////////////////////////////////// srt::sync::CThread::CThread() { m_thread = pthread_t(); } srt::sync::CThread::CThread(void *(*start_routine) (void *), void *arg) { create(start_routine, arg); } #if HAVE_FULL_CXX11 srt::sync::CThread& srt::sync::CThread::operator=(CThread&& other) #else srt::sync::CThread& srt::sync::CThread::operator=(CThread& other) #endif { if (joinable()) { // If the thread has already terminated, then // pthread_join() returns immediately. // But we have to check it has terminated before replacing it. LOGC(inlog.Error, log << "IPE: Assigning to a thread that is not terminated!"); #ifndef DEBUG #ifndef __ANDROID__ // In case of production build the hanging thread should be terminated // to avoid hang ups and align with C++11 implementation. // There is no pthread_cancel on Android. See #1476. This error should not normally // happen, but if it happen, then detaching the thread. pthread_cancel(m_thread); #endif // __ANDROID__ #else join(); #endif } // Move thread handler from other m_thread = other.m_thread; other.m_thread = pthread_t(); return *this; } #if !HAVE_FULL_CXX11 void srt::sync::CThread::create_thread(void *(*start_routine) (void *), void *arg) { SRT_ASSERT(!joinable()); create(start_routine, arg); } #endif bool srt::sync::CThread::joinable() const { return !pthread_equal(m_thread, pthread_t()); } void srt::sync::CThread::join() { void *retval; const int ret SRT_ATR_UNUSED = pthread_join(m_thread, &retval); if (ret != 0) { LOGC(inlog.Error, log << "pthread_join failed with " << ret); } #ifdef HEAVY_LOGGING else { HLOGC(inlog.Debug, log << "pthread_join SUCCEEDED"); } #endif // After joining, joinable should be false m_thread = pthread_t(); return; } void srt::sync::CThread::create(void *(*start_routine) (void *), void *arg) { const int st = pthread_create(&m_thread, NULL, start_routine, arg); if (st != 0) { LOGC(inlog.Error, log << "pthread_create failed with " << st); throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0); } } //////////////////////////////////////////////////////////////////////////////// // // CThreadError class - thread local storage error wrapper // //////////////////////////////////////////////////////////////////////////////// namespace srt { namespace sync { class CThreadError { public: CThreadError() { pthread_key_create(&m_ThreadSpecKey, ThreadSpecKeyDestroy); // This is a global object and as such it should be called in the // main application thread or at worst in the thread that has first // run `srt_startup()` function and so requested the SRT library to // be dynamically linked. Most probably in this very thread the API // errors will be reported, so preallocate the ThreadLocalSpecific // object for this error description. // This allows std::bac_alloc to crash the program during // the initialization of the SRT library (likely it would be // during the DL constructor, still way before any chance of // doing any operations here). This will prevent SRT from running // into trouble while trying to operate. CUDTException* ne = new CUDTException(); pthread_setspecific(m_ThreadSpecKey, ne); } ~CThreadError() { // Likely all objects should be deleted in all // threads that have exited, but std::this_thread didn't exit // yet :). ThreadSpecKeyDestroy(pthread_getspecific(m_ThreadSpecKey)); pthread_key_delete(m_ThreadSpecKey); } void set(const CUDTException& e) { CUDTException* cur = get(); // If this returns NULL, it means that there was an unexpected // memory allocation error. Simply ignore this request if so // happened, and then when trying to get the error description // the application will always get the memory allocation error. // There's no point in doing anything else here; lack of memory // must be prepared for prematurely, and that was already done. if (!cur) return; *cur = e; } /*[[nullable]]*/ CUDTException* get() { if (!pthread_getspecific(m_ThreadSpecKey)) { // This time if this can't be done due to memory allocation // problems, just allow this value to be NULL, which during // getting the error description will redirect to a memory // allocation error. // It would be nice to somehow ensure that this object is // created in every thread of the application using SRT, but // POSIX thread API doesn't contain any possibility to have // a creation callback that would apply to every thread in // the application (as it is for C++11 thread_local storage). CUDTException* ne = new(std::nothrow) CUDTException(); pthread_setspecific(m_ThreadSpecKey, ne); return ne; } return (CUDTException*)pthread_getspecific(m_ThreadSpecKey); } static void ThreadSpecKeyDestroy(void* e) { delete (CUDTException*)e; } private: pthread_key_t m_ThreadSpecKey; }; // Threal local error will be used by CUDTUnited // that has a static scope // This static makes this object file-private access so that // the access is granted only for the accessor functions. static CThreadError s_thErr; void SetThreadLocalError(const CUDTException& e) { s_thErr.set(e); } CUDTException& GetThreadLocalError() { // In POSIX version we take into account the possibility // of having an allocation error here. Therefore we need to // allow thie value to return NULL and have some fallback // for that case. The dynamic memory allocation failure should // be the only case as to why it is unable to get the pointer // to the error description. static CUDTException resident_alloc_error (MJ_SYSTEMRES, MN_MEMORY); CUDTException* curx = s_thErr.get(); if (!curx) return resident_alloc_error; return *curx; } } // namespace sync } // namespace srt srt-1.4.4/srtcore/threadname.h000066400000000000000000000147241412557703600163140ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_THREADNAME_H #define INC_SRT_THREADNAME_H // NOTE: // HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H // HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H // HAVE_PTHREAD_GETNAME_NP // HAVE_PTHREAD_GETNAME_NP // Are detected and set in ../CMakeLists.txt. // OS Availability of pthread_getname_np(..) and pthread_setname_np(..):: // MacOS(10.6) // iOS(3.2) // AIX(7.1) // FreeBSD(version?), OpenBSD(Version?) // Linux-GLIBC(GLIBC-2.12). // Linux-MUSL(MUSL-1.1.20 Partial Implementation. See below). // MINGW-W64(4.0.6) #if defined(HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) \ || defined(HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) #include #if defined(HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) \ && !defined(HAVE_PTHREAD_GETNAME_NP) #define HAVE_PTHREAD_GETNAME_NP 1 #endif #if defined(HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) \ && !defined(HAVE_PTHREAD_SETNAME_NP) #define HAVE_PTHREAD_SETNAME_NP 1 #endif #endif #if (defined(HAVE_PTHREAD_GETNAME_NP) && defined(HAVE_PTHREAD_GETNAME_NP)) \ || defined(__linux__) // NOTE: // Linux pthread_getname_np() and pthread_setname_np() became available // in GLIBC-2.12 and later. // Some Linux runtimes do not have pthread_getname_np(), but have // pthread_setname_np(), for instance MUSL at least as of v1.1.20. // So using the prctl() for Linux is more portable. #if defined(__linux__) #include #endif #include #endif #include #include #include #include "common.h" #include "sync.h" namespace srt { class ThreadName { #if (defined(HAVE_PTHREAD_GETNAME_NP) && defined(HAVE_PTHREAD_GETNAME_NP)) \ || defined(__linux__) class ThreadNameImpl { public: static const size_t BUFSIZE = 64; static const bool DUMMY_IMPL = false; static bool get(char* namebuf) { #if defined(__linux__) // since Linux 2.6.11. The buffer should allow space for up to 16 // bytes; the returned string will be null-terminated. return prctl(PR_GET_NAME, (unsigned long)namebuf, 0, 0) != -1; #elif defined(HAVE_PTHREAD_GETNAME_NP) return pthread_getname_np(pthread_self(), namebuf, BUFSIZE) == 0; #else #error "unsupported platform" #endif } static bool set(const char* name) { SRT_ASSERT(name != NULL); #if defined(__linux__) // The name can be up to 16 bytes long, including the terminating // null byte. (If the length of the string, including the terminating // null byte, exceeds 16 bytes, the string is silently truncated.) return prctl(PR_SET_NAME, (unsigned long)name, 0, 0) != -1; #elif defined(HAVE_PTHREAD_SETNAME_NP) #if defined(__APPLE__) return pthread_setname_np(name) == 0; #else return pthread_setname_np(pthread_self(), name) == 0; #endif #else #error "unsupported platform" #endif } explicit ThreadNameImpl(const std::string& name) : reset(false) { tid = pthread_self(); if (!get(old_name)) return; reset = set(name.c_str()); if (reset) return; // Try with a shorter name. 15 is the upper limit supported by Linux, // other platforms should support a larger value. So 15 should works // on all platforms. const size_t max_len = 15; if (name.size() > max_len) reset = set(name.substr(0, max_len).c_str()); } ~ThreadNameImpl() { if (!reset) return; // ensure it's called on the right thread if (tid == pthread_self()) set(old_name); } private: ThreadNameImpl(ThreadNameImpl& other); ThreadNameImpl& operator=(const ThreadNameImpl& other); private: bool reset; pthread_t tid; char old_name[BUFSIZE]; }; #else class ThreadNameImpl { public: static const bool DUMMY_IMPL = true; static const size_t BUFSIZE = 64; static bool get(char* output) { // The default implementation will simply try to get the thread ID std::ostringstream bs; bs << "T" << srt::sync::this_thread::get_id(); size_t s = bs.str().copy(output, BUFSIZE - 1); output[s] = '\0'; return true; } static bool set(const char*) { return false; } ThreadNameImpl(const std::string&) {} ~ThreadNameImpl() // just to make it "non-trivially-destructible" for compatibility with normal version { } }; #endif // platform dependent impl // Why delegate to impl: // 1. to make sure implementation on different platforms have the same interface. // 2. it's simple to add some wrappers like get(const std::string &). ThreadNameImpl impl; public: static const bool DUMMY_IMPL = ThreadNameImpl::DUMMY_IMPL; static const size_t BUFSIZE = ThreadNameImpl::BUFSIZE; /// @brief Print thread ID to the provided buffer. /// The size of the destination buffer is assumed to be at least ThreadName::BUFSIZE. /// @param [out] output destination buffer to get thread name /// @return true on success, false on failure static bool get(char* output) { return ThreadNameImpl::get(output); } static bool get(std::string& name) { char buf[BUFSIZE]; bool ret = get(buf); if (ret) name = buf; return ret; } static bool set(const std::string& name) { return ThreadNameImpl::set(name.c_str()); } explicit ThreadName(const std::string& name) : impl(name) { } private: ThreadName(const ThreadName&); ThreadName(const char*); ThreadName& operator=(const ThreadName& other); }; } // namespace srt #endif srt-1.4.4/srtcore/tsbpd_time.cpp000066400000000000000000000235451412557703600166720ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2021 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include "tsbpd_time.h" #include "logging.h" #include "logger_defs.h" #include "packet.h" using namespace srt_logging; using namespace srt::sync; namespace srt { #if SRT_DEBUG_TRACE_DRIFT class drift_logger { typedef srt::sync::steady_clock steady_clock; public: drift_logger() {} ~drift_logger() { ScopedLock lck(m_mtx); m_fout.close(); } void trace(unsigned ackack_timestamp, int rtt_us, int64_t drift_sample, int64_t drift, int64_t overdrift, const srt::sync::steady_clock::time_point& pkt_base, const srt::sync::steady_clock::time_point& tsbpd_base) { using namespace srt::sync; ScopedLock lck(m_mtx); create_file(); // std::string str_tnow = srt::sync::FormatTime(steady_clock::now()); // str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [STDY]' part std::string str_tbase = srt::sync::FormatTime(tsbpd_base); str_tbase.resize(str_tbase.size() - 7); // remove trailing ' [STDY]' part std::string str_pkt_base = srt::sync::FormatTime(pkt_base); str_pkt_base.resize(str_pkt_base.size() - 7); // remove trailing ' [STDY]' part // m_fout << str_tnow << ","; m_fout << count_microseconds(steady_clock::now() - m_start_time) << ","; m_fout << ackack_timestamp << ","; m_fout << rtt_us << ","; m_fout << drift_sample << ","; m_fout << drift << ","; m_fout << overdrift << ","; m_fout << str_pkt_base << ","; m_fout << str_tbase << "\n"; m_fout.flush(); } private: void print_header() { m_fout << "usElapsedStd,usAckAckTimestampStd,"; m_fout << "usRTTStd,usDriftSampleStd,usDriftStd,usOverdriftStd,tsPktBase,TSBPDBase\n"; } void create_file() { if (m_fout.is_open()) return; m_start_time = srt::sync::steady_clock::now(); std::string str_tnow = srt::sync::FormatTimeSys(m_start_time); str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part while (str_tnow.find(':') != std::string::npos) { str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); } const std::string fname = "drift_trace_" + str_tnow + ".csv"; m_fout.open(fname, std::ofstream::out); if (!m_fout) std::cerr << "IPE: Failed to open " << fname << "!!!\n"; print_header(); } private: srt::sync::Mutex m_mtx; std::ofstream m_fout; srt::sync::steady_clock::time_point m_start_time; }; drift_logger g_drift_logger; #endif // SRT_DEBUG_TRACE_DRIFT bool CTsbpdTime::addDriftSample(uint32_t usPktTimestamp, int usRTTSample) { if (!m_bTsbPdMode) return false; const time_point tsNow = steady_clock::now(); ScopedLock lck(m_mtxRW); // Remember the first RTT sample measured. Ideally we need RTT0 - the one from the handshaking phase, // because TSBPD base is initialized there. But HS-based RTT is not yet implemented. // Take the first one assuming it is close to RTT0. if (m_iFirstRTT == -1) { m_iFirstRTT = usRTTSample; } // A change in network delay has to be taken into account. The only way to get some estimation of it // is to estimate RTT change and assume that the change of the one way network delay is // approximated by the half of the RTT change. const duration tdRTTDelta = microseconds_from((usRTTSample - m_iFirstRTT) / 2); const time_point tsPktBaseTime = getPktTsbPdBaseTime(usPktTimestamp); const steady_clock::duration tdDrift = tsNow - tsPktBaseTime - tdRTTDelta; const bool updated = m_DriftTracer.update(count_microseconds(tdDrift)); if (updated) { IF_HEAVY_LOGGING(const steady_clock::time_point oldbase = m_tsTsbPdTimeBase); steady_clock::duration overdrift = microseconds_from(m_DriftTracer.overdrift()); m_tsTsbPdTimeBase += overdrift; HLOGC(brlog.Debug, log << "DRIFT=" << FormatDuration(tdDrift) << " AVG=" << (m_DriftTracer.drift() / 1000.0) << "ms, TB: " << FormatTime(oldbase) << " EXCESS: " << FormatDuration(overdrift) << " UPDATED TO: " << FormatTime(m_tsTsbPdTimeBase)); } else { HLOGC(brlog.Debug, log << "DRIFT=" << FormatDuration(tdDrift) << " TB REMAINS: " << FormatTime(m_tsTsbPdTimeBase)); } #if SRT_DEBUG_TRACE_DRIFT g_drift_logger.trace(usPktTimestamp, usRTTSample, count_microseconds(tdDrift), m_DriftTracer.drift(), m_DriftTracer.overdrift(), tsPktBaseTime, m_tsTsbPdTimeBase); #endif return updated; } void CTsbpdTime::setTsbPdMode(const steady_clock::time_point& timebase, bool wrap, duration delay) { m_bTsbPdMode = true; m_bTsbPdWrapCheck = wrap; // Timebase passed here comes is calculated as: // Tnow - hspkt.m_iTimeStamp // where hspkt is the packet with SRT_CMD_HSREQ message. // // This function is called in the HSREQ reception handler only. m_tsTsbPdTimeBase = timebase; m_tdTsbPdDelay = delay; } void CTsbpdTime::applyGroupTime(const steady_clock::time_point& timebase, bool wrp, uint32_t delay, const steady_clock::duration& udrift) { // Same as setTsbPdMode, but predicted to be used for group members. // This synchronizes the time from the INTERNAL TIMEBASE of an existing // socket's internal timebase. This is required because the initial time // base stays always the same, whereas the internal timebase undergoes // adjustment as the 32-bit timestamps in the sockets wrap. The socket // newly added to the group must get EXACTLY the same internal timebase // or otherwise the TsbPd time calculation will ship different results // on different member sockets. m_bTsbPdMode = true; m_tsTsbPdTimeBase = timebase; m_bTsbPdWrapCheck = wrp; m_tdTsbPdDelay = microseconds_from(delay); m_DriftTracer.forceDrift(count_microseconds(udrift)); } void CTsbpdTime::applyGroupDrift(const steady_clock::time_point& timebase, bool wrp, const steady_clock::duration& udrift) { // This is only when a drift was updated on one of the group members. HLOGC(brlog.Debug, log << "rcv-buffer: group synch uDRIFT: " << m_DriftTracer.drift() << " -> " << FormatDuration(udrift) << " TB: " << FormatTime(m_tsTsbPdTimeBase) << " -> " << FormatTime(timebase)); m_tsTsbPdTimeBase = timebase; m_bTsbPdWrapCheck = wrp; m_DriftTracer.forceDrift(count_microseconds(udrift)); } CTsbpdTime::time_point CTsbpdTime::getTsbPdTimeBase(uint32_t timestamp_us) const { // A data packet within [TSBPD_WRAP_PERIOD; 2 * TSBPD_WRAP_PERIOD] would end TSBPD wrap-aware state. // Some incoming control packets may not update the TSBPD base (calling updateTsbPdTimeBase(..)), // but may come before a data packet with a timestamp in this range. Therefore the whole range should be tracked. const int64_t carryover_us = (m_bTsbPdWrapCheck && timestamp_us <= 2 * TSBPD_WRAP_PERIOD) ? int64_t(CPacket::MAX_TIMESTAMP) + 1 : 0; return (m_tsTsbPdTimeBase + microseconds_from(carryover_us)); } CTsbpdTime::time_point CTsbpdTime::getPktTsbPdTime(uint32_t usPktTimestamp) const { return getPktTsbPdBaseTime(usPktTimestamp) + m_tdTsbPdDelay + microseconds_from(m_DriftTracer.drift()); } CTsbpdTime::time_point CTsbpdTime::getPktTsbPdBaseTime(uint32_t usPktTimestamp) const { return getTsbPdTimeBase(usPktTimestamp) + microseconds_from(usPktTimestamp); } void CTsbpdTime::updateTsbPdTimeBase(uint32_t usPktTimestamp) { if (m_bTsbPdWrapCheck) { // Wrap check period. if ((usPktTimestamp >= TSBPD_WRAP_PERIOD) && (usPktTimestamp <= (TSBPD_WRAP_PERIOD * 2))) { /* Exiting wrap check period (if for packet delivery head) */ m_bTsbPdWrapCheck = false; m_tsTsbPdTimeBase += microseconds_from(int64_t(CPacket::MAX_TIMESTAMP) + 1); LOGC(tslog.Debug, log << "tsbpd wrap period ends with ts=" << usPktTimestamp << " - NEW TIME BASE: " << FormatTime(m_tsTsbPdTimeBase) << " drift: " << m_DriftTracer.drift() << "us"); } return; } // Check if timestamp is within the TSBPD_WRAP_PERIOD before reaching the MAX_TIMESTAMP. if (usPktTimestamp > (CPacket::MAX_TIMESTAMP - TSBPD_WRAP_PERIOD)) { // Approching wrap around point, start wrap check period (if for packet delivery head) m_bTsbPdWrapCheck = true; LOGC(tslog.Debug, log << "tsbpd wrap period begins with ts=" << usPktTimestamp << " TIME BASE: " << FormatTime(m_tsTsbPdTimeBase) << " drift: " << m_DriftTracer.drift() << "us."); } } void CTsbpdTime::getInternalTimeBase(time_point& w_tb, bool& w_wrp, duration& w_udrift) const { ScopedLock lck(m_mtxRW); w_tb = m_tsTsbPdTimeBase; w_udrift = microseconds_from(m_DriftTracer.drift()); w_wrp = m_bTsbPdWrapCheck; } } // namespace srt srt-1.4.4/srtcore/tsbpd_time.h000066400000000000000000000167001412557703600163320ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2021 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_TSBPD_TIME_H #define INC_SRT_TSBPD_TIME_H #include "platform_sys.h" #include "sync.h" #include "utilities.h" namespace srt { /// @brief TimeStamp-Based Packet Delivery Mode (TSBPD) time conversion logic. /// Used by the receiver to calculate delivery time of data packets. /// See SRT RFC Section "Timestamp-Based Packet Delivery". class CTsbpdTime { typedef srt::sync::steady_clock steady_clock; typedef steady_clock::time_point time_point; typedef steady_clock::duration duration; typedef srt::sync::Mutex Mutex; public: CTsbpdTime() : m_iFirstRTT(-1) , m_bTsbPdMode(false) , m_tdTsbPdDelay(0) , m_bTsbPdWrapCheck(false) { } /// Set TimeStamp-Based Packet Delivery Mode (receiver). /// @param [in] timebase local time base (uSec) of packet time stamps including buffering delay. /// @param [in] wrap wrapping period. /// @param [in] delay negotiated TsbPD delay (buffering latency). void setTsbPdMode(const time_point& timebase, bool wrap, duration delay); /// @brief Check if TSBPD logic is enabled. /// @return true if TSBPD is enabled. bool isEnabled() const { return m_bTsbPdMode; } /// @brief Apply new state derived from other members of a socket group. /// @param timebase TSBPD base time. /// @param wrp wrap period (enabled or not). /// @param delay TSBPD delay. /// @param udrift clock drift. void applyGroupTime(const time_point& timebase, bool wrp, uint32_t delay, const duration& udrift); /// @brief Apply new clock state (TSBPD base and drift) derived from other members of a socket group. /// @param timebase TSBPD base time. /// @param wrp state of the wrapping period (enabled or disabled). /// @param udrift clock drift. void applyGroupDrift(const time_point& timebase, bool wrp, const duration& udrift); /// @brief Add new drift sample from an ACK-ACKACK pair. /// ACKACK packets are sent immediately (except for UDP buffering). /// Therefore their timestamp roughly corresponds to the time of sending /// and can be used to estimate clock drift. /// /// @param [in] pktTimestamp Timestamp of the arrived ACKACK packet. /// @param [in] usRTTSample RTT sample from an ACK-ACKACK pair. /// /// @return true if TSBPD base time has changed, false otherwise. bool addDriftSample(uint32_t pktTimestamp, int usRTTSample); /// @brief Handle timestamp of data packet when 32-bit integer carryover is about to happen. /// When packet timestamp approaches CPacket::MAX_TIMESTAMP, the TSBPD base time should be /// shifted accordingly to correctly handle new packets with timestamps starting from zero. /// @param usPktTimestamp timestamp field value of a data packet. void updateTsbPdTimeBase(uint32_t usPktTimestamp); /// @brief Get TSBPD base time adjusted for carryover, which occurs when /// a packet's timestamp exceeds the UINT32_MAX and continues from zero. /// @param [in] usPktTimestamp 32-bit value of packet timestamp field (microseconds). /// /// @return TSBPD base time for a provided packet timestamp. time_point getTsbPdTimeBase(uint32_t usPktTimestamp) const; /// @brief Get packet TSBPD time without buffering delay and clock drift, which is /// the target time for delivering the packet to an upstream application. /// Essentially: getTsbPdTimeBase(usPktTimestamp) + usPktTimestamp /// @param [in] usPktTimestamp 32-bit value of packet timestamp field (microseconds). /// /// @return Packet TSBPD base time without buffering delay. time_point getPktTsbPdBaseTime(uint32_t usPktTimestamp) const; /// @brief Get packet TSBPD time with buffering delay and clock drift, which is /// the target time for delivering the packet to an upstream application /// (including drift and carryover effects, if any). /// Essentially: getPktTsbPdBaseTime(usPktTimestamp) + m_tdTsbPdDelay + drift() /// @param [in] usPktTimestamp 32-bit value of packet timestamp field (microseconds). /// /// @return Packet TSBPD time with buffering delay. time_point getPktTsbPdTime(uint32_t usPktTimestamp) const; /// @brief Get current drift value. /// @return current drift value. int64_t drift() const { return m_DriftTracer.drift(); } /// @brief Get current overdrift value. /// @return current overdrift value. int64_t overdrift() const { return m_DriftTracer.overdrift(); } /// @brief Get internal state to apply to another member of a socket group. /// @param w_tb TsbPd base time. /// @param w_udrift drift value. /// @param w_wrp wrap check. void getInternalTimeBase(time_point& w_tb, bool& w_wrp, duration& w_udrift) const; private: int m_iFirstRTT; // First measured RTT sample. bool m_bTsbPdMode; // Receiver buffering and TSBPD is active when true. duration m_tdTsbPdDelay; // Negotiated buffering delay. /// @brief Local time base for TsbPd. /// @note m_tsTsbPdTimeBase is changed in the following cases: /// 1. Initialized upon SRT_CMD_HSREQ packet as the difference with the current time: /// = (NOW - PACKET_TIMESTAMP), at the time of HSREQ reception. /// 2. Shifted forward on timestamp overflow (@see CTsbpdTime::updateTsbPdTimeBase), when overflow /// of the timestamp field value of a data packet is detected. /// += CPacket::MAX_TIMESTAMP + 1 /// 3. Clock drift (@see CTsbpdTime::addDriftSample, executed exclusively /// from ACKACK handler). This is updated with (positive or negative) TSBPD_DRIFT_MAX_VALUE /// once the value of average drift exceeds this value in either direction. /// += (+/-)TSBPD_DRIFT_MAX_VALUE /// /// @note The TSBPD base time is expected to hold the following condition: /// (PACKET_TIMESTAMP + m_tsTsbPdTimeBase + drift) == NOW. /// Then it can be used to estimate the origin time of a data packet, and calculate its delivery time /// with buffering delay applied. time_point m_tsTsbPdTimeBase; /// @note Packet timestamps wrap around every 01h11m35s (32-bit in usec). /// A wrap check period starts 30 seconds (TSBPD_WRAP_PERIOD) before the wrap point. /// During the wrap check period, packet timestamps smaller than 30 seconds /// are considered to have been wrapped around. /// The wrap check period ends 30 seconds after the wrap point, /// after which the TSBPD base time is adjusted. bool m_bTsbPdWrapCheck; // true: check packet time stamp wraparound (overflow). static const uint32_t TSBPD_WRAP_PERIOD = (30 * 1000000); // 30 seconds (in usec) for timestamp wrapping period. /// Maximum clock drift (microseconds) above which TsbPD base time is already adjusted. static const int TSBPD_DRIFT_MAX_VALUE = 5000; /// Number of samples (ACKACK packets) on which to perform drift calculation and compensation. static const int TSBPD_DRIFT_MAX_SAMPLES = 1000; DriftTracer m_DriftTracer; /// Protect simultaneous change of state (read/write). mutable Mutex m_mtxRW; }; } // namespace srt #endif // INC_SRT_TSBPD_TIME_H srt-1.4.4/srtcore/udt.h000066400000000000000000000277031412557703600150010ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 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 INC_SRT_UDT_H #define INC_SRT_UDT_H #include "srt.h" /* * SRT_ENABLE_THREADCHECK IS SET IN MAKEFILE, NOT HERE */ #if defined(SRT_ENABLE_THREADCHECK) #include "threadcheck.h" #else #define THREAD_STATE_INIT(name) #define THREAD_EXIT() #define THREAD_PAUSED() #define THREAD_RESUMED() #define INCREMENT_THREAD_ITERATIONS() #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 //////////////////////////////////////////////////////////////////////////////// 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 }; typedef SRTSOCKET UDTSOCKET; //legacy alias #ifdef __cplusplus class CUDTException; namespace UDT { typedef CUDTException ERRORINFO; typedef CPerfMon TRACEINFO; // This facility is used only for select() function. // This is considered obsolete and the epoll() functionality rather should be used. typedef std::set UDSET; #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()) SRT_API extern const SRTSOCKET INVALID_SOCK; #undef ERROR SRT_API extern const int ERROR; SRT_API int startup(); SRT_API int cleanup(); SRT_API SRTSOCKET socket(); inline SRTSOCKET socket(int , int , int ) { return socket(); } SRT_API int bind(SRTSOCKET u, const struct sockaddr* name, int namelen); SRT_API int bind2(SRTSOCKET u, UDPSOCKET udpsock); SRT_API int listen(SRTSOCKET u, int backlog); SRT_API SRTSOCKET accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen); SRT_API int connect(SRTSOCKET u, const struct sockaddr* name, int namelen); SRT_API int close(SRTSOCKET u); SRT_API int getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); SRT_API int getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); SRT_API int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); SRT_API int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); SRT_API int send(SRTSOCKET u, const char* buf, int len, int flags); SRT_API int recv(SRTSOCKET u, char* buf, int len, int flags); SRT_API int sendmsg(SRTSOCKET u, const char* buf, int len, int ttl = -1, bool inorder = false, int64_t srctime = 0); SRT_API int recvmsg(SRTSOCKET u, char* buf, int len, uint64_t& srctime); SRT_API int recvmsg(SRTSOCKET u, char* buf, int len); SRT_API int64_t sendfile(SRTSOCKET u, std::fstream& ifs, int64_t& offset, int64_t size, int block = 364000); SRT_API int64_t recvfile(SRTSOCKET u, std::fstream& ofs, int64_t& offset, int64_t size, int block = 7280000); SRT_API int64_t sendfile2(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block = 364000); SRT_API int64_t recvfile2(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block = 7280000); // select and selectEX are DEPRECATED; please use epoll. SRT_API int select(int nfds, UDSET* readfds, UDSET* writefds, UDSET* exceptfds, const struct timeval* timeout); SRT_API int selectEx(const std::vector& fds, std::vector* readfds, std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); SRT_API int epoll_create(); SRT_API int epoll_add_usock(int eid, SRTSOCKET u, const int* events = NULL); SRT_API int epoll_add_ssock(int eid, SYSSOCKET s, const int* events = NULL); SRT_API int epoll_remove_usock(int eid, SRTSOCKET u); SRT_API int epoll_remove_ssock(int eid, SYSSOCKET s); SRT_API int epoll_update_usock(int eid, SRTSOCKET u, const int* events = NULL); SRT_API int epoll_update_ssock(int eid, SYSSOCKET s, const int* events = NULL); SRT_API int epoll_wait(int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); SRT_API int epoll_wait2(int eid, SRTSOCKET* readfds, int* rnum, SRTSOCKET* writefds, int* wnum, int64_t msTimeOut, SYSSOCKET* lrfds = NULL, int* lrnum = NULL, SYSSOCKET* lwfds = NULL, int* lwnum = NULL); SRT_API int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); SRT_API int epoll_release(int eid); SRT_API ERRORINFO& getlasterror(); SRT_API int getlasterror_code(); SRT_API const char* getlasterror_desc(); SRT_API int bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear = true); SRT_API SRT_SOCKSTATUS getsockstate(SRTSOCKET u); } // namespace UDT // This is a log configuration used inside SRT. // Applications using SRT, if they want to use the logging mechanism // are free to create their own logger configuration objects for their // own logger FA objects, or create their own. The object of this type // is required to initialize the logger FA object. namespace srt_logging { struct LogConfig; } SRT_API extern srt_logging::LogConfig srt_logger_config; namespace srt { // This is a C++ SRT API extension. This is not a part of legacy UDT API. SRT_API void setloglevel(srt_logging::LogLevel::type ll); SRT_API void addlogfa(srt_logging::LogFA fa); SRT_API void dellogfa(srt_logging::LogFA fa); SRT_API void resetlogfa(std::set fas); SRT_API void resetlogfa(const int* fara, size_t fara_size); SRT_API void setlogstream(std::ostream& stream); SRT_API void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler); SRT_API void setlogflags(int flags); SRT_API bool setstreamid(SRTSOCKET u, const std::string& sid); SRT_API std::string getstreamid(SRTSOCKET u); // Namespace alias namespace logging { using namespace srt_logging; } } // namespace srt // Planned deprecated removal: rel1.6.0 // There's also no portable way possible to enforce a deprecation // compiler warning, so leaving as is. namespace UDT { // Backward-compatible aliases, just for a case someone was using it. using srt::setloglevel; using srt::addlogfa; using srt::dellogfa; using srt::resetlogfa; using srt::setlogstream; using srt::setloghandler; using srt::setlogflags; using srt::setstreamid; using srt::getstreamid; } #endif /* __cplusplus */ #endif srt-1.4.4/srtcore/utilities.h000066400000000000000000000754061412557703600162230ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_UTILITIES_H #define INC_SRT_UTILITIES_H // Windows warning disabler #define _CRT_SECURE_NO_WARNINGS 1 #include "platform_sys.h" #include "srt_attr_defs.h" // defines HAVE_CXX11 // Happens that these are defined, undefine them in advance #undef min #undef max #include #include #include #include #include #include #include #include #include #include #if HAVE_CXX11 #include #endif #include #include #include // -------------- UTILITIES ------------------------ // --- ENDIAN --- // Copied from: https://gist.github.com/panzi/6856583 // License: Public Domain. #if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__) # define __WINDOWS__ #endif #if defined(__linux__) || defined(__CYGWIN__) || defined(__GNU__) || defined(__GLIBC__) # include // GLIBC-2.8 and earlier does not provide these macros. // See http://linux.die.net/man/3/endian // From https://gist.github.com/panzi/6856583 # if defined(__GLIBC__) \ && ( !defined(__GLIBC_MINOR__) \ || ((__GLIBC__ < 2) \ || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 9))) ) # include # if defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN) # define htole32(x) (x) # define le32toh(x) (x) # elif defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN) # define htole16(x) ((((((uint16_t)(x)) >> 8))|((((uint16_t)(x)) << 8))) # define le16toh(x) ((((((uint16_t)(x)) >> 8))|((((uint16_t)(x)) << 8))) # define htole32(x) (((uint32_t)htole16(((uint16_t)(((uint32_t)(x)) >> 16)))) | (((uint32_t)htole16(((uint16_t)(x)))) << 16)) # define le32toh(x) (((uint32_t)le16toh(((uint16_t)(((uint32_t)(x)) >> 16)))) | (((uint32_t)le16toh(((uint16_t)(x)))) << 16)) # else # error Byte Order not supported or not defined. # endif # endif #elif defined(__APPLE__) # include # define htobe16(x) OSSwapHostToBigInt16(x) # define htole16(x) OSSwapHostToLittleInt16(x) # define be16toh(x) OSSwapBigToHostInt16(x) # define le16toh(x) OSSwapLittleToHostInt16(x) # define htobe32(x) OSSwapHostToBigInt32(x) # define htole32(x) OSSwapHostToLittleInt32(x) # define be32toh(x) OSSwapBigToHostInt32(x) # define le32toh(x) OSSwapLittleToHostInt32(x) # define htobe64(x) OSSwapHostToBigInt64(x) # define htole64(x) OSSwapHostToLittleInt64(x) # define be64toh(x) OSSwapBigToHostInt64(x) # define le64toh(x) OSSwapLittleToHostInt64(x) # define __BYTE_ORDER BYTE_ORDER # define __BIG_ENDIAN BIG_ENDIAN # define __LITTLE_ENDIAN LITTLE_ENDIAN # define __PDP_ENDIAN PDP_ENDIAN #elif defined(__OpenBSD__) # include #elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) # include #ifndef be16toh # define be16toh(x) betoh16(x) #endif #ifndef le16toh # define le16toh(x) letoh16(x) #endif #ifndef be32toh # define be32toh(x) betoh32(x) #endif #ifndef le32toh # define le32toh(x) letoh32(x) #endif #ifndef be64toh # define be64toh(x) betoh64(x) #endif #ifndef le64toh # define le64toh(x) letoh64(x) #endif #elif defined(SUNOS) // SunOS/Solaris #include #include #define __LITTLE_ENDIAN 1234 #define __BIG_ENDIAN 4321 # if defined(_BIG_ENDIAN) #define __BYTE_ORDER __BIG_ENDIAN #define be64toh(x) (x) #define be32toh(x) (x) #define be16toh(x) (x) #define le16toh(x) ((uint16_t)BSWAP_16(x)) #define le32toh(x) BSWAP_32(x) #define le64toh(x) BSWAP_64(x) #define htobe16(x) (x) #define htole16(x) ((uint16_t)BSWAP_16(x)) #define htobe32(x) (x) #define htole32(x) BSWAP_32(x) #define htobe64(x) (x) #define htole64(x) BSWAP_64(x) # else #define __BYTE_ORDER __LITTLE_ENDIAN #define be64toh(x) BSWAP_64(x) #define be32toh(x) ntohl(x) #define be16toh(x) ntohs(x) #define le16toh(x) (x) #define le32toh(x) (x) #define le64toh(x) (x) #define htobe16(x) htons(x) #define htole16(x) (x) #define htobe32(x) htonl(x) #define htole32(x) (x) #define htobe64(x) BSWAP_64(x) #define htole64(x) (x) # endif #elif defined(__WINDOWS__) # include # if BYTE_ORDER == LITTLE_ENDIAN # define htobe16(x) htons(x) # define htole16(x) (x) # define be16toh(x) ntohs(x) # define le16toh(x) (x) # define htobe32(x) htonl(x) # define htole32(x) (x) # define be32toh(x) ntohl(x) # define le32toh(x) (x) # define htobe64(x) htonll(x) # define htole64(x) (x) # define be64toh(x) ntohll(x) # define le64toh(x) (x) # elif BYTE_ORDER == BIG_ENDIAN /* that would be xbox 360 */ # define htobe16(x) (x) # define htole16(x) __builtin_bswap16(x) # define be16toh(x) (x) # define le16toh(x) __builtin_bswap16(x) # define htobe32(x) (x) # define htole32(x) __builtin_bswap32(x) # define be32toh(x) (x) # define le32toh(x) __builtin_bswap32(x) # define htobe64(x) (x) # define htole64(x) __builtin_bswap64(x) # define be64toh(x) (x) # define le64toh(x) __builtin_bswap64(x) # else # error byte order not supported # endif # define __BYTE_ORDER BYTE_ORDER # define __BIG_ENDIAN BIG_ENDIAN # define __LITTLE_ENDIAN LITTLE_ENDIAN # define __PDP_ENDIAN PDP_ENDIAN #else # error Endian: platform not supported #endif // Hardware <--> Network (big endian) convention inline void HtoNLA(uint32_t* dst, const uint32_t* src, size_t size) { for (size_t i = 0; i < size; ++ i) dst[i] = htonl(src[i]); } inline void NtoHLA(uint32_t* dst, const uint32_t* src, size_t size) { for (size_t i = 0; i < size; ++ i) dst[i] = ntohl(src[i]); } // Hardware <--> Intel (little endian) convention inline void HtoILA(uint32_t* dst, const uint32_t* src, size_t size) { for (size_t i = 0; i < size; ++ i) dst[i] = htole32(src[i]); } inline void ItoHLA(uint32_t* dst, const uint32_t* src, size_t size) { for (size_t i = 0; i < size; ++ i) dst[i] = le32toh(src[i]); } // Bit numbering utility. // // This is something that allows you to turn 32-bit integers into bit fields. // Although bitfields are part of C++ language, they are not designed to be // interchanged with 32-bit numbers, and any attempt to doing it (by placing // inside a union, for example) is nonportable (order of bitfields inside // same-covering 32-bit integer number is dependent on the endian), so they are // popularly disregarded as useless. Instead the 32-bit numbers with bits // individually selected is preferred, with usually manual playing around with // & and | operators, as well as << and >>. This tool is designed to simplify // the use of them. This can be used to qualify a range of bits inside a 32-bit // number to be a separate number, you can "wrap" it by placing the integer // value in the range of these bits, as well as "unwrap" (extract) it from // the given place. For your own safety, use one prefix to all constants that // concern bit ranges intended to be inside the same "bit container". // // Usage: typedef Bits MASKTYPE; // MASKTYPE is a name of your choice. // // With this defined, you can use the following members: // - MASKTYPE::mask - to get the int32_t value with bimask (used bits set to 1, others to 0) // - MASKTYPE::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 a 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 static T unwrapt(uint32_t bitset) { return static_cast(unwrap(bitset)); } }; //inline int32_t Bit(size_t b) { return 1 << b; } // XXX This would work only with 'constexpr', but this is // available only in C++11. In C++03 this can be only done // using a macro. // // Actually this can be expressed in C++11 using a better technique, // such as user-defined literals: // 2_bit --> 1 >> 2 #ifdef BIT #undef BIT #endif #define BIT(x) (1 << (x)) // ------------------------------------------------------------ // This is something that reminds a structure consisting of fields // of the same type, implemented as an array. It's parametrized // by the type of fields and the type, which's values should be // used for indexing (preferably an enum type). Whatever type is // used for indexing, it is converted to size_t for indexing the // actual array. // // The user should use it as an array: ds[DS_NAME], stating // that DS_NAME is of enum type passed as 3rd parameter. // However trying to do ds[0] would cause a compile error. template struct DynamicStruct { FieldType inarray[NoOfFields]; 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 + NoOfFields, 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; } // std::addressof in C++11, // needs to be provided for C++03 template inline RefType* AddressOf(RefType& r) { return (RefType*)(&(unsigned char&)(r)); } template struct explicit_t { T inobject; explicit_t(const T& uo): inobject(uo) {} operator T() const { return inobject; } private: template explicit_t(const X& another); }; // This is required for Printable function if you have a container of pairs, // but this function has a different definition for C++11 and C++03. namespace srt_pair_op { template std::ostream& operator<<(std::ostream& s, const std::pair& v) { s << "{" << v.first << " " << v.second << "}"; return s; } } #if HAVE_CXX11 template inline auto Move(In& i) -> decltype(std::move(i)) { return std::move(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(); } // We need to use UniquePtr, in the form of C++03 it will be a #define. // Naturally will be used std::move() so that it can later painlessly // switch to C++11. template using UniquePtr = std::unique_ptr; template inline std::string Printable(const Container& in, Value /*pseudoargument*/, Args&&... args) { using namespace srt_pair_op; std::ostringstream os; Print(os, args...); os << "[ "; for (auto i: in) os << Value(i) << " "; os << "]"; return os.str(); } template inline std::string Printable(const Container& in) { using namespace srt_pair_op; using Value = typename Container::value_type; return Printable(in, Value()); } template auto map_get(Map& m, const Key& key, typename Map::mapped_type def = typename Map::mapped_type()) -> typename Map::mapped_type { auto it = m.find(key); return it == m.end() ? def : it->second; } template auto map_getp(Map& m, const Key& key) -> typename Map::mapped_type* { auto it = m.find(key); return it == m.end() ? nullptr : std::addressof(it->second); } template auto map_getp(const Map& m, const Key& key) -> typename Map::mapped_type const* { auto it = m.find(key); return it == m.end() ? nullptr : std::addressof(it->second); } #else // The unique_ptr requires C++11, and the rvalue-reference feature, // so here we're simulate the behavior using the old std::auto_ptr. // This is only to make a "move" call transparent and look ok towards // the C++11 code. template std::auto_ptr_ref Move(const std::auto_ptr_ref& in) { return in; } // We need to provide also some fixes for this type that were not present in auto_ptr, // but they are present in unique_ptr. // C++03 doesn't have a templated typedef, but still we need some things // that can only function as a class. template class UniquePtr: public std::auto_ptr { typedef std::auto_ptr Base; public: // This is a template - so method names must be declared explicitly typedef typename Base::element_type element_type; using Base::get; using Base::reset; // All constructor declarations must be repeated. // "Constructor delegation" is also only C++11 feature. explicit UniquePtr(element_type* p = 0) throw() : Base(p) {} UniquePtr(UniquePtr& a) throw() : Base(a) { } template UniquePtr(UniquePtr& a) throw() : Base(a) {} UniquePtr& operator=(UniquePtr& a) throw() { return Base::operator=(a); } template UniquePtr& operator=(UniquePtr& a) throw() { return Base::operator=(a); } // Good, now we need to add some parts of the API of unique_ptr. bool operator==(const UniquePtr& two) const { return get() == two.get(); } bool operator!=(const UniquePtr& two) const { return get() != two.get(); } bool operator==(const element_type* two) const { return get() == two; } bool operator!=(const element_type* two) const { return get() != two; } operator bool () { return 0!= get(); } }; // A primitive one-argument versions of Sprint and Printable template inline std::string Sprint(const Arg1& arg) { std::ostringstream sout; sout << arg; return sout.str(); } template inline std::string Printable(const Container& in) { using namespace srt_pair_op; typedef typename Container::value_type Value; std::ostringstream os; os << "[ "; for (typename Container::const_iterator i = in.begin(); i != in.end(); ++i) os << Value(*i) << " "; os << "]"; return os.str(); } template typename Map::mapped_type map_get(Map& m, const Key& key, typename Map::mapped_type def = typename Map::mapped_type()) { typename Map::iterator it = m.find(key); return it == m.end() ? def : it->second; } template typename Map::mapped_type map_get(const Map& m, const Key& key, typename Map::mapped_type def = typename Map::mapped_type()) { typename Map::const_iterator it = m.find(key); return it == m.end() ? def : it->second; } template typename Map::mapped_type* map_getp(Map& m, const Key& key) { typename Map::iterator it = m.find(key); return it == m.end() ? (typename Map::mapped_type*)0 : &(it->second); } template typename Map::mapped_type const* map_getp(const Map& m, const Key& key) { typename Map::const_iterator it = m.find(key); return it == m.end() ? (typename Map::mapped_type*)0 : &(it->second); } #endif // Printable with prefix added for every element. // Useful when printing a container of sockets or sequence numbers. template inline std::string PrintableMod(const Container& in, const std::string& prefix) { using namespace srt_pair_op; typedef typename Container::value_type Value; std::ostringstream os; os << "[ "; for (typename Container::const_iterator y = in.begin(); y != in.end(); ++y) os << prefix << Value(*y) << " "; os << "]"; return os.str(); } template inline void FilterIf(InputIterator bg, InputIterator nd, OutputIterator out, TransFunction fn) { for (InputIterator i = bg; i != nd; ++i) { std::pair result = fn(*i); if (!result.second) continue; *out++ = result.first; } } template inline void insert_uniq(std::vector& v, const ArgValue& val) { typename std::vector::iterator i = std::find(v.begin(), v.end(), val); if (i != v.end()) return; v.push_back(val); } template struct CallbackHolder { void* opaque; Signature* fn; CallbackHolder(): opaque(NULL), fn(NULL) {} void set(void* o, Signature* f) { // Test if the pointer is a pointer to function. Don't let // other type of pointers here. #if HAVE_CXX11 static_assert(std::is_function::value, "CallbackHolder is for functions only!"); #else // This is a poor-man's replacement, which should in most compilers // generate a warning, if `Signature` resolves to a value type. // This would make an illegal pointer cast from a value to a function type. // Casting function-to-function, however, should not. Unfortunately // newer compilers disallow that, too (when a signature differs), but // then they should better use the C++11 way, much more reliable and safer. void* (*testfn)(void*) = (void*(*)(void*))f; (void)(testfn); #endif opaque = o; fn = f; } operator bool() { return fn != NULL; } }; #define CALLBACK_CALL(holder,...) (*holder.fn)(holder.opaque, __VA_ARGS__) inline std::string FormatBinaryString(const uint8_t* bytes, size_t size) { if ( size == 0 ) return ""; //char buf[256]; using namespace std; ostringstream os; // I know, it's funny to use sprintf and ostringstream simultaneously, // but " %02X" in iostream is: << " " << hex << uppercase << setw(2) << setfill('0') << VALUE << setw(1) // Too noisy. OTOH ostringstream solves the problem of memory allocation // for a string of unpredictable size. //sprintf(buf, "%02X", int(bytes[0])); os.fill('0'); os.width(2); os.setf(ios::basefield, ios::hex); os.setf(ios::uppercase); //os << buf; os << int(bytes[0]); for (size_t i = 1; i < size; ++i) { //sprintf(buf, " %02X", int(bytes[i])); //os << buf; os << int(bytes[i]); } return os.str(); } /// This class is useful in every place where /// the time drift should be traced. It's currently in use in every /// solution that implements any kind of TSBPD. template class DriftTracer { int64_t m_qDrift; int64_t m_qOverdrift; int64_t m_qDriftSum; unsigned m_uDriftSpan; public: DriftTracer() : m_qDrift(0) , m_qOverdrift(0) , m_qDriftSum(0) , m_uDriftSpan(0) {} bool update(int64_t driftval) { m_qDriftSum += driftval; ++m_uDriftSpan; // I moved it here to calculate accumulated overdrift. if (CLEAR_ON_UPDATE) m_qOverdrift = 0; if (m_uDriftSpan < MAX_SPAN) return false; // 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 (std::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; } // For group overrides void forceDrift(int64_t driftval) { m_qDrift = driftval; } // 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() returned 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() const { return m_qDrift; } int64_t overdrift() const { return m_qOverdrift; } }; template struct MapProxy { std::map& mp; const KeyType& key; MapProxy(std::map& m, const KeyType& k): mp(m), key(k) {} void operator=(const ValueType& val) { mp[key] = val; } typename std::map::iterator find() { return mp.find(key); } typename std::map::const_iterator find() const { return mp.find(key); } operator ValueType() const { typename std::map::const_iterator p = find(); if (p == mp.end()) return ""; return p->second; } ValueType deflt(const ValueType& defval) const { typename std::map::const_iterator p = find(); if (p == mp.end()) return defval; return p->second; } bool exists() const { return find() != mp.end(); } }; /// Print some hash-based stamp of the first 16 bytes in the buffer inline std::string BufferStamp(const char* mem, size_t size) { using namespace std; char spread[16]; if (size < 16) memset((spread + size), 0, 16 - size); memcpy((spread), mem, min(size_t(16), size)); // Now prepare 4 cells for uint32_t. union { uint32_t sum; char cells[4]; }; memset((cells), 0, 4); for (size_t x = 0; x < 4; ++x) for (size_t y = 0; y < 4; ++y) { cells[x] += spread[x+4*y]; } // Convert to hex string ostringstream os; os << hex << uppercase << setfill('0') << setw(8) << sum; return os.str(); } template inline void Split(const std::string & str, char delimiter, OutputIterator tokens) { if ( str.empty() ) return; // May cause crash and won't extract anything anyway std::size_t start; std::size_t end = -1; do { start = end + 1; end = str.find(delimiter, start); *tokens = str.substr( start, (end == std::string::npos) ? std::string::npos : end - start); ++tokens; } while (end != std::string::npos); } inline std::string SelectNot(const std::string& unwanted, const std::string& s1, const std::string& s2) { if (s1 == unwanted) return s2; // might be unwanted, too, but then, there's nothing you can do anyway if (s2 == unwanted) return s1; // Both have wanted values, so now compare if they are same if (s1 == s2) return s1; // occasionally there's a winner // Irresolvable situation. return std::string(); } inline std::string SelectDefault(const std::string& checked, const std::string& def) { if (checked == "") return def; return checked; } template inline size_t safe_advance(It& it, size_t num, It end) { while ( it != end && num ) { --num; ++it; } return num; // will be effectively 0, if reached the required point, or >0, if end was by that number earlier } // This is available only in C++17, dunno why not C++11 as it's pretty useful. template inline ATR_CONSTEXPR size_t Size(const V (&)[N]) ATR_NOEXCEPT { return N; } template inline ValueType avg_iir(ValueType old_value, ValueType new_value) { return (old_value * (DEPRLEN - 1) + new_value) / DEPRLEN; } template inline ValueType avg_iir_w(ValueType old_value, ValueType new_value, size_t new_val_weight) { return (old_value * (DEPRLEN - new_val_weight) + new_value * new_val_weight) / DEPRLEN; } // Property accessor definitions // // "Property" is a special method that accesses given field. // This relies only on a convention, which is the following: // // V x = object.prop(); <-- get the property's value // object.prop(x); <-- set the property a value // // Properties might be also chained when setting: // // object.prop1(v1).prop2(v2).prop3(v3); // // Properties may be defined various even very complicated // ways, which is simply providing a method with body. In order // to define a property simplest possible way, that is, refer // directly to the field that keeps it, here are the following macros: // // Prefix: SRTU_PROPERTY_ // Followed by: // - access type: RO, WO, RW, RR, RRW // - chain flag: optional _CHAIN // Where access type is: // - RO - read only. Defines reader accessor. The accessor method will be const. // - RR - read reference. The accessor isn't const to allow reference passthrough. // - WO - write only. Defines writer accessor. // - RW - combines RO and WO. // - RRW - combines RR and WO. // // The _CHAIN marker is optional for macros providing writable accessors // for properties. The difference is that while simple write accessors return // void, the chaining accessors return the reference to the object for which // the write accessor was called so that you can call the next accessor (or // any other method as well) for the result. #define SRTU_PROPERTY_RR(type, name, field) type name() { return field; } #define SRTU_PROPERTY_RO(type, name, field) type name() const { return field; } #define SRTU_PROPERTY_WO(type, name, field) void set_##name(type arg) { field = arg; } #define SRTU_PROPERTY_WO_CHAIN(otype, type, name, field) otype& set_##name(type arg) { field = arg; return *this; } #define SRTU_PROPERTY_RW(type, name, field) SRTU_PROPERTY_RO(type, name, field); SRTU_PROPERTY_WO(type, name, field) #define SRTU_PROPERTY_RRW(type, name, field) SRTU_PROPERTY_RR(type, name, field); SRTU_PROPERTY_WO(type, name, field) #define SRTU_PROPERTY_RW_CHAIN(otype, type, name, field) SRTU_PROPERTY_RO(type, name, field); SRTU_PROPERTY_WO_CHAIN(otype, type, name, field) #define SRTU_PROPERTY_RRW_CHAIN(otype, type, name, field) SRTU_PROPERTY_RR(type, name, field); SRTU_PROPERTY_WO_CHAIN(otype, type, name, field) #endif srt-1.4.4/srtcore/version.h.in000066400000000000000000000021341412557703600162660ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_VERSION_H #define INC_SRT_VERSION_H // To construct version value #define SRT_MAKE_VERSION(major, minor, patch) \ ((patch) + ((minor)*0x100) + ((major)*0x10000)) #define SRT_MAKE_VERSION_VALUE SRT_MAKE_VERSION #define SRT_VERSION_MAJOR @SRT_VERSION_MAJOR@ #define SRT_VERSION_MINOR @SRT_VERSION_MINOR@ #define SRT_VERSION_PATCH @SRT_VERSION_PATCH@ #cmakedefine SRT_VERSION_BUILD @CI_BUILD_NUMBER_STRING@ #define SRT_VERSION_STRING "@SRT_VERSION@" #define SRT_VERSION_VALUE \ SRT_MAKE_VERSION_VALUE( \ SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH ) #endif // INC_SRT_VERSION_H srt-1.4.4/srtcore/window.cpp000066400000000000000000000201641412557703600160410ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 01/22/2011 modified by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include #include #include "common.h" #include "window.h" #include using namespace std; using namespace srt::sync; 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].tsTimeStamp = steady_clock::now(); 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, const steady_clock::time_point& currtime) { // Head has not exceeded the physical boundary of the window if (r_iHead >= r_iTail) { for (int i = r_iTail, n = r_iHead; i < n; ++ i) { // Looking for an identical ACK Seq. No. if (seq == r_aSeq[i].iACKSeqNo) { // Return the Data ACK it carried r_ack = r_aSeq[i].iACK; // Calculate RTT estimate const int rtt = count_microseconds(currtime - r_aSeq[i].tsTimeStamp); if (i + 1 == r_iHead) { r_iTail = r_iHead = 0; r_aSeq[0].iACKSeqNo = SRT_SEQNO_NONE; } else r_iTail = (i + 1) % size; return rtt; } } // The record about ACK is not found in the buffer, RTT can not be calculated 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 an identical ACK Seq. No. if (seq == r_aSeq[j % size].iACKSeqNo) { // Return the Data ACK it carried j %= size; r_ack = r_aSeq[j].iACK; // Calculate RTT estimate const int rtt = count_microseconds(currtime - r_aSeq[j].tsTimeStamp); if (j == r_iHead) { r_iTail = r_iHead = 0; r_aSeq[0].iACKSeqNo = -1; } else r_iTail = (j + 1) % size; return rtt; } } // The record about ACK is not found in the buffer, RTT can not be calculated 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] = srt::CPacket::SRT_MAX_PAYLOAD_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 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::CPacket::SRT_DATA_HDR_SIZE * count); //Add protocol headers to bytes received bytesps = (unsigned long)ceil(1000000.0 / (double(sum) / double(bytes))); return (int)ceil(1000000.0 / (sum / count)); } else { bytesps = 0; return 0; } } int CPktTimeWindowTools::getBandwidth_in(const int* window, int* replica, size_t psize) { // This calculation does more-less the following: // // 1. Having example window: // - 50, 51, 100, 55, 80, 1000, 600, 1500, 1200, 10, 90 // 2. This window is now sorted, but we only know the value in the middle: // - 10, 50, 51, 55, 80, [[90]], 100, 600, 1000, 1200, 1500 // 3. Now calculate: // - lower: 90/8 = 11.25 // - upper: 90*8 = 720 // 4. Now calculate the arithmetic median from all these values, // but drop those from outside the range: // - 10, (11<) [ 50, 51, 55, 80, 90, 100, 600, ] (>720) 1000, 1200, 1500 // 5. Calculate the median from the extracted range, // NOTE: the median is actually repeated once, so size is +1. // // values = { 50, 51, 55, 80, 90, 100, 600 }; // sum = 90 + accumulate(values); ==> 1026 // median = sum/(1 + values.size()); ==> 147 // // For comparison: the overall arithmetic median from this window == 430 // // 6. Returned value = 1M/median // get median value, but cannot change the original value order in the window std::copy(window, window + psize - 1, replica); std::nth_element(replica, replica + (psize / 2), replica + psize - 1); //std::sort(replica, replica + psize); <--- was used for debug, just leave it as a mark 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))); } srt-1.4.4/srtcore/window.h000066400000000000000000000307551412557703600155150ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the University of Illinois nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /***************************************************************************** written by Yunhong Gu, last updated 01/22/2011 modified by Haivision Systems Inc. *****************************************************************************/ #ifndef INC_SRT_WINDOW_H #define INC_SRT_WINDOW_H #ifndef _WIN32 #include #include #endif #include "packet.h" #include "udt.h" namespace ACKWindowTools { struct Seq { int32_t iACKSeqNo; // Seq. No. of the ACK packet int32_t iACK; // Data packet Seq. No. carried by the ACK packet srt::sync::steady_clock::time_point tsTimeStamp; // 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, const srt::sync::steady_clock::time_point& currtime); } template class CACKWindow { public: CACKWindow() : m_aSeq(), m_iHead(0), m_iTail(0) { m_aSeq[0].iACKSeqNo = SRT_SEQNO_NONE; } ~CACKWindow() {} /// Write an ACK record into the window. /// @param [in] seq Seq. No. of the ACK packet /// @param [in] ack Data packet Seq. No. carried by the ACK packet void store(int32_t seq, int32_t ack) { return ACKWindowTools::store(m_aSeq, SIZE, m_iHead, m_iTail, seq, ack); } /// Search the ACKACK "seq" in the window, find out the data packet "ack" /// and calculate RTT estimate based on the ACK/ACKACK pair /// @param [in] seq Seq. No. of the ACK packet carried within ACKACK /// @param [out] ack Acknowledged data packet Seq. No. from the ACK packet that matches the ACKACK /// @param [in] currtime The timestamp of ACKACK packet reception by the receiver /// @return RTT int acknowledge(int32_t seq, int32_t& r_ack, const srt::sync::steady_clock::time_point& currtime) { return ACKWindowTools::acknowledge(m_aSeq, SIZE, m_iHead, m_iTail, seq, r_ack, currtime); } private: typedef ACKWindowTools::Seq Seq; Seq m_aSeq[SIZE]; int m_iHead; // Pointer to the latest 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_tsLastArrTime(srt::sync::steady_clock::now()), m_tsCurrArrTime(), m_tsProbeTime(), m_Probe1Sequence(SRT_SEQNO_NONE) { // Exception: up to CUDT ctor srt::sync::setupMutex(m_lockPktWindow, "PktWindow"); srt::sync::setupMutex(m_lockProbeWindow, "ProbeWindow"); CPktTimeWindowTools::initializeWindowArrays(m_aPktWindow, m_aProbeWindow, m_aBytesWindow, ASIZE, PSIZE); } ~CPktTimeWindow() { } public: /// 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& w_bytesps) const { // Lock access to the packet Window srt::sync::ScopedLock cg(m_lockPktWindow); int pktReplica[ASIZE]; // packet information window (inter-packet time) return getPktRcvSpeed_in(m_aPktWindow, pktReplica, m_aBytesWindow, ASIZE, (w_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 srt::sync::ScopedLock 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) { srt::sync::ScopedLock cg(m_lockPktWindow); m_tsCurrArrTime = srt::sync::steady_clock::now(); // record the packet interval between the current and the last one m_aPktWindow[m_iPktWindowPtr] = (int) srt::sync::count_microseconds(m_tsCurrArrTime - m_tsLastArrTime); 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_tsLastArrTime = m_tsCurrArrTime; } /// Shortcut to test a packet for possible probe 1 or 2 void probeArrival(const srt::CPacket& pkt, bool unordered) { const int inorder16 = pkt.m_iSeqNo & PUMASK_SEQNO_PROBE; // for probe1, we want 16th packet if (inorder16 == 0) { probe1Arrival(pkt, unordered); } if (unordered) return; // for probe2, we want 17th packet if (inorder16 == 1) { probe2Arrival(pkt); } } /// Record the arrival time of the first probing packet. void probe1Arrival(const srt::CPacket& pkt, bool unordered) { if (unordered && pkt.m_iSeqNo == m_Probe1Sequence) { // Reset the starting probe into "undefined", when // a packet has come as retransmitted before the // measurement at arrival of 17th could be taken. m_Probe1Sequence = SRT_SEQNO_NONE; return; } m_tsProbeTime = srt::sync::steady_clock::now(); m_Probe1Sequence = pkt.m_iSeqNo; // Record the sequence where 16th packet probe was taken } /// Record the arrival time of the second probing packet and the interval between packet pairs. void probe2Arrival(const srt::CPacket& pkt) { // Reject probes that don't refer to the very next packet // towards the one that was lately notified by probe1Arrival. // Otherwise the result can be stupid. // Simply, in case when this wasn't called exactly for the // expected packet pair, behave as if the 17th packet was lost. // no start point yet (or was reset) OR not very next packet if (m_Probe1Sequence == SRT_SEQNO_NONE || CSeqNo::incseq(m_Probe1Sequence) != pkt.m_iSeqNo) return; // Grab the current time before trying to acquire // a mutex. This might add extra delay and therefore // screw up the measurement. const srt::sync::steady_clock::time_point now = srt::sync::steady_clock::now(); // Lock access to the packet Window srt::sync::ScopedLock cg(m_lockProbeWindow); m_tsCurrArrTime = now; // Reset the starting probe to prevent checking if the // measurement was already taken. m_Probe1Sequence = SRT_SEQNO_NONE; // record the probing packets interval // Adjust the time for what a complete packet would have take const int64_t timediff = srt::sync::count_microseconds(m_tsCurrArrTime - m_tsProbeTime); const int64_t timediff_times_pl_size = timediff * srt::CPacket::SRT_MAX_PAYLOAD_SIZE; // Let's take it simpler than it is coded here: // (stating that a packet has never zero size) // // probe_case = (now - previous_packet_time) * SRT_MAX_PAYLOAD_SIZE / pktsz; // // Meaning: if the packet is fully packed, probe_case = timediff. // Otherwise the timediff will be "converted" to a time that a fully packed packet "would take", // provided the arrival time is proportional to the payload size and skipping // the ETH+IP+UDP+SRT header part elliminates the constant packet delivery time influence. // const size_t pktsz = pkt.getLength(); m_aProbeWindow[m_iProbeWindowPtr] = pktsz ? int(timediff_times_pl_size / pktsz) : int(timediff); // OLD CODE BEFORE BSTATS: // record the probing packets interval // m_aProbeWindow[m_iProbeWindowPtr] = int(m_tsCurrArrTime - m_tsProbeTime); // 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 srt::sync::Mutex 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 srt::sync::Mutex m_lockProbeWindow; // Used to synchronize access to the probe window int m_iLastSentTime; // Last packet sending time int m_iMinPktSndInt; // Minimum packet sending interval srt::sync::steady_clock::time_point m_tsLastArrTime; // Last packet arrival time srt::sync::steady_clock::time_point m_tsCurrArrTime; // Current packet arrival time srt::sync::steady_clock::time_point m_tsProbeTime; // Arrival time of the first probing packet int32_t m_Probe1Sequence; // Sequence number for which the arrival time was notified private: CPktTimeWindow(const CPktTimeWindow&); CPktTimeWindow &operator=(const CPktTimeWindow&); }; #endif srt-1.4.4/test/000077500000000000000000000000001412557703600133215ustar00rootroot00000000000000srt-1.4.4/test/any.hpp000066400000000000000000000371501412557703600146270ustar00rootroot00000000000000// // Implementation of N4562 std::experimental::any (merged into C++17) for C++11 compilers. // // See also: // + http://en.cppreference.com/w/cpp/any // + http://en.cppreference.com/w/cpp/experimental/any // + http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#any // + https://cplusplus.github.io/LWG/lwg-active.html#2509 // // // Copyright (c) 2016 Denilson das Mercês Amorim // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef LINB_ANY_HPP #define LINB_ANY_HPP #pragma once #include #include #include #if defined(PARTICLE) #if !defined(__cpp_exceptions) && !defined(ANY_IMPL_NO_EXCEPTIONS) && !defined(ANY_IMPL_EXCEPTIONS) # define ANY_IMPL_NO_EXCEPTIONS # endif #else // you can opt-out of exceptions by definining ANY_IMPL_NO_EXCEPTIONS, // but you must ensure not to cast badly when passing an `any' object to any_cast(any) #endif #if defined(PARTICLE) #if !defined(__cpp_rtti) && !defined(ANY_IMPL_NO_RTTI) && !defined(ANY_IMPL_RTTI) # define ANY_IMPL_NO_RTTI # endif #else // you can opt-out of RTTI by defining ANY_IMPL_NO_RTTI, // in order to disable functions working with the typeid of a type #endif namespace linb { class bad_any_cast : public std::bad_cast { public: const char* what() const noexcept override { return "bad any cast"; } }; class any final { public: /// Constructs an object of type any with an empty state. any() : vtable(nullptr) { } /// Constructs an object of type any with an equivalent state as other. any(const any& rhs) : vtable(rhs.vtable) { if(!rhs.empty()) { rhs.vtable->copy(rhs.storage, this->storage); } } /// Constructs an object of type any with a state equivalent to the original state of other. /// rhs is left in a valid but otherwise unspecified state. any(any&& rhs) noexcept : vtable(rhs.vtable) { if(!rhs.empty()) { rhs.vtable->move(rhs.storage, this->storage); rhs.vtable = nullptr; } } /// Same effect as this->clear(). ~any() { this->clear(); } /// Constructs an object of type any that contains an object of type T direct-initialized with std::forward(value). /// /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. template::type, any>::value>::type> any(ValueType&& value) { static_assert(std::is_copy_constructible::type>::value, "T shall satisfy the CopyConstructible requirements."); this->construct(std::forward(value)); } /// Has the same effect as any(rhs).swap(*this). No effects if an exception is thrown. any& operator=(const any& rhs) { any(rhs).swap(*this); return *this; } /// Has the same effect as any(std::move(rhs)).swap(*this). /// /// The state of *this is equivalent to the original state of rhs and rhs is left in a valid /// but otherwise unspecified state. any& operator=(any&& rhs) noexcept { any(std::move(rhs)).swap(*this); return *this; } /// Has the same effect as any(std::forward(value)).swap(*this). No effect if a exception is thrown. /// /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. template::type, any>::value>::type> any& operator=(ValueType&& value) { static_assert(std::is_copy_constructible::type>::value, "T shall satisfy the CopyConstructible requirements."); any(std::forward(value)).swap(*this); return *this; } /// If not empty, destroys the contained object. void clear() noexcept { if(!empty()) { this->vtable->destroy(storage); this->vtable = nullptr; } } /// Returns true if *this has no contained object, otherwise false. bool empty() const noexcept { return this->vtable == nullptr; } #ifndef ANY_IMPL_NO_RTTI /// If *this has a contained object of type T, typeid(T); otherwise typeid(void). const std::type_info& type() const noexcept { return empty()? typeid(void) : this->vtable->type(); } #endif /// Exchange the states of *this and rhs. void swap(any& rhs) noexcept { if(this->vtable != rhs.vtable) { any tmp(std::move(rhs)); // move from *this to rhs. rhs.vtable = this->vtable; if(this->vtable != nullptr) { this->vtable->move(this->storage, rhs.storage); //this->vtable = nullptr; -- unneeded, see below } // move from tmp (previously rhs) to *this. this->vtable = tmp.vtable; if(tmp.vtable != nullptr) { tmp.vtable->move(tmp.storage, this->storage); tmp.vtable = nullptr; } } else // same types { if(this->vtable != nullptr) this->vtable->swap(this->storage, rhs.storage); } } private: // Storage and Virtual Method Table union storage_union { using stack_storage_t = typename std::aligned_storage<2 * sizeof(void*), std::alignment_of::value>::type; void* dynamic; stack_storage_t stack; // 2 words for e.g. shared_ptr }; /// Base VTable specification. struct vtable_type { // Note: The caller is responssible for doing .vtable = nullptr after destructful operations // such as destroy() and/or move(). #ifndef ANY_IMPL_NO_RTTI /// The type of the object this vtable is for. const std::type_info& (*type)() noexcept; #endif /// Destroys the object in the union. /// The state of the union after this call is unspecified, caller must ensure not to use src anymore. void(*destroy)(storage_union&) noexcept; /// Copies the **inner** content of the src union into the yet unitialized dest union. /// As such, both inner objects will have the same state, but on separate memory locations. void(*copy)(const storage_union& src, storage_union& dest); /// Moves the storage from src to the yet unitialized dest union. /// The state of src after this call is unspecified, caller must ensure not to use src anymore. void(*move)(storage_union& src, storage_union& dest) noexcept; /// Exchanges the storage between lhs and rhs. void(*swap)(storage_union& lhs, storage_union& rhs) noexcept; }; /// VTable for dynamically allocated storage. template struct vtable_dynamic { #ifndef ANY_IMPL_NO_RTTI static const std::type_info& type() noexcept { return typeid(T); } #endif static void destroy(storage_union& storage) noexcept { //assert(reinterpret_cast(storage.dynamic)); delete reinterpret_cast(storage.dynamic); } static void copy(const storage_union& src, storage_union& dest) { dest.dynamic = new T(*reinterpret_cast(src.dynamic)); } static void move(storage_union& src, storage_union& dest) noexcept { dest.dynamic = src.dynamic; src.dynamic = nullptr; } static void swap(storage_union& lhs, storage_union& rhs) noexcept { // just exchage the storage pointers. std::swap(lhs.dynamic, rhs.dynamic); } }; /// VTable for stack allocated storage. template struct vtable_stack { #ifndef ANY_IMPL_NO_RTTI static const std::type_info& type() noexcept { return typeid(T); } #endif static void destroy(storage_union& storage) noexcept { reinterpret_cast(&storage.stack)->~T(); } static void copy(const storage_union& src, storage_union& dest) { new (&dest.stack) T(reinterpret_cast(src.stack)); } static void move(storage_union& src, storage_union& dest) noexcept { // one of the conditions for using vtable_stack is a nothrow move constructor, // so this move constructor will never throw a exception. new (&dest.stack) T(std::move(reinterpret_cast(src.stack))); destroy(src); } static void swap(storage_union& lhs, storage_union& rhs) noexcept { storage_union tmp_storage; move(rhs, tmp_storage); move(lhs, rhs); move(tmp_storage, lhs); } }; /// Whether the type T must be dynamically allocated or can be stored on the stack. template struct requires_allocation : std::integral_constant::value // N4562 §6.3/3 [any.class] && sizeof(T) <= sizeof(storage_union::stack) && std::alignment_of::value <= std::alignment_of::value)> {}; /// Returns the pointer to the vtable of the type T. template static vtable_type* vtable_for_type() { using VTableType = typename std::conditional::value, vtable_dynamic, vtable_stack>::type; static vtable_type table = { #ifndef ANY_IMPL_NO_RTTI VTableType::type, #endif VTableType::destroy, VTableType::copy, VTableType::move, VTableType::swap, }; return &table; } protected: template friend const T* any_cast(const any* operand) noexcept; template friend T* any_cast(any* operand) noexcept; #ifndef ANY_IMPL_NO_RTTI /// Same effect as is_same(this->type(), t); bool is_typed(const std::type_info& t) const { return is_same(this->type(), t); } #endif #ifndef ANY_IMPL_NO_RTTI /// Checks if two type infos are the same. /// /// If ANY_IMPL_FAST_TYPE_INFO_COMPARE is defined, checks only the address of the /// type infos, otherwise does an actual comparision. Checking addresses is /// only a valid approach when there's no interaction with outside sources /// (other shared libraries and such). static bool is_same(const std::type_info& a, const std::type_info& b) { #ifdef ANY_IMPL_FAST_TYPE_INFO_COMPARE return &a == &b; #else return a == b; #endif } #endif /// Casts (with no type_info checks) the storage pointer as const T*. template const T* cast() const noexcept { return requires_allocation::type>::value? reinterpret_cast(storage.dynamic) : reinterpret_cast(&storage.stack); } /// Casts (with no type_info checks) the storage pointer as T*. template T* cast() noexcept { return requires_allocation::type>::value? reinterpret_cast(storage.dynamic) : reinterpret_cast(&storage.stack); } private: storage_union storage; // on offset(0) so no padding for align vtable_type* vtable; template typename std::enable_if::value>::type do_construct(ValueType&& value) { storage.dynamic = new T(std::forward(value)); } template typename std::enable_if::value>::type do_construct(ValueType&& value) { new (&storage.stack) T(std::forward(value)); } /// Chooses between stack and dynamic allocation for the type decay_t, /// assigns the correct vtable, and constructs the object on our storage. template void construct(ValueType&& value) { using T = typename std::decay::type; this->vtable = vtable_for_type(); do_construct(std::forward(value)); } }; namespace detail { template inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::true_type) { return std::move(*p); } template inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::false_type) { return *p; } } /// Performs *any_cast>>(&operand), or throws bad_any_cast on failure. template inline ValueType any_cast(const any& operand) { auto p = any_cast::type>::type>(&operand); #ifndef ANY_IMPL_NO_EXCEPTIONS if(p == nullptr) throw bad_any_cast(); #endif return *p; } /// Performs *any_cast>(&operand), or throws bad_any_cast on failure. template inline ValueType any_cast(any& operand) { auto p = any_cast::type>(&operand); #ifndef ANY_IMPL_NO_EXCEPTIONS if(p == nullptr) throw bad_any_cast(); #endif return *p; } /// /// If ValueType is MoveConstructible and isn't a lvalue reference, performs /// std::move(*any_cast>(&operand)), otherwise /// *any_cast>(&operand). Throws bad_any_cast on failure. /// template inline ValueType any_cast(any&& operand) { using can_move = std::integral_constant::value && !std::is_lvalue_reference::value>; auto p = any_cast::type>(&operand); #ifndef ANY_IMPL_NO_EXCEPTIONS if(p == nullptr) throw bad_any_cast(); #endif return detail::any_cast_move_if_true(p, can_move()); } /// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object /// contained by operand, otherwise nullptr. template inline const ValueType* any_cast(const any* operand) noexcept { using T = typename std::decay::type; #ifndef ANY_IMPL_NO_RTTI if (operand && operand->is_typed(typeid(T))) #else if (operand && operand->vtable == any::vtable_for_type()) #endif return operand->cast(); else return nullptr; } /// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object /// contained by operand, otherwise nullptr. template inline ValueType* any_cast(any* operand) noexcept { using T = typename std::decay::type; #ifndef ANY_IMPL_NO_RTTI if (operand && operand->is_typed(typeid(T))) #else if (operand && operand->vtable == any::vtable_for_type()) #endif return operand->cast(); else return nullptr; } } namespace std { inline void swap(linb::any& lhs, linb::any& rhs) noexcept { lhs.swap(rhs); } } #endif srt-1.4.4/test/filelist.maf000066400000000000000000000010221412557703600156140ustar00rootroot00000000000000HEADERS any.hpp SOURCES test_buffer.cpp test_bonding.cpp test_common.cpp test_connection_timeout.cpp test_many_connections.cpp test_cryspr.cpp test_enforced_encryption.cpp test_epoll.cpp test_fec_rebuilding.cpp test_file_transmission.cpp test_ipv6.cpp test_list.cpp test_listen_callback.cpp test_muxer.cpp test_seqno.cpp test_socket_options.cpp test_sync.cpp test_threadname.cpp test_timer.cpp test_unitqueue.cpp test_utilities.cpp test_reuseaddr.cpp # Tests for bonding only - put here! SOURCES - ENABLE_EXPERIMENTAL_BONDING srt-1.4.4/test/test_bonding.cpp000066400000000000000000000247141412557703600165140ustar00rootroot00000000000000 #include #include #include #include #include #include #include #ifdef _WIN32 #define usleep(x) Sleep(x / 1000) #else #include #endif #if ENABLE_EXPERIMENTAL_BONDING #include "gtest/gtest.h" #include "srt.h" #include "netinet_any.h" TEST(Bonding, SRTConnectGroup) { struct sockaddr_in sa; srt_startup(); const int ss = srt_create_group(SRT_GTYPE_BROADCAST); ASSERT_NE(ss, SRT_ERROR); std::vector targets; for (int i = 0; i < 2; ++i) { sa.sin_family = AF_INET; sa.sin_port = htons(4200 + i); ASSERT_EQ(inet_pton(AF_INET, "192.168.1.237", &sa.sin_addr), 1); const SRT_SOCKGROUPCONFIG gd = srt_prepare_endpoint(NULL, (struct sockaddr*)&sa, sizeof sa); targets.push_back(gd); } std::future closing_promise = std::async(std::launch::async, [](int ss) { std::this_thread::sleep_for(std::chrono::seconds(2)); std::cerr << "Closing group" << std::endl; srt_close(ss); }, ss); std::cout << "srt_connect_group calling " << std::endl; const int st = srt_connect_group(ss, targets.data(), targets.size()); std::cout << "srt_connect_group returned " << st << std::endl; closing_promise.wait(); // Delete config objects before prospective exception for (auto& gd: targets) srt_delete_config(gd.config); int res = srt_close(ss); if (res == SRT_ERROR) { std::cerr << "srt_close: " << srt_getlasterror_str() << std::endl; } srt_cleanup(); } void listening_thread(bool should_read) { const SRTSOCKET server_sock = srt_create_socket(); sockaddr_in bind_sa; memset(&bind_sa, 0, sizeof bind_sa); bind_sa.sin_family = AF_INET; ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1); bind_sa.sin_port = htons(4200); ASSERT_NE(srt_bind(server_sock, (sockaddr*)&bind_sa, sizeof bind_sa), -1); const int yes = 1; srt_setsockflag(server_sock, SRTO_GROUPCONNECT, &yes, sizeof yes); const int no = 1; srt_setsockflag(server_sock, SRTO_RCVSYN, &no, sizeof no); const int eid = srt_epoll_create(); const int listen_event = SRT_EPOLL_IN | SRT_EPOLL_ERR; srt_epoll_add_usock(eid, server_sock, &listen_event); ASSERT_NE(srt_listen(server_sock, 5), -1); std::cout << "Listen: wait for acceptability\n"; int fds[2]; int fds_len = 2; int ers[2]; int ers_len = 2; int wr = srt_epoll_wait(eid, fds, &fds_len, ers, &ers_len, 5000, 0, 0, 0, 0); ASSERT_NE(wr, -1); std::cout << "Listen: reported " << fds_len << " acceptable and " << ers_len << " errors\n"; ASSERT_GT(fds_len, 0); ASSERT_EQ(fds[0], server_sock); sockaddr_any scl; int acp = srt_accept(server_sock, (scl.get()), (&scl.len)); ASSERT_NE(acp & SRTGROUP_MASK, 0); if (should_read) { std::cout << "Listener will read packets...\n"; // Read everything until closed int n = 0; for (;;) { char buf[1500]; int rd = srt_recv(acp, buf, 1500); if (rd == -1) { std::cout << "Listener read " << n << " packets, stopping\n"; break; } ++n; } } srt_close(acp); std::cout << "Listen: wait 7 seconds\n"; std::this_thread::sleep_for(std::chrono::seconds(7)); // srt_accept.. } void ConnectCallback(void* /*opaq*/, SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token) { std::cout << "Connect callback. Socket: " << sock << ", error: " << error << ", token: " << token << '\n'; } TEST(Bonding, NonBlockingGroupConnect) { srt_startup(); const int ss = srt_create_group(SRT_GTYPE_BROADCAST); ASSERT_NE(ss, SRT_ERROR); std::cout << "Created group socket: " << ss << '\n'; int no = 0; ASSERT_NE(srt_setsockopt(ss, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode ASSERT_NE(srt_setsockopt(ss, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode const int poll_id = srt_epoll_create(); // Will use this epoll to wait for srt_accept(...) const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR); srt_connect_callback(ss, &ConnectCallback, this); sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_port = htons(4200); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); sockaddr_in safail = sa; safail.sin_port = htons(4201); // port where we have no listener std::future listen_promise = std::async(std::launch::async, std::bind(&listening_thread, false)); std::cout << "Connecting two sockets " << std::endl; { const int sockid = srt_connect(ss, (sockaddr*) &sa, sizeof sa); EXPECT_GT(sockid, 0) << "Socket " << 1; sa.sin_port = htons(4201); // Changing port so that second connect fails std::cout << "Socket created: " << sockid << '\n'; ASSERT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR); } { const int sockid = srt_connect(ss, (sockaddr*) &safail, sizeof safail); EXPECT_GT(sockid, 0) << "Socket " << 2; safail.sin_port = htons(4201); // Changing port so that second connect fails std::cout << "Socket created: " << sockid << '\n'; ASSERT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR); } std::cout << "Returned from connecting two sockets " << std::endl; const int default_len = 3; int rlen = default_len; SRTSOCKET read[default_len]; int wlen = default_len; SRTSOCKET write[default_len]; for (int j = 0; j < 2; ++j) { const int epoll_res = srt_epoll_wait(poll_id, read, &rlen, write, &wlen, 5000, /* timeout */ 0, 0, 0, 0); std::cout << "Epoll result: " << epoll_res << '\n'; std::cout << "Epoll rlen: " << rlen << ", wlen: " << wlen << '\n'; for (int i = 0; i < rlen; ++i) { std::cout << "Epoll read[" << i << "]: " << read[i] << '\n'; } for (int i = 0; i < wlen; ++i) { std::cout << "Epoll write[" << i << "]: " << write[i] << " (removed from epoll)\n"; EXPECT_EQ(srt_epoll_remove_usock(poll_id, write[i]), 0); } } listen_promise.wait(); EXPECT_EQ(srt_close(ss), 0) << "srt_close: %s\n" << srt_getlasterror_str(); srt_cleanup(); } void ConnectCallback_Close(void* /*opaq*/, SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token) { std::cout << "Connect callback. Socket: " << sock << ", error: " << error << ", token: " << token << '\n'; if (error == SRT_SUCCESS) return; // XXX WILL CAUSE DEADLOCK! srt_close(sock); } TEST(Bonding, CloseGroupAndSocket) { srt_startup(); const int ss = srt_create_group(SRT_GTYPE_BROADCAST); ASSERT_NE(ss, SRT_ERROR); std::cout << "Created group socket: " << ss << '\n'; int no = 0; ASSERT_NE(srt_setsockopt(ss, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode ASSERT_NE(srt_setsockopt(ss, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode const int poll_id = srt_epoll_create(); // Will use this epoll to wait for srt_accept(...) const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR); srt_connect_callback(ss, &ConnectCallback_Close, this); sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_port = htons(4200); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); std::future listen_promise = std::async(std::launch::async, std::bind(listening_thread, true)); std::cout << "Connecting two sockets " << std::endl; for (int i = 0; i < 2; ++i) { const int sockid = srt_connect(ss, (sockaddr*) &sa, sizeof sa); EXPECT_GT(sockid, 0) << "Socket " << i; sa.sin_port = htons(4201); // Changing port so that second connect fails std::cout << "Socket created: " << sockid << '\n'; ASSERT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR); } std::cout << "Returned from connecting two sockets " << std::endl; const int default_len = 3; int rlen = default_len; SRTSOCKET read[default_len]; int wlen = default_len; SRTSOCKET write[default_len]; for (int j = 0; j < 2; ++j) { const int epoll_res = srt_epoll_wait(poll_id, read, &rlen, write, &wlen, 5000, /* timeout */ 0, 0, 0, 0); std::cout << "Epoll result: " << epoll_res << '\n'; std::cout << "Epoll rlen: " << rlen << ", wlen: " << wlen << '\n'; for (int i = 0; i < rlen; ++i) { std::cout << "Epoll read[" << i << "]: " << read[i] << '\n'; } for (int i = 0; i < wlen; ++i) { std::cout << "Epoll write[" << i << "]: " << write[i] << " (removed from epoll)\n"; EXPECT_EQ(srt_epoll_remove_usock(poll_id, write[i]), 0); } } // Some basic checks for group stats SRT_TRACEBSTATS stats; EXPECT_EQ(srt_bstats(ss, &stats, true), SRT_SUCCESS); EXPECT_EQ(stats.pktSent, 0); EXPECT_EQ(stats.pktSentTotal, 0); EXPECT_EQ(stats.pktSentUnique, 0); EXPECT_EQ(stats.pktSentUniqueTotal, 0); EXPECT_EQ(stats.pktRecv, 0); EXPECT_EQ(stats.pktRecvTotal, 0); EXPECT_EQ(stats.pktRecvUnique, 0); EXPECT_EQ(stats.pktRecvUniqueTotal, 0); EXPECT_EQ(stats.pktRcvDrop, 0); EXPECT_EQ(stats.pktRcvDropTotal, 0); std::cout << "Starting thread for sending:\n"; std::thread sender([ss] { char buf[1316]; memset(buf, 1, sizeof(buf)); int n = 0; for (int i = 0; i < 10000; ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (srt_send(ss, buf, 1316) == -1) { std::cout << "[Sender] sending failure, exitting after sending " << n << " packets\n"; break; } ++n; } }); std::cout << "Will close sending in 300ms...\n"; std::this_thread::sleep_for(std::chrono::milliseconds(300)); EXPECT_EQ(srt_close(ss), 0) << "srt_close: %s\n" << srt_getlasterror_str(); std::cout << "CLOSED GROUP. Now waiting for sender to exit...\n"; sender.join(); listen_promise.wait(); srt_cleanup(); } #endif // ENABLE_EXPERIMENTAL_BONDING srt-1.4.4/test/test_buffer.cpp000066400000000000000000000122031412557703600163330ustar00rootroot00000000000000#include #include "gtest/gtest.h" #include "buffer.h" using namespace srt; TEST(CRcvBuffer, Create) { const int buffer_size_pkts = 128; CUnitQueue unit_queue; CRcvBuffer rcv_buffer(&unit_queue, buffer_size_pkts); EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - 1); // logic } TEST(CRcvBuffer, FullBuffer) { const int buffer_size_pkts = 16; CUnitQueue unit_queue; unit_queue.init(buffer_size_pkts, 1500, AF_INET); CRcvBuffer rcv_buffer(&unit_queue, buffer_size_pkts); const size_t payload_size = 1456; // Add a number of units (packets) to the buffer // equal to the buffer size in packets for (int i = 0; i < rcv_buffer.getAvailBufSize(); ++i) { CUnit* unit = unit_queue.getNextAvailUnit(); EXPECT_NE(unit, nullptr); unit->m_Packet.setLength(payload_size); EXPECT_EQ(rcv_buffer.addData(unit, i), 0); } EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - 1); // logic rcv_buffer.ackData(buffer_size_pkts - 1); EXPECT_EQ(rcv_buffer.getAvailBufSize(), 0); // Try to add more data than the available size of the buffer CUnit* unit = unit_queue.getNextAvailUnit(); EXPECT_NE(unit, nullptr); EXPECT_EQ(rcv_buffer.addData(unit, 1), -1); std::array buff; for (int i = 0; i < buffer_size_pkts - 1; ++i) { const int res = rcv_buffer.readBuffer(buff.data(), buff.size()); EXPECT_EQ(size_t(res), payload_size); } } // In this test case a packet is added to receiver buffer with offset 1, // thus leaving offset 0 with an empty pointer. // The buffer sais it is not empty, and the data is available // to be read, but reading should cause error. TEST(CRcvBuffer, ReadDataIPE) { const int buffer_size_pkts = 16; CUnitQueue unit_queue; unit_queue.init(buffer_size_pkts, 1500, AF_INET); CRcvBuffer rcv_buffer(&unit_queue, buffer_size_pkts); const size_t payload_size = 1456; // Add a number of units (packets) to the buffer // equal to the buffer size in packets CUnit* unit = unit_queue.getNextAvailUnit(); EXPECT_NE(unit, nullptr); unit->m_Packet.setLength(payload_size); EXPECT_EQ(rcv_buffer.addData(unit, 1), 0); EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - 1); EXPECT_FALSE(rcv_buffer.isRcvDataAvailable()); rcv_buffer.ackData(1); EXPECT_TRUE(rcv_buffer.isRcvDataAvailable()); EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - 2); std::cerr << "Expecting IPE message: \n"; std::array buff; const int res = rcv_buffer.readBuffer(buff.data(), buff.size()); EXPECT_EQ(res, -1); } TEST(CRcvBuffer, ReadData) { const int buffer_size_pkts = 16; CUnitQueue unit_queue; unit_queue.init(buffer_size_pkts, 1500, AF_INET); CRcvBuffer rcv_buffer(&unit_queue, buffer_size_pkts); const size_t payload_size = 1456; // Add a number of units (packets) to the buffer // equal to the buffer size in packets CUnit* unit = unit_queue.getNextAvailUnit(); EXPECT_NE(unit, nullptr); unit->m_Packet.setLength(payload_size); EXPECT_EQ(rcv_buffer.addData(unit, 0), 0); EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - 1); EXPECT_FALSE(rcv_buffer.isRcvDataAvailable()); rcv_buffer.ackData(1); EXPECT_TRUE(rcv_buffer.isRcvDataAvailable()); EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - 2); std::array buff; const int res = rcv_buffer.readBuffer(buff.data(), buff.size()); EXPECT_EQ(size_t(res), payload_size); } TEST(CRcvBuffer, AddData) { const int buffer_size_pkts = 16; CUnitQueue unit_queue; unit_queue.init(buffer_size_pkts, 1500, AF_INET); CRcvBuffer rcv_buffer(&unit_queue, buffer_size_pkts); EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - 1); // logic const size_t payload_size = 1456; // Add 10 units (packets) to the buffer for (int i = 0; i < 10; ++i) { CUnit* unit = unit_queue.getNextAvailUnit(); EXPECT_NE(unit, nullptr); unit->m_Packet.setLength(payload_size); EXPECT_EQ(rcv_buffer.addData(unit, i), 0); } // The available buffer size remains the same // The value is reported by SRT receiver like this: // data[ACKD_BUFFERLEFT] = m_pRcvBuffer->getAvailBufSize(); EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - 1); EXPECT_FALSE(rcv_buffer.isRcvDataAvailable()); // Now acknowledge two packets const int ack_pkts = 2; rcv_buffer.ackData(2); EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - 1 - ack_pkts); EXPECT_TRUE(rcv_buffer.isRcvDataAvailable()); std::array buff; for (int i = 0; i < ack_pkts; ++i) { const int res = rcv_buffer.readBuffer(buff.data(), buff.size()); EXPECT_EQ(size_t(res), payload_size); EXPECT_EQ(rcv_buffer.getAvailBufSize(), buffer_size_pkts - ack_pkts + i); } // Add packet to the same position CUnit* unit = unit_queue.getNextAvailUnit(); EXPECT_NE(unit, nullptr); unit->m_Packet.setLength(payload_size); EXPECT_EQ(rcv_buffer.addData(unit, 1), -1); } srt-1.4.4/test/test_common.cpp000066400000000000000000000035121412557703600163550ustar00rootroot00000000000000#include #include #include "gtest/gtest.h" #include "utilities.h" #include "common.h" void test_cipaddress_pton(const char* peer_ip, int family, const uint32_t (&ip)[4]) { const int port = 4200; // Peer sockaddr_storage ss; ss.ss_family = family; void* sin_addr = nullptr; if (family == AF_INET) { sockaddr_in* const sa = (sockaddr_in*)&ss; sa->sin_port = htons(port); sin_addr = &sa->sin_addr; } else // IPv6 { sockaddr_in6* const sa = (sockaddr_in6*)&ss; sa->sin6_port = htons(port); sin_addr = &sa->sin6_addr; } ASSERT_EQ(inet_pton(family, peer_ip, sin_addr), 1); const sockaddr_any peer(ss); // HOST sockaddr_any host(family); host.hport(port); CIPAddress::pton(host, ip, peer); EXPECT_EQ(peer, host) << "Peer " << peer.str() << " host " << host.str(); } // Example IPv4 address: 192.168.0.1 TEST(CIPAddress, IPv4_pton) { const char* peer_ip = "192.168.0.1"; const uint32_t ip[4] = {htobe32(0xC0A80001), 0, 0, 0}; test_cipaddress_pton(peer_ip, AF_INET, ip); } // Example IPv6 address: 2001:db8:85a3:8d3:1319:8a2e:370:7348 TEST(CIPAddress, IPv6_pton) { const char* peer_ip = "2001:db8:85a3:8d3:1319:8a2e:370:7348"; const uint32_t ip[4] = {htobe32(0x20010db8), htobe32(0x85a308d3), htobe32(0x13198a2e), htobe32(0x03707348)}; test_cipaddress_pton(peer_ip, AF_INET6, ip); } // Example IPv4 address: 192.168.0.1 // Maps to IPv6 address: 0:0:0:0:0:FFFF:192.168.0.1 // Simplified: ::FFFF:192.168.0.1 TEST(CIPAddress, IPv4_in_IPv6_pton) { const char* peer_ip = "::ffff:192.168.0.1"; const uint32_t ip[4] = {0, 0, htobe32(0x0000FFFF), htobe32(0xC0A80001)}; test_cipaddress_pton(peer_ip, AF_INET6, ip); } srt-1.4.4/test/test_connection_timeout.cpp000066400000000000000000000170531412557703600207770ustar00rootroot00000000000000#include #include #ifdef _WIN32 #define INC_SRT_WIN_WINTIME // exclude gettimeofday from srt headers #else typedef int SOCKET; #define INVALID_SOCKET ((SOCKET)-1) #define closesocket close #endif #include"platform_sys.h" #include "srt.h" using namespace std; class TestConnectionTimeout : public ::testing::Test { protected: TestConnectionTimeout() { // initialization code here } ~TestConnectionTimeout() { // cleanup any pending stuff, but no exceptions allowed } protected: // SetUp() is run immediately before a test starts. void SetUp() override { ASSERT_EQ(srt_startup(), 0); m_sa.sin_family = AF_INET; m_sa.sin_addr.s_addr = INADDR_ANY; m_udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ASSERT_NE(m_udp_sock, -1); // Find unused a port not used by any other service. // Otherwise srt_connect may actually connect. int bind_res = -1; const sockaddr* psa = reinterpret_cast(&m_sa); for (int port = 5000; port <= 5555; ++port) { m_sa.sin_port = htons(port); bind_res = ::bind(m_udp_sock, psa, sizeof m_sa); if (bind_res >= 0) { cerr << "Running test on port " << port << "\n"; break; } } ASSERT_GE(bind_res, 0); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &m_sa.sin_addr), 1); } void TearDown() override { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. ASSERT_NE(closesocket(m_udp_sock), -1); srt_cleanup(); } protected: SOCKET m_udp_sock = INVALID_SOCKET; sockaddr_in m_sa = sockaddr_in(); }; /** * The test creates a socket and tries to connect to a localhost port 5555 * in a non-blocking mode. This means we wait on epoll for a notification * about SRT_EPOLL_OUT | SRT_EPOLL_ERR events on the socket calling srt_epoll_wait(...). * The test expects a connection timeout to happen within the time, * set with SRTO_CONNTIMEO (500 ms). * The expected behavior is to return from srt_epoll_wait(...) * * @remarks Inspired by Max Tomilov (maxtomilov) in issue #468 */ TEST_F(TestConnectionTimeout, Nonblocking) { const SRTSOCKET client_sock = srt_create_socket(); ASSERT_GT(client_sock, 0); // socket_id should be > 0 // First let's check the default connection timeout value. // It should be 3 seconds (3000 ms) int conn_timeout = 0; int conn_timeout_len = sizeof conn_timeout; EXPECT_EQ(srt_getsockopt(client_sock, 0, SRTO_CONNTIMEO, &conn_timeout, &conn_timeout_len), SRT_SUCCESS); EXPECT_EQ(conn_timeout, 3000); // Set connection timeout to 500 ms to reduce the test execution time const int connection_timeout_ms = 300; EXPECT_EQ(srt_setsockopt(client_sock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), SRT_SUCCESS); const int yes = 1; const int no = 0; ASSERT_EQ(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_SUCCESS); // for async connect ASSERT_EQ(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_SUCCESS); // for async connect ASSERT_EQ(srt_setsockopt(client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_SUCCESS); ASSERT_EQ(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_SUCCESS); const int pollid = srt_epoll_create(); ASSERT_GE(pollid, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(srt_epoll_add_usock(pollid, client_sock, &epoll_out), SRT_ERROR); const sockaddr* psa = reinterpret_cast(&m_sa); ASSERT_NE(srt_connect(client_sock, psa, sizeof m_sa), SRT_ERROR); // Socket readiness for connection is checked by polling on WRITE allowed sockets. { int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; const chrono::steady_clock::time_point chrono_ts_start = chrono::steady_clock::now(); // Here we check the connection timeout. // Epoll timeout is set 100 ms greater than socket's TTL EXPECT_EQ(srt_epoll_wait(pollid, read, &rlen, write, &wlen, connection_timeout_ms + 100, // +100 ms 0, 0, 0, 0) /* Expected return value is 2. We have only 1 socket, but * sockets with exceptions are returned to both read and write sets. */ , 2); // Check the actual timeout const chrono::steady_clock::time_point chrono_ts_end = chrono::steady_clock::now(); const auto delta_ms = chrono::duration_cast(chrono_ts_end - chrono_ts_start).count(); // Confidence interval border : +/-80 ms EXPECT_LE(delta_ms, connection_timeout_ms + 80) << "Timeout was: " << delta_ms; EXPECT_GE(delta_ms, connection_timeout_ms - 80) << "Timeout was: " << delta_ms; EXPECT_EQ(rlen, 1); EXPECT_EQ(read[0], client_sock); EXPECT_EQ(wlen, 1); EXPECT_EQ(write[0], client_sock); } EXPECT_EQ(srt_epoll_remove_usock(pollid, client_sock), SRT_SUCCESS); EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); (void)srt_epoll_release(pollid); } /** * The test creates a socket and tries to connect to a localhost port 5555 * in a blocking mode. The srt_connect function is expected to return * SRT_ERROR, and the error_code should be SRT_ENOSERVER, meaning a * connection timeout. * This test is a regression test for an issue described in PR #833. * Under certain conditions m_bConnecting variable on a socket * might not be reset to false after a connection attempt has failed. * In that case any further call to srt_connect will return SRT_ECONNSOCK: * Operation not supported: Cannot do this operation on a CONNECTED socket * */ TEST_F(TestConnectionTimeout, BlockingLoop) { const SRTSOCKET client_sock = srt_create_socket(); ASSERT_GT(client_sock, 0); // socket_id should be > 0 // Set connection timeout to 999 ms to reduce the test execution time. // Also need to hit a time point between two threads: // srt_connect will check TTL every second, // CRcvQueue::worker will wait on a socket for 10 ms. // Need to have a condition, when srt_connect will process the timeout. const int connection_timeout_ms = 999; EXPECT_EQ(srt_setsockopt(client_sock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), SRT_SUCCESS); const sockaddr* psa = reinterpret_cast(&m_sa); for (int i = 0; i < 10; ++i) { const chrono::steady_clock::time_point chrono_ts_start = chrono::steady_clock::now(); EXPECT_EQ(srt_connect(client_sock, psa, sizeof m_sa), SRT_ERROR); const auto delta_ms = chrono::duration_cast(chrono::steady_clock::now() - chrono_ts_start).count(); // Confidence interval border : +/-200 ms EXPECT_LE(delta_ms, connection_timeout_ms + 200) << "Timeout was: " << delta_ms; EXPECT_GE(delta_ms, connection_timeout_ms - 200) << "Timeout was: " << delta_ms; const int error_code = srt_getlasterror(nullptr); EXPECT_EQ(error_code, SRT_ENOSERVER); if (error_code != SRT_ENOSERVER) { cerr << "Connection attempt no. " << i << " resulted with: " << error_code << " " << srt_getlasterror_str() << "\n"; break; } } EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); } srt-1.4.4/test/test_cryspr.cpp000066400000000000000000000617511412557703600164200ustar00rootroot00000000000000#include #include #include "gtest/gtest.h" #ifdef SRT_ENABLE_ENCRYPTION #include "common.h" #include "hcrypt.h" #include "version.h" #if (CRYSPR_VERSION_NUMBER >= 0x010100) #define WITH_FIPSMODE 1 /* 1: openssl-evp branch */ #endif #define UT_PKT_MAXLEN 1500 const void *nullPtr = NULL; /* TestCRYSPRmethods: Test presense of required cryspr methods */ class TestCRYSPRmethods : public ::testing::Test { protected: TestCRYSPRmethods() { // initialization code here cryspr_m = NULL; cryspr_fbm = NULL; } ~TestCRYSPRmethods() { // cleanup any pending stuff, but no exceptions allowed } // SetUp() is run immediately before a test starts. #if defined(__GNUC__) && (__GNUC___ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) // override only supported for GCC>=4.7 void SetUp() override { #else void SetUp() { #endif cryspr_m = cryspr4SRT(); cryspr_fbm = crysprInit(&cryspr_fb); ASSERT_NE(cryspr_m, nullPtr); ASSERT_NE(cryspr_fbm, nullPtr); ASSERT_EQ(cryspr_fbm, &cryspr_fb); } #if defined(__GNUC__) && (__GNUC___ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) void TearDown() override { #else void TearDown() { #endif // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. } protected: CRYSPR_methods *cryspr_m; /* methods */ CRYSPR_methods cryspr_fb, *cryspr_fbm; /* fall back methods */ // CRYSPR_cb *cryspr_cb; /* Control block */ }; TEST_F(TestCRYSPRmethods, MethodOpen) { EXPECT_NE(cryspr_m, nullPtr); EXPECT_NE(cryspr_m->open, nullPtr); } TEST_F(TestCRYSPRmethods, init) { ASSERT_NE(cryspr_m, nullPtr); } #if WITH_FIPSMODE TEST_F(TestCRYSPRmethods, fipsmode) { if(cryspr_m->fips_mode_set == NULL || cryspr_m->fips_mode_set == cryspr_fbm->fips_mode_set ) { #if defined(CRYSPR_FIPSMODE) //undef: not supported, 0: supported and Off by default, 1: enabled by default EXPECT_NE(cryspr_m->fips_mode_set, cryspr_fbm->fips_mode_set); //Fallback method cannot set FIPS mode EXPECT_EQ(cryspr_m->fips_mode_set(CRYSPR_FIPSMODE ? 0 : 1), CRYSPR_FIPSMODE); EXPECT_EQ(cryspr_m->fips_mode_set(CRYSPR_FIPSMODE), (CRYSPR_FIPSMODE? 0 : 1)); #endif /* CRYSPR_FIPSMODE */ } } #endif /* WITH_FIPSMODE */ TEST_F(TestCRYSPRmethods, open) { EXPECT_NE(cryspr_m->open, nullPtr); } TEST_F(TestCRYSPRmethods, close) { EXPECT_NE(cryspr_m->close, nullPtr); } TEST_F(TestCRYSPRmethods, prng) { EXPECT_NE(cryspr_m->prng, nullPtr); } TEST_F(TestCRYSPRmethods, aes_set_key) { EXPECT_NE(cryspr_m->aes_set_key, nullPtr); } TEST_F(TestCRYSPRmethods, AESecb) { if(cryspr_m->km_wrap == cryspr_fbm->km_wrap) { /* fallback KM_WRAP method used * AES-ECB method then required */ EXPECT_NE(cryspr_m->aes_ecb_cipher, nullPtr); EXPECT_NE(cryspr_m->aes_ecb_cipher, cryspr_fbm->aes_ecb_cipher); } } TEST_F(TestCRYSPRmethods, AESctr) { EXPECT_NE(cryspr_m->aes_ctr_cipher, nullPtr); } TEST_F(TestCRYSPRmethods, SHA1) { if(cryspr_m->sha1_msg_digest == NULL || cryspr_m->km_pbkdf2 == cryspr_fbm->km_pbkdf2 ) { /* fallback PBKDF2 used * then sha1 method required. */ EXPECT_NE(cryspr_m->sha1_msg_digest, nullPtr); EXPECT_NE(cryspr_m->sha1_msg_digest, cryspr_fbm->sha1_msg_digest); } } /* CRYSPR control block test */ class TestCRYSPRcypto : public ::testing::Test { protected: TestCRYSPRcypto() { // initialization code here cryspr_m = NULL; cryspr_fbm = NULL; cryspr_cb = NULL; } ~TestCRYSPRcypto() { // cleanup any pending stuff, but no exceptions allowed } // SetUp() is run immediately before a test starts. #if defined(__GNUC__) && (__GNUC___ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) // override only supported for GCC>=4.7 void SetUp() override { #else void SetUp() { #endif cryspr_m = cryspr4SRT(); cryspr_fbm = crysprInit(&cryspr_fb); ASSERT_NE(cryspr_m, nullPtr); ASSERT_NE(cryspr_fbm, nullPtr); ASSERT_EQ(cryspr_fbm, &cryspr_fb); cryspr_cb = cryspr_m->open(cryspr_m, UT_PKT_MAXLEN); ASSERT_NE(cryspr_cb, nullPtr); } #if defined(__GNUC__) && (__GNUC___ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) void TearDown() override { #else void TearDown() { #endif // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. if (cryspr_m && cryspr_cb) { EXPECT_EQ(cryspr_m->close(cryspr_cb), 0); } } protected: CRYSPR_methods *cryspr_m; /* methods */ CRYSPR_methods cryspr_fb, *cryspr_fbm; /* fall back methods */ CRYSPR_cb *cryspr_cb; /* Control block */ }; TEST_F(TestCRYSPRcypto, CtrlBlock) { EXPECT_EQ(cryspr_m, cryspr_cb->cryspr); //methods set in control block } /*PBKDF2-----------------------------------------------------------------------------------------*/ /* See https://asecuritysite.com/encryption/PBKDF2z to generate "known good" PBKDF2 hash */ /* Test Vector 1.1 to 1.3 */ struct UTVcryspr_pbkdf2 { const char *name; const char *passwd; const char *salt; int itr; size_t keklen; unsigned char kek[256/8]; }; /* PBKDF2 test vectors */ struct UTVcryspr_pbkdf2 pbkdf2_tv[] = { {//[0] /* testname */ "PBKDF2 tv1.128", /* passwd */ "000000000000", /* salt */ "00000000", /* iteration */ 2048, /* keklen */ 128/8, /* kek */ {0xb6,0xbf,0x5f,0x0c,0xdd,0x25,0xe8,0x58,0x23,0xfd,0x84,0x7a,0xb2,0xb6,0x7f,0x79} }, {//[1] /* testname */ "PBKDF2 tv1.192", /* passwd */ "000000000000", /* salt */ "00000000", /* iteration */ 2048, /* keklen */ 192/8, /* kek */ {0xb6,0xbf,0x5f,0x0c,0xdd,0x25,0xe8,0x58,0x23,0xfd,0x84,0x7a,0xb2,0xb6,0x7f,0x79, 0x90,0xab,0xca,0x6e,0xf0,0x02,0xf1,0xad} }, {//[2] /* testname */ "PBKDF2 tv1.256", /* passwd */ "000000000000", /* salt */ "00000000", /* iteration */ 2048, /* keklen */ 256/8, /* kek */ {0xb6,0xbf,0x5f,0x0c,0xdd,0x25,0xe8,0x58,0x23,0xfd,0x84,0x7a,0xb2,0xb6,0x7f,0x79, 0x90,0xab,0xca,0x6e,0xf0,0x02,0xf1,0xad,0x19,0x59,0xcf,0x18,0xac,0x91,0x53,0x3d} }, {//[3] /* testname */ "PBKDF2 tv2.1", /* passwd */ "password", /* salt */ "salt", /* iteration */ 1, /* keklen */ 20, /* kek */ {0x0c,0x60,0xc8,0x0f,0x96,0x1f,0x0e,0x71,0xf3,0xa9,0xb5,0x24,0xaf,0x60,0x12,0x06, 0x2f,0xe0,0x37,0xa6} }, {//[4] /* testname */ "PBKDF2 tv2.20", /* passwd */ "password", /* salt */ "salt", /* iteration */ 2, /* keklen */ 20, /* kek */ {0xea,0x6c,0x01,0x4d,0xc7,0x2d,0x6f,0x8c,0xcd,0x1e,0xd9,0x2a,0xce,0x1d,0x41,0xf0, 0xd8,0xde,0x89,0x57} }, {//[5] /* testname */ "PBKDF2 tv2.4096", /* passwd */ "password", /* salt */ "salt", /* iteration */ 4096, /* keklen */ 20, /* kek */ {0x4b,0x00,0x79,0x01,0xb7,0x65,0x48,0x9a,0xbe,0xad,0x49,0xd9,0x26,0xf7,0x21,0xd0, 0x65,0xa4,0x29,0xc1} }, {//[6] /* testname */ "PBKDF2 tv3.0", /* passwd */ "passwordPASSWORDpassword", /* salt */ "saltSALTsaltSALTsaltSALTsaltSALTsalt", /* iteration */ 4096, /* keklen */ 25, /* kek */ {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} }, }; void test_pbkdf2( CRYSPR_methods *cryspr_m, CRYSPR_cb *cryspr_cb, size_t tvi) //test vector index { unsigned char kek[256/8]; if(tvi < sizeof(pbkdf2_tv)/sizeof(pbkdf2_tv[0])) { struct UTVcryspr_pbkdf2 *tv = &pbkdf2_tv[tvi]; ASSERT_NE(cryspr_m->km_pbkdf2, nullPtr); cryspr_m->km_pbkdf2( cryspr_cb, (char *)tv->passwd, /* passphrase */ strnlen(tv->passwd, 80), /* passphrase len */ (unsigned char *)tv->salt, /* salt */ strnlen(tv->salt, 80), /* salt_len */ tv->itr, /* iterations */ tv->keklen, /* desired key len {(}16,24,32}*/ kek); /* derived key */ EXPECT_EQ(memcmp(kek, tv->kek, tv->keklen),0); } } TEST_F(TestCRYSPRcypto, PBKDF2_tv1_k128) { test_pbkdf2(cryspr_m, cryspr_cb, 0); } TEST_F(TestCRYSPRcypto, PBKDF2_tv1_k192) { test_pbkdf2(cryspr_m, cryspr_cb, 1); } TEST_F(TestCRYSPRcypto, PBKDF2_tv1_k256) { test_pbkdf2(cryspr_m, cryspr_cb, 2); } TEST_F(TestCRYSPRcypto, PBKDF2_tv2_i1) { test_pbkdf2(cryspr_m, cryspr_cb, 3); } TEST_F(TestCRYSPRcypto, PBKDF2_tv2_i20) { test_pbkdf2(cryspr_m, cryspr_cb, 4); } TEST_F(TestCRYSPRcypto, PBKDF2_tv2_i4096) { test_pbkdf2(cryspr_m, cryspr_cb, 5); } TEST_F(TestCRYSPRcypto, PBKDF2_tv3_0) { test_pbkdf2(cryspr_m, cryspr_cb, 6); } /*AES KeyWrap -----------------------------------------------------------------------------------*/ struct UTVcryspr_km_wrap { const char *name; unsigned char sek[256/8]; /* key to wrap (unwrap result)*/ size_t seklen; unsigned char kek[256/8]; unsigned char wrap[8+256/8]; /* wrapped sek (wrap result) */ }; /* KMWRAP/KMUNWRAP test vectors */ struct UTVcryspr_km_wrap UTV_cryspr_km_wrap[] = { {//[0] /*name */ "tv1.128", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 128/8, /* kek */ {0xb6,0xbf,0x5f,0x0c,0xdd,0x25,0xe8,0x58,0x23,0xfd,0x84,0x7a,0xb2,0xb6,0x7f,0x79}, /* wrap */ {0xF8,0xB6,0x12,0x1B,0xF2,0x03,0x62,0x40,0x80,0x32,0x60,0x8D,0xED,0x0B,0x8E,0x4B, 0x29,0x7E,0x80,0x17,0x4E,0x89,0x68,0xF1} }, {//[1] /*name */ "tv1.192", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 192/8, /* kek */ {0xb6,0xbf,0x5f,0x0c,0xdd,0x25,0xe8,0x58,0x23,0xfd,0x84,0x7a,0xb2,0xb6,0x7f,0x79, 0x90,0xab,0xca,0x6e,0xf0,0x02,0xf1,0xad}, /* wrap */ {0xC1,0xA6,0x58,0x9E,0xC0,0x52,0x6D,0x37,0x84,0x3C,0xBD,0x3B,0x02,0xDD,0x79,0x3F, 0xE6,0x14,0x2D,0x81,0x69,0x4B,0x8E,0x07,0x26,0x4F,0xCD,0x86,0xD6,0x6A,0x70,0x62}, }, {//[2] /*name */ "tv1.256", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 256/8, /* kek */ {0xb6,0xbf,0x5f,0x0c,0xdd,0x25,0xe8,0x58,0x23,0xfd,0x84,0x7a,0xb2,0xb6,0x7f,0x79, 0x90,0xab,0xca,0x6e,0xf0,0x02,0xf1,0xad,0x19,0x59,0xcf,0x18,0xac,0x91,0x53,0x3d}, /* wrap */ {0x94,0xBE,0x9C,0xA6,0x7A,0x27,0x20,0x56,0xED,0xEA,0xA0,0x8F,0x71,0xB1,0xF1,0x85, 0xF6,0xC5,0x67,0xF4,0xA9,0xC2,0x1E,0x78,0x49,0x36,0xA5,0xAE,0x60,0xD0,0x1C,0x30, 0x68,0x27,0x4F,0x66,0x56,0x5A,0x55,0xAA}, }, }; void test_kmwrap( CRYSPR_methods *cryspr_m, CRYSPR_cb *cryspr_cb, size_t tvi) //Test vector index { unsigned char wrap[HAICRYPT_WRAPKEY_SIGN_SZ+256/8]; int rc1,rc2; if (tvi < sizeof(UTV_cryspr_km_wrap)/sizeof(UTV_cryspr_km_wrap[0])) { struct UTVcryspr_km_wrap *tv = &UTV_cryspr_km_wrap[tvi]; size_t wraplen=HAICRYPT_WRAPKEY_SIGN_SZ+tv->seklen; if(cryspr_m && cryspr_cb) { ASSERT_NE(cryspr_m->km_setkey, nullPtr); ASSERT_NE(cryspr_m->km_wrap, nullPtr); rc1 = cryspr_m->km_setkey( cryspr_cb, true, //Wrap tv->kek, tv->seklen); rc2 = cryspr_m->km_wrap( cryspr_cb, wrap, tv->sek, tv->seklen); ASSERT_EQ(rc1, 0); ASSERT_EQ(rc2, 0); EXPECT_EQ(memcmp(tv->wrap, wrap, wraplen), 0); } } } void test_kmunwrap( CRYSPR_methods *cryspr_m, CRYSPR_cb *cryspr_cb, size_t tvi) //Test vector index { unsigned char sek[256/8]; int rc1,rc2; if(tvi < sizeof(UTV_cryspr_km_wrap)/sizeof(UTV_cryspr_km_wrap[0])) { struct UTVcryspr_km_wrap *tv = &UTV_cryspr_km_wrap[tvi]; size_t wraplen=HAICRYPT_WRAPKEY_SIGN_SZ+tv->seklen; if(cryspr_m && cryspr_cb) { ASSERT_NE(cryspr_m->km_setkey, nullPtr); ASSERT_NE(cryspr_m->km_unwrap, nullPtr); rc1 = cryspr_m->km_setkey( cryspr_cb, false, //Unwrap tv->kek, tv->seklen); rc2 = cryspr_m->km_unwrap( cryspr_cb, sek, tv->wrap, wraplen); ASSERT_EQ(rc1, 0); ASSERT_EQ(rc2, 0); EXPECT_EQ(memcmp(tv->sek, sek, tv->seklen), 0); } } } TEST_F(TestCRYSPRcypto, KMWRAP_tv1_k128) { test_kmwrap(cryspr_m, cryspr_cb, 0); } TEST_F(TestCRYSPRcypto, KMWRAP_tv1_k192) { test_kmwrap(cryspr_m, cryspr_cb, 1); } TEST_F(TestCRYSPRcypto, KMWRAP_tv1_k256) { test_kmwrap(cryspr_m, cryspr_cb, 2); } TEST_F(TestCRYSPRcypto, KMUNWRAP_tv1_k128) { test_kmunwrap(cryspr_m, cryspr_cb, 0); } TEST_F(TestCRYSPRcypto, KMUNWRAP_tv1_k192) { test_kmunwrap(cryspr_m, cryspr_cb, 1); } TEST_F(TestCRYSPRcypto, KMUNWRAP_tv1_k256) { test_kmunwrap(cryspr_m, cryspr_cb, 2); } /*AES ECB -----------------------------------------------------------------------------------*/ #if !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP) /* AES-ECB test vectors */ struct UTVcryspr_aes_ecb { const char *name; unsigned char sek[256/8]; /* Stream Encrypting Key*/ size_t seklen; const char *cleartxt; /* clear text (decrypt result0 */ unsigned char ciphertxt[32]; /* cipher text (encrypt result) */ size_t outlen; }; struct UTVcryspr_aes_ecb UTV_cryspr_aes_ecb[] = { {//[0] /*name */ "tv1.128", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 128/8, /* cleartxt */ "0000000000000000", /* ciphertxt */ {0xE0,0x86,0x82,0xBE,0x5F,0x2B,0x18,0xA6,0xE8,0x43,0x7A,0x15,0xB1,0x10,0xD4,0x18}, /* cipherlen */ 16, }, {//[1] /*name */ "tv1.192", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 192/8, /* cleartxt */ "0000000000000000", /* ciphertxt */ {0xCC,0xFE,0xD9,0x9E,0x38,0xE9,0x60,0xF5,0xD7,0xE1,0xC5,0x9F,0x56,0x3A,0x49,0x9D}, /* cipherlen */ 16, }, {//[2] /*name */ "tv1.256", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 256/8, /* cleartxt */ "0000000000000000", /* ciphertxt */ {0x94,0xB1,0x3A,0x9F,0x4C,0x09,0xD4,0xD7,0x00,0x2C,0x3F,0x11,0x7D,0xB1,0x7C,0x8B}, /* cipherlen */ 16, }, {//[3] /*name */ "tv2.128", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 128/8, /* cleartxt */ "00000000000000000000000000000000", /* ciphertxt */ {0xE0,0x86,0x82,0xBE,0x5F,0x2B,0x18,0xA6,0xE8,0x43,0x7A,0x15,0xB1,0x10,0xD4,0x18, 0xE0,0x86,0x82,0xBE,0x5F,0x2B,0x18,0xA6,0xE8,0x43,0x7A,0x15,0xB1,0x10,0xD4,0x18}, /* cipherlen */ 32, }, {//[4] /*name */ "tv2.192", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 192/8, /* cleartxt */ "00000000000000000000000000000000", /* ciphertxt */ {0xCC,0xFE,0xD9,0x9E,0x38,0xE9,0x60,0xF5,0xD7,0xE1,0xC5,0x9F,0x56,0x3A,0x49,0x9D, 0xCC,0xFE,0xD9,0x9E,0x38,0xE9,0x60,0xF5,0xD7,0xE1,0xC5,0x9F,0x56,0x3A,0x49,0x9D}, /* cipherlen */ 32, }, {//[5] /*name */ "tv2.256", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 256/8, /* cleartxt */ "00000000000000000000000000000000", /* ciphertxt */ {0x94,0xB1,0x3A,0x9F,0x4C,0x09,0xD4,0xD7,0x00,0x2C,0x3F,0x11,0x7D,0xB1,0x7C,0x8B, 0x94,0xB1,0x3A,0x9F,0x4C,0x09,0xD4,0xD7,0x00,0x2C,0x3F,0x11,0x7D,0xB1,0x7C,0x8B}, /* cipherlen */ 32, }, }; void test_AESecb( CRYSPR_methods *cryspr_m, CRYSPR_cb *cryspr_cb, size_t tvi, bool bEncrypt) { unsigned char result[128]; unsigned char *intxt; unsigned char *outtxt; int rc1,rc2; if(tvi < sizeof(UTV_cryspr_aes_ecb)/sizeof(UTV_cryspr_aes_ecb[0])) { struct UTVcryspr_aes_ecb *tv = &UTV_cryspr_aes_ecb[tvi]; size_t txtlen=strnlen((const char *)tv->cleartxt, 100); size_t outlen=sizeof(result); ASSERT_NE(cryspr_m->aes_set_key, nullPtr); ASSERT_NE(cryspr_m->aes_ecb_cipher, nullPtr); rc1 = cryspr_m->aes_set_key( bEncrypt, tv->sek, /* Stream encrypting Key */ tv->seklen, #if WITH_FIPSMODE cryspr_cb->aes_sek[0]); #else &cryspr_cb->aes_sek[0]); #endif if(bEncrypt) { intxt=(unsigned char *)tv->cleartxt; outtxt=(unsigned char *)tv->ciphertxt; }else{ intxt=(unsigned char *)tv->ciphertxt; outtxt=(unsigned char *)tv->cleartxt; } rc2 = cryspr_m->aes_ecb_cipher( bEncrypt, /* true:encrypt, false:decrypt */ #if WITH_FIPSMODE cryspr_cb->aes_sek[0], /* CRYpto Service PRovider AES Key context */ #else &cryspr_cb->aes_sek[0], /* CRYpto Service PRovider AES Key context */ #endif intxt, /* src */ txtlen, /* length */ result, /* dest */ &outlen); /* dest length */ ASSERT_EQ(rc1, 0); ASSERT_EQ(rc2, 0); ASSERT_EQ(outlen, ((txtlen+(CRYSPR_AESBLKSZ-1))/CRYSPR_AESBLKSZ)*CRYSPR_AESBLKSZ); EXPECT_EQ(memcmp(outtxt, result, txtlen), 0); } } #define ENCRYPT true #define DECRYPT false TEST_F(TestCRYSPRcypto, EncryptAESecb_tv1_128) { test_AESecb(cryspr_m, cryspr_cb, 0, ENCRYPT); } TEST_F(TestCRYSPRcypto, EncryptAESecb_tv1_192) { test_AESecb(cryspr_m, cryspr_cb, 1, ENCRYPT); } TEST_F(TestCRYSPRcypto, EncryptAESecb_tv1_256) { test_AESecb(cryspr_m, cryspr_cb, 2, ENCRYPT); } TEST_F(TestCRYSPRcypto, EncryptAESecb_tv2_128) { test_AESecb(cryspr_m, cryspr_cb, 3, ENCRYPT); } TEST_F(TestCRYSPRcypto, EncryptAESecb_tv2_192) { test_AESecb(cryspr_m, cryspr_cb, 4, ENCRYPT); } TEST_F(TestCRYSPRcypto, EncryptAESecb_tv2_256) { test_AESecb(cryspr_m, cryspr_cb, 5, ENCRYPT); } TEST_F(TestCRYSPRcypto, DecryptAESecb_tv1_128) { test_AESecb(cryspr_m, cryspr_cb, 0, DECRYPT); } TEST_F(TestCRYSPRcypto, DecryptAESecb_tv1_192) { test_AESecb(cryspr_m, cryspr_cb, 1, DECRYPT); } TEST_F(TestCRYSPRcypto, DecryptAESecb_tv1_256) { test_AESecb(cryspr_m, cryspr_cb, 2, DECRYPT); } TEST_F(TestCRYSPRcypto, DecryptAESecb_tv2_128) { test_AESecb(cryspr_m, cryspr_cb, 3, DECRYPT); } TEST_F(TestCRYSPRcypto, DecryptAESecb_tv2_192) { test_AESecb(cryspr_m, cryspr_cb, 4, DECRYPT); } TEST_F(TestCRYSPRcypto, DecryptAESecb_tv2_256) { test_AESecb(cryspr_m, cryspr_cb, 5, DECRYPT); } #endif /* !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP) */ /*AES CTR -----------------------------------------------------------------------------------*/ #if CRYSPR_HAS_AESCTR struct UTVcryspr_aes_ctr { const char *name; unsigned char sek[256/8]; /* Stream Encrypting Key*/ size_t seklen; unsigned char iv[CRYSPR_AESBLKSZ];/* initial vector */ const char *cleartxt; /* clear text (decrypt result0 */ unsigned char ciphertxt[24]; /* cipher text (encrypt result) */ }; /* AES-CTR test vectors */ struct UTVcryspr_aes_ctr UTV_cryspr_aes_ctr[] = { {//[0] /*name */ "tv1.128", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 128/8, /* iv */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* cleartxt */ "000000000000000000000000", /* ciphertxt */ {0x56,0xD9,0x7B,0xE4,0xDF,0xBA,0x1C,0x0B,0xB8,0x7C,0xCA,0x69,0xFA,0x04,0x1B,0x1E, 0x68,0xD2,0xCC,0xFE,0xCA,0x4E,0x00,0x51}, }, {//[1] /*name */ "tv1.192", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 192/8, /* iv */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* cleartxt */ "000000000000000000000000", /* ciphertxt */ {0x9A,0xD0,0x59,0xA2,0x9C,0x8F,0x62,0x93,0xD8,0xC4,0x99,0x5E,0xF9,0x00,0x3B,0xE7, 0xFD,0x03,0x82,0xBA,0xF7,0x43,0xC7,0x7B}, }, {//[2] /*name */ "tv1.256", /* sek */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* seklen */ 256/8, /* iv */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* cleartxt */ "000000000000000000000000", /* ciphertxt */ {0xEC,0xA5,0xF0,0x48,0x92,0x70,0xB9,0xB9,0x9D,0x78,0x92,0x24,0xA2,0xB4,0x10,0xB7, 0x63,0x3F,0xBA,0xCB,0xF7,0x75,0x06,0x89} }, }; void test_AESctr( CRYSPR_methods *cryspr_m, CRYSPR_cb *cryspr_cb, size_t tvi, bool bEncrypt) { unsigned char result[100]; unsigned char ivec[CRYSPR_AESBLKSZ]; unsigned char *intxt; unsigned char *outtxt; int rc1,rc2; if(tvi < sizeof(UTV_cryspr_aes_ctr)/sizeof(UTV_cryspr_aes_ctr[0])) { struct UTVcryspr_aes_ctr *tv = &UTV_cryspr_aes_ctr[tvi]; size_t txtlen=strnlen((const char *)tv->cleartxt, 100); ASSERT_NE(cryspr_m->aes_set_key, nullPtr); ASSERT_NE(cryspr_m->aes_ctr_cipher, nullPtr); rc1 = cryspr_m->aes_set_key( true, //For CTR, Encrypt key is used for both encryption and decryption tv->sek, /* Stream encrypting Key */ tv->seklen, #if WITH_FIPSMODE cryspr_cb->aes_sek[0]); #else &cryspr_cb->aes_sek[0]); #endif if(bEncrypt) { intxt=(unsigned char *)tv->cleartxt; outtxt=(unsigned char *)tv->ciphertxt; }else{ intxt=(unsigned char *)tv->ciphertxt; outtxt=(unsigned char *)tv->cleartxt; } memcpy(ivec, tv->iv, sizeof(ivec)); //cipher ivec not const rc2 = cryspr_m->aes_ctr_cipher( bEncrypt, /* true:encrypt, false:decrypt */ #if WITH_FIPSMODE cryspr_cb->aes_sek[0], /* CRYpto Service PRovider AES Key context */ #else &cryspr_cb->aes_sek[0], /* CRYpto Service PRovider AES Key context */ #endif ivec, /* iv */ intxt, /* src */ txtlen, /* length */ result); /* dest */ ASSERT_EQ(rc1, 0); ASSERT_EQ(rc2, 0); EXPECT_EQ(memcmp(outtxt, result, txtlen), 0); } } #define ENCRYPT true #define DECRYPT false TEST_F(TestCRYSPRcypto, EncryptAESctr_tv1_128) { test_AESctr(cryspr_m, cryspr_cb, 0, ENCRYPT); } TEST_F(TestCRYSPRcypto, EncryptAESctr_tv1_192) { test_AESctr(cryspr_m, cryspr_cb, 1, ENCRYPT); } TEST_F(TestCRYSPRcypto, EncryptAESctr_tv1_256) { test_AESctr(cryspr_m, cryspr_cb, 2, ENCRYPT); } TEST_F(TestCRYSPRcypto, DecryptAESctr_tv1_128) { test_AESctr(cryspr_m, cryspr_cb, 0, DECRYPT); } TEST_F(TestCRYSPRcypto, DecryptAESctr_tv1_192) { test_AESctr(cryspr_m, cryspr_cb, 1, DECRYPT); } TEST_F(TestCRYSPRcypto, DecryptAESctr_tv1_256) { test_AESctr(cryspr_m, cryspr_cb, 2, DECRYPT); } #endif /* CRYSPR_HAS_AESCTR */ #endif /* SRT_ENABLE_ENCRYPTION */ srt-1.4.4/test/test_enforced_encryption.cpp000066400000000000000000000770201412557703600211310ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Written by: * Haivision Systems Inc. */ #include #include #include #include #include "srt.h" #include "sync.h" enum PEER_TYPE { PEER_CALLER = 0, PEER_LISTENER = 1, PEER_COUNT = 2, // Number of peers }; enum CHECK_SOCKET_TYPE { CHECK_SOCKET_CALLER = 0, CHECK_SOCKET_ACCEPTED = 1, CHECK_SOCKET_COUNT = 2, // Number of peers }; enum TEST_CASE { TEST_CASE_A_1 = 0, TEST_CASE_A_2, TEST_CASE_A_3, TEST_CASE_A_4, TEST_CASE_A_5, TEST_CASE_B_1, TEST_CASE_B_2, TEST_CASE_B_3, TEST_CASE_B_4, TEST_CASE_B_5, TEST_CASE_C_1, TEST_CASE_C_2, TEST_CASE_C_3, TEST_CASE_C_4, TEST_CASE_C_5, TEST_CASE_D_1, TEST_CASE_D_2, TEST_CASE_D_3, TEST_CASE_D_4, TEST_CASE_D_5, }; struct TestResultNonBlocking { int connect_ret; int accept_ret; int epoll_wait_ret; int epoll_event; int socket_state[CHECK_SOCKET_COUNT]; int km_state [CHECK_SOCKET_COUNT]; }; struct TestResultBlocking { int connect_ret; int accept_ret; int socket_state[CHECK_SOCKET_COUNT]; int km_state[CHECK_SOCKET_COUNT]; }; template struct TestCase { bool enforcedenc [PEER_COUNT]; const std::string (&password)[PEER_COUNT]; TResult expected_result; }; typedef TestCase TestCaseNonBlocking; typedef TestCase TestCaseBlocking; static const std::string s_pwd_a ("s!t@r#i$c^t"); static const std::string s_pwd_b ("s!t@r#i$c^tu"); static const std::string s_pwd_no(""); /* * TESTING SCENARIO * Both peers exchange HandShake v5. * Listener is sender in a non-blocking mode * Caller is receiver in a non-blocking mode * Cases B.2-B.4 are specific. Here we have incompatible password settings, but * listener accepts it, while caller rejects it. In this case we have a short-living * confusion state: The connection is accepted on the listener side, and the listener * sends back the conclusion handshake, but caller will reject it. * * Because of that, we should ignore what will happen in the listener as this is * just a matter of luck: if the listener thread is lucky, it will report the socket * to accept, so epoll will signal it and accept will report it, and moreover, further * good luck on this socket would make the state check return SRTS_CONNECTED. Without * this good luck, the caller might be quick enough to reject the handshake and send * the UMSG_SHUTDOWN packet to the peer. If it gets with it before acceptance, it will * withdraw the socket before it could be reported by accept. * * Still, we check predictable things here, so we accept two possibilities: * - The accepted socket wasn't reported at all * - The accepted socket was reported, and after `srt_connect` is done, it should turn to SRTS_BROKEN. * * This embraces both cases when the accepted socket was broken in the beginning, and when it was CONNECTED * in the beginning, but broke soon thereafter. * * This behavior is predicted and accepted - it's also the reason that setting ENFORCEDENC to false is * NOT RECOMMENDED on a listener socket that isn't intended to accept only connections from known callers * that are known to have set this flag also to false. * * In the cases C.2-C.4 it is the listener who rejects the connection, so we don't have an accepted socket * and the situation is always the same and clear in the beginning. The caller cannot continue with the * connection after listener accepted it, even if it tolerates incompatible password settings. */ const int IGNORE_EPOLL = -2; const int IGNORE_SRTS = -1; const TestCaseNonBlocking g_test_matrix_non_blocking[] = { // ENFORCEDENC | Password | | EPoll wait | socket_state | KM State // caller | listener | caller | listener | connect_ret accept_ret | ret | event | caller accepted | caller listener /*A.1 */ { {true, true }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, /*A.2 */ { {true, true }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, /*A.3 */ { {true, true }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, /*A.4 */ { {true, true }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, /*A.5 */ { {true, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*B.1 */ { {true, false }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, /*B.2 */ { {true, false }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, 0, IGNORE_EPOLL, 0, {SRTS_CONNECTING, SRTS_BROKEN}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, /*B.3 */ { {true, false }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, 0, IGNORE_EPOLL, 0, {SRTS_CONNECTING, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*B.4 */ { {true, false }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, 0, IGNORE_EPOLL, 0, {SRTS_CONNECTING, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_NOSECRET}}}, /*B.5 */ { {true, false }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*C.1 */ { {false, true }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, /*C.2 */ { {false, true }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, /*C.3 */ { {false, true }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, /*C.4 */ { {false, true }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, /*C.5 */ { {false, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*D.1 */ { {false, false }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, /*D.2 */ { {false, false }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, /*D.3 */ { {false, false }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*D.4 */ { {false, false }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_NOSECRET, SRT_KM_S_NOSECRET}}}, /*D.5 */ { {false, false }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, }; /* * TESTING SCENARIO * Both peers exchange HandShake v5. * Listener is sender in a blocking mode * Caller is receiver in a blocking mode * * In the cases B.2-B.4 the caller will reject the connection due to the enforced encryption check * of the HS response from the listener on the stage of the KM response check. * While the listener accepts the connection with the connected state. So the caller sends UMSG_SHUTDOWN * to notify the listener that it has closed the connection. The accepted socket gets the SRTS_BROKEN states. * For these cases a special accept_ret = -2 is used, that allows the accepted socket to be broken or already closed. * * In the cases C.2-C.4 it is the listener who rejects the connection, so we don't have an accepted socket. */ const TestCaseBlocking g_test_matrix_blocking[] = { // ENFORCEDENC | Password | | socket_state | KM State // caller | listener | caller | listener | connect_ret accept_ret | caller accepted | caller listener /*A.1 */ { {true, true }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, /*A.2 */ { {true, true }, {s_pwd_a, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, /*A.3 */ { {true, true }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, /*A.4 */ { {true, true }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, /*A.5 */ { {true, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*B.1 */ { {true, false }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, /*B.2 */ { {true, false }, {s_pwd_a, s_pwd_b}, { SRT_INVALID_SOCK, -2, {SRTS_OPENED, SRTS_BROKEN}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, /*B.3 */ { {true, false }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, -2, {SRTS_OPENED, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*B.4 */ { {true, false }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, -2, {SRTS_OPENED, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_NOSECRET}}}, /*B.5 */ { {true, false }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*C.1 */ { {false, true }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, /*C.2 */ { {false, true }, {s_pwd_a, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, /*C.3 */ { {false, true }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, /*C.4 */ { {false, true }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, /*C.5 */ { {false, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*D.1 */ { {false, false }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, /*D.2 */ { {false, false }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, /*D.3 */ { {false, false }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, /*D.4 */ { {false, false }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_NOSECRET, SRT_KM_S_NOSECRET}}}, /*D.5 */ { {false, false }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, }; class TestEnforcedEncryption : public ::testing::Test { protected: TestEnforcedEncryption() { // initialization code here } ~TestEnforcedEncryption() { // cleanup any pending stuff, but no exceptions allowed } protected: // SetUp() is run immediately before a test starts. void SetUp() { ASSERT_EQ(srt_startup(), 0); m_pollid = srt_epoll_create(); ASSERT_GE(m_pollid, 0); m_caller_socket = srt_create_socket(); ASSERT_NE(m_caller_socket, SRT_INVALID_SOCK); ASSERT_NE(srt_setsockflag(m_caller_socket, SRTO_SENDER, &s_yes, sizeof s_yes), SRT_ERROR); ASSERT_NE(srt_setsockopt (m_caller_socket, 0, SRTO_TSBPDMODE, &s_yes, sizeof s_yes), SRT_ERROR); m_listener_socket = srt_create_socket(); ASSERT_NE(m_listener_socket, SRT_INVALID_SOCK); ASSERT_NE(srt_setsockflag(m_listener_socket, SRTO_SENDER, &s_no, sizeof s_no), SRT_ERROR); ASSERT_NE(srt_setsockopt (m_listener_socket, 0, SRTO_TSBPDMODE, &s_yes, sizeof s_yes), SRT_ERROR); // Will use this epoll to wait for srt_accept(...) const int epoll_out = SRT_EPOLL_IN | SRT_EPOLL_ERR; ASSERT_NE(srt_epoll_add_usock(m_pollid, m_listener_socket, &epoll_out), SRT_ERROR); } void TearDown() { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. ASSERT_NE(srt_close(m_caller_socket), SRT_ERROR); ASSERT_NE(srt_close(m_listener_socket), SRT_ERROR); srt_cleanup(); } public: int SetEnforcedEncryption(PEER_TYPE peer, bool value) { const SRTSOCKET &socket = peer == PEER_CALLER ? m_caller_socket : m_listener_socket; return srt_setsockopt(socket, 0, SRTO_ENFORCEDENCRYPTION, value ? &s_yes : &s_no, sizeof s_yes); } bool GetEnforcedEncryption(PEER_TYPE peer_type) { const SRTSOCKET socket = peer_type == PEER_CALLER ? m_caller_socket : m_listener_socket; bool optval; int optlen = sizeof optval; EXPECT_EQ(srt_getsockopt(socket, 0, SRTO_ENFORCEDENCRYPTION, (void*)&optval, &optlen), SRT_SUCCESS); return optval ? true : false; } int SetPassword(PEER_TYPE peer_type, const std::basic_string &pwd) { const SRTSOCKET socket = peer_type == PEER_CALLER ? m_caller_socket : m_listener_socket; return srt_setsockopt(socket, 0, SRTO_PASSPHRASE, pwd.c_str(), (int) pwd.size()); } int GetKMState(SRTSOCKET socket) { int km_state = 0; int opt_size = sizeof km_state; EXPECT_EQ(srt_getsockopt(socket, 0, SRTO_KMSTATE, reinterpret_cast(&km_state), &opt_size), SRT_SUCCESS); return km_state; } int GetSocetkOption(SRTSOCKET socket, SRT_SOCKOPT opt) { int val = 0; int size = sizeof val; EXPECT_EQ(srt_getsockopt(socket, 0, opt, reinterpret_cast(&val), &size), SRT_SUCCESS); return val; } template int WaitOnEpoll(const TResult &expect); template const TestCase& GetTestMatrix(TEST_CASE test_case) const; template void TestConnect(TEST_CASE test_case/*, bool is_blocking*/) { const bool is_blocking = std::is_same::value; if (is_blocking) { ASSERT_NE(srt_setsockopt( m_caller_socket, 0, SRTO_RCVSYN, &s_yes, sizeof s_yes), SRT_ERROR); ASSERT_NE(srt_setsockopt( m_caller_socket, 0, SRTO_SNDSYN, &s_yes, sizeof s_yes), SRT_ERROR); ASSERT_NE(srt_setsockopt(m_listener_socket, 0, SRTO_RCVSYN, &s_yes, sizeof s_yes), SRT_ERROR); ASSERT_NE(srt_setsockopt(m_listener_socket, 0, SRTO_SNDSYN, &s_yes, sizeof s_yes), SRT_ERROR); } else { ASSERT_NE(srt_setsockopt( m_caller_socket, 0, SRTO_RCVSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode ASSERT_NE(srt_setsockopt( m_caller_socket, 0, SRTO_SNDSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode ASSERT_NE(srt_setsockopt(m_listener_socket, 0, SRTO_RCVSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode ASSERT_NE(srt_setsockopt(m_listener_socket, 0, SRTO_SNDSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode } // Prepare input state const TestCase &test = GetTestMatrix(test_case); ASSERT_EQ(SetEnforcedEncryption(PEER_CALLER, test.enforcedenc[PEER_CALLER]), SRT_SUCCESS); ASSERT_EQ(SetEnforcedEncryption(PEER_LISTENER, test.enforcedenc[PEER_LISTENER]), SRT_SUCCESS); ASSERT_EQ(SetPassword(PEER_CALLER, test.password[PEER_CALLER]), SRT_SUCCESS); ASSERT_EQ(SetPassword(PEER_LISTENER, test.password[PEER_LISTENER]), SRT_SUCCESS); const TResult &expect = test.expected_result; // Start testing volatile bool caller_done = false; sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5200); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); sockaddr* psa = (sockaddr*)&sa; ASSERT_NE(srt_bind(m_listener_socket, psa, sizeof sa), SRT_ERROR); ASSERT_NE(srt_listen(m_listener_socket, 4), SRT_ERROR); auto accepting_thread = std::thread([&] { const int epoll_event = WaitOnEpoll(expect); // In a blocking mode we expect a socket returned from srt_accept() if the srt_connect succeeded. // In a non-blocking mode we expect a socket returned from srt_accept() if the srt_connect succeeded, // otherwise SRT_INVALID_SOCKET after the listening socket is closed. sockaddr_in client_address; int length = sizeof(sockaddr_in); SRTSOCKET accepted_socket = -1; if (epoll_event == SRT_EPOLL_IN) { accepted_socket = srt_accept(m_listener_socket, (sockaddr*)&client_address, &length); std::cout << "ACCEPT: done, result=" << accepted_socket << std::endl; } else { std::cout << "ACCEPT: NOT done\n"; } if (accepted_socket == SRT_INVALID_SOCK) { std::cerr << "[T] ACCEPT ERROR: " << srt_getlasterror_str() << std::endl; } else { std::cerr << "[T] ACCEPT SUCCEEDED: @" << accepted_socket << "\n"; } EXPECT_NE(accepted_socket, 0); if (expect.accept_ret == SRT_INVALID_SOCK) { EXPECT_EQ(accepted_socket, SRT_INVALID_SOCK); } else if (expect.accept_ret != -2) { EXPECT_NE(accepted_socket, SRT_INVALID_SOCK); } if (accepted_socket != SRT_INVALID_SOCK && expect.socket_state[CHECK_SOCKET_ACCEPTED] != IGNORE_SRTS) { if (m_is_tracing) { std::cerr << "EARLY Socket state accepted: " << m_socket_state[srt_getsockstate(accepted_socket)] << " (expected: " << m_socket_state[expect.socket_state[CHECK_SOCKET_ACCEPTED]] << ")\n"; std::cerr << "KM State accepted: " << m_km_state[GetKMState(accepted_socket)] << '\n'; std::cerr << "RCV KM State accepted: " << m_km_state[GetSocetkOption(accepted_socket, SRTO_RCVKMSTATE)] << '\n'; std::cerr << "SND KM State accepted: " << m_km_state[GetSocetkOption(accepted_socket, SRTO_SNDKMSTATE)] << '\n'; } // We have to wait some time for the socket to be able to process the HS responce from the caller. // In test cases B2 - B4 the socket is expected to change its state from CONNECTED to BROKEN // due to KM mismatches do { std::this_thread::sleep_for(std::chrono::milliseconds(50)); } while (!caller_done); // Special case when the expected state is "broken": if so, tolerate every possible // socket state, just NOT LESS than SRTS_BROKEN, and also don't read any flags on that socket. if (expect.socket_state[CHECK_SOCKET_ACCEPTED] == SRTS_BROKEN) { EXPECT_GE(srt_getsockstate(accepted_socket), SRTS_BROKEN); } else { EXPECT_EQ(srt_getsockstate(accepted_socket), expect.socket_state[CHECK_SOCKET_ACCEPTED]); EXPECT_EQ(GetSocetkOption(accepted_socket, SRTO_SNDKMSTATE), expect.km_state[CHECK_SOCKET_ACCEPTED]); } if (m_is_tracing) { const SRT_SOCKSTATUS status = srt_getsockstate(accepted_socket); std::cerr << "LATE Socket state accepted: " << m_socket_state[status] << " (expected: " << m_socket_state[expect.socket_state[CHECK_SOCKET_ACCEPTED]] << ")\n"; } } }); const int connect_ret = srt_connect(m_caller_socket, psa, sizeof sa); EXPECT_EQ(connect_ret, expect.connect_ret); if (connect_ret == SRT_ERROR && connect_ret != expect.connect_ret) { std::cerr << "UNEXPECTED! srt_connect returned error: " << srt_getlasterror_str() << " (code " << srt_getlasterror(NULL) << ")\n"; } caller_done = true; if (is_blocking == false) accepting_thread.join(); if (m_is_tracing) { std::cerr << "Socket state caller: " << m_socket_state[srt_getsockstate(m_caller_socket)] << "\n"; std::cerr << "Socket state listener: " << m_socket_state[srt_getsockstate(m_listener_socket)] << "\n"; std::cerr << "KM State caller: " << m_km_state[GetKMState(m_caller_socket)] << '\n'; std::cerr << "RCV KM State caller: " << m_km_state[GetSocetkOption(m_caller_socket, SRTO_RCVKMSTATE)] << '\n'; std::cerr << "SND KM State caller: " << m_km_state[GetSocetkOption(m_caller_socket, SRTO_SNDKMSTATE)] << '\n'; std::cerr << "KM State listener: " << m_km_state[GetKMState(m_listener_socket)] << '\n'; } // If a blocking call to srt_connect() returned error, then the state is not valid, // but we still check it because we know what it should be. This way we may see potential changes in the core behavior. if (is_blocking) { EXPECT_EQ(srt_getsockstate(m_caller_socket), expect.socket_state[CHECK_SOCKET_CALLER]); } // A caller socket, regardless of the mode, if it's not expected to be connected, check negatively. if (expect.socket_state[CHECK_SOCKET_CALLER] == SRTS_CONNECTED) { EXPECT_EQ(srt_getsockstate(m_caller_socket), SRTS_CONNECTED); } else { // If the socket is not expected to be connected (might be CONNECTING), // then it is ok if it's CONNECTING or BROKEN. EXPECT_NE(srt_getsockstate(m_caller_socket), SRTS_CONNECTED); } EXPECT_EQ(GetSocetkOption(m_caller_socket, SRTO_RCVKMSTATE), expect.km_state[CHECK_SOCKET_CALLER]); EXPECT_EQ(srt_getsockstate(m_listener_socket), SRTS_LISTENING); EXPECT_EQ(GetKMState(m_listener_socket), SRT_KM_S_UNSECURED); if (is_blocking) { // srt_accept() has no timeout, so we have to close the socket and wait for the thread to exit. // Just give it some time and close the socket. std::this_thread::sleep_for(std::chrono::milliseconds(50)); ASSERT_NE(srt_close(m_listener_socket), SRT_ERROR); accepting_thread.join(); } } private: // put in any custom data members that you need SRTSOCKET m_caller_socket = SRT_INVALID_SOCK; SRTSOCKET m_listener_socket = SRT_INVALID_SOCK; int m_pollid = 0; const bool s_yes = true; const bool s_no = false; const bool m_is_tracing = false; static const char* m_km_state[]; static const char* const* m_socket_state; }; template<> int TestEnforcedEncryption::WaitOnEpoll(const TestResultBlocking &) { return SRT_EPOLL_IN; } static std::ostream& PrintEpollEvent(std::ostream& os, int events, int et_events) { using namespace std; static pair const namemap [] = { make_pair(SRT_EPOLL_IN, "R"), make_pair(SRT_EPOLL_OUT, "W"), make_pair(SRT_EPOLL_ERR, "E"), make_pair(SRT_EPOLL_UPDATE, "U") }; int N = Size(namemap); for (int i = 0; i < N; ++i) { if (events & namemap[i].first) { os << "["; if (et_events & namemap[i].first) os << "^"; os << namemap[i].second << "]"; } } return os; } template<> int TestEnforcedEncryption::WaitOnEpoll(const TestResultNonBlocking &expect) { const int default_len = 3; SRT_EPOLL_EVENT ready[default_len]; const int epoll_res = srt_epoll_uwait(m_pollid, ready, default_len, 500); std::cerr << "Epoll wait result: " << epoll_res; if (epoll_res > 0) { std::cerr << " FOUND: @" << ready[0].fd << " in "; PrintEpollEvent(std::cerr, ready[0].events, 0); } else { std::cerr << " NOTHING READY"; } std::cerr << std::endl; // Expect: -2 means that if (expect.epoll_wait_ret != IGNORE_EPOLL) { EXPECT_EQ(epoll_res, expect.epoll_wait_ret); } if (epoll_res == SRT_ERROR) { std::cerr << "Epoll returned error: " << srt_getlasterror_str() << " (code " << srt_getlasterror(NULL) << ")\n"; return 0; } // We have exactly one socket here and we expect to return // only this one, or nothing. if (epoll_res != 0) { EXPECT_EQ(epoll_res, 1); EXPECT_EQ(ready[0].fd, m_listener_socket); } return epoll_res == 0 ? 0 : int(ready[0].events); } template<> const TestCase& TestEnforcedEncryption::GetTestMatrix(TEST_CASE test_case) const { return g_test_matrix_blocking[test_case]; } template<> const TestCase& TestEnforcedEncryption::GetTestMatrix(TEST_CASE test_case) const { return g_test_matrix_non_blocking[test_case]; } const char* TestEnforcedEncryption::m_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 }; static const char* const socket_state_array[] = { "IGNORE_SRTS", "SRTS_INVALID", "SRTS_INIT", "SRTS_OPENED", "SRTS_LISTENING", "SRTS_CONNECTING", "SRTS_CONNECTED", "SRTS_BROKEN", "SRTS_CLOSING", "SRTS_CLOSED", "SRTS_NONEXIST" }; // A trick that allows the array to be indexed by -1 const char* const* TestEnforcedEncryption::m_socket_state = socket_state_array+1; /** * @fn TestEnforcedEncryption.PasswordLength * @brief The password length should belong to the interval of [10; 80] */ TEST_F(TestEnforcedEncryption, PasswordLength) { #ifdef SRT_ENABLE_ENCRYPTION // Empty string sets password to none EXPECT_EQ(SetPassword(PEER_CALLER, std::string("")), SRT_SUCCESS); EXPECT_EQ(SetPassword(PEER_LISTENER, std::string("")), SRT_SUCCESS); EXPECT_EQ(SetPassword(PEER_CALLER, std::string("too_short")), SRT_ERROR); EXPECT_EQ(SetPassword(PEER_LISTENER, std::string("too_short")), SRT_ERROR); std::string long_pwd; const int pwd_len = 81; // 80 is the maximum password length accepted long_pwd.reserve(pwd_len); const char start_char = '!'; // Please ensure to be within the valid ASCII symbols! ASSERT_LT(pwd_len + start_char, 126); for (int i = 0; i < pwd_len; ++i) long_pwd.push_back(static_cast(start_char + i)); EXPECT_EQ(SetPassword(PEER_CALLER, long_pwd), SRT_ERROR); EXPECT_EQ(SetPassword(PEER_LISTENER, long_pwd), SRT_ERROR); EXPECT_EQ(SetPassword(PEER_CALLER, std::string("proper_len")), SRT_SUCCESS); EXPECT_EQ(SetPassword(PEER_LISTENER, std::string("proper_length")), SRT_SUCCESS); #else EXPECT_EQ(SetPassword(PEER_CALLER, "whateverpassword"), SRT_ERROR); #endif } /** * @fn TestEnforcedEncryption.SetGetDefault * @brief The default value for the enforced encryption should be ON */ TEST_F(TestEnforcedEncryption, SetGetDefault) { EXPECT_EQ(GetEnforcedEncryption(PEER_CALLER), true); EXPECT_EQ(GetEnforcedEncryption(PEER_LISTENER), true); EXPECT_EQ(SetEnforcedEncryption(PEER_CALLER, false), SRT_SUCCESS); EXPECT_EQ(SetEnforcedEncryption(PEER_LISTENER, false), SRT_SUCCESS); EXPECT_EQ(GetEnforcedEncryption(PEER_CALLER), false); EXPECT_EQ(GetEnforcedEncryption(PEER_LISTENER), false); } #define CREATE_TEST_CASE_BLOCKING(CASE_NUMBER, DESC) TEST_F(TestEnforcedEncryption, CASE_NUMBER##_Blocking_##DESC)\ {\ TestConnect(TEST_##CASE_NUMBER);\ } #define CREATE_TEST_CASE_NONBLOCKING(CASE_NUMBER, DESC) TEST_F(TestEnforcedEncryption, CASE_NUMBER##_NonBlocking_##DESC)\ {\ TestConnect(TEST_##CASE_NUMBER);\ } #define CREATE_TEST_CASES(CASE_NUMBER, DESC) \ CREATE_TEST_CASE_NONBLOCKING(CASE_NUMBER, DESC) \ CREATE_TEST_CASE_BLOCKING(CASE_NUMBER, DESC) #ifdef SRT_ENABLE_ENCRYPTION CREATE_TEST_CASES(CASE_A_1, Enforced_On_On_Pwd_Set_Set_Match) CREATE_TEST_CASES(CASE_A_2, Enforced_On_On_Pwd_Set_Set_Mismatch) CREATE_TEST_CASES(CASE_A_3, Enforced_On_On_Pwd_Set_None) CREATE_TEST_CASES(CASE_A_4, Enforced_On_On_Pwd_None_Set) #endif CREATE_TEST_CASES(CASE_A_5, Enforced_On_On_Pwd_None_None) #ifdef SRT_ENABLE_ENCRYPTION CREATE_TEST_CASES(CASE_B_1, Enforced_On_Off_Pwd_Set_Set_Match) CREATE_TEST_CASES(CASE_B_2, Enforced_On_Off_Pwd_Set_Set_Mismatch) CREATE_TEST_CASES(CASE_B_3, Enforced_On_Off_Pwd_Set_None) CREATE_TEST_CASES(CASE_B_4, Enforced_On_Off_Pwd_None_Set) #endif CREATE_TEST_CASES(CASE_B_5, Enforced_On_Off_Pwd_None_None) #ifdef SRT_ENABLE_ENCRYPTION CREATE_TEST_CASES(CASE_C_1, Enforced_Off_On_Pwd_Set_Set_Match) CREATE_TEST_CASES(CASE_C_2, Enforced_Off_On_Pwd_Set_Set_Mismatch) CREATE_TEST_CASES(CASE_C_3, Enforced_Off_On_Pwd_Set_None) CREATE_TEST_CASES(CASE_C_4, Enforced_Off_On_Pwd_None_Set) #endif CREATE_TEST_CASES(CASE_C_5, Enforced_Off_On_Pwd_None_None) #ifdef SRT_ENABLE_ENCRYPTION CREATE_TEST_CASES(CASE_D_1, Enforced_Off_Off_Pwd_Set_Set_Match) CREATE_TEST_CASES(CASE_D_2, Enforced_Off_Off_Pwd_Set_Set_Mismatch) CREATE_TEST_CASES(CASE_D_3, Enforced_Off_Off_Pwd_Set_None) CREATE_TEST_CASES(CASE_D_4, Enforced_Off_Off_Pwd_None_Set) #endif CREATE_TEST_CASES(CASE_D_5, Enforced_Off_Off_Pwd_None_None) srt-1.4.4/test/test_epoll.cpp000066400000000000000000000574141412557703600162120ustar00rootroot00000000000000#include #include #include #include #include #include "gtest/gtest.h" #include "api.h" #include "epoll.h" using namespace std; TEST(CEPoll, InfiniteWait) { ASSERT_EQ(srt_startup(), 0); const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); ASSERT_EQ(srt_epoll_wait(epoll_id, nullptr, nullptr, nullptr, nullptr, -1, 0, 0, 0, 0), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitNoSocketsInEpoll) { ASSERT_EQ(srt_startup(), 0); const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_EQ(srt_epoll_wait(epoll_id, read, &rlen, write, &wlen, -1, 0, 0, 0, 0), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitNoSocketsInEpoll2) { ASSERT_EQ(srt_startup(), 0); const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); SRT_EPOLL_EVENT events[2]; ASSERT_EQ(srt_epoll_uwait(epoll_id, events, 2, -1), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitEmptyCall) { ASSERT_EQ(srt_startup(), 0); SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); const int no = 0; ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(srt_epoll_add_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); ASSERT_EQ(srt_epoll_wait(epoll_id, 0, NULL, 0, NULL, -1, 0, 0, 0, 0), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, UWaitEmptyCall) { ASSERT_EQ(srt_startup(), 0); SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); const int no = 0; ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(srt_epoll_add_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); ASSERT_EQ(srt_epoll_uwait(epoll_id, NULL, 10, -1), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitAllSocketsInEpollReleased) { ASSERT_EQ(srt_startup(), 0); SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); const int yes = 1; const int no = 0; ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(srt_epoll_add_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); ASSERT_NE(srt_epoll_remove_usock(epoll_id, client_sock), SRT_ERROR); int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_EQ(srt_epoll_wait(epoll_id, read, &rlen, write, &wlen, -1, 0, 0, 0, 0), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitAllSocketsInEpollReleased2) { ASSERT_EQ(srt_startup(), 0); SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); const int yes = 1; const int no = 0; ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(srt_epoll_add_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); ASSERT_NE(srt_epoll_remove_usock(epoll_id, client_sock), SRT_ERROR); SRT_EPOLL_EVENT events[2]; ASSERT_EQ(srt_epoll_uwait(epoll_id, events, 2, -1), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WrongEpoll_idOnAddUSock) { ASSERT_EQ(srt_startup(), 0); SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); const int no = 0; ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; /* We intentionally pass the wrong socket ID. The error should be returned.*/ ASSERT_EQ(srt_epoll_add_usock(epoll_id + 1, client_sock, &epoll_out), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, HandleEpollEvent) { ASSERT_EQ(srt_startup(), 0); SRTSOCKET client_sock = srt_create_socket(); EXPECT_NE(client_sock, SRT_ERROR); const int yes = 1; const int no = 0; EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect EXPECT_NE(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); CEPoll epoll; const int epoll_id = epoll.create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(epoll.update_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); set epoll_ids = { epoll_id }; epoll.update_events(client_sock, epoll_ids, SRT_EPOLL_ERR, true); set readset; set writeset; set* rval = &readset; set* wval = &writeset; ASSERT_NE(epoll.wait(epoll_id, rval, wval, -1, nullptr, nullptr), SRT_ERROR); try { int no_events = 0; EXPECT_EQ(epoll.update_usock(epoll_id, client_sock, &no_events), 0); } catch (CUDTException &ex) { cerr << ex.getErrorMessage() << endl; throw; } try { EXPECT_EQ(epoll.release(epoll_id), 0); } catch (CUDTException &ex) { cerr << ex.getErrorMessage() << endl; throw; } EXPECT_EQ(srt_cleanup(), 0); } // In this test case a caller connects to a listener on a localhost. // Then the caller closes the connection, and listener is expected to // be notified about connection break via polling the accepted socket. TEST(CEPoll, NotifyConnectionBreak) { ASSERT_EQ(srt_startup(), 0); // 1. Prepare client SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); const int yes SRT_ATR_UNUSED = 1; const int no SRT_ATR_UNUSED = 0; ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int client_epoll_id = srt_epoll_create(); ASSERT_GE(client_epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; /* We intentionally pass the wrong socket ID. The error should be returned.*/ EXPECT_EQ(srt_epoll_add_usock(client_epoll_id, client_sock, &epoll_out), SRT_SUCCESS); sockaddr_in sa_client; memset(&sa_client, 0, sizeof sa_client); sa_client.sin_family = AF_INET; sa_client.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa_client.sin_addr), 1); // 2. Prepare server SRTSOCKET server_sock = srt_create_socket(); ASSERT_NE(server_sock, SRT_ERROR); ASSERT_NE(srt_setsockopt(server_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(server_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int server_epoll_id = srt_epoll_create(); ASSERT_GE(server_epoll_id, 0); int epoll_mode = SRT_EPOLL_IN | SRT_EPOLL_ERR; srt_epoll_add_usock(server_epoll_id, server_sock, &epoll_mode); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(server_sock, (sockaddr*)& sa, sizeof(sa)); srt_listen(server_sock, 1); auto connect_res = std::async(std::launch::async, [&client_sock, &sa]() { return srt_connect(client_sock, (sockaddr*)& sa, sizeof(sa)); }); const int default_len = 3; int rlen = default_len; SRTSOCKET read[default_len]; int wlen = default_len; SRTSOCKET write[default_len]; // Wait on epoll for connection const int epoll_res = srt_epoll_wait(server_epoll_id, read, &rlen, write, &wlen, 5000, /* timeout */ 0, 0, 0, 0); EXPECT_EQ(epoll_res, 1); if (epoll_res == SRT_ERROR) { std::cerr << "Epoll returned error: " << srt_getlasterror_str() << " (code " << srt_getlasterror(NULL) << ")\n"; } // Wait for the caller connection thread to return connection result EXPECT_EQ(connect_res.get(), SRT_SUCCESS); sockaddr_in scl; int sclen = sizeof scl; SRTSOCKET sock = srt_accept(server_sock, (sockaddr*)& scl, &sclen); EXPECT_NE(sock, SRT_INVALID_SOCK); int epoll_io = srt_epoll_create(); int modes = SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR; EXPECT_NE(srt_epoll_add_usock(epoll_io, sock, &modes), SRT_ERROR); // The caller will close connection after 1 second auto close_res = std::async(std::launch::async, [&client_sock]() { cout << "(async call): WILL CLOSE client connection in 3s\n"; this_thread::sleep_for(chrono::seconds(1)); cout << "(async call): Closing client connection\n"; return srt_close(client_sock); }); int timeout_ms = -1; int ready[2] = { SRT_INVALID_SOCK, SRT_INVALID_SOCK }; int len = 2; cout << "TEST: entering INFINITE WAIT\n"; const int epoll_wait_res = srt_epoll_wait(epoll_io, ready, &len, nullptr, nullptr, timeout_ms, 0, 0, 0, 0); cout << "TEST: return from INFINITE WAIT\n"; if (epoll_wait_res == SRT_ERROR) cerr << "socket::read::epoll " << to_string(srt_getlasterror(nullptr)); EXPECT_EQ(epoll_wait_res, 1); EXPECT_EQ(len, 1); EXPECT_EQ(ready[0], sock); // Wait for the caller to close connection // There should be no wait, as epoll should wait untill connection is closed. EXPECT_EQ(close_res.get(), SRT_SUCCESS); const SRT_SOCKSTATUS state = srt_getsockstate(sock); const bool state_valid = state == SRTS_BROKEN || state == SRTS_CLOSING || state == SRTS_CLOSED; EXPECT_TRUE(state_valid); if (!state_valid) cerr << "socket state: " << state << endl; EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, HandleEpollEvent2) { ASSERT_EQ(srt_startup(), 0); SRTSOCKET client_sock = srt_create_socket(); EXPECT_NE(client_sock, SRT_ERROR); const int yes = 1; const int no = 0; EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect EXPECT_NE(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); CEPoll epoll; const int epoll_id = epoll.create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR | SRT_EPOLL_ET; ASSERT_NE(epoll.update_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); set epoll_ids = { epoll_id }; epoll.update_events(client_sock, epoll_ids, SRT_EPOLL_ERR, true); SRT_EPOLL_EVENT fds[1024]; int result = epoll.uwait(epoll_id, fds, 1024, -1); ASSERT_EQ(result, 1); ASSERT_EQ(fds[0].events, int(SRT_EPOLL_ERR)); // Edge-triggered means that after one wait call was done, the next // call to this event should no longer report it. Now use timeout 0 // to return immediately. result = epoll.uwait(epoll_id, fds, 1024, 0); ASSERT_EQ(result, 0); try { int no_events = 0; EXPECT_EQ(epoll.update_usock(epoll_id, client_sock, &no_events), 0); } catch (CUDTException &ex) { cerr << ex.getErrorMessage() << endl; throw; } try { EXPECT_EQ(epoll.release(epoll_id), 0); } catch (CUDTException &ex) { cerr << ex.getErrorMessage() << endl; throw; } EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, HandleEpollNoEvent) { ASSERT_EQ(srt_startup(), 0); SRTSOCKET client_sock = srt_create_socket(); EXPECT_NE(client_sock, SRT_ERROR); const int yes = 1; const int no = 0; EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect EXPECT_NE(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); CEPoll epoll; const int epoll_id = epoll.create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(epoll.update_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); SRT_EPOLL_EVENT fds[1024]; // Use timeout 0 because with -1 this call would hang up int result = epoll.uwait(epoll_id, fds, 1024, 0); ASSERT_EQ(result, 0); try { int no_events = 0; EXPECT_EQ(epoll.update_usock(epoll_id, client_sock, &no_events), 0); } catch (CUDTException &ex) { cerr << ex.getErrorMessage() << endl; throw; } try { EXPECT_EQ(epoll.release(epoll_id), 0); } catch (CUDTException &ex) { cerr << ex.getErrorMessage() << endl; throw; } EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, ThreadedUpdate) { ASSERT_EQ(srt_startup(), 0); SRTSOCKET client_sock = srt_create_socket(); EXPECT_NE(client_sock, SRT_ERROR); const int no = 0; EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect CEPoll epoll; const int epoll_id = epoll.create(); ASSERT_GE(epoll_id, 0); ASSERT_EQ(epoll.setflags(epoll_id, SRT_EPOLL_ENABLE_EMPTY), 0); thread td = thread( [&epoll, epoll_id, client_sock]() { cerr << "Spawned thread to add sockets to eid (wait 1s to order execution)\n"; this_thread::sleep_for(chrono::seconds(1)); // Make sure that uwait will be called as first cerr << "ADDING sockets to eid\n"; const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(epoll.update_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); set epoll_ids = { epoll_id }; epoll.update_events(client_sock, epoll_ids, SRT_EPOLL_ERR, true); cerr << "THREAD END\n"; }); SRT_EPOLL_EVENT fds[1024]; cerr << "Entering infinite-wait by uwait:\n"; int result = epoll.uwait(epoll_id, fds, 1024, -1); cerr << "Exit no longer infinite-wait by uwait, result=" << result << "\n"; ASSERT_EQ(result, 1); ASSERT_EQ(fds[0].events, int(SRT_EPOLL_ERR)); cerr << "THREAD JOIN...\n"; td.join(); cerr << "...JOINED\n"; try { int no_events = 0; EXPECT_EQ(epoll.update_usock(epoll_id, client_sock, &no_events), 0); } catch (CUDTException &ex) { cerr << ex.getErrorMessage() << endl; throw; } try { EXPECT_EQ(epoll.release(epoll_id), 0); } catch (CUDTException &ex) { cerr << ex.getErrorMessage() << endl; throw; } EXPECT_EQ(srt_cleanup(), 0); } class TestEPoll: public testing::Test { protected: int m_client_pollid = SRT_ERROR; SRTSOCKET m_client_sock = SRT_ERROR; void clientSocket() { int yes = 1; int no = 0; m_client_sock = srt_create_socket(); ASSERT_NE(m_client_sock, SRT_ERROR); ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockflag(m_client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); int epoll_out = SRT_EPOLL_OUT; srt_epoll_add_usock(m_client_pollid, m_client_sock, &epoll_out); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(9999); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); sockaddr* psa = (sockaddr*)&sa; ASSERT_NE(srt_connect(m_client_sock, psa, sizeof sa), SRT_ERROR); // Socket readiness for connection is checked by polling on WRITE allowed sockets. { int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_NE(srt_epoll_wait(m_client_pollid, read, &rlen, write, &wlen, -1, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR); ASSERT_EQ(rlen, 0); // get exactly one write event without reads ASSERT_EQ(wlen, 1); // get exactly one write event without reads ASSERT_EQ(write[0], m_client_sock); // for our client socket } char buffer[1316] = {1, 2, 3, 4}; ASSERT_NE(srt_sendmsg(m_client_sock, buffer, sizeof buffer, -1, // infinit ttl true // in order must be set to true ), SRT_ERROR); // disable receiving OUT events int epoll_err = SRT_EPOLL_ERR; ASSERT_EQ(0, srt_epoll_update_usock(m_client_pollid, m_client_sock, &epoll_err)); { int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; EXPECT_EQ(SRT_ERROR, srt_epoll_wait(m_client_pollid, read, &rlen, write, &wlen, 1000, 0, 0, 0, 0)); const int last_error = srt_getlasterror(NULL); EXPECT_EQ(SRT_ETIMEOUT, last_error) << last_error; } } int m_server_pollid = SRT_ERROR; void createServerSocket(SRTSOCKET& w_servsock) { int yes = 1; int no = 0; SRTSOCKET servsock = srt_create_socket(); ASSERT_NE(servsock, SRT_ERROR); ASSERT_NE(srt_setsockopt(servsock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(servsock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); int epoll_in = SRT_EPOLL_IN; srt_epoll_add_usock(m_server_pollid, servsock, &epoll_in); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(9999); sa.sin_addr.s_addr = INADDR_ANY; sockaddr* psa = (sockaddr*)&sa; ASSERT_NE(srt_bind(servsock, psa, sizeof sa), SRT_ERROR); ASSERT_NE(srt_listen(servsock, SOMAXCONN), SRT_ERROR); w_servsock = servsock; } void runServer(SRTSOCKET servsock) { int epoll_in = SRT_EPOLL_IN; { // wait for connection from client int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_NE(srt_epoll_wait(m_server_pollid, read, &rlen, write, &wlen, -1, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes ASSERT_EQ(read[0], servsock); // read event is for bind socket } sockaddr_in scl; int sclen = sizeof scl; SRTSOCKET acpsock = srt_accept(servsock, (sockaddr*)&scl, &sclen); ASSERT_NE(acpsock, SRT_INVALID_SOCK); srt_epoll_add_usock(m_server_pollid, acpsock, &epoll_in); // wait for input { // wait for 1316 packet from client int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_NE(srt_epoll_wait(m_server_pollid, read, &rlen, write, &wlen, -1, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes ASSERT_EQ(read[0], acpsock); // read event is for bind socket } char buffer[1316]; ASSERT_EQ(srt_recvmsg(acpsock, buffer, sizeof buffer), 1316); char pattern[4] = {1, 2, 3, 4}; EXPECT_TRUE(std::mismatch(pattern, pattern+4, buffer).first == pattern+4); std::cout << "serverSocket waiting..." << std::endl; { int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_EQ(-1, srt_epoll_wait(m_server_pollid, read, &rlen, write, &wlen, 2000, 0, 0, 0, 0)); const int last_error = srt_getlasterror(NULL); ASSERT_EQ(SRT_ETIMEOUT, last_error) << last_error; } std::cout << "serverSocket finished waiting" << std::endl; srt_close(acpsock); srt_close(servsock); } void SetUp() override { ASSERT_EQ(srt_startup(), 0); m_client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, m_client_pollid); m_server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, m_server_pollid); } void TearDown() override { (void)srt_epoll_release(m_client_pollid); (void)srt_epoll_release(m_server_pollid); srt_cleanup(); } }; TEST_F(TestEPoll, SimpleAsync) { SRTSOCKET ss = SRT_INVALID_SOCK; createServerSocket( (ss) ); std::thread client([this] { clientSocket(); }); runServer(ss); client.join(); // Make sure client has exit before you delete the socket srt_close(m_client_sock); // cannot close m_client_sock after srt_sendmsg because of issue in api.c:2346 } srt-1.4.4/test/test_fec_rebuilding.cpp000066400000000000000000000716051412557703600200360ustar00rootroot00000000000000#include #include #include #include "gtest/gtest.h" #include "packet.h" #include "fec.h" #include "core.h" #include "packetfilter.h" #include "packetfilter_api.h" // For direct imp access #include "api.h" using namespace std; using namespace srt; class TestFECRebuilding: public testing::Test { protected: FECFilterBuiltin* fec = nullptr; vector provided; vector> source; int sockid = 54321; int isn = 123456; size_t plsize = 1316; TestFECRebuilding() { // Required to make ParseCorrectorConfig work PacketFilter::globalInit(); } void SetUp() override { int timestamp = 10; SrtFilterInitializer init = { sockid, isn - 1, // It's passed in this form to PacketFilter constructor, it should increase it isn - 1, // XXX Probably this better be changed. plsize, CSrtConfig::DEF_BUFFER_SIZE }; // Make configuration row-only with size 7 string conf = "fec,rows:1,cols:7"; provided.clear(); fec = new FECFilterBuiltin(init, provided, conf); int32_t seq = isn; for (int i = 0; i < 7; ++i) { source.emplace_back(new CPacket); CPacket& p = *source.back(); p.allocate(SRT_LIVE_MAX_PLSIZE); uint32_t* hdr = p.getHeader(); // Fill in the values hdr[SRT_PH_SEQNO] = seq; hdr[SRT_PH_MSGNO] = 1 | MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO); hdr[SRT_PH_ID] = sockid; hdr[SRT_PH_TIMESTAMP] = timestamp; // Fill in the contents. // Randomly chose the size int minsize = 732; int divergence = plsize - minsize - 1; size_t length = minsize + rand() % divergence; p.setLength(length); for (size_t b = 0; b < length; ++b) { p.data()[b] = rand() % 255; } timestamp += 10; seq = CSeqNo::incseq(seq); } } void TearDown() override { delete fec; } }; namespace srt { class TestMockCUDT { public: CUDT* core; bool checkApplyFilterConfig(const string& s) { return core->checkApplyFilterConfig(s); } }; } // The expected whole procedure of connection using FEC is // expected to: // // 1. Successfully set the FEC option for correct filter type. // - STOP ON FAILURE: unknown filter type (the table below, case D) // 2. Perform the connection and integrate configurations. // - STOP on failed integration (the table below, cases A and B) // 3. Deliver on both sides identical configurations consisting // of combined configurations and completed with default values. // - Not possible if stopped before. // // Test coverage for the above cases: // // Success cases in all of the above: ConfigExchange, Connection, ConnectionReorder // Failure cases: // 1. ConfigExchangeFaux - setting unknown filter type // 2. ConfigExchangeFaux, RejectionConflict, RejectionIncomplete, RejectionIncompleteEmpty // // For config exchange we have several possibilities here: // // - any same parameters with different values are rejected (Case A) // - resulting configuiration should have the `cols` value set (Cases B) // // The configuration API rules that control correctness: // // 1. The first word defines an existing filter type. // 2. Parameters are defined in whatever order. // 3. Some parameters are optional and have default values. Others are mandatory. // 4. A parameter provided twice remains with the last specification. // 5. A parameter with empty value is like not provided parameter. // 6. Only parameters handled by given filter type are allowed. // 7. Every parameter may have limitations on the provided value: // a. Numeric values in appropriate range // b. String-enumeration with only certain values allowed // // Additionally there are rules for configuration integration: // // 8. Configuration consists of parameters provided in both sides. // 9. Parameters lacking after integration are set to default values. // 10. Parameters specified on both sides (including type) must be equal. // 11. Empty configuration blindly accepts the configuration from the peer. // 12. The final configuration must provide mandatory parameters // // Restrictive rules type are: 1, 6, 7, 10 // // Case description: // A: Conflicting values on the same parameter (rejection, rule 10 failure) // B: Missing a mandatory parameter (rejection, rule 12 failure) // C: Successful setting and combining parameters // 1: rules (positive): 1, 3, 6, 7(part), 8, 9, 12 // 2: rules (positive): 1, 2, 3, 6, 7(part), 9, 10, 12 // 3,4: rules (positive): 1, 2, 3(all), 6, 7(all), 8, 10, 12 // 5: rules (positive): 1, 3, 4, 5, 6, 7, 8, 9, 12 // 6: rules (positive): 1, 3, 6, 7, 8, 11, 12 // D: Unknown filter type (failed option, rule 1) // E: Incorrect values of the parameters (failed option, rule 7) // F: Unknown excessive parameters (failed option, rule 6) // // Case |Party A | Party B | Situation | Test coverage //------|------------------------|--------------------|---------------------|--------------- // A |fec,cols:10 | fec,cols:20 | Conflict | ConfigExchangeFaux, RejectionConflict // B1 |fec,rows:10 | fec,arq:never | Missing `cols` | RejectionIncomplete // B2 |fec,rows:10 | | Missing `cols` | RejectionIncompleteEmpty // C1 |fec,cols:10,rows:10 | fec | OK | ConfigExchange, Connection // C2 |fec,cols:10,rows:10 | fec,rows:10,cols:10| OK | ConnectionReorder // C3 |FULL 1 (see below) | FULL 2 (see below) | OK | ConnectionFull1 // C4 |FULL 3 (see below) | FULL 4 (see below) | OK | ConnectionFull2 // C5 |fec,cols:,cols:10 | fec,cols:,rows:10 | OK | ConnectionMess // C6 |fec,rows:20,cols:20 | | OK | ConnectionForced // D |FEC,Cols:10 | (unimportant) | Option rejected | ConfigExchangeFaux // E1 |fec,cols:-10 | (unimportant) | Option rejected | ConfigExchangeFaux // E2 |fec,cols:10,rows:0 | (unimportant) | Option rejected | ConfigExchangeFaux // E3 |fec,cols:10,rows:-1 | (unimportant) | Option rejected | ConfigExchangeFaux // E4 |fec,cols:10,layout:x (*)| (unimportant) | Option rejected | ConfigExchangeFaux // E5 |fec,cols:10,arq:x (*) | (unimportant) | Option rejected | ConfigExchangeFaux // F |fec,cols:10,weight:2 | (unimportant) | Option rejected | ConfigExchangeFaux // // (*) Here is just an example of a longer string that surely is wrong for this parameter. // // The configurations for FULL (cases C3 and C4) are longer and use all possible // values in different order: // 1. fec,cols:10,rows:20,arq:never,layout:even // 1. fec,layout:even,rows:20,cols:10,arq:never // 1. fec,cols:10,rows:20,arq:always,layout:even // 1. fec,layout:even,rows:20,cols:10,arq:always bool filterConfigSame(const string& config1, const string& config2) { vector config1_vector; Split(config1, ',', back_inserter(config1_vector)); sort(config1_vector.begin(), config1_vector.end()); vector config2_vector; Split(config2, ',', back_inserter(config2_vector)); sort(config2_vector.begin(), config2_vector.end()); return config1_vector == config2_vector; } TEST(TestFEC, ConfigExchange) { srt_startup(); CUDTSocket* s1; SRTSOCKET sid1 = CUDT::uglobal()->newSocket(&s1); TestMockCUDT m1; m1.core = &s1->core(); // Can't access the configuration storage without // accessing the private fields, so let's use the official API char fec_config1 [] = "fec,cols:10,rows:10"; srt_setsockflag(sid1, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1); EXPECT_TRUE(m1.checkApplyFilterConfig("fec,cols:10,arq:never")); char fec_configback[200]; int fec_configback_size = 200; srt_getsockflag(sid1, SRTO_PACKETFILTER, fec_configback, &fec_configback_size); // Order of parameters may differ, so store everything in a vector and sort it. string exp_config = "fec,cols:10,rows:10,arq:never,layout:staircase"; EXPECT_TRUE(filterConfigSame(fec_configback, exp_config)); srt_cleanup(); } TEST(TestFEC, ConfigExchangeFaux) { srt_startup(); CUDTSocket* s1; SRTSOCKET sid1 = CUDT::uglobal()->newSocket(&s1); const char* fec_config_wrong [] = { "FEC,Cols:20", // D: unknown filter "fec,cols:-10", // E1: invalid value for cols "fec,cols:10,rows:0", // E2: invalid value for rows "fec,cols:10,rows:-1", // E3: invalid value for rows "fec,cols:10,layout:stairwars", // E4: invalid value for layout "fec,cols:10,arq:sometimes", // E5: invalid value for arq "fec,cols:10,weight:2" // F: invalid parameter name }; for (auto badconfig: fec_config_wrong) { ASSERT_EQ(srt_setsockflag(sid1, SRTO_PACKETFILTER, badconfig, strlen(badconfig)), -1); } TestMockCUDT m1; m1.core = &s1->core(); // Can't access the configuration storage without // accessing the private fields, so let's use the official API char fec_config1 [] = "fec,cols:20,rows:10"; EXPECT_NE(srt_setsockflag(sid1, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); cout << "(NOTE: expecting a failure message)\n"; EXPECT_FALSE(m1.checkApplyFilterConfig("fec,cols:10,arq:never")); srt_cleanup(); } TEST(TestFEC, Connection) { srt_startup(); SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:10"; const char fec_config2 [] = "fec,cols:10,arq:never"; const char fec_config_final [] = "fec,cols:10,rows:10,arq:never,layout:staircase"; ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); srt_listen(l, 1); auto connect_res = std::async(std::launch::async, [&s, &sa]() { return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); SRTSOCKET la[] = { l }; // Given 2s timeout for accepting as it has occasionally happened with Travis // that 1s might not be enough. SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config char result_config1[200] = ""; int result_config1_size = 200; char result_config2[200] = ""; int result_config2_size = 200; EXPECT_NE(srt_getsockflag(s, SRTO_PACKETFILTER, result_config1, &result_config1_size), -1); EXPECT_NE(srt_getsockflag(a, SRTO_PACKETFILTER, result_config2, &result_config2_size), -1); string caller_config = result_config1; string accept_config = result_config2; EXPECT_EQ(caller_config, accept_config); EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); srt_cleanup(); } TEST(TestFEC, ConnectionReorder) { srt_startup(); SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:10"; const char fec_config2 [] = "fec,rows:10,cols:10"; const char fec_config_final [] = "fec,cols:10,rows:10,arq:onreq,layout:staircase"; ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); srt_listen(l, 1); auto connect_res = std::async(std::launch::async, [&s, &sa]() { return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config char result_config1[200] = ""; int result_config1_size = 200; char result_config2[200] = ""; int result_config2_size = 200; srt_getsockflag(s, SRTO_PACKETFILTER, result_config1, &result_config1_size); srt_getsockflag(a, SRTO_PACKETFILTER, result_config2, &result_config2_size); string caller_config = result_config1; string accept_config = result_config2; EXPECT_EQ(caller_config, accept_config); EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); srt_cleanup(); } TEST(TestFEC, ConnectionFull1) { srt_startup(); SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:20,arq:never,layout:even"; const char fec_config2 [] = "fec,layout:even,rows:20,cols:10,arq:never"; const char fec_config_final [] = "fec,cols:10,rows:20,arq:never,layout:even"; ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); srt_listen(l, 1); auto connect_res = std::async(std::launch::async, [&s, &sa]() { return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config char result_config1[200] = ""; int result_config1_size = 200; char result_config2[200] = ""; int result_config2_size = 200; srt_getsockflag(s, SRTO_PACKETFILTER, result_config1, &result_config1_size); srt_getsockflag(a, SRTO_PACKETFILTER, result_config2, &result_config2_size); string caller_config = result_config1; string accept_config = result_config2; EXPECT_EQ(caller_config, accept_config); EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); srt_cleanup(); } TEST(TestFEC, ConnectionFull2) { srt_startup(); SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:20,arq:always,layout:even"; const char fec_config2 [] = "fec,layout:even,rows:20,cols:10,arq:always"; const char fec_config_final [] = "fec,cols:10,rows:20,arq:always,layout:even"; ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); srt_listen(l, 1); auto connect_res = std::async(std::launch::async, [&s, &sa]() { return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config char result_config1[200] = ""; int result_config1_size = 200; char result_config2[200] = ""; int result_config2_size = 200; srt_getsockflag(s, SRTO_PACKETFILTER, result_config1, &result_config1_size); srt_getsockflag(a, SRTO_PACKETFILTER, result_config2, &result_config2_size); string caller_config = result_config1; string accept_config = result_config2; EXPECT_EQ(caller_config, accept_config); EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); srt_cleanup(); } TEST(TestFEC, ConnectionMess) { srt_startup(); SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:,cols:10"; const char fec_config2 [] = "fec,cols:,rows:10"; const char fec_config_final [] = "fec,cols:10,rows:10,arq:onreq,layout:staircase"; ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); srt_listen(l, 1); auto connect_res = std::async(std::launch::async, [&s, &sa]() { return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config char result_config1[200] = ""; int result_config1_size = 200; char result_config2[200] = ""; int result_config2_size = 200; srt_getsockflag(s, SRTO_PACKETFILTER, result_config1, &result_config1_size); srt_getsockflag(a, SRTO_PACKETFILTER, result_config2, &result_config2_size); string caller_config = result_config1; string accept_config = result_config2; EXPECT_EQ(caller_config, accept_config); EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); srt_cleanup(); } TEST(TestFEC, ConnectionForced) { srt_startup(); SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,rows:20,cols:20"; const char fec_config_final [] = "fec,cols:20,rows:20"; ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); srt_listen(l, 1); auto connect_res = std::async(std::launch::async, [&s, &sa]() { return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config char result_config1[200] = ""; int result_config1_size = 200; char result_config2[200] = ""; int result_config2_size = 200; srt_getsockflag(s, SRTO_PACKETFILTER, result_config1, &result_config1_size); srt_getsockflag(a, SRTO_PACKETFILTER, result_config2, &result_config2_size); EXPECT_TRUE(filterConfigSame(result_config1, fec_config_final)); EXPECT_TRUE(filterConfigSame(result_config2, fec_config_final)); srt_cleanup(); } TEST(TestFEC, RejectionConflict) { srt_startup(); SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:10"; const char fec_config2 [] = "fec,cols:20,arq:never"; srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1); srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1); srt_listen(l, 1); auto connect_res = std::async(std::launch::async, [&s, &sa]() { return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); EXPECT_EQ(connect_res.get(), SRT_ERROR); EXPECT_EQ(srt_getrejectreason(s), SRT_REJ_FILTER); bool no = false; // Set non-blocking so that srt_accept can return // immediately with failure. Just to make sure that // the connection is not about to be established, // also on the listener side. srt_setsockflag(l, SRTO_RCVSYN, &no, sizeof no); sockaddr_in scl; int sclen = sizeof scl; EXPECT_EQ(srt_accept(l, (sockaddr*)& scl, &sclen), SRT_ERROR); srt_cleanup(); } TEST(TestFEC, RejectionIncompleteEmpty) { srt_startup(); SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,rows:10"; srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1); srt_listen(l, 1); auto connect_res = std::async(std::launch::async, [&s, &sa]() { return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); EXPECT_EQ(connect_res.get(), SRT_ERROR); EXPECT_EQ(srt_getrejectreason(s), SRT_REJ_FILTER); bool no = false; // Set non-blocking so that srt_accept can return // immediately with failure. Just to make sure that // the connection is not about to be established, // also on the listener side. srt_setsockflag(l, SRTO_RCVSYN, &no, sizeof no); sockaddr_in scl; int sclen = sizeof scl; EXPECT_EQ(srt_accept(l, (sockaddr*)& scl, &sclen), SRT_ERROR); srt_cleanup(); } TEST(TestFEC, RejectionIncomplete) { srt_startup(); SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,rows:10"; const char fec_config2 [] = "fec,arq:never"; srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1); srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1); srt_listen(l, 1); auto connect_res = std::async(std::launch::async, [&s, &sa]() { return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); EXPECT_EQ(connect_res.get(), SRT_ERROR); EXPECT_EQ(srt_getrejectreason(s), SRT_REJ_FILTER); bool no = false; // Set non-blocking so that srt_accept can return // immediately with failure. Just to make sure that // the connection is not about to be established, // also on the listener side. srt_setsockflag(l, SRTO_RCVSYN, &no, sizeof no); sockaddr_in scl; int sclen = sizeof scl; EXPECT_EQ(srt_accept(l, (sockaddr*)& scl, &sclen), SRT_ERROR); srt_cleanup(); } TEST_F(TestFECRebuilding, Prepare) { // Stuff in prepared packets into the source fec. int32_t seq; for (int i = 0; i < 7; ++i) { CPacket& p = *source[i].get(); // Feed it simultaneously into the sender FEC fec->feedSource(p); seq = p.getSeqNo(); } SrtPacket fec_ctl(SRT_LIVE_MAX_PLSIZE); // Use the sequence number of the last packet, as usual. bool have_fec_ctl = fec->packControlPacket(fec_ctl, seq); EXPECT_EQ(have_fec_ctl, true); } TEST_F(TestFECRebuilding, NoRebuild) { // Stuff in prepared packets into the source fec. int32_t seq; for (int i = 0; i < 7; ++i) { CPacket& p = *source[i].get(); // Feed it simultaneously into the sender FEC fec->feedSource(p); seq = p.getSeqNo(); } SrtPacket fec_ctl(SRT_LIVE_MAX_PLSIZE); // Use the sequence number of the last packet, as usual. const bool have_fec_ctl = fec->packControlPacket(fec_ctl, seq); ASSERT_EQ(have_fec_ctl, true); // By having all packets and FEC CTL packet, now stuff in // these packets into the receiver FECFilterBuiltin::loss_seqs_t loss; // required as return, ignore for (int i = 0; i < 7; ++i) { // SKIP packet 4 to simulate loss if (i == 4 || i == 6) continue; // Stuff in the packet into the FEC filter bool want_passthru = fec->receive(*source[i], loss); EXPECT_EQ(want_passthru, true); } // Prepare a real packet basing on the SrtPacket. // XXX Consider packing this into a callable function as this // is a code directly copied from PacketFilter::packControlPacket. unique_ptr fecpkt ( new CPacket ); uint32_t* chdr = fecpkt->getHeader(); memcpy(chdr, fec_ctl.hdr, SRT_PH_E_SIZE * sizeof(*chdr)); // The buffer can be assigned. fecpkt->m_pcData = fec_ctl.buffer; fecpkt->setLength(fec_ctl.length); // This sets only the Packet Boundary flags, while all other things: // - Order // - Rexmit // - Crypto // - Message Number // will be set to 0/false fecpkt->m_iMsgNo = MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO); // ... and then fix only the Crypto flags fecpkt->setMsgCryptoFlags(EncryptionKeySpec(0)); // And now receive the FEC control packet bool want_passthru_fec = fec->receive(*fecpkt, loss); EXPECT_EQ(want_passthru_fec, false); // Confirm that it's been eaten up EXPECT_EQ(provided.size(), 0U); // Confirm that nothing was rebuilt /* // XXX With such a short sequence, losses will not be reported. // You need at least one packet past the row, even in 1-row config. // Probably a better way for loss collection should be devised. ASSERT_EQ(loss.size(), 2); EXPECT_EQ(loss[0].first, isn + 4); EXPECT_EQ(loss[1].first, isn + 6); */ } TEST_F(TestFECRebuilding, Rebuild) { // Stuff in prepared packets into the source fec-> int32_t seq; for (int i = 0; i < 7; ++i) { CPacket& p = *source[i].get(); // Feed it simultaneously into the sender FEC fec->feedSource(p); seq = p.getSeqNo(); } SrtPacket fec_ctl(SRT_LIVE_MAX_PLSIZE); // Use the sequence number of the last packet, as usual. const bool have_fec_ctl = fec->packControlPacket(fec_ctl, seq); ASSERT_EQ(have_fec_ctl, true); // By having all packets and FEC CTL packet, now stuff in // these packets into the receiver FECFilterBuiltin::loss_seqs_t loss; // required as return, ignore for (int i = 0; i < 7; ++i) { // SKIP packet 4 to simulate loss if (i == 4) continue; // Stuff in the packet into the FEC filter bool want_passthru = fec->receive(*source[i], loss); EXPECT_EQ(want_passthru, true); } // Prepare a real packet basing on the SrtPacket. // XXX Consider packing this into a callable function as this // is a code directly copied from PacketFilter::packControlPacket. unique_ptr fecpkt ( new CPacket ); uint32_t* chdr = fecpkt->getHeader(); memcpy(chdr, fec_ctl.hdr, SRT_PH_E_SIZE * sizeof(*chdr)); // The buffer can be assigned. fecpkt->m_pcData = fec_ctl.buffer; fecpkt->setLength(fec_ctl.length); // This sets only the Packet Boundary flags, while all other things: // - Order // - Rexmit // - Crypto // - Message Number // will be set to 0/false fecpkt->m_iMsgNo = MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO); // ... and then fix only the Crypto flags fecpkt->setMsgCryptoFlags(EncryptionKeySpec(0)); // And now receive the FEC control packet const bool want_passthru_fec = fec->receive(*fecpkt, loss); EXPECT_EQ(want_passthru_fec, false); // Confirm that it's been eaten up EXPECT_EQ(loss.size(), 0U); ASSERT_EQ(provided.size(), 1U); SrtPacket& rebuilt = provided[0]; CPacket& skipped = *source[4]; // Set artificially the SN_REXMIT flag in the skipped source packet // because the rebuilt packet shall have REXMIT flag set. skipped.m_iMsgNo |= MSGNO_REXMIT::wrap(true); // Compare the header EXPECT_EQ(skipped.getHeader()[SRT_PH_SEQNO], rebuilt.hdr[SRT_PH_SEQNO]); EXPECT_EQ(skipped.getHeader()[SRT_PH_MSGNO], rebuilt.hdr[SRT_PH_MSGNO]); EXPECT_EQ(skipped.getHeader()[SRT_PH_ID], rebuilt.hdr[SRT_PH_ID]); EXPECT_EQ(skipped.getHeader()[SRT_PH_TIMESTAMP], rebuilt.hdr[SRT_PH_TIMESTAMP]); // Compare sizes and contents ASSERT_EQ(skipped.size(), rebuilt.size()); EXPECT_EQ(memcmp(skipped.data(), rebuilt.data(), rebuilt.size()), 0); } srt-1.4.4/test/test_file_transmission.cpp000066400000000000000000000121311412557703600206120ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2020 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Based on the proposal by Russell Greene (Issue #440) * */ #include #ifdef _WIN32 #define INC_SRT_WIN_WINTIME // exclude gettimeofday from srt headers #endif #include "srt.h" #include #include #include #include //#pragma comment (lib, "ws2_32.lib") TEST(Transmission, FileUpload) { srt_startup(); // Generate the source file // We need a file that will contain more data // than can be contained in one sender buffer. SRTSOCKET sock_lsn = srt_create_socket(), sock_clr = srt_create_socket(); int tt = SRTT_FILE; srt_setsockflag(sock_lsn, SRTO_TRANSTYPE, &tt, sizeof tt); srt_setsockflag(sock_clr, SRTO_TRANSTYPE, &tt, sizeof tt); // Configure listener sockaddr_in sa_lsn = sockaddr_in(); sa_lsn.sin_family = AF_INET; sa_lsn.sin_addr.s_addr = INADDR_ANY; sa_lsn.sin_port = htons(5555); // Find unused a port not used by any other service. // Otherwise srt_connect may actually connect. int bind_res = -1; for (int port = 5000; port <= 5555; ++port) { sa_lsn.sin_port = htons(port); bind_res = srt_bind(sock_lsn, (sockaddr*)&sa_lsn, sizeof sa_lsn); if (bind_res == 0) { std::cout << "Running test on port " << port << "\n"; break; } ASSERT_TRUE(bind_res == SRT_EINVOP) << "Bind failed not due to an occupied port. Result " << bind_res; } ASSERT_GE(bind_res, 0); srt_bind(sock_lsn, (sockaddr*)&sa_lsn, sizeof sa_lsn); int optval = 0; int optlen = sizeof optval; ASSERT_EQ(srt_getsockflag(sock_lsn, SRTO_SNDBUF, &optval, &optlen), 0); const size_t filesize = 7 * optval; { std::cout << "WILL CREATE source file with size=" << filesize << " (= 7 * " << optval << "[sndbuf])\n"; std::ofstream outfile("file.source", std::ios::out | std::ios::binary); ASSERT_EQ(!!outfile, true) << srt_getlasterror_str(); srand(time(0)); for (size_t i = 0; i < filesize; ++i) { char outbyte = rand() % 255; outfile.write(&outbyte, 1); } } srt_listen(sock_lsn, 1); // Start listener-receiver thread bool thread_exit = false; auto client = std::thread([&] { sockaddr_in remote; int len = sizeof remote; const SRTSOCKET accepted_sock = srt_accept(sock_lsn, (sockaddr*)&remote, &len); ASSERT_GT(accepted_sock, 0); if (accepted_sock == SRT_INVALID_SOCK) { std::cerr << srt_getlasterror_str() << std::endl; EXPECT_NE(srt_close(sock_lsn), SRT_ERROR); return; } std::ofstream copyfile("file.target", std::ios::out | std::ios::trunc | std::ios::binary); std::vector buf(1456); for (;;) { int n = srt_recv(accepted_sock, buf.data(), 1456); ASSERT_NE(n, SRT_ERROR); if (n == 0) { std::cerr << "Received 0 bytes, breaking.\n"; break; } // Write to file any amount of data received copyfile.write(buf.data(), n); } EXPECT_NE(srt_close(accepted_sock), SRT_ERROR); thread_exit = true; }); sockaddr_in sa = sockaddr_in(); sa.sin_family = AF_INET; sa.sin_port = sa_lsn.sin_port; ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); srt_connect(sock_clr, (sockaddr*)&sa, sizeof(sa)); std::cout << "Connection initialized" << std::endl; std::ifstream ifile("file.source", std::ios::in | std::ios::binary); std::vector buf(1456); for (;;) { size_t n = ifile.read(buf.data(), 1456).gcount(); size_t shift = 0; while (n > 0) { const int st = srt_send(sock_clr, buf.data()+shift, n); ASSERT_GT(st, 0) << srt_getlasterror_str(); n -= st; shift += st; } if (ifile.eof()) { break; } ASSERT_EQ(ifile.good(), true); } // Finished sending, close the socket std::cout << "Finished sending, closing sockets:\n"; srt_close(sock_clr); srt_close(sock_lsn); std::cout << "Sockets closed, joining receiver thread\n"; client.join(); std::ifstream tarfile("file.target"); EXPECT_EQ(!!tarfile, true); tarfile.seekg(0, std::ios::end); size_t tar_size = tarfile.tellg(); EXPECT_EQ(tar_size, filesize); std::cout << "Comparing files\n"; // Compare files tarfile.seekg(0, std::ios::end); ifile.seekg(0, std::ios::beg); for (size_t i = 0; i < tar_size; ++i) { EXPECT_EQ(ifile.get(), tarfile.get()); } EXPECT_EQ(ifile.get(), EOF); EXPECT_EQ(tarfile.get(), EOF); remove("file.source"); remove("file.target"); (void)srt_cleanup(); } srt-1.4.4/test/test_ipv6.cpp000066400000000000000000000127661412557703600157640ustar00rootroot00000000000000#include "gtest/gtest.h" #include #include #include "srt.h" #include "netinet_any.h" class TestIPv6 : public ::testing::Test { protected: int yes = 1; int no = 0; TestIPv6() { // initialization code here } ~TestIPv6() { // cleanup any pending stuff, but no exceptions allowed } protected: // SetUp() is run immediately before a test starts. void SetUp() { ASSERT_GE(srt_startup(), 0); m_caller_sock = srt_create_socket(); ASSERT_NE(m_caller_sock, SRT_ERROR); m_listener_sock = srt_create_socket(); ASSERT_NE(m_listener_sock, SRT_ERROR); } void TearDown() { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. srt_close(m_listener_sock); srt_close(m_caller_sock); srt_cleanup(); } public: void ClientThread(int family, const std::string& address) { sockaddr_any sa (family); sa.hport(m_listen_port); EXPECT_EQ(inet_pton(family, address.c_str(), sa.get_addr()), 1); std::cout << "Calling: " << address << "(" << fam[family] << ")\n"; const int connect_res = srt_connect(m_caller_sock, (sockaddr*)&sa, sizeof sa); EXPECT_NE(connect_res, SRT_ERROR) << "srt_connect() failed with: " << srt_getlasterror_str(); if (connect_res == SRT_ERROR) srt_close(m_listener_sock); PrintAddresses(m_caller_sock, "CALLER"); } std::map fam = { {AF_INET, "IPv4"}, {AF_INET6, "IPv6"} }; void ShowAddress(std::string src, const sockaddr_any& w) { EXPECT_NE(fam.count(w.family()), 0U) << "INVALID FAMILY"; std::cout << src << ": " << w.str() << " (" << fam[w.family()] << ")" << std::endl; } sockaddr_any DoAccept() { sockaddr_any sc1; SRTSOCKET accepted_sock = srt_accept(m_listener_sock, sc1.get(), &sc1.len); EXPECT_NE(accepted_sock, SRT_INVALID_SOCK) << "accept() failed with: " << srt_getlasterror_str(); if (accepted_sock == SRT_INVALID_SOCK) { return sockaddr_any(); } PrintAddresses(accepted_sock, "ACCEPTED"); sockaddr_any sn; EXPECT_NE(srt_getsockname(accepted_sock, sn.get(), &sn.len), SRT_ERROR); EXPECT_NE(sn.get_addr(), nullptr); if (sn.get_addr() != nullptr) { const int32_t ipv6_zero[] = { 0, 0, 0, 0 }; EXPECT_NE(memcmp(ipv6_zero, sn.get_addr(), sizeof ipv6_zero), 0) << "EMPTY address in srt_getsockname"; } srt_close(accepted_sock); return sn; } private: void PrintAddresses(SRTSOCKET sock, const char* who) { sockaddr_any sa; int sa_len = (int) sa.storage_size(); srt_getsockname(sock, sa.get(), &sa_len); ShowAddress(std::string(who) + " Sock name: ", sa); //std::cout << who << " Sock name: " << << sa.str() << std::endl; sa_len = (int) sa.storage_size(); srt_getpeername(sock, sa.get(), &sa_len); //std::cout << who << " Peer name: " << << sa.str() << std::endl; ShowAddress(std::string(who) + " Peer name: ", sa); } protected: SRTSOCKET m_caller_sock; SRTSOCKET m_listener_sock; const int m_listen_port = 4200; }; TEST_F(TestIPv6, v4_calls_v6_mapped) { sockaddr_any sa (AF_INET6); sa.hport(m_listen_port); ASSERT_EQ(srt_setsockflag(m_listener_sock, SRTO_IPV6ONLY, &no, sizeof no), 0); ASSERT_NE(srt_bind(m_listener_sock, sa.get(), sa.size()), SRT_ERROR); ASSERT_NE(srt_listen(m_listener_sock, SOMAXCONN), SRT_ERROR); std::thread client(&TestIPv6::ClientThread, this, AF_INET, "127.0.0.1"); const sockaddr_any sa_accepted = DoAccept(); EXPECT_EQ(sa_accepted.str(), "::ffff:127.0.0.1:4200"); client.join(); } TEST_F(TestIPv6, v6_calls_v6_mapped) { sockaddr_any sa (AF_INET6); sa.hport(m_listen_port); ASSERT_EQ(srt_setsockflag(m_listener_sock, SRTO_IPV6ONLY, &no, sizeof no), 0); ASSERT_NE(srt_bind(m_listener_sock, sa.get(), sa.size()), SRT_ERROR); ASSERT_NE(srt_listen(m_listener_sock, SOMAXCONN), SRT_ERROR); std::thread client(&TestIPv6::ClientThread, this, AF_INET6, "::1"); const sockaddr_any sa_accepted = DoAccept(); EXPECT_EQ(sa_accepted.str(), "::1:4200"); client.join(); } TEST_F(TestIPv6, v6_calls_v6) { sockaddr_any sa (AF_INET6); sa.hport(m_listen_port); // This time bind the socket exclusively to IPv6. ASSERT_EQ(srt_setsockflag(m_listener_sock, SRTO_IPV6ONLY, &yes, sizeof yes), 0); ASSERT_EQ(inet_pton(AF_INET6, "::1", sa.get_addr()), 1); ASSERT_NE(srt_bind(m_listener_sock, sa.get(), sa.size()), SRT_ERROR); ASSERT_NE(srt_listen(m_listener_sock, SOMAXCONN), SRT_ERROR); std::thread client(&TestIPv6::ClientThread, this, AF_INET6, "::1"); const sockaddr_any sa_accepted = DoAccept(); EXPECT_EQ(sa_accepted.str(), "::1:4200"); client.join(); } TEST_F(TestIPv6, v6_calls_v4) { sockaddr_any sa (AF_INET); sa.hport(m_listen_port); // This time bind the socket exclusively to IPv4. ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", sa.get_addr()), 1); ASSERT_NE(srt_bind(m_listener_sock, sa.get(), sa.size()), SRT_ERROR); ASSERT_NE(srt_listen(m_listener_sock, SOMAXCONN), SRT_ERROR); std::thread client(&TestIPv6::ClientThread, this, AF_INET6, "0::FFFF:127.0.0.1"); const sockaddr_any sa_accepted = DoAccept(); EXPECT_EQ(sa_accepted.str(), "127.0.0.1:4200"); client.join(); } srt-1.4.4/test/test_list.cpp000066400000000000000000000520721412557703600160450ustar00rootroot00000000000000#include #include "gtest/gtest.h" #include "common.h" using namespace std; #include "list.h" class CSndLossListTest : public ::testing::Test { protected: void SetUp() override { m_lossList = new CSndLossList(CSndLossListTest::SIZE); } void TearDown() override { delete m_lossList; } void CheckEmptyArray() { EXPECT_EQ(m_lossList->getLossLength(), 0); EXPECT_EQ(m_lossList->popLostSeq(), -1); } void CleanUpList() { while (m_lossList->popLostSeq() != -1); } CSndLossList* m_lossList; public: const int SIZE = 256; }; /// Check the state of the freshly created list. /// Capacity, loss length and pop(). TEST_F(CSndLossListTest, Create) { CheckEmptyArray(); } /////////////////////////////////////////////////////////////////////////////// /// /// The first group of tests checks insert and pop() /// /////////////////////////////////////////////////////////////////////////////// /// Insert and pop one element from the list. TEST_F(CSndLossListTest, InsertPopOneElem) { EXPECT_EQ(m_lossList->insert(1, 1), 1); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 1); CheckEmptyArray(); } TEST_F(CSndLossListTest, InsertNegativeSeqno) { cerr << "Expecting IPE message:" << endl; EXPECT_EQ(m_lossList->insert(1, SRT_SEQNO_NONE), 0); EXPECT_EQ(m_lossList->insert(SRT_SEQNO_NONE, SRT_SEQNO_NONE), 0); EXPECT_EQ(m_lossList->insert(SRT_SEQNO_NONE, 1), 0); CheckEmptyArray(); } /// Insert two elements at once and pop one by one TEST_F(CSndLossListTest, InsertPopTwoElemsRange) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 1); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 2); CheckEmptyArray(); } /// Insert 1 and 4 and pop() one by one TEST_F(CSndLossListTest, InsertPopTwoElems) { EXPECT_EQ(m_lossList->insert(1, 1), 1); EXPECT_EQ(m_lossList->insert(4, 4), 1); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 1); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 4); CheckEmptyArray(); } /// Insert 1 and 2 and pop() one by one TEST_F(CSndLossListTest, InsertPopTwoSerialElems) { EXPECT_EQ(m_lossList->insert(1, 1), 1); EXPECT_EQ(m_lossList->insert(2, 2), 1); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 1); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 2); CheckEmptyArray(); } /// Insert (1,2) and 4, then pop one by one TEST_F(CSndLossListTest, InsertPopRangeAndSingle) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 4), 1); EXPECT_EQ(m_lossList->getLossLength(), 3); EXPECT_EQ(m_lossList->popLostSeq(), 1); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 2); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 4); CheckEmptyArray(); } /// Insert 1, 4, 2, 0, then pop TEST_F(CSndLossListTest, InsertPopFourElems) { EXPECT_EQ(m_lossList->insert(1, 1), 1); EXPECT_EQ(m_lossList->insert(4, 4), 1); EXPECT_EQ(m_lossList->insert(0, 0), 1); EXPECT_EQ(m_lossList->insert(2, 2), 1); EXPECT_EQ(m_lossList->getLossLength(), 4); EXPECT_EQ(m_lossList->popLostSeq(), 0); EXPECT_EQ(m_lossList->getLossLength(), 3); EXPECT_EQ(m_lossList->popLostSeq(), 1); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 2); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 4); CheckEmptyArray(); } /// Insert (1,2) and 4, then pop one by one TEST_F(CSndLossListTest, InsertCoalesce) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 4), 1); EXPECT_EQ(m_lossList->insert(3, 3), 1); EXPECT_EQ(m_lossList->getLossLength(), 4); EXPECT_EQ(m_lossList->popLostSeq(), 1); EXPECT_EQ(m_lossList->getLossLength(), 3); EXPECT_EQ(m_lossList->popLostSeq(), 2); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 3); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 4); CheckEmptyArray(); } /////////////////////////////////////////////////////////////////////////////// /// /// The group of tests checks remove() from different positions in the list, /// /////////////////////////////////////////////////////////////////////////////// /// /// /// TEST_F(CSndLossListTest, BasicRemoveInListNodeHead01) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 4), 1); EXPECT_EQ(m_lossList->getLossLength(), 3); // Remove up to element 4 m_lossList->removeUpTo(4); EXPECT_EQ(m_lossList->getLossLength(), 0); EXPECT_EQ(m_lossList->popLostSeq(), -1); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNodeHead02) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 5), 2); EXPECT_EQ(m_lossList->getLossLength(), 4); m_lossList->removeUpTo(4); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 5); EXPECT_EQ(m_lossList->getLossLength(), 0); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNodeHead03) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 4), 1); EXPECT_EQ(m_lossList->insert(8, 8), 1); EXPECT_EQ(m_lossList->getLossLength(), 4); m_lossList->removeUpTo(4); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 8); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNodeHead04) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 6), 3); EXPECT_EQ(m_lossList->insert(8, 8), 1); EXPECT_EQ(m_lossList->getLossLength(), 6); m_lossList->removeUpTo(4); EXPECT_EQ(m_lossList->getLossLength(), 3); EXPECT_EQ(m_lossList->popLostSeq(), 5); EXPECT_EQ(m_lossList->popLostSeq(), 6); EXPECT_EQ(m_lossList->popLostSeq(), 8); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead01) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 5), 2); EXPECT_EQ(m_lossList->getLossLength(), 4); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 0); EXPECT_EQ(m_lossList->popLostSeq(), -1); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead02) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 5), 2); EXPECT_EQ(m_lossList->insert(8, 8), 1); EXPECT_EQ(m_lossList->getLossLength(), 5); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 8); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead03) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 8), 5); EXPECT_EQ(m_lossList->getLossLength(), 7); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 3); EXPECT_EQ(m_lossList->popLostSeq(), 6); EXPECT_EQ(m_lossList->popLostSeq(), 7); EXPECT_EQ(m_lossList->popLostSeq(), 8); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead04) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 8), 5); EXPECT_EQ(m_lossList->insert(10, 12), 3); EXPECT_EQ(m_lossList->getLossLength(), 10); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 6); EXPECT_EQ(m_lossList->popLostSeq(), 6); EXPECT_EQ(m_lossList->popLostSeq(), 7); EXPECT_EQ(m_lossList->popLostSeq(), 8); EXPECT_EQ(m_lossList->popLostSeq(), 10); EXPECT_EQ(m_lossList->popLostSeq(), 11); EXPECT_EQ(m_lossList->popLostSeq(), 12); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead05) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 8), 5); EXPECT_EQ(m_lossList->insert(10, 12), 3); EXPECT_EQ(m_lossList->getLossLength(), 10); m_lossList->removeUpTo(9); EXPECT_EQ(m_lossList->getLossLength(), 3); EXPECT_EQ(m_lossList->popLostSeq(), 10); EXPECT_EQ(m_lossList->popLostSeq(), 11); EXPECT_EQ(m_lossList->popLostSeq(), 12); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead06) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 8), 5); EXPECT_EQ(m_lossList->insert(10, 12), 3); EXPECT_EQ(m_lossList->getLossLength(), 10); m_lossList->removeUpTo(50); EXPECT_EQ(m_lossList->getLossLength(), 0); EXPECT_EQ(m_lossList->popLostSeq(), -1); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead07) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(4, 8), 5); EXPECT_EQ(m_lossList->insert(10, 12), 3); EXPECT_EQ(m_lossList->getLossLength(), 10); m_lossList->removeUpTo(-50); EXPECT_EQ(m_lossList->getLossLength(), 10); EXPECT_EQ(m_lossList->popLostSeq(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 4); EXPECT_EQ(m_lossList->popLostSeq(), 5); EXPECT_EQ(m_lossList->popLostSeq(), 6); EXPECT_EQ(m_lossList->popLostSeq(), 7); EXPECT_EQ(m_lossList->popLostSeq(), 8); EXPECT_EQ(m_lossList->popLostSeq(), 10); EXPECT_EQ(m_lossList->popLostSeq(), 11); EXPECT_EQ(m_lossList->popLostSeq(), 12); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead08) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(5, 6), 2); EXPECT_EQ(m_lossList->getLossLength(), 4); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 1); m_lossList->removeUpTo(6); EXPECT_EQ(m_lossList->getLossLength(), 0); EXPECT_EQ(m_lossList->popLostSeq(), -1); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead09) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(5, 6), 2); EXPECT_EQ(m_lossList->getLossLength(), 4); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->insert(1, 2), 2); m_lossList->removeUpTo(6); EXPECT_EQ(m_lossList->getLossLength(), 0); EXPECT_EQ(m_lossList->popLostSeq(), -1); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead10) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(5, 6), 2); EXPECT_EQ(m_lossList->insert(10, 10), 1); EXPECT_EQ(m_lossList->getLossLength(), 5); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->insert(1, 2), 2); m_lossList->removeUpTo(7); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 10); CheckEmptyArray(); } TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead11) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(5, 6), 2); EXPECT_EQ(m_lossList->getLossLength(), 4); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->insert(1, 2), 2); m_lossList->removeUpTo(7); EXPECT_EQ(m_lossList->getLossLength(), 0); EXPECT_EQ(m_lossList->popLostSeq(), -1); CheckEmptyArray(); } ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// TEST_F(CSndLossListTest, InsertRemoveInsert01) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->insert(5, 6), 2); EXPECT_EQ(m_lossList->getLossLength(), 4); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->insert(1, 2), 2); m_lossList->removeUpTo(6); EXPECT_EQ(m_lossList->getLossLength(), 0); EXPECT_EQ(m_lossList->popLostSeq(), -1); CheckEmptyArray(); } ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// TEST_F(CSndLossListTest, InsertHead01) { EXPECT_EQ(m_lossList->insert(1, 2), 2); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 1); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 2); CheckEmptyArray(); } TEST_F(CSndLossListTest, InsertHead02) { EXPECT_EQ(m_lossList->insert(1, 1), 1); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 1); CheckEmptyArray(); } TEST_F(CSndLossListTest, InsertHeadIncrease01) { EXPECT_EQ(m_lossList->insert(1, 1), 1); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->insert(2, 2), 1); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 1); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 2); CheckEmptyArray(); } TEST_F(CSndLossListTest, InsertHeadOverlap01) { EXPECT_EQ(m_lossList->insert(1, 5), 5); EXPECT_EQ(m_lossList->getLossLength(), 5); EXPECT_EQ(m_lossList->insert(6, 8), 3); EXPECT_EQ(m_lossList->getLossLength(), 8); EXPECT_EQ(m_lossList->insert(2, 10), 2); EXPECT_EQ(m_lossList->getLossLength(), 10); for (int i = 1; i < 11; i++) { EXPECT_EQ(m_lossList->popLostSeq(), i); EXPECT_EQ(m_lossList->getLossLength(), 10 - i); } CheckEmptyArray(); } TEST_F(CSndLossListTest, InsertHeadOverlap02) { EXPECT_EQ(m_lossList->insert(1, 5), 5); EXPECT_EQ(m_lossList->getLossLength(), 5); EXPECT_EQ(m_lossList->insert(6, 8), 3); EXPECT_EQ(m_lossList->getLossLength(), 8); EXPECT_EQ(m_lossList->insert(2, 7), 0); EXPECT_EQ(m_lossList->getLossLength(), 8); EXPECT_EQ(m_lossList->insert(5, 5), 0); EXPECT_EQ(m_lossList->getLossLength(), 8); for (int i = 1; i < 9; i++) { EXPECT_EQ(m_lossList->popLostSeq(), i); EXPECT_EQ(m_lossList->getLossLength(), 8 - i); } CheckEmptyArray(); } TEST_F(CSndLossListTest, InsertHeadNegativeOffset01) { EXPECT_EQ(m_lossList->insert(10000000, 10000000), 1); EXPECT_EQ(m_lossList->insert(10000001, 10000001), 1); EXPECT_EQ(m_lossList->getLossLength(), 2); // The offset of the sequence number being added does not fit // into the size of the loss list, it must be ignored. // Normally this situation should not happen. cerr << "Expecting IPE message:" << endl; EXPECT_EQ(m_lossList->insert(1, 1), 0); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 10000000); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 10000001); CheckEmptyArray(); } // Check the part of the loss report the can fit into the list // goes into the list. TEST_F(CSndLossListTest, InsertHeadNegativeOffset02) { const int32_t head_seqno = 10000000; EXPECT_EQ(m_lossList->insert(head_seqno, head_seqno), 1); EXPECT_EQ(m_lossList->insert(head_seqno + 1, head_seqno + 1), 1); EXPECT_EQ(m_lossList->getLossLength(), 2); // The offset of the sequence number being added does not fit // into the size of the loss list, it must be ignored. // Normally this situation should not happen. const int32_t outofbound_seqno = head_seqno - CSndLossListTest::SIZE; EXPECT_EQ(m_lossList->insert(outofbound_seqno - 1, outofbound_seqno + 1), 3); EXPECT_EQ(m_lossList->getLossLength(), 5); EXPECT_EQ(m_lossList->popLostSeq(), outofbound_seqno - 1); EXPECT_EQ(m_lossList->getLossLength(), 4); EXPECT_EQ(m_lossList->popLostSeq(), outofbound_seqno); EXPECT_EQ(m_lossList->getLossLength(), 3); EXPECT_EQ(m_lossList->popLostSeq(), outofbound_seqno + 1); EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 10000000); EXPECT_EQ(m_lossList->getLossLength(), 1); EXPECT_EQ(m_lossList->popLostSeq(), 10000001); CheckEmptyArray(); } ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// TEST_F(CSndLossListTest, InsertFullListCoalesce) { for (int i = 1; i <= CSndLossListTest::SIZE; i++) EXPECT_EQ(m_lossList->insert(i, i), 1); EXPECT_EQ(m_lossList->getLossLength(), CSndLossListTest::SIZE); // Inserting additional element: 1 item more than list size. // Given all elements coalesce into one entry, there is a place to insert it, // but sequence span now exceeds list size. EXPECT_EQ(m_lossList->insert(CSndLossListTest::SIZE + 1, CSndLossListTest::SIZE + 1), 0); EXPECT_EQ(m_lossList->getLossLength(), CSndLossListTest::SIZE); for (int i = 1; i <= CSndLossListTest::SIZE; i++) { EXPECT_EQ(m_lossList->popLostSeq(), i); EXPECT_EQ(m_lossList->getLossLength(), CSndLossListTest::SIZE - i); } EXPECT_EQ(m_lossList->popLostSeq(), -1); EXPECT_EQ(m_lossList->getLossLength(), 0); CheckEmptyArray(); } TEST_F(CSndLossListTest, InsertFullListNoCoalesce) { // We will insert each element with a gap of one elements. // This should lead to having space for only [i; SIZE] sequence numbers. for (int i = 1; i <= CSndLossListTest::SIZE / 2; i++) EXPECT_EQ(m_lossList->insert(2 * i, 2 * i), 1); // At this point the list has every second element empty // [0]:taken, [1]: empty, [2]: taken, [3]: empty, ... EXPECT_EQ(m_lossList->getLossLength(), CSndLossListTest::SIZE / 2); // Inserting additional element out of the list span must fail. const int seqno1 = CSndLossListTest::SIZE + 2; EXPECT_EQ(m_lossList->insert(seqno1, seqno1), 0); // There should however be a place for one element right after the last inserted one. const int seqno_last = CSndLossListTest::SIZE + 1; EXPECT_EQ(m_lossList->insert(seqno_last, seqno_last), 1); const int initial_length = m_lossList->getLossLength(); EXPECT_EQ(initial_length, CSndLossListTest::SIZE / 2 + 1); for (int i = 1; i <= CSndLossListTest::SIZE / 2; i++) { EXPECT_EQ(m_lossList->popLostSeq(), 2 * i); EXPECT_EQ(m_lossList->getLossLength(), initial_length - i); } EXPECT_EQ(m_lossList->popLostSeq(), seqno_last); EXPECT_EQ(m_lossList->popLostSeq(), -1); EXPECT_EQ(m_lossList->getLossLength(), 0); CheckEmptyArray(); } TEST_F(CSndLossListTest, InsertFullListNegativeOffset) { for (int i = 10000000; i < 10000000 + CSndLossListTest::SIZE; i++) m_lossList->insert(i, i); EXPECT_EQ(m_lossList->getLossLength(), CSndLossListTest::SIZE); m_lossList->insert(1, CSndLossListTest::SIZE + 1); EXPECT_EQ(m_lossList->getLossLength(), CSndLossListTest::SIZE); for (int i = 10000000; i < 10000000 + CSndLossListTest::SIZE; i++) { EXPECT_EQ(m_lossList->popLostSeq(), i); EXPECT_EQ(m_lossList->getLossLength(), CSndLossListTest::SIZE - (i - 10000000 + 1)); } EXPECT_EQ(m_lossList->popLostSeq(), -1); EXPECT_EQ(m_lossList->getLossLength(), 0); CheckEmptyArray(); } TEST_F(CSndLossListTest, InsertPositiveOffsetTooFar) { const int32_t head_seqno = 1000; EXPECT_EQ(m_lossList->insert(head_seqno, head_seqno), 1); EXPECT_EQ(m_lossList->getLossLength(), 1); // The offset of the sequence number being added does not fit // into the size of the loss list, it must be ignored. // Normally this situation should not happen. const int32_t outofbound_seqno = head_seqno + CSndLossListTest::SIZE; m_lossList->insert(outofbound_seqno, outofbound_seqno); const int32_t outofbound_seqno2 = head_seqno + 2 * CSndLossListTest::SIZE; m_lossList->insert(outofbound_seqno2, outofbound_seqno2); } ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// TEST_F(CSndLossListTest, InsertNoUpdateElement01) { EXPECT_EQ(m_lossList->insert(0, 1), 2); EXPECT_EQ(m_lossList->insert(3, 5), 3); m_lossList->removeUpTo(3); // Remove all to seq no 3 EXPECT_EQ(m_lossList->insert(4, 5), 0); // Element not updated EXPECT_EQ(m_lossList->getLossLength(), 2); EXPECT_EQ(m_lossList->popLostSeq(), 4); EXPECT_EQ(m_lossList->popLostSeq(), 5); } TEST_F(CSndLossListTest, InsertNoUpdateElement03) { EXPECT_EQ(m_lossList->insert(1, 5), 5); EXPECT_EQ(m_lossList->getLossLength(), 5); EXPECT_EQ(m_lossList->insert(6, 8), 3); EXPECT_EQ(m_lossList->getLossLength(), 8); EXPECT_EQ(m_lossList->insert(2, 5), 0); EXPECT_EQ(m_lossList->getLossLength(), 8); } ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// TEST_F(CSndLossListTest, InsertUpdateElement01) { EXPECT_EQ(m_lossList->insert(1, 5), 5); EXPECT_EQ(m_lossList->getLossLength(), 5); EXPECT_EQ(m_lossList->insert(1, 8), 3); EXPECT_EQ(m_lossList->getLossLength(), 8); EXPECT_EQ(m_lossList->insert(2, 5), 0); EXPECT_EQ(m_lossList->getLossLength(), 8); } srt-1.4.4/test/test_listen_callback.cpp000066400000000000000000000134311412557703600202000ustar00rootroot00000000000000#include #include #include #include #ifdef _WIN32 #define INC_SRT_WIN_WINTIME // exclude gettimeofday from srt headers #endif #include "srt.h" #include "utilities.h" srt_listen_callback_fn SrtTestListenCallback; /** * This test makes a service and a client connecting to it. * The service sets up a callback function on the listener. * The listener sets up different passwords depending on the user. * The test tests: * - correct connection with correct password * - rejected connection with wrong password * - rejected connection on nonexistent user */ TEST(Core, ListenCallback) { using namespace std; ASSERT_EQ(srt_startup(), 0); // Create server on 127.0.0.1:5555 const SRTSOCKET server_sock = srt_create_socket(); ASSERT_GT(server_sock, 0); // socket_id should be > 0 sockaddr_in bind_sa; memset(&bind_sa, 0, sizeof bind_sa); bind_sa.sin_family = AF_INET; ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1); bind_sa.sin_port = htons(5555); ASSERT_NE(srt_bind(server_sock, (sockaddr*)&bind_sa, sizeof bind_sa), -1); ASSERT_NE(srt_listen(server_sock, 5), -1); (void)srt_listen_callback(server_sock, &SrtTestListenCallback, NULL); // Create client to connect to the above server SRTSOCKET client_sock; sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(5555); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); sockaddr* psa = (sockaddr*)&sa; cerr << "TEST 1: Connect to an encrypted socket correctly (should succeed)\n"; client_sock = srt_create_socket(); ASSERT_GT(client_sock, 0); // socket_id should be > 0 string username_spec = "#!::u=admin"; string password = "thelocalmanager"; ASSERT_NE(srt_setsockflag(client_sock, SRTO_STREAMID, username_spec.c_str(), username_spec.size()), -1); #if SRT_ENABLE_ENCRYPTION ASSERT_NE(srt_setsockflag(client_sock, SRTO_PASSPHRASE, password.c_str(), password.size()), -1); #endif // EXPECTED RESULT: connected successfully EXPECT_NE(srt_connect(client_sock, psa, sizeof sa), SRT_ERROR); // Close the socket EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); cerr << "TEST 2: Connect with a wrong password (should reject the handshake)\n"; #if SRT_ENABLE_ENCRYPTION client_sock = srt_create_socket(); ASSERT_GT(client_sock, 0); // socket_id should be > 0 password = "thelokalmanager"; // (typo :D) ASSERT_NE(srt_setsockflag(client_sock, SRTO_STREAMID, username_spec.c_str(), username_spec.size()), -1); ASSERT_NE(srt_setsockflag(client_sock, SRTO_PASSPHRASE, password.c_str(), password.size()), -1); // EXPECTED RESULT: connection rejected EXPECT_EQ(srt_connect(client_sock, psa, sizeof sa), SRT_ERROR); // Close the socket EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); #endif cerr << "TEST 3: Connect with wrong username (should exit on exception)\n"; client_sock = srt_create_socket(); ASSERT_GT(client_sock, 0); // socket_id should be > 0 username_spec = "#!::u=haivision"; password = "thelocalmanager"; // (typo :D) ASSERT_NE(srt_setsockflag(client_sock, SRTO_STREAMID, username_spec.c_str(), username_spec.size()), -1); #if SRT_ENABLE_ENCRYPTION ASSERT_NE(srt_setsockflag(client_sock, SRTO_PASSPHRASE, password.c_str(), password.size()), -1); #endif // EXPECTED RESULT: connection rejected EXPECT_EQ(srt_connect(client_sock, psa, sizeof sa), SRT_ERROR); // Close the socket EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); (void)srt_cleanup(); } int SrtTestListenCallback(void* opaq, SRTSOCKET ns SRT_ATR_UNUSED, int hsversion, const struct sockaddr* peeraddr, const char* streamid) { using namespace std; if (opaq) { cerr << "ERROR: opaq expected NULL, as passed\n"; return -1; // enforce EXPECT to fail } if (hsversion != 5) { cerr << "ERROR: hsversion expected 5\n"; return -1; } if (!peeraddr) { // XXX Might be better to check the content, too. cerr << "ERROR: null peeraddr\n"; return -1; } static const map passwd { {"admin", "thelocalmanager"}, {"user", "verylongpassword"} }; // Try the "standard interpretation" with username at key u string username; static const char stdhdr [] = "#!::"; uint32_t* pattern = (uint32_t*)stdhdr; bool found = false; if (strlen(streamid) > 4 && *(uint32_t*)streamid == *pattern) { vector items; Split(streamid+4, ',', back_inserter(items)); for (auto& i: items) { vector kv; Split(i, '=', back_inserter(kv)); if (kv.size() == 2 && kv[0] == "u") { username = kv[1]; found = true; } } if (!found) { cerr << "TEST: USER NOT FOUND, returning false.\n"; return -1; } } else { // By default the whole streamid is username username = streamid; } // This hook sets the password to the just accepted socket // depending on the user // When not found, it will throw an exception cerr << "TEST: Accessing user '" << username << "', might throw if not found\n"; string exp_pw = passwd.at(username); #if SRT_ENABLE_ENCRYPTION cerr << "TEST: Setting password '" << exp_pw << "' as per user '" << username << "'\n"; EXPECT_EQ(srt_setsockflag(ns, SRTO_PASSPHRASE, exp_pw.c_str(), exp_pw.size()), SRT_SUCCESS); #endif // Checking that SRTO_RCVLATENCY (PRE option) can be altered in the listener callback. int optval = 200; EXPECT_EQ(srt_setsockflag(ns, SRTO_RCVLATENCY, &optval, sizeof optval), SRT_SUCCESS); return 0; } srt-1.4.4/test/test_many_connections.cpp000066400000000000000000000120311412557703600204270ustar00rootroot00000000000000#define _CRT_RAND_S // For Windows, rand_s #include #include #include #ifdef _WIN32 #include #define rand_r rand_s #define INC_SRT_WIN_WINTIME // exclude gettimeofday from srt headers #else typedef int SOCKET; #define INVALID_SOCKET ((SOCKET)-1) #define closesocket close #endif #include"platform_sys.h" #include "srt.h" #include "netinet_any.h" #include "api.h" using namespace std; class TestConnection : public ::testing::Test { protected: TestConnection() { // initialization code here } ~TestConnection() { // cleanup any pending stuff, but no exceptions allowed } // It should be as much as possible, but how many sockets can // be withstood, depends on the platform. Currently used CI test // servers seem not to withstand more than 240. static const size_t NSOCK = 60; protected: // SetUp() is run immediately before a test starts. void SetUp() override { ASSERT_EQ(srt_startup(), 0); m_sa.sin_family = AF_INET; m_sa.sin_addr.s_addr = INADDR_ANY; m_udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ASSERT_NE(m_udp_sock, -1); // Find unused a port not used by any other service. // Otherwise srt_connect may actually connect. int bind_res = -1; const sockaddr* psa = reinterpret_cast(&m_sa); for (int port = 5000; port <= 5555; ++port) { m_sa.sin_port = htons(port); bind_res = ::bind(m_udp_sock, psa, sizeof m_sa); if (bind_res >= 0) { cerr << "Running test on port " << port << "\n"; break; } } // Close the socket to free the port. ASSERT_NE(closesocket(m_udp_sock), -1); ASSERT_GE(bind_res, 0); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &m_sa.sin_addr), 1); // Fill the buffer with random data time_t now; time(&now); unsigned int randseed = now % 255; srand(randseed); for (int i = 0; i < SRT_LIVE_DEF_PLSIZE; ++i) buf[i] = rand_r(&randseed) % 255; m_server_sock = srt_create_socket(); ASSERT_NE(srt_bind(m_server_sock, psa, sizeof m_sa), -1); ASSERT_NE(srt_listen(m_server_sock, NSOCK), -1); } void TearDown() override { srt_cleanup(); } void AcceptLoop() { //cerr << "[T] Accepting connections\n"; for (;;) { sockaddr_any addr; int len = sizeof addr; int acp = srt_accept(m_server_sock, addr.get(), &len); if (acp == -1) { cerr << "[T] Accept error at " << m_accepted.size() << "/" << NSOCK << ": " << srt_getlasterror_str() << endl; break; } //cerr << "[T] Got new acp @" << acp << endl; m_accepted.push_back(acp); } m_accept_exit = true; cerr << "[T] Closing those accepted ones\n"; for (auto s: m_accepted) { srt_close(s); } cerr << "[T] End Accept Loop\n"; } protected: SOCKET m_udp_sock = INVALID_SOCKET; sockaddr_in m_sa = sockaddr_in(); SRTSOCKET m_server_sock = SRT_INVALID_SOCK; vector m_accepted; char buf[SRT_LIVE_DEF_PLSIZE]; SRTSOCKET srt_socket_list[NSOCK]; volatile bool m_accept_exit = false; }; TEST_F(TestConnection, Multiple) { size_t size = SRT_LIVE_DEF_PLSIZE; sockaddr_in lsa = m_sa; const sockaddr* psa = reinterpret_cast(&lsa); auto ex = std::async([this] { return AcceptLoop(); }); cout << "Opening " << NSOCK << " connections\n"; for (size_t i = 0; i < NSOCK; i++) { srt_socket_list[i] = srt_create_socket(); // Give it 60s timeout, many platforms fail to process // so many connections in a short time. int conntimeo = 60; srt_setsockflag(srt_socket_list[i], SRTO_CONNTIMEO, &conntimeo, sizeof conntimeo); //cerr << "Connecting #" << i << " to " << sockaddr_any(psa).str() << "...\n"; //cerr << "Connecting to: " << sockaddr_any(psa).str() << endl; ASSERT_NE(srt_connect(srt_socket_list[i], psa, sizeof lsa), SRT_ERROR); // Set now async sending so that sending isn't blocked int no = 0; ASSERT_NE(srt_setsockflag(srt_socket_list[i], SRTO_SNDSYN, &no, sizeof no), -1); } for (size_t j = 1; j <= 100; j++) { for (size_t i = 0; i < NSOCK; i++) { EXPECT_GT(srt_send(srt_socket_list[i], buf, size), 0); } } cout << "Sending finished, closing caller sockets\n"; for (size_t i = 0; i < NSOCK; i++) { EXPECT_EQ(srt_close(srt_socket_list[i]), SRT_SUCCESS); } // Up to this moment the server sock should survive cout << "Closing server socket\n"; EXPECT_EQ(m_accept_exit, false); // Close server socket to break the accept loop EXPECT_EQ(srt_close(m_server_sock), 0); ex.wait(); } srt-1.4.4/test/test_muxer.cpp000066400000000000000000000153041412557703600162270ustar00rootroot00000000000000#include "gtest/gtest.h" #include #include "srt.h" class TestMuxer : public ::testing::Test { protected: TestMuxer() { // initialization code here } ~TestMuxer() { // cleanup any pending stuff, but no exceptions allowed } protected: // SetUp() is run immediately before a test starts. void SetUp() { ASSERT_GE(srt_startup(), 0); m_caller_sock = srt_create_socket(); ASSERT_NE(m_caller_sock, SRT_ERROR); m_server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, m_server_pollid); m_client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, m_client_pollid); int yes = 1; int no = 0; ASSERT_NE(srt_setsockopt(m_caller_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockflag(m_caller_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); ASSERT_NE(srt_setsockopt(m_caller_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); int epoll_out = SRT_EPOLL_OUT; srt_epoll_add_usock(m_client_pollid, m_caller_sock, &epoll_out); } void TearDown() { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. srt_epoll_release(m_client_pollid); srt_epoll_release(m_server_pollid); srt_close(m_listener_sock_ipv4); srt_close(m_listener_sock_ipv6); srt_cleanup(); } public: void ClientThread() { sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(m_listen_port); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); ASSERT_NE(srt_connect(m_caller_sock, (sockaddr*)&sa, sizeof sa), SRT_ERROR); // Socket readiness for connection is checked by polling on WRITE allowed sockets. { int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_NE(srt_epoll_wait(m_client_pollid, read, &rlen, write, &wlen, -1, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR); ASSERT_EQ(rlen, 0); // get exactly one write event without reads ASSERT_EQ(wlen, 1); // get exactly one write event without reads ASSERT_EQ(write[0], m_caller_sock); // for our client socket } char buffer[1316] = {1, 2, 3, 4}; ASSERT_NE(srt_sendmsg(m_caller_sock, buffer, sizeof buffer, -1, // infinit ttl true // in order must be set to true ), SRT_ERROR); } protected: SRTSOCKET m_caller_sock; SRTSOCKET m_listener_sock_ipv4; SRTSOCKET m_listener_sock_ipv6; int m_client_pollid = SRT_ERROR; int m_server_pollid = SRT_ERROR; const int m_listen_port = 4200; }; TEST_F(TestMuxer, IPv4_and_IPv6) { int yes = 1; int no = 0; // 1. Create IPv4 listening socket m_listener_sock_ipv4 = srt_create_socket(); ASSERT_NE(m_listener_sock_ipv4, SRT_ERROR); ASSERT_NE(srt_setsockopt(m_listener_sock_ipv4, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(m_listener_sock_ipv4, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); // 2. Add the IPv4 socket to epoll int epoll_in = SRT_EPOLL_IN; srt_epoll_add_usock(m_server_pollid, m_listener_sock_ipv4, &epoll_in); // 3. Bind to IPv4 address. sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(m_listen_port); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); ASSERT_NE(srt_bind(m_listener_sock_ipv4, (sockaddr*)&sa, sizeof sa), SRT_ERROR); ASSERT_NE(srt_listen(m_listener_sock_ipv4, SOMAXCONN), SRT_ERROR); // 4. Create IPv6 socket bound to the same port as IPv4 socket m_listener_sock_ipv6 = srt_create_socket(); ASSERT_NE(m_listener_sock_ipv6, SRT_ERROR); sockaddr_in6 sa_v6; memset(&sa_v6, 0, sizeof sa_v6); sa_v6.sin6_family = AF_INET6; sa_v6.sin6_port = htons(m_listen_port); ASSERT_EQ(inet_pton(AF_INET6, "::1", &sa_v6.sin6_addr), 1); // Set the IPv6only flag for the socket that should be bound to the same port // as another socket binding to IPv4 address, otherwise the binding may fail, // depending on the current value of IPV6ONLY option. ASSERT_EQ(srt_setsockflag(m_listener_sock_ipv6, SRTO_IPV6ONLY, &yes, sizeof yes), 0); ASSERT_NE(srt_bind(m_listener_sock_ipv6, (sockaddr*)&sa_v6, sizeof(sa_v6)), SRT_ERROR); std::thread client(&TestMuxer::ClientThread, this); { // wait for connection from client int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_NE(srt_epoll_wait(m_server_pollid, read, &rlen, write, &wlen, -1, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(read[0], m_listener_sock_ipv4) << "Read event on wrong socket"; } sockaddr_storage scl; int sclen = sizeof scl; SRTSOCKET accepted_sock = srt_accept(m_listener_sock_ipv4, (sockaddr*)&scl, &sclen); ASSERT_NE(accepted_sock, SRT_INVALID_SOCK); srt_epoll_add_usock(m_server_pollid, accepted_sock, &epoll_in); // wait for input char buffer[1316]; { // wait for 1316 packet from client int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_NE(srt_epoll_wait(m_server_pollid, read, &rlen, write, &wlen, -1, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); ASSERT_EQ(rlen, 1); // get exactly one read event without writes //ASSERT_EQ(wlen, 0); // get exactly one read event without writes ASSERT_EQ(read[0], accepted_sock); // read event is for bind socket } char pattern[4] = {1, 2, 3, 4}; ASSERT_EQ(srt_recvmsg(accepted_sock, buffer, sizeof buffer), 1316); ASSERT_EQ(memcmp(pattern, buffer, 4), 0); srt_close(accepted_sock); client.join(); } srt-1.4.4/test/test_reuseaddr.cpp000066400000000000000000000277171412557703600170600ustar00rootroot00000000000000#include "gtest/gtest.h" #include #ifndef _WIN32 #include #endif #include "common.h" #include "srt.h" #include "udt.h" // Copied from ../apps/apputil.cpp, can't really link this file here. sockaddr_any CreateAddr(const std::string& name, unsigned short port, int pref_family = AF_INET) { using namespace std; // Handle empty name. // If family is specified, empty string resolves to ANY of that family. // If not, it resolves to IPv4 ANY (to specify IPv6 any, use [::]). if (name == "") { sockaddr_any result(pref_family == AF_INET6 ? pref_family : AF_INET); result.hport(port); return result; } bool first6 = pref_family != AF_INET; int families[2] = {AF_INET6, AF_INET}; if (!first6) { families[0] = AF_INET; families[1] = AF_INET6; } for (int i = 0; i < 2; ++i) { int family = families[i]; sockaddr_any result (family); // Try to resolve the name by pton first if (inet_pton(family, name.c_str(), result.get_addr()) == 1) { result.hport(port); // same addr location in ipv4 and ipv6 return result; } } // If not, try to resolve by getaddrinfo // This time, use the exact value of pref_family sockaddr_any result; addrinfo fo = { 0, pref_family, 0, 0, 0, 0, NULL, NULL }; addrinfo* val = nullptr; int erc = getaddrinfo(name.c_str(), nullptr, &fo, &val); if (erc == 0) { result.set(val->ai_addr); result.len = result.size(); result.hport(port); // same addr location in ipv4 and ipv6 } freeaddrinfo(val); return result; } #ifdef _WIN32 // On Windows there's a function for it, but it requires an extra // iphlp library to be attached to the executable, which is kinda // problematic. Temporarily block tests using this function on Windows. std::string GetLocalIP() { std::cout << "!!!WARNING!!!: GetLocalIP not supported, test FORCEFULLY passed\n"; return ""; } #else struct IfAddr { ifaddrs* handle; IfAddr() { getifaddrs(&handle); } ~IfAddr() { freeifaddrs(handle); } }; std::string GetLocalIP() { struct ifaddrs * ifa=NULL; void * tmpAddrPtr=NULL; IfAddr ifAddr; for (ifa = ifAddr.handle; ifa != NULL; ifa = ifa->ifa_next) { if (!ifa->ifa_addr) { continue; } if (ifa->ifa_addr->sa_family == AF_INET) { // is a valid IP4 Address sockaddr_in* psin = (struct sockaddr_in *)ifa->ifa_addr; tmpAddrPtr=&psin->sin_addr; if (ntohl(psin->sin_addr.s_addr) == 0x7f000001) // Skip 127.0.0.1 continue; char addressBuffer[INET_ADDRSTRLEN] = ""; inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); return addressBuffer; printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); } else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6 // is a valid IP6 Address tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; char addressBuffer[INET6_ADDRSTRLEN] = ""; inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN); return addressBuffer; } } return ""; } #endif int client_pollid = SRT_ERROR; SRTSOCKET m_client_sock = SRT_ERROR; void clientSocket(std::string ip, int port, bool expect_success) { int yes = 1; int no = 0; int family = AF_INET; if (ip.substr(0, 2) == "6.") { family = AF_INET6; ip = ip.substr(2); } m_client_sock = srt_create_socket(); ASSERT_NE(m_client_sock, SRT_ERROR); ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockflag(m_client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); int epoll_out = SRT_EPOLL_OUT; srt_epoll_add_usock(client_pollid, m_client_sock, &epoll_out); sockaddr_any sa = CreateAddr(ip, port, family); std::cout << "Connecting to: " << sa.str() << std::endl; int connect_res = srt_connect(m_client_sock, sa.get(), sa.size()); if (connect_res == -1) { std::cout << "srt_connect: " << srt_getlasterror_str() << std::endl; } if (expect_success) { EXPECT_NE(connect_res, -1); // Socket readiness for connection is checked by polling on WRITE allowed sockets. { int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; EXPECT_NE(srt_epoll_wait(client_pollid, read, &rlen, write, &wlen, -1, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR); EXPECT_EQ(rlen, 0); // get exactly one write event without reads EXPECT_EQ(wlen, 1); // get exactly one write event without reads EXPECT_EQ(write[0], m_client_sock); // for our client socket } char buffer[1316] = {1, 2, 3, 4}; EXPECT_NE(srt_sendmsg(m_client_sock, buffer, sizeof buffer, -1, // infinit ttl true // in order must be set to true ), SRT_ERROR); } else { EXPECT_EQ(connect_res, -1); } std::cout << "Client exit\n"; } int server_pollid = SRT_ERROR; void serverSocket(std::string ip, int port, bool expect_success) { int yes = 1; int no = 0; SRTSOCKET m_bindsock = srt_create_socket(); ASSERT_NE(m_bindsock, SRT_ERROR); ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); int epoll_in = SRT_EPOLL_IN; srt_epoll_add_usock(server_pollid, m_bindsock, &epoll_in); sockaddr_any sa = CreateAddr(ip, port); std::cout << "Bind to: " << sa.str() << std::endl; int bind_res = srt_bind(m_bindsock, sa.get(), sa.size()); if (!expect_success) { std::cout << "Binding should fail: " << srt_getlasterror_str() << std::endl; ASSERT_EQ(bind_res, SRT_ERROR); return; } ASSERT_NE(bind_res, SRT_ERROR); ASSERT_NE(srt_listen(m_bindsock, SOMAXCONN), SRT_ERROR); if (ip == "0.0.0.0") ip = "127.0.0.1"; // override wildcard else if ( ip == "::") ip = "::1"; std::thread client(clientSocket, ip, port, expect_success); { // wait for connection from client int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_NE(srt_epoll_wait(server_pollid, read, &rlen, write, &wlen, 3000, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes ASSERT_EQ(read[0], m_bindsock); // read event is for bind socket } sockaddr_any scl; SRTSOCKET m_sock = srt_accept(m_bindsock, scl.get(), &scl.len); if (m_sock == -1) { std::cout << "srt_accept: " << srt_getlasterror_str() << std::endl; } ASSERT_NE(m_sock, SRT_INVALID_SOCK); sockaddr_any showacp = (sockaddr*)&scl; std::cout << "Accepted from: " << showacp.str() << std::endl; srt_epoll_add_usock(server_pollid, m_sock, &epoll_in); // wait for input char buffer[1316]; { // wait for 1316 packet from client int rlen = 2; SRTSOCKET read[2]; int wlen = 2; SRTSOCKET write[2]; ASSERT_NE(srt_epoll_wait(server_pollid, read, &rlen, write, &wlen, -1, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes ASSERT_EQ(read[0], m_sock); // read event is for bind socket } char pattern[4] = {1, 2, 3, 4}; ASSERT_EQ(srt_recvmsg(m_sock, buffer, sizeof buffer), 1316); EXPECT_EQ(memcmp(pattern, buffer, sizeof pattern), 0); client.join(); srt_close(m_sock); srt_close(m_bindsock); srt_close(m_client_sock); // cannot close m_client_sock after srt_sendmsg because of issue in api.c:2346 std::cout << "Server exit\n"; } TEST(ReuseAddr, SameAddr1) { ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); std::thread server_1(serverSocket, "127.0.0.1", 5000, true); server_1.join(); std::thread server_2(serverSocket, "127.0.0.1", 5000, true); server_2.join(); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); srt_cleanup(); } TEST(ReuseAddr, SameAddr2) { std::string localip = GetLocalIP(); if (localip == "") return; // DISABLE TEST if this doesn't work. ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); std::thread server_1(serverSocket, localip, 5000, true); server_1.join(); std::thread server_2(serverSocket, localip, 5000, true); server_2.join(); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); srt_cleanup(); } TEST(ReuseAddr, DiffAddr) { std::string localip = GetLocalIP(); if (localip == "") return; // DISABLE TEST if this doesn't work. ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); serverSocket("127.0.0.1", 5000, true); serverSocket(localip, 5000, true); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); srt_cleanup(); } TEST(ReuseAddr, Wildcard) { #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; return; #endif std::string localip = GetLocalIP(); if (localip == "") return; // DISABLE TEST if this doesn't work. ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); serverSocket("0.0.0.0", 5000, true); serverSocket(localip, 5000, false); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); srt_cleanup(); } TEST(ReuseAddr, ProtocolVersion) { #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; return; #endif ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); serverSocket("::", 5000, true); serverSocket("0.0.0.0", 5000, true); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); srt_cleanup(); } srt-1.4.4/test/test_seqno.cpp000066400000000000000000000060751412557703600162210ustar00rootroot00000000000000#include "gtest/gtest.h" #include "common.h" #include "core.h" using namespace srt; const int32_t CSeqNo::m_iSeqNoTH; const int32_t CSeqNo::m_iMaxSeqNo; TEST(CSeqNo, constants) { // Compare two seq#, considering the wraping. EXPECT_EQ(CSeqNo::m_iMaxSeqNo, 0x7FFFFFFF); EXPECT_EQ(CSeqNo::m_iSeqNoTH, 0x3FFFFFFF); } TEST(CSeqNo, seqcmp) { // Compare two seq#, considering the wraping. EXPECT_EQ(CSeqNo::seqcmp(0x7FFFFFFF, 0x7FFFFFFF), 0); // abs(seq1 - seq2) < 0x3FFFFFFF : seq1 - seq2 EXPECT_EQ(CSeqNo::seqcmp(128, 1), 127); EXPECT_EQ(CSeqNo::seqcmp(1, 128), -127); // abs(seq1 - seq2) >= 0x3FFFFFFF : seq2 - seq1 EXPECT_EQ(CSeqNo::seqcmp(0x7FFFFFFF, 1), int(0x80000002)); // -2147483646 EXPECT_EQ(CSeqNo::seqcmp(1, 0x7FFFFFFF), 0x7FFFFFFE); // 2147483646 } TEST(CSeqNo, seqoff) { // seqoff: offset from the 2nd to the 1st seq# EXPECT_EQ(CSeqNo::seqoff(0x7FFFFFFF, 0x7FFFFFFF), 0); // distance(seq2 - seq1) EXPECT_EQ(CSeqNo::seqoff(125, 1), -124); EXPECT_EQ(CSeqNo::seqoff(1, 0x7FFFFFFF), -2); EXPECT_EQ(CSeqNo::seqoff(0x7FFFFFFF, 1), 2); } TEST(CSeqNo, seqlen) { EXPECT_EQ(CSeqNo::seqlen(125, 125), 1); EXPECT_EQ(CSeqNo::seqlen(125, 126), 2); EXPECT_EQ(CSeqNo::seqlen(2147483647, 0), 2); EXPECT_EQ(CSeqNo::seqlen(0, 2147483647), -2147483648); } TEST(CUDT, getFlightSpan) { const int test_values[3][3] = { // lastack curseq span { 125, 124, 0 }, // all sent packets are acknowledged { 125, 125, 1 }, { 125, 130, 6 } }; for (auto val : test_values) { EXPECT_EQ(CUDT::getFlightSpan(val[0], val[1]), val[2]) << "Span(" << val[0] << ", " << val[1] << ")"; } } TEST(CSeqNo, incseq) { // incseq: increase the seq# by 1 EXPECT_EQ(CSeqNo::incseq(1), 2); EXPECT_EQ(CSeqNo::incseq(125), 126); EXPECT_EQ(CSeqNo::incseq(0x7FFFFFFF), 0); EXPECT_EQ(CSeqNo::incseq(0x3FFFFFFF), 0x40000000); } TEST(CSeqNo, decseq) { // decseq: decrease the seq# by 1 EXPECT_EQ(CSeqNo::decseq(1), 0); EXPECT_EQ(CSeqNo::decseq(125), 124); EXPECT_EQ(CSeqNo::decseq(0), 0x7FFFFFFF); EXPECT_EQ(CSeqNo::decseq(0x40000000), 0x3FFFFFFF); } TEST(CSeqNo, incseqint) { // incseq: increase the seq# by 1 EXPECT_EQ(CSeqNo::incseq(1, 1), 2); EXPECT_EQ(CSeqNo::incseq(125, 1), 126); EXPECT_EQ(CSeqNo::incseq(0x7FFFFFFF, 1), 0); EXPECT_EQ(CSeqNo::incseq(0x3FFFFFFF, 1), 0x40000000); EXPECT_EQ(CSeqNo::incseq(0x3FFFFFFF, 0x3FFFFFFF), 0x7FFFFFFE); EXPECT_EQ(CSeqNo::incseq(0x3FFFFFFF, 0x40000000), 0x7FFFFFFF); EXPECT_EQ(CSeqNo::incseq(0x3FFFFFFF, 0x40000001), 0x00000000); } TEST(CSeqNo, decseqint) { // decseq: decrease the seq# by 1 EXPECT_EQ(CSeqNo::decseq(1, 1), 0); EXPECT_EQ(CSeqNo::decseq(125, 1), 124); EXPECT_EQ(CSeqNo::decseq(0, 1), 0x7FFFFFFF); EXPECT_EQ(CSeqNo::decseq(0x40000000, 1), 0x3FFFFFFF); } srt-1.4.4/test/test_socket_options.cpp000066400000000000000000001121711412557703600201320ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2019 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Written by: * Haivision Systems Inc. */ #include #include #include #include // SRT includes #include "any.hpp" #include "socketconfig.h" #include "srt.h" using namespace std; class TestSocketOptions : public ::testing::Test { protected: TestSocketOptions() { // initialization code here } ~TestSocketOptions() { // cleanup any pending stuff, but no exceptions allowed } public: void BindListener() { // Specify address of the listener sockaddr* psa = (sockaddr*)&m_sa; ASSERT_NE(srt_bind(m_listen_sock, psa, sizeof m_sa), SRT_ERROR); } void StartListener() { BindListener(); srt_listen(m_listen_sock, 1); } int Connect() { sockaddr* psa = (sockaddr*)&m_sa; return srt_connect(m_caller_sock, psa, sizeof m_sa); } SRTSOCKET EstablishConnection() { auto accept_async = [](SRTSOCKET listen_sock) { sockaddr_in client_address; int length = sizeof(sockaddr_in); const SRTSOCKET accepted_socket = srt_accept(listen_sock, (sockaddr*)&client_address, &length); return accepted_socket; }; auto accept_res = async(launch::async, accept_async, m_listen_sock); const int connect_res = Connect(); EXPECT_EQ(connect_res, SRT_SUCCESS); const SRTSOCKET accepted_sock = accept_res.get(); EXPECT_NE(accepted_sock, SRT_INVALID_SOCK); return accepted_sock; } protected: // SetUp() is run immediately before a test starts. void SetUp() { ASSERT_GE(srt_startup(), 0); const int yes = 1; memset(&m_sa, 0, sizeof m_sa); m_sa.sin_family = AF_INET; m_sa.sin_port = htons(5200); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &m_sa.sin_addr), 1); m_caller_sock = srt_create_socket(); ASSERT_NE(m_caller_sock, SRT_INVALID_SOCK); ASSERT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_RCVSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect ASSERT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_SNDSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect m_listen_sock = srt_create_socket(); ASSERT_NE(m_listen_sock, SRT_INVALID_SOCK); ASSERT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_RCVSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect ASSERT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_SNDSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect } void TearDown() { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. ASSERT_NE(srt_close(m_caller_sock), SRT_ERROR); ASSERT_NE(srt_close(m_listen_sock), SRT_ERROR); srt_cleanup(); } protected: // put in any custom data members that you need sockaddr_in m_sa; SRTSOCKET m_caller_sock = SRT_INVALID_SOCK; SRTSOCKET m_listen_sock = SRT_INVALID_SOCK; int m_pollid = 0; }; enum class RestrictionType { PREBIND = 0, PRE = 1, POST = 2 }; const char* RestrictionTypeStr(RestrictionType val) { switch (val) { case RestrictionType::PREBIND: return "PREBIND"; break; case RestrictionType::PRE: return "PRE"; break; case RestrictionType::POST: return "POST"; break; default: break; } return "INVALID"; } struct OptionTestEntry { SRT_SOCKOPT optid; const char* optname; // TODO: move to a separate array, function or std::map. RestrictionType restriction; // TODO: consider using SRTO_R_PREBIND, etc. from core.cpp size_t opt_len; linb::any min_val; linb::any max_val; linb::any dflt_val; linb::any ndflt_val; vector invalid_vals; }; static const size_t UDP_HDR_SIZE = 28; // 20 bytes IPv4 + 8 bytes of UDP { u16 sport, dport, len, csum }. static const size_t DFT_MTU_SIZE = 1500; // Default MTU size static const size_t SRT_PKT_SIZE = DFT_MTU_SIZE - UDP_HDR_SIZE; // MTU without UDP header const OptionTestEntry g_test_matrix_options[] = { // Option ID, Option Name | Restriction | optlen | min | max | default | nondefault | invalid vals | //SRTO_BINDTODEVICE //{ SRTO_CONGESTION, "SRTO_CONGESTION", RestrictionType::PRE, 4, "live", "file", "live", "file", {"liv", ""} }, { SRTO_CONNTIMEO, "SRTO_CONNTIMEO", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 3000, 250, {-1} }, { SRTO_DRIFTTRACER, "SRTO_DRIFTTRACER", RestrictionType::POST, sizeof(bool), false, true, true, false, {} }, { SRTO_ENFORCEDENCRYPTION, "SRTO_ENFORCEDENCRYPTION", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, //SRTO_EVENT { SRTO_FC, "SRTO_FC", RestrictionType::PRE, sizeof(int), 32, INT32_MAX, 25600, 10000, {-1, 31} }, //SRTO_GROUPCONNECT //SRTO_GROUPSTABTIMEO //SRTO_GROUPTYPE //SRTO_INPUTBW //SRTO_IPTOS //SRTO_IPTTL //SRTO_IPV6ONLY //SRTO_ISN { SRTO_KMPREANNOUNCE, "SRTO_KMPREANNOUNCE", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0, 1024, {-1} }, { SRTO_KMREFRESHRATE, "SRTO_KMREFRESHRATE", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0, 1024, {-1} }, //SRTO_KMSTATE { SRTO_LATENCY, "SRTO_LATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 120, 200, {-1} }, //SRTO_LINGER { SRTO_LOSSMAXTTL, "SRTO_LOSSMAXTTL", RestrictionType::POST, sizeof(int), 0, INT32_MAX, 0, 10, {} }, //SRTO_MAXBW { SRTO_MESSAGEAPI, "SRTO_MESSAGEAPI", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, //SRTO_MININPUTBW { SRTO_MINVERSION, "SRTO_MINVERSION", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0x010000, 0x010300, {} }, { SRTO_MSS, "SRTO_MSS", RestrictionType::PREBIND, sizeof(int), 76, 65536, 1500, 1400, {-1, 0, 75} }, { SRTO_NAKREPORT, "SRTO_NAKREPORT", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, { SRTO_OHEADBW, "SRTO_OHEADBW", RestrictionType::POST, sizeof(int), 5, 100, 25, 20, {-1, 0, 4, 101} }, //SRTO_PACKETFILTER //SRTO_PASSPHRASE { SRTO_PAYLOADSIZE, "SRTO_PAYLOADSIZE", RestrictionType::PRE, sizeof(int), 0, 1456, 1316, 1400, {-1, 1500} }, //SRTO_PBKEYLEN //SRTO_PEERIDLETIMEO { SRTO_PEERIDLETIMEO, "SRTO_PEERIDLETIMEO", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 5000, 4500, {-1} }, { SRTO_PEERLATENCY, "SRTO_PEERLATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0, 180, {-1} }, //SRTO_PEERVERSION { SRTO_RCVBUF, "SRTO_RCVBUF", RestrictionType::PREBIND, sizeof(int), (int)(32 * SRT_PKT_SIZE), 2147483256, (int)(8192 * SRT_PKT_SIZE), 1000000, {-1} }, //SRTO_RCVDATA //SRTO_RCVKMSTATE { SRTO_RCVLATENCY, "SRTO_RCVLATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 120, 1100, {-1} }, //SRTO_RCVSYN { SRTO_RCVTIMEO, "SRTO_RCVTIMEO", RestrictionType::POST, sizeof(int), -1, INT32_MAX, -1, 2000, {-2} }, //SRTO_RENDEZVOUS { SRTO_RETRANSMITALGO, "SRTO_RETRANSMITALGO", RestrictionType::PRE, sizeof(int), 0, 1, 1, 0, {-1, 2} }, //SRTO_REUSEADDR //SRTO_SENDER { SRTO_SNDBUF, "SRTO_SNDBUF", RestrictionType::PREBIND, sizeof(int), (int)(32 * SRT_PKT_SIZE), 2147483256, (int)(8192 * SRT_PKT_SIZE), 1000000, {-1} }, //SRTO_SNDDATA { SRTO_SNDDROPDELAY, "SRTO_SNDDROPDELAY", RestrictionType::POST, sizeof(int), -1, INT32_MAX, 0, 1500, {-2} }, //SRTO_SNDKMSTATE //SRTO_SNDSYN { SRTO_SNDTIMEO, "SRTO_SNDTIMEO", RestrictionType::POST, sizeof(int), -1, INT32_MAX, -1, 1400, {-2} }, //SRTO_STATE //SRTO_STREAMID { SRTO_TLPKTDROP, "SRTO_TLPKTDROP", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, //SRTO_TRANSTYPE //SRTO_TSBPDMODE //SRTO_UDP_RCVBUF //SRTO_UDP_SNDBUF //SRTO_VERSION }; template void CheckGetSockOpt(const OptionTestEntry& entry, SRTSOCKET sock, const ValueType& value, const char* desc) { ValueType opt_val; int opt_len = 0; EXPECT_EQ(srt_getsockopt(sock, 0, entry.optid, &opt_val, &opt_len), SRT_SUCCESS) << "Getting " << entry.optname << " returned error: " << srt_getlasterror_str(); EXPECT_EQ(opt_val, value) << desc << ": Wrong " << entry.optname << " value " << opt_val; EXPECT_EQ(opt_len, entry.opt_len) << desc << "Wrong " << entry.optname << " value length"; } typedef char const* strptr; template<> void CheckGetSockOpt(const OptionTestEntry& entry, SRTSOCKET sock, const strptr& value, const char* desc) { char opt_val[16]; int opt_len = 0; EXPECT_EQ(srt_getsockopt(sock, 0, entry.optid, &opt_val, &opt_len), SRT_SUCCESS) << "Getting " << entry.optname << " returned error: " << srt_getlasterror_str(); EXPECT_EQ(strncmp(opt_val, value, min(opt_len, entry.opt_len)), 0) << desc << ": Wrong " << entry.optname << " value " << opt_val; EXPECT_EQ(opt_len, entry.opt_len) << desc << "Wrong " << entry.optname << " value length"; } template void CheckSetSockOpt(const OptionTestEntry& entry, SRTSOCKET sock, const ValueType& value, int expect_return, const char* desc) { ValueType opt_val = value; int opt_len = entry.opt_len; EXPECT_EQ(srt_setsockopt(sock, 0, entry.optid, &opt_val, opt_len), expect_return) << "Setting " << entry.optname << " to " << opt_val << " must " << (expect_return == SRT_SUCCESS ? "succeed" : "fail"); if (expect_return == SRT_SUCCESS) { CheckGetSockOpt(entry, sock, value, desc); } // TODO: else check the previous value is in force } template bool CheckDefaultValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* desc) { try { const ValueType dflt_val = linb::any_cast(entry.dflt_val); CheckGetSockOpt(entry, sock, dflt_val, desc); } catch (const linb::bad_any_cast&) { std::cerr << entry.optname << " default value type: " << entry.dflt_val.type().name() << "\n"; return false; } return true; } template bool CheckSetNonDefaultValue(const OptionTestEntry& entry, SRTSOCKET sock, int expected_return, const char* desc) { try { /*const ValueType dflt_val = linb::any_cast(entry.dflt_val); const ValueType min_val = linb::any_cast(entry.min_val); const ValueType max_val = linb::any_cast(entry.max_val);*/ //const ValueType ndflt_val = (min_val != dflt_val) ? min_val : max_val; const ValueType ndflt_val = linb::any_cast(entry.ndflt_val);; CheckSetSockOpt(entry, sock, ndflt_val, expected_return, desc); } catch (const linb::bad_any_cast&) { std::cerr << entry.optname << " non-default value type: " << entry.ndflt_val.type().name() << "\n"; return false; } return true; } template bool CheckMinValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* desc) { try { const ValueType min_val = linb::any_cast(entry.min_val); CheckSetSockOpt(entry, sock, min_val, SRT_SUCCESS, desc); const ValueType dflt_val = linb::any_cast(entry.dflt_val); CheckSetSockOpt(entry, sock, dflt_val, SRT_SUCCESS, desc); } catch (const linb::bad_any_cast&) { std::cerr << entry.optname << " min value type: " << entry.min_val.type().name() << "\n"; return false; } return true; } template bool CheckMaxValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* desc) { try { const ValueType max_val = linb::any_cast(entry.max_val); CheckSetSockOpt(entry, sock, max_val, SRT_SUCCESS, desc); } catch (const linb::bad_any_cast&) { std::cerr << entry.optname << " max value type: " << entry.max_val.type().name() << "\n"; return false; } return true; } template bool CheckInvalidValues(const OptionTestEntry& entry, SRTSOCKET sock, const char* sock_name) { for (const auto& inval : entry.invalid_vals) { try { const ValueType val = linb::any_cast(inval); CheckSetSockOpt(entry, sock, val, SRT_ERROR, sock_name); } catch (const linb::bad_any_cast&) { std::cerr << entry.optname << " value type: " << inval.type().name() << "\n"; return false; } } return true; } TEST_F(TestSocketOptions, DefaultVals) { for (const auto& entry : g_test_matrix_options) { const char* test_desc = "[Caller, default]"; if (entry.dflt_val.type() == typeid(bool)) { EXPECT_TRUE(CheckDefaultValue(entry, m_caller_sock, test_desc)); } else if (entry.dflt_val.type() == typeid(int)) { EXPECT_TRUE(CheckDefaultValue(entry, m_caller_sock, test_desc)); } else if (entry.dflt_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckDefaultValue(entry, m_caller_sock, test_desc)); } else if (entry.dflt_val.type() == typeid(const char*)) { EXPECT_TRUE(CheckDefaultValue(entry, m_caller_sock, test_desc)); } else { FAIL() << entry.optname << ": Unexpected type " << entry.dflt_val.type().name(); } } } TEST_F(TestSocketOptions, MaxVals) { // Note: Changing SRTO_FC changes SRTO_RCVBUF limitation for (const auto& entry : g_test_matrix_options) { if (entry.optid == SRTO_KMPREANNOUNCE || entry.optid == SRTO_KMREFRESHRATE) { cerr << "Skipping " << entry.optname << "\n"; continue; } const char* test_desc = "[Caller, max value]"; if (entry.max_val.type() == typeid(bool)) { EXPECT_TRUE(CheckMaxValue(entry, m_caller_sock, test_desc)); } else if (entry.max_val.type() == typeid(int)) { EXPECT_TRUE(CheckMaxValue(entry, m_caller_sock, test_desc)); } else if (entry.max_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckMaxValue(entry, m_caller_sock, test_desc)); } else { FAIL() << "Unexpected type " << entry.max_val.type().name(); } // TODO: back to default ? } } TEST_F(TestSocketOptions, MinVals) { // Note: Changing SRTO_FC changes SRTO_RCVBUF limitation for (const auto& entry : g_test_matrix_options) { const char* test_desc = "[Caller, min val]"; if (entry.min_val.type() == typeid(bool)) { EXPECT_TRUE(CheckMinValue(entry, m_caller_sock, test_desc)); } else if (entry.min_val.type() == typeid(int)) { EXPECT_TRUE(CheckMinValue(entry, m_caller_sock, test_desc)); } else if (entry.min_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckMinValue(entry, m_caller_sock, test_desc)); } else { FAIL() << entry.optname << ": Unexpected type " << entry.min_val.type().name(); } // TODO: back to default } } TEST_F(TestSocketOptions, InvalidVals) { // Note: Changing SRTO_FC changes SRTO_RCVBUF limitation for (const auto& entry : g_test_matrix_options) { const char* desc = "[Caller, invalid val]"; if (entry.dflt_val.type() == typeid(bool)) { EXPECT_TRUE(CheckInvalidValues(entry, m_caller_sock, desc)); } else if (entry.dflt_val.type() == typeid(int)) { EXPECT_TRUE(CheckInvalidValues(entry, m_caller_sock, desc)); } else if (entry.dflt_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckInvalidValues(entry, m_caller_sock, desc)); } else { FAIL() << "Unexpected type " << entry.dflt_val.type().name(); } // TODO: expect default is still in force? } } // TODO: taken from test_enforced_encryption static const char* const socket_state_array[] = { "IGNORE_SRTS", "SRTS_INVALID", "SRTS_INIT", "SRTS_OPENED", "SRTS_LISTENING", "SRTS_CONNECTING", "SRTS_CONNECTED", "SRTS_BROKEN", "SRTS_CLOSING", "SRTS_CLOSED", "SRTS_NONEXIST" }; // A trick that allows the array to be indexed by -1 const char* const* g_socket_state = socket_state_array + 1; #if 0 // No socket option can be set in blocking mode because m_ConnectionLock is required by both srt_setsockopt and srt_connect // TODO: Use non-blocking mode TEST_F(TestSocketOptions, RestrictionCallerConnecting) { // The default SRTO_CONNTIMEO is 3 seconds. It is assumed all socket options could be checked. auto connect_async = [this]() { return Connect(); }; auto connect_res = async(launch::async, connect_async); for (int i = 0; i < 100; ++i) { if (srt_getsockstate(m_caller_sock) == SRTS_CONNECTING) break; this_thread::sleep_for(chrono::microseconds(100)); } cout << "Running test\n"; for (const auto& entry : g_test_matrix_options) { if (entry.restriction != RestrictionType::PRE) continue; // Setting a valid minimum value EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, entry.optid, &entry.min_val, entry.opt_len), SRT_ERROR) << "Setting " << entry.optname << " (PRE) must not succeed while connecting. Sock state: " << g_socket_state[srt_getsockstate(m_caller_sock)]; } connect_res.get(); } #endif TEST_F(TestSocketOptions, RestrictionBind) { BindListener(); for (const auto& entry : g_test_matrix_options) { const char* test_desc = "[Caller, after bind]"; const int expected_res = (entry.restriction == RestrictionType::PREBIND) ? SRT_ERROR : SRT_SUCCESS; if (entry.dflt_val.type() == typeid(bool)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) << "Sock state : " << g_socket_state[srt_getsockstate(m_listen_sock)]; } else if (entry.dflt_val.type() == typeid(int)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) << "Sock state : " << g_socket_state[srt_getsockstate(m_listen_sock)]; } else if (entry.dflt_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) << "Sock state : " << g_socket_state[srt_getsockstate(m_listen_sock)]; } else { FAIL() << "Unexpected type " << entry.dflt_val.type().name(); } } } // Check that only socket option with POST binding can be set on a listener socket in "listening" state. TEST_F(TestSocketOptions, RestrictionListening) { StartListener(); for (const auto& entry : g_test_matrix_options) { const int expected_res = (entry.restriction != RestrictionType::POST) ? SRT_ERROR : SRT_SUCCESS; // Setting a valid minimum value const char* test_desc ="[Listener, listening]"; if (entry.dflt_val.type() == typeid(bool)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(m_listen_sock)]; } else if (entry.dflt_val.type() == typeid(int)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(m_listen_sock)]; } else if (entry.dflt_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(m_listen_sock)]; } else { FAIL() << "Unexpected type " << entry.dflt_val.type().name(); } } } // Check that only socket option with POST binding can be set on a connected socket (caller and accepted). TEST_F(TestSocketOptions, RestrictionConnected) { StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); for (const auto& entry : g_test_matrix_options) { const int expected_res = (entry.restriction != RestrictionType::POST) ? SRT_ERROR : SRT_SUCCESS; // Setting a valid minimum value for (SRTSOCKET sock : { m_caller_sock, accepted_sock }) { const char* test_desc = sock == m_caller_sock ? "[Caller, connected]" : "[Accepted, connected]"; if (entry.dflt_val.type() == typeid(bool)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, sock, expected_res, test_desc)) << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(sock)]; } else if (entry.dflt_val.type() == typeid(int)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, sock, expected_res, test_desc)) << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(sock)]; } else if (entry.dflt_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, sock, expected_res, test_desc)) << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(sock)]; } else { FAIL() << "Unexpected type " << entry.dflt_val.type().name(); } } } } // TODO: TEST_F(TestSocketOptions, CheckInheritedAfterConnection) // Check that accepted socket has correct socket option values. // Check setting and getting SRT_MININPUTBW TEST_F(TestSocketOptions, TLPktDropInherits) { const bool tlpktdrop_dflt = true; const bool tlpktdrop_new = false; bool optval = tlpktdrop_dflt; int optlen = (int)(sizeof optval); EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_TLPKTDROP, &tlpktdrop_new, sizeof tlpktdrop_new), SRT_SUCCESS); EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_TLPKTDROP, &optval, &optlen), SRT_SUCCESS); EXPECT_EQ(optval, tlpktdrop_new); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values for (SRTSOCKET sock : { m_listen_sock, accepted_sock }) { optval = tlpktdrop_dflt; optlen = (int)(sizeof optval); EXPECT_EQ(srt_getsockopt(sock, 0, SRTO_TLPKTDROP, &optval, &optlen), SRT_SUCCESS); EXPECT_EQ(optlen, (int)(sizeof optval)); EXPECT_EQ(optval, tlpktdrop_new); } this_thread::sleep_for(chrono::seconds(2)); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } TEST_F(TestSocketOptions, Latency) { const int latency_a = 140; const int latency_b = 100; const int latency_dflt = 120; int optval; int optlen = (int)(sizeof optval); EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_RCVLATENCY, &latency_a, sizeof latency_a), SRT_SUCCESS); EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_PEERLATENCY, &latency_b, sizeof latency_b), SRT_SUCCESS); EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_RCVLATENCY, &optval, &optlen), SRT_SUCCESS); EXPECT_EQ(optval, latency_a); EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_PEERLATENCY, &optval, &optlen), SRT_SUCCESS); EXPECT_EQ(optval, latency_b); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check caller socket EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_RCVLATENCY, &optval, &optlen), SRT_SUCCESS); EXPECT_EQ(optval, latency_dflt); EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_PEERLATENCY, &optval, &optlen), SRT_SUCCESS); EXPECT_EQ(optval, latency_a); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_RCVLATENCY, &optval, &optlen), SRT_SUCCESS); EXPECT_EQ(optval, latency_a); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_PEERLATENCY, &optval, &optlen), SRT_SUCCESS); EXPECT_EQ(optval, latency_dflt); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } /// A regression test for issue #735, fixed by PR #843. /// Checks propagation of listener's socket option SRTO_LOSSMAXTTL /// on SRT sockets being accepted. TEST_F(TestSocketOptions, LossMaxTTL) { const int loss_max_ttl = 5; ASSERT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_LOSSMAXTTL, &loss_max_ttl, sizeof loss_max_ttl), SRT_SUCCESS); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); int opt_val = 0; int opt_len = 0; ASSERT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_LOSSMAXTTL, &opt_val, &opt_len), SRT_SUCCESS); EXPECT_EQ(opt_val, loss_max_ttl) << "Wrong SRTO_LOSSMAXTTL value on the accepted socket"; EXPECT_EQ(size_t(opt_len), sizeof opt_len) << "Wrong SRTO_LOSSMAXTTL value length on the accepted socket"; SRT_TRACEBSTATS stats; EXPECT_EQ(srt_bstats(accepted_sock, &stats, 0), SRT_SUCCESS); EXPECT_EQ(stats.pktReorderTolerance, loss_max_ttl); ASSERT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_LOSSMAXTTL, &opt_val, &opt_len), SRT_SUCCESS); EXPECT_EQ(opt_val, loss_max_ttl) << "Wrong SRTO_LOSSMAXTTL value on the listener socket"; EXPECT_EQ(size_t(opt_len), sizeof opt_len) << "Wrong SRTO_LOSSMAXTTL value length on the listener socket"; ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } // Try to set/get SRTO_MININPUTBW with wrong optlen TEST_F(TestSocketOptions, MinInputBWWrongLen) { int64_t mininputbw = 0; int optlen = (int)(sizeof mininputbw) - 1; EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &mininputbw, &optlen), SRT_ERROR); EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); optlen += 2; EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &mininputbw, &optlen), SRT_SUCCESS) << "Bigger storage is allowed"; EXPECT_EQ(optlen, (int)(sizeof mininputbw)); EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &mininputbw, sizeof mininputbw - 1), SRT_ERROR); EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &mininputbw, sizeof mininputbw + 1), SRT_ERROR); EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); } // Check the default SRTO_MININPUTBW is SRT_PACING_MAXBW_DEFAULT TEST_F(TestSocketOptions, MinInputBWDefault) { const int mininputbw_expected = 0; int64_t mininputbw = 1; int optlen = (int)(sizeof mininputbw); EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &mininputbw, &optlen), SRT_SUCCESS); EXPECT_EQ(optlen, (int)(sizeof mininputbw)); EXPECT_EQ(mininputbw, mininputbw_expected); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check both listener and accepted socket have default values for (SRTSOCKET sock : { m_listen_sock, accepted_sock }) { optlen = (int)(sizeof mininputbw); EXPECT_EQ(srt_getsockopt(sock, 0, SRTO_MININPUTBW, &mininputbw, &optlen), SRT_SUCCESS); EXPECT_EQ(optlen, (int)(sizeof mininputbw)); EXPECT_EQ(mininputbw, mininputbw_expected); } ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } // Check setting and getting SRT_MININPUTBW TEST_F(TestSocketOptions, MinInputBWSet) { const int64_t mininputbw_dflt = 0; const int64_t mininputbw = 50000000; int optlen = (int)(sizeof mininputbw); int64_t bw = -100; EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &bw, sizeof bw), SRT_ERROR) << "Has to be a non-negative number"; EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &bw, &optlen), SRT_SUCCESS); EXPECT_EQ(bw, mininputbw_dflt); bw = mininputbw; EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &bw, sizeof bw), SRT_SUCCESS); EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &bw, &optlen), SRT_SUCCESS); EXPECT_EQ(bw, mininputbw); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values for (SRTSOCKET sock : { m_listen_sock, accepted_sock }) { optlen = (int)(sizeof bw); EXPECT_EQ(srt_getsockopt(sock, 0, SRTO_MININPUTBW, &bw, &optlen), SRT_SUCCESS); EXPECT_EQ(optlen, (int)(sizeof bw)); EXPECT_EQ(bw, mininputbw); } ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } // Check setting and getting SRTO_MININPUTBW in runtime TEST_F(TestSocketOptions, MinInputBWRuntime) { const int64_t mininputbw = 50000000; // Establish a connection StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Test a connected socket int64_t bw = mininputbw; int optlen = (int)(sizeof bw); EXPECT_EQ(srt_setsockopt(accepted_sock, 0, SRTO_MININPUTBW, &bw, sizeof bw), SRT_SUCCESS); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_MININPUTBW, &bw, &optlen), SRT_SUCCESS); EXPECT_EQ(bw, mininputbw); bw = 0; EXPECT_EQ(srt_setsockopt(accepted_sock, 0, SRTO_INPUTBW, &bw, sizeof bw), SRT_SUCCESS); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_INPUTBW, &bw, &optlen), SRT_SUCCESS); EXPECT_EQ(bw, 0); EXPECT_EQ(srt_setsockopt(accepted_sock, 0, SRTO_MAXBW, &bw, sizeof bw), SRT_SUCCESS); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_MAXBW, &bw, &optlen), SRT_SUCCESS); EXPECT_EQ(bw, 0); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_MININPUTBW, &bw, &optlen), SRT_SUCCESS); EXPECT_EQ(bw, mininputbw); const int64_t new_mininputbw = 20000000; bw = new_mininputbw; EXPECT_EQ(srt_setsockopt(accepted_sock, 0, SRTO_MININPUTBW, &bw, sizeof bw), SRT_SUCCESS); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_MININPUTBW, &bw, &optlen), SRT_SUCCESS); EXPECT_EQ(bw, new_mininputbw); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } TEST_F(TestSocketOptions, StreamIDWrongLen) { char buffer[CSrtConfig::MAX_SID_LENGTH + 135]; for (size_t i = 0; i < sizeof buffer; ++i) buffer[i] = 'a' + i % 25; EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, buffer, CSrtConfig::MAX_SID_LENGTH+1), SRT_ERROR); EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); } // Try to set/get a 13-character string in SRTO_STREAMID. // This tests checks that the StreamID is set to the correct size // while it is transmitted as 16 characters in the Stream ID HS extension. TEST_F(TestSocketOptions, StreamIDOdd) { // 13 characters, that is, 3*4+1 string sid_odd = "something1234"; EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_odd.c_str(), sid_odd.size()), SRT_SUCCESS); char buffer[CSrtConfig::MAX_SID_LENGTH + 135]; int buffer_len = sizeof buffer; EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(std::string(buffer), sid_odd); EXPECT_EQ(size_t(buffer_len), sid_odd.size()); EXPECT_EQ(strlen(buffer), sid_odd.size()); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values for (size_t i = 0; i < sizeof buffer; ++i) buffer[i] = 'a'; buffer_len = (int)(sizeof buffer); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(size_t(buffer_len), sid_odd.size()); EXPECT_EQ(strlen(buffer), sid_odd.size()); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } TEST_F(TestSocketOptions, StreamIDEven) { // 12 characters = 4*3, that is, aligned to 4 string sid_even = "123412341234"; EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_even.c_str(), sid_even.size()), SRT_SUCCESS); char buffer[CSrtConfig::MAX_SID_LENGTH + 135]; int buffer_len = sizeof buffer; EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(std::string(buffer), sid_even); EXPECT_EQ(size_t(buffer_len), sid_even.size()); EXPECT_EQ(strlen(buffer), sid_even.size()); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values for (size_t i = 0; i < sizeof buffer; ++i) buffer[i] = 'a'; buffer_len = (int)(sizeof buffer); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(size_t(buffer_len), sid_even.size()); EXPECT_EQ(strlen(buffer), sid_even.size()); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } TEST_F(TestSocketOptions, StreamIDAlmostFull) { // 12 characters = 4*3, that is, aligned to 4 string sid_amost_full; for (size_t i = 0; i < CSrtConfig::MAX_SID_LENGTH-2; ++i) sid_amost_full += 'x'; // Just to manipulate the last ones. size_t size = sid_amost_full.size(); sid_amost_full[size-2] = 'y'; sid_amost_full[size-1] = 'z'; EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_amost_full.c_str(), sid_amost_full.size()), SRT_SUCCESS); char buffer[CSrtConfig::MAX_SID_LENGTH + 135]; int buffer_len = sizeof buffer; EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(std::string(buffer), sid_amost_full); EXPECT_EQ(size_t(buffer_len), sid_amost_full.size()); EXPECT_EQ(strlen(buffer), sid_amost_full.size()); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values for (size_t i = 0; i < sizeof buffer; ++i) buffer[i] = 'a'; buffer_len = (int)(sizeof buffer); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(size_t(buffer_len), sid_amost_full.size()); EXPECT_EQ(strlen(buffer), sid_amost_full.size()); EXPECT_EQ(buffer[sid_amost_full.size()-1], 'z'); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } TEST_F(TestSocketOptions, StreamIDFull) { // 12 characters = 4*3, that is, aligned to 4 string sid_full; for (size_t i = 0; i < CSrtConfig::MAX_SID_LENGTH; ++i) sid_full += 'x'; // Just to manipulate the last ones. size_t size = sid_full.size(); sid_full[size-2] = 'y'; sid_full[size-1] = 'z'; EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_full.c_str(), sid_full.size()), SRT_SUCCESS); char buffer[CSrtConfig::MAX_SID_LENGTH + 135]; int buffer_len = sizeof buffer; EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(std::string(buffer), sid_full); EXPECT_EQ(size_t(buffer_len), sid_full.size()); EXPECT_EQ(strlen(buffer), sid_full.size()); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values for (size_t i = 0; i < sizeof buffer; ++i) buffer[i] = 'a'; buffer_len = (int)(sizeof buffer); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(size_t(buffer_len), sid_full.size()); EXPECT_EQ(strlen(buffer), sid_full.size()); EXPECT_EQ(buffer[sid_full.size()-1], 'z'); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } TEST_F(TestSocketOptions, StreamIDLenListener) { string stream_id_13 = "something1234"; EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_STREAMID, stream_id_13.c_str(), stream_id_13.size()), SRT_SUCCESS); char buffer[648]; int buffer_len = sizeof buffer; EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values for (SRTSOCKET sock : { m_caller_sock, accepted_sock }) { for (size_t i = 0; i < sizeof buffer; ++i) buffer[i] = 'a'; buffer_len = (int)(sizeof buffer); EXPECT_EQ(srt_getsockopt(sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(buffer_len, 0) << (sock == accepted_sock ? "ACCEPTED" : "LISTENER"); } ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } srt-1.4.4/test/test_sync.cpp000066400000000000000000000543701412557703600160510ustar00rootroot00000000000000#include "gtest/gtest.h" #include #include #include #include #include // std::accumulate #include // Used in FormatTime test #include "sync.h" #include "common.h" // This test set requires support for C++14 // * Uses "'" as a separator: 100'000 // * Uses operator"ms" at al from chrono using namespace std; using namespace srt::sync; // GNUC supports C++14 starting from version 5 //#if defined(__GNUC__) && (__GNUC__ < 5) ////namespace srt // constexpr chrono::milliseconds operator"" ms( // unsigned long long _Val) { // return integral milliseconds // return chrono::milliseconds(_Val); //} //#endif TEST(SyncDuration, BasicChecks) { const steady_clock::duration d = steady_clock::duration(); EXPECT_EQ(d.count(), 0); EXPECT_TRUE(d == d); // operator== EXPECT_FALSE(d != d); // operator!= EXPECT_EQ(d, steady_clock::duration::zero()); EXPECT_EQ(d, microseconds_from(0)); EXPECT_EQ(d, milliseconds_from(0)); EXPECT_EQ(d, seconds_from(0)); EXPECT_EQ(count_milliseconds(d), 0); EXPECT_EQ(count_microseconds(d), 0); EXPECT_EQ(count_seconds(d), 0); const steady_clock::duration a = d + milliseconds_from(120); EXPECT_EQ(a, milliseconds_from(120)); EXPECT_EQ(count_milliseconds(a), 120); EXPECT_EQ(count_microseconds(a), 120000); EXPECT_EQ(count_seconds(a), 0); } /// Check operations on (uint32_t + 1) TEST(SyncDuration, DurationFrom) { const int64_t val = int64_t(numeric_limits::max()) + 1; const steady_clock::duration us_from = microseconds_from(val); EXPECT_EQ(count_microseconds(us_from), val); const steady_clock::duration ms_from = milliseconds_from(val); EXPECT_EQ(count_milliseconds(ms_from), val); const steady_clock::duration s_from = seconds_from(val); EXPECT_EQ(count_seconds(s_from), val); } TEST(SyncDuration, RelOperators) { const steady_clock::duration a = steady_clock::duration(); EXPECT_EQ(a.count(), 0); EXPECT_TRUE(a == a); // operator== EXPECT_FALSE(a != a); // operator!= EXPECT_FALSE(a > a); // operator> EXPECT_FALSE(a < a); // operator< EXPECT_TRUE(a <= a); // operator<= EXPECT_TRUE(a >= a); // operator>= const steady_clock::duration b = a + milliseconds_from(120); EXPECT_FALSE(b == a); // operator== EXPECT_TRUE(b != a); // operator!= EXPECT_TRUE(b > a); // operator> EXPECT_FALSE(a > b); // operator> EXPECT_FALSE(b < a); // operator< EXPECT_TRUE(a < b); // operator< EXPECT_FALSE(b <= a); // operator<= EXPECT_TRUE(a <= b); // operator<= EXPECT_TRUE(b >= a); // operator>= EXPECT_FALSE(a >= b); // operator>= const steady_clock::duration c = steady_clock::duration(numeric_limits::max()); EXPECT_EQ(c.count(), numeric_limits::max()); const steady_clock::duration d = steady_clock::duration(numeric_limits::min()); EXPECT_EQ(d.count(), numeric_limits::min()); } TEST(SyncDuration, OperatorMinus) { const steady_clock::duration a = seconds_from(5); const steady_clock::duration b = milliseconds_from(3500); EXPECT_EQ(count_milliseconds(a - b), 1500); EXPECT_EQ(count_milliseconds(b - a), -1500); EXPECT_EQ((a - a).count(), 0); } TEST(SyncDuration, OperatorMinusEq) { const steady_clock::duration a = seconds_from(5); const steady_clock::duration b = milliseconds_from(3500); steady_clock::duration c = a; EXPECT_EQ(c, a); c -= b; EXPECT_EQ(count_milliseconds(c), 1500); c = b; EXPECT_EQ(c, b); c -= a; EXPECT_EQ(count_milliseconds(c), -1500); } TEST(SyncDuration, OperatorPlus) { const steady_clock::duration a = seconds_from(5); const steady_clock::duration b = milliseconds_from(3500); EXPECT_EQ(count_milliseconds(a + b), 8500); EXPECT_EQ(count_milliseconds(b + a), 8500); } TEST(SyncDuration, OperatorPlusEq) { const steady_clock::duration a = seconds_from(5); const steady_clock::duration b = milliseconds_from(3500); steady_clock::duration c = a; EXPECT_EQ(c, a); c += b; EXPECT_EQ(count_milliseconds(c), 8500); c = b; EXPECT_EQ(c, b); c += a; EXPECT_EQ(count_milliseconds(c), 8500); } TEST(SyncDuration, OperatorMultInt) { const steady_clock::duration a = milliseconds_from(3500); EXPECT_EQ(count_milliseconds(a), 3500); EXPECT_EQ(count_milliseconds(a * 2), 7000); } TEST(SyncDuration, OperatorMultIntEq) { steady_clock::duration a = milliseconds_from(3500); EXPECT_EQ(count_milliseconds(a), 3500); a *= 2; EXPECT_EQ(count_milliseconds(a), 7000); } TEST(SyncRandom, GenRandomInt) { vector mn(64); for (int i = 0; i < 2048; ++i) { const int rand_val = genRandomInt(0, 63); ASSERT_GE(rand_val, 0); ASSERT_LE(rand_val, 63); ++mn[rand_val]; } // Uncomment to see the distribution. // for (size_t i = 0; i < mn.size(); ++i) // { // cout << i << '\t'; // for (int j=0; j= a); EXPECT_FALSE(b >= a); EXPECT_TRUE(a > b); EXPECT_FALSE(a > a); EXPECT_TRUE(a <= a); EXPECT_TRUE(b <= a); EXPECT_FALSE(a <= b); EXPECT_FALSE(a < a); EXPECT_TRUE(b < a); EXPECT_FALSE(a < b); } #ifndef ENABLE_STDCXX_SYNC TEST(SyncTimePoint, OperatorMinus) { const int64_t delta = 1024; const steady_clock::time_point a(numeric_limits::max()); const steady_clock::time_point b(numeric_limits::max() - delta); EXPECT_EQ((a - b).count(), delta); EXPECT_EQ((b - a).count(), -delta); } TEST(SyncTimePoint, OperatorEq) { const int64_t delta = 1024; const steady_clock::time_point a(numeric_limits::max() - delta); const steady_clock::time_point b = a; EXPECT_EQ(a, b); } TEST(SyncTimePoint, OperatorMinusPlusDuration) { const int64_t delta = 1024; const steady_clock::time_point a(numeric_limits::max()); const steady_clock::time_point b(numeric_limits::max() - delta); EXPECT_EQ((a + steady_clock::duration(-delta)), b); EXPECT_EQ((b + steady_clock::duration(+delta)), a); EXPECT_EQ((a - steady_clock::duration(+delta)), b); EXPECT_EQ((b - steady_clock::duration(-delta)), a); } TEST(SyncTimePoint, OperatorPlusEqDuration) { const int64_t delta = 1024; const steady_clock::time_point a(numeric_limits::max()); const steady_clock::time_point b(numeric_limits::max() - delta); steady_clock::time_point r = a; EXPECT_EQ(r, a); r += steady_clock::duration(-delta); EXPECT_EQ(r, b); r = b; EXPECT_EQ(r, b); r += steady_clock::duration(+delta); EXPECT_EQ(r, a); r = a; EXPECT_EQ(r, a); r -= steady_clock::duration(+delta); EXPECT_EQ((a - steady_clock::duration(+delta)), b); EXPECT_EQ((b - steady_clock::duration(-delta)), a); } TEST(SyncTimePoint, OperatorMinusEqDuration) { const int64_t delta = 1024; const steady_clock::time_point a(numeric_limits::max()); const steady_clock::time_point b(numeric_limits::max() - delta); steady_clock::time_point r = a; EXPECT_EQ(r, a); r -= steady_clock::duration(+delta); EXPECT_EQ(r, b); r = b; EXPECT_EQ(r, b); r -= steady_clock::duration(-delta); EXPECT_EQ(r, a); } #endif /*****************************************************************************/ /* * UniqueLock tests */ /*****************************************************************************/ TEST(SyncUniqueLock, LockUnlock) { Mutex mtx; UniqueLock lock(mtx); EXPECT_FALSE(mtx.try_lock()); lock.unlock(); EXPECT_TRUE(mtx.try_lock()); mtx.unlock(); lock.lock(); EXPECT_FALSE(mtx.try_lock()); } TEST(SyncUniqueLock, Scope) { Mutex mtx; { UniqueLock lock(mtx); EXPECT_FALSE(mtx.try_lock()); } EXPECT_TRUE(mtx.try_lock()); mtx.unlock(); } /*****************************************************************************/ /* * SyncEvent tests */ /*****************************************************************************/ TEST(SyncEvent, WaitFor) { Mutex mutex; Condition cond; cond.init(); for (int timeout_us : {50, 100, 500, 1000, 101000, 1001000}) { const steady_clock::duration timeout = microseconds_from(timeout_us); UniqueLock lock(mutex); const steady_clock::time_point start = steady_clock::now(); const bool on_timeout = !cond.wait_for(lock, timeout); const steady_clock::time_point stop = steady_clock::now(); const steady_clock::duration waittime = stop - start; const int64_t waittime_us = count_microseconds(waittime); #if defined(ENABLE_STDCXX_SYNC) || !defined(_WIN32) // This check somehow fails on AppVeyor Windows VM with VS 2015 and pthreads. // - SyncEvent::wait_for( 50us) took 6us // - SyncEvent::wait_for(100us) took 4us if (on_timeout) { const int tolerance = timeout_us/1000; EXPECT_GE(waittime_us, timeout_us - tolerance); } #endif if (on_timeout) { // Give it 100 times the timeout, as this is // considered more than "crazy long", whereas we only // want to check if it has waited a finite amount of time. EXPECT_LE(waittime_us, 10 * 1001000); // biggest wait value } string spurious = on_timeout ? "" : " (SPURIOUS)"; if (timeout_us < 1000) { cerr << "SyncEvent::wait_for(" << timeout_us << "us) took " << waittime_us << "us" << spurious << endl; } else { cerr << "SyncEvent::wait_for(" << count_milliseconds(timeout) << " ms) took " << (waittime_us / 1000.0) << " ms" << spurious << endl; } } cond.destroy(); } TEST(SyncEvent, WaitForNotifyOne) { Mutex mutex; Condition cond; cond.init(); const steady_clock::duration timeout = seconds_from(5); auto wait_async = [](Condition* cond, Mutex* mutex, const steady_clock::duration& timeout) { UniqueLock lock(*mutex); return cond->wait_for(lock, timeout); }; auto wait_async_res = async(launch::async, wait_async, &cond, &mutex, timeout); EXPECT_EQ(wait_async_res.wait_for(chrono::milliseconds(100)), future_status::timeout); cond.notify_one(); ASSERT_EQ(wait_async_res.wait_for(chrono::milliseconds(100)), future_status::ready); const bool wait_for_res = wait_async_res.get(); EXPECT_TRUE(wait_for_res) << "Woken up by a notification"; cond.destroy(); } TEST(SyncEvent, WaitNotifyOne) { Mutex mutex; Condition cond; cond.init(); auto wait_async = [](Condition* cond, Mutex* mutex) { UniqueLock lock(*mutex); return cond->wait(lock); }; auto wait_async_res = async(launch::async, wait_async, &cond, &mutex); EXPECT_EQ(wait_async_res.wait_for(chrono::milliseconds(100)), future_status::timeout); cond.notify_one(); ASSERT_EQ(wait_async_res.wait_for(chrono::milliseconds(100)), future_status::ready); wait_async_res.get(); cond.destroy(); } TEST(SyncEvent, WaitForTwoNotifyOne) { Mutex mutex; Condition cond; vector notified_clients; cond.init(); const steady_clock::duration timeout = seconds_from(3); const int VAL_SIGNAL = 42; const int VAL_NO_SIGNAL = 0; volatile bool resource_ready = true; auto wait_async = [&](Condition* cond, Mutex* mutex, const steady_clock::duration& timeout, int id) { UniqueLock lock(*mutex); if (cond->wait_for(lock, timeout) && resource_ready) { notified_clients.push_back(id); resource_ready = false; return VAL_SIGNAL; } return VAL_NO_SIGNAL; }; using future_t = decltype(async(launch::async, wait_async, &cond, &mutex, timeout, 0)); future_t future_result[2] = { async(launch::async, wait_async, &cond, &mutex, timeout, 0), async(launch::async, wait_async, &cond, &mutex, timeout, 1) }; for (auto& wr: future_result) { ASSERT_EQ(wr.wait_for(chrono::milliseconds(100)), future_status::timeout); } { ScopedLock lk(mutex); cond.notify_one(); } using wait_t = decltype(future_t().wait_for(chrono::microseconds(0))); wait_t wait_state[2] = { move(future_result[0].wait_for(chrono::microseconds(500))), move(future_result[1].wait_for(chrono::microseconds(500))) }; cerr << "SyncEvent::WaitForTwoNotifyOne: NOTIFICATION came from " << notified_clients.size() << " clients:"; for (auto& nof: notified_clients) cerr << " " << nof; cerr << endl; // Now exactly one waiting thread should become ready // Error if: 0 (none ready) or 2 (both ready, while notify_one was used) ASSERT_EQ(notified_clients.size(), 1U); const int ready = notified_clients[0]; const int not_ready = (ready + 1) % 2; int future_val[2]; // The READY client must have a valid value. ASSERT_TRUE(future_result[ready].valid()); future_val[ready] = future_result[ready].get(); // The NOT READY client MIGHT have a valid value, in which case we take expected 0, // or maybe not, in which case we set -1 value. Either of both must be the // result for the test to be valid. if (future_result[not_ready].valid()) { future_val[not_ready] = future_result[not_ready].get(); } else { future_val[not_ready] = VAL_NO_SIGNAL-1; // to match LE comparison } string disp_future[16]; disp_future[int(future_status::timeout)] = "timeout"; disp_future[int(future_status::ready)] = "ready"; // Informational text cerr << "SyncEvent::WaitForTwoNotifyOne: READY THREAD: " << ready << " STATUS " << disp_future[int(wait_state[ready])] //<< " RESULT " << disp_state[0+future_val[ready]] << endl; << " RESULT " << future_val[ready] << endl; cerr << "SyncEvent::WaitForTwoNotifyOne: TMOUT THREAD: " << not_ready << " STATUS " << disp_future[int(wait_state[not_ready])] //<< " RESULT " << disp_state[0+future_val[not_ready]] << endl; << " RESULT " << future_val[not_ready] << endl; // The one that got the signal, should exit ready. // The one that didn't get the signal, should exit timeout. EXPECT_EQ(wait_state[ready], future_status::ready); EXPECT_EQ(wait_state[not_ready], future_status::timeout); // Same, expect these future to return the value // TURNED OFF for Windows, as there happens to be a // "spurious" signal causing this condition to fail, // even though it is declared valid and timed out. EXPECT_EQ(future_val[ready], VAL_SIGNAL); EXPECT_LE(future_val[not_ready], VAL_NO_SIGNAL); cond.destroy(); } TEST(SyncEvent, WaitForTwoNotifyAll) { Mutex mutex; Condition cond; cond.init(); const steady_clock::duration timeout = seconds_from(3); auto wait_async = [](Condition* cond, Mutex* mutex, const steady_clock::duration& timeout) { UniqueLock lock(*mutex); return cond->wait_for(lock, timeout); }; auto wait_async1_res = async(launch::async, wait_async, &cond, &mutex, timeout); auto wait_async2_res = async(launch::async, wait_async, &cond, &mutex, timeout); EXPECT_EQ(wait_async1_res.wait_for(chrono::milliseconds(100)), future_status::timeout); EXPECT_EQ(wait_async2_res.wait_for(chrono::milliseconds(100)), future_status::timeout); cond.notify_all(); // Now only one waiting thread should become ready const future_status status1 = wait_async1_res.wait_for(chrono::milliseconds(100)); const future_status status2 = wait_async2_res.wait_for(chrono::milliseconds(100)); EXPECT_EQ(status1, future_status::ready); EXPECT_EQ(status2, future_status::ready); // Expect both threads to wake up by condition EXPECT_TRUE(wait_async1_res.get()); EXPECT_TRUE(wait_async2_res.get()); cond.destroy(); } TEST(SyncEvent, WaitForNotifyAll) { Mutex mutex; Condition cond; cond.init(); const steady_clock::duration timeout = seconds_from(5); auto wait_async = [](Condition* cond, Mutex* mutex, const steady_clock::duration& timeout) { UniqueLock lock(*mutex); return cond->wait_for(lock, timeout); }; auto wait_async_res = async(launch::async, wait_async, &cond, &mutex, timeout); EXPECT_EQ(wait_async_res.wait_for(chrono::milliseconds(500)), future_status::timeout); cond.notify_all(); ASSERT_EQ(wait_async_res.wait_for(chrono::milliseconds(500)), future_status::ready); const bool wait_for_res = wait_async_res.get(); EXPECT_TRUE(wait_for_res) << "Woken up by condition"; cond.destroy(); } /*****************************************************************************/ /* * CThread */ /*****************************************************************************/ void* dummythread(void* param) { *(bool*)(param) = true; return nullptr; } TEST(SyncThread, Joinable) { CThread foo; volatile bool thread_finished = false; StartThread(foo, dummythread, (void*)&thread_finished, "DumyThread"); EXPECT_TRUE(foo.joinable()); while (!thread_finished) { std::this_thread::sleep_for(chrono::milliseconds(50)); } EXPECT_TRUE(foo.joinable()); foo.join(); EXPECT_FALSE(foo.joinable()); } /*****************************************************************************/ /* * FormatTime */ /*****************************************************************************/ #if !defined(__GNUC__) || defined(__clang__) || (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) //#if !defined(__GNUC__) || (__GNUC__ > 4) //#if !defined(__GNUC__) || (__GNUC__ >= 5) // g++ before 4.9 (?) does not support regex and crashes on execution. TEST(Sync, FormatTime) { auto parse_time = [](const string& timestr) -> long long { // Example string: 1D 02:10:55.972651 [STD] const regex rex("([[:digit:]]+D )?([[:digit:]]{2}):([[:digit:]]{2}):([[:digit:]]{2}).([[:digit:]]{6,}) \\[STDY\\]"); std::smatch sm; EXPECT_TRUE(regex_match(timestr, sm, rex)); EXPECT_LE(sm.size(), 6U); if (sm.size() != 6 && sm.size() != 5) return 0; // Day may be missing if zero const long long d = sm[1].matched ? std::stoi(sm[1]) : 0; const long long h = std::stoll(sm[2]); const long long m = std::stoll(sm[3]); const long long s = std::stoll(sm[4]); const long long u = std::stoll(sm[5]); return u + s * 1000000 + m * 60000000 + h * 60 * 60 * 1000000 + d * 24 * 60 * 60 * 1000000; }; auto print_timediff = [&parse_time](const string& desc, const string& time, const string& time_base) { const long long diff = parse_time(time) - parse_time(time_base); cerr << desc << time << " (" << diff << " us)" << endl; }; const auto a = steady_clock::now(); const string time1 = FormatTime(a); const string time2 = FormatTime(a); const string time3 = FormatTime(a + milliseconds_from(500)); const string time4 = FormatTime(a + seconds_from(1)); const string time5 = FormatTime(a + seconds_from(5)); const string time6 = FormatTime(a + milliseconds_from(-4350)); cerr << "Current time formated: " << time1 << endl; const long long diff_2_1 = parse_time(time2) - parse_time(time1); cerr << "Same time formated again: " << time2 << " (" << diff_2_1 << " us)" << endl; print_timediff("Same time formated again: ", time2, time1); print_timediff("Time +500 ms formated: ", time3, time1); print_timediff("Time +1 sec formated: ", time4, time1); print_timediff("Time +5 sec formated: ", time5, time1); print_timediff("Time -4350 ms formated: ", time6, time1); EXPECT_TRUE(time1 == time2); } TEST(Sync, FormatTimeSys) { auto parse_time = [](const string& timestr) -> long long { const regex rex("([[:digit:]]{2}):([[:digit:]]{2}):([[:digit:]]{2}).([[:digit:]]{6}) \\[SYST\\]"); std::smatch sm; EXPECT_TRUE(regex_match(timestr, sm, rex)); EXPECT_EQ(sm.size(), 5U); if (sm.size() != 5) return 0; const long long h = std::stoi(sm[1]); const long long m = std::stoi(sm[2]); const long long s = std::stoi(sm[3]); const long long u = std::stoi(sm[4]); return u + s * 1000000 + m * 60000000 + h * 60 * 60 * 1000000; }; auto print_timediff = [&parse_time](const string& desc, const string& time, const string& time_base) { const long long diff = parse_time(time) - parse_time(time_base); cerr << desc << time << " (" << diff << " us)" << endl; }; const steady_clock::time_point a = steady_clock::now(); const string time1 = FormatTimeSys(a); const string time2 = FormatTimeSys(a); const string time3 = FormatTimeSys(a + milliseconds_from(500)); const string time4 = FormatTimeSys(a + seconds_from(1)); const string time5 = FormatTimeSys(a + seconds_from(5)); const string time6 = FormatTimeSys(a + milliseconds_from(-4350)); cerr << "Current time formated: " << time1 << endl; const long long diff_2_1 = parse_time(time2) - parse_time(time1); cerr << "Same time formated again: " << time2 << " (" << diff_2_1 << " us)" << endl; print_timediff("Same time formated again: ", time2, time1); print_timediff("Time +500 ms formated: ", time3, time1); print_timediff("Time +1 sec formated: ", time4, time1); print_timediff("Time +5 sec formated: ", time5, time1); print_timediff("Time -4350 ms formated: ", time6, time1); EXPECT_TRUE(time1 == time2); } #endif srt-1.4.4/test/test_threadname.cpp000066400000000000000000000030131412557703600171710ustar00rootroot00000000000000#include #include #include "gtest/gtest.h" #include "threadname.h" using namespace srt; TEST(ThreadName, GetSet) { std::string name("getset"); char buf[ThreadName::BUFSIZE * 2]; memset(buf, 'a', sizeof(buf)); ASSERT_EQ(ThreadName::get(buf), true); // ensure doesn't write out-of-range size_t max = ThreadName::BUFSIZE - 1; ASSERT_LE(strlen(buf), max); if (ThreadName::DUMMY_IMPL) return; ASSERT_EQ(ThreadName::set(name), true); memset(buf, 'a', sizeof(buf)); ASSERT_EQ(ThreadName::get(buf), true); ASSERT_EQ(buf, name); } TEST(ThreadName, AutoReset) { const std::string old_name("old"); std::string new_name("new-name"); if (ThreadName::DUMMY_IMPL) { // just make sure the API is correct ThreadName t(std::string("test")); return; } ASSERT_EQ(ThreadName::set(old_name), true); std::string name; ASSERT_EQ(ThreadName::get(name), true); ASSERT_EQ(name, old_name); { ThreadName threadName(new_name); ASSERT_EQ(ThreadName::get(name), true); ASSERT_EQ(name, new_name); } ASSERT_EQ(ThreadName::get(name), true); ASSERT_EQ(name, old_name); { new_name.resize(std::max(512, ThreadName::BUFSIZE * 2), 'z'); ThreadName threadName(new_name); ASSERT_EQ(ThreadName::get(name), true); ASSERT_EQ(new_name.compare(0, name.size(), name), 0); } ASSERT_EQ(ThreadName::get(name), true); ASSERT_EQ(name, old_name); } srt-1.4.4/test/test_timer.cpp000066400000000000000000000024151412557703600162060ustar00rootroot00000000000000#include "gtest/gtest.h" #include #include #include #include // std::accumulate #include "common.h" #include "sync.h" TEST(CTimer, DISABLED_SleeptoAccuracy) { using namespace std; using namespace srt::sync; const int num_samples = 1000; array sleeps_us; const uint64_t sleep_intervals_us[] = { 1, 5, 10, 50, 100, 250, 500, 1000, 5000, 10000 }; CTimer timer; for (uint64_t interval_us : sleep_intervals_us) { for (int i = 0; i < num_samples; i++) { steady_clock::time_point currtime = steady_clock::now(); timer.sleep_until(currtime + microseconds_from(interval_us)); steady_clock::time_point new_time = steady_clock::now(); sleeps_us[i] = count_microseconds(new_time - currtime); } cerr << "Target sleep duration: " << interval_us << " us\n"; cerr << "avg sleep duration: " << accumulate(sleeps_us.begin(), sleeps_us.end(), (uint64_t) 0) / num_samples << " us\n"; cerr << "min sleep duration: " << *min_element(sleeps_us.begin(), sleeps_us.end()) << " us\n"; cerr << "max sleep duration: " << *max_element(sleeps_us.begin(), sleeps_us.end()) << " us\n"; cerr << "\n"; } } srt-1.4.4/test/test_unitqueue.cpp000066400000000000000000000052701412557703600171140ustar00rootroot00000000000000#include #include #include "gtest/gtest.h" #include "queue.h" using namespace std; using namespace srt; /// Create CUnitQueue with queue size of 4 units. /// The size of 4 is chosen on purpose, because /// CUnitQueue::getNextAvailUnit(..) has the following /// condition `if (m_iCount * 10 > m_iSize * 9)`. With m_iSize = 4 /// it will be false up until m_iCount becomes 4. /// And there was an issue in getNextAvailUnit(..) in taking /// the very last element of the queue (it was skipped). TEST(CUnitQueue, Increase) { const int buffer_size_pkts = 4; CUnitQueue unit_queue; unit_queue.init(buffer_size_pkts, 1500, AF_INET); vector taken_units; for (int i = 0; i < 5 * buffer_size_pkts; ++i) { CUnit* unit = unit_queue.getNextAvailUnit(); ASSERT_NE(unit, nullptr); unit_queue.makeUnitGood(unit); taken_units.push_back(unit); } } /// Create CUnitQueue with queue size of 4 units. /// Then after requesting the 5th unit, free the previous /// four units. This makes the previous queue completely free. /// Requesting the 5th unit, there would be 3 units available in the /// beginning of the same queue. TEST(CUnitQueue, IncreaseAndFree) { const int buffer_size_pkts = 4; CUnitQueue unit_queue; unit_queue.init(buffer_size_pkts, 1500, AF_INET); CUnit* taken_unit = nullptr; for (int i = 0; i < 5 * buffer_size_pkts; ++i) { CUnit* unit = unit_queue.getNextAvailUnit(); ASSERT_NE(unit, nullptr); unit_queue.makeUnitGood(unit); if (taken_unit) unit_queue.makeUnitFree(taken_unit); taken_unit = unit; } } /// Create CUnitQueue with queue size of 4 units. /// Then after requesting the 5th unit, free the previous /// four units. This makes the previous queue completely free. /// Requesting the 9th unit, there would be 4 units available in the /// Thus the test checks if TEST(CUnitQueue, IncreaseAndFreeGrouped) { const int buffer_size_pkts = 4; CUnitQueue unit_queue; unit_queue.init(buffer_size_pkts, 1500, AF_INET); vector taken_units; for (int i = 0; i < 5 * buffer_size_pkts; ++i) { CUnit* unit = unit_queue.getNextAvailUnit(); ASSERT_NE(unit, nullptr); unit_queue.makeUnitGood(unit); if (taken_units.size() >= buffer_size_pkts) { for_each(taken_units.begin(), taken_units.end(), [&unit_queue](CUnit* u) { unit_queue.makeUnitFree(u); }); taken_units.clear(); } taken_units.push_back(unit); EXPECT_LE(unit_queue.capacity(), 2 * buffer_size_pkts) << "Buffer capacity should not exceed two queues of 4 units"; } } srt-1.4.4/test/test_utilities.cpp000066400000000000000000000201671412557703600171050ustar00rootroot00000000000000#include #include #include #include #include #include "gtest/gtest.h" #define SRT_TEST_CIRCULAR_BUFFER #include "api.h" #include "common.h" using namespace std; // To test CircularBuffer struct Double { double d; size_t instance; static size_t sourceid; Double(): d(0.0) { instance = ++sourceid; IF_HEAVY_LOGGING(cerr << "(Double/" << instance << ": empty costruction)\n"); } Double(double dd): d(dd) { instance = ++sourceid; IF_HEAVY_LOGGING(cerr << "(Double:/" << instance << " init construction:" << dd << ")\n"); } Double(const Double& dd): d(dd.d) { instance = ++sourceid; IF_HEAVY_LOGGING(cerr << "(Double:/" << instance << " copy construction:" << dd.d << " object/" << dd.instance << ")\n"); } operator double() { return d; } ~Double() { IF_HEAVY_LOGGING(cerr << "(Double:/" << instance << " destruction:" << d << ")\n"); } void operator=(double dd) { IF_HEAVY_LOGGING(cerr << "(Double:/" << instance << " copy assignment:" << d << " -> " << dd << " value)\n"); d = dd; } void operator=(const Double& dd) { IF_HEAVY_LOGGING(cerr << "(Double:/" << instance << " copy assignment:" << d << " -> " << dd.d << " object/" << dd.instance << ")\n"); d = dd.d; } // Required for template-based gtest :( friend bool operator==(const Double& l, double r) { return l.d == r; } friend bool operator==(double l, const Double r) { return l == r.d; } bool operator == (const Double& r) { return d == r; } }; size_t Double::sourceid = 0; template inline void ShowCircularBuffer(const CircularBuffer& buf) { cerr << "SIZE: " << buf.size() << " FREE:" << buf.spaceleft() << " BEGIN:" << buf.m_xBegin << " END: " << buf.m_xEnd << endl; for (int i = 0; i < buf.size(); ++i) { Double v; if (buf.get(i, (v))) cerr << "[" << i << "] = " << v << endl; else cerr << "[" << i << "] EMPTY!\n"; } } struct Add { Double v; Add(const Double& vv): v(vv) {} void operator()(Double& accessed, bool isnew) { if (isnew) accessed = v; else accessed = Double(accessed.d + v.d); } }; TEST(CircularBuffer, Overall) { using namespace std; // Create some odd number of elements in a circular buffer. CircularBuffer buf(7); cerr << dec; // Now, add 3 elements to it and check if succeeded. buf.push(11.2); buf.push(12.3); buf.push(13.4); IF_HEAVY_LOGGING(cerr << "After adding 3 elements: size=" << buf.size() << " capacity=" << buf.capacity() << ":\n"); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); ASSERT_EQ(buf.size(), 3); IF_HEAVY_LOGGING(cerr << "Adding element at position 5:\n"); EXPECT_TRUE(buf.set(5, 15.5)); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); ASSERT_EQ(buf.size(), 6); IF_HEAVY_LOGGING(cerr << "Adding element at position 7 (should fail):\n"); EXPECT_FALSE(buf.set(7, 10.0)); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); ASSERT_EQ(buf.size(), 6); IF_HEAVY_LOGGING(cerr << "Dropping first 2 elements:\n"); buf.drop(2); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); ASSERT_EQ(buf.size(), 4); IF_HEAVY_LOGGING(cerr << "Adding again element at position 6 (should roll):\n"); buf.set(6, 22.1); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); IF_HEAVY_LOGGING(cerr << "Adding element at existing position 2 (overwrite):\n"); buf.set(2, 33.1); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); IF_HEAVY_LOGGING(cerr << "Adding element at existing position 3 (no overwrite):\n"); buf.set(3, 44.4, false); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); Double output; // [0] = 13.4 (after dropping first 2 elements) EXPECT_TRUE(buf.get(0, (output))); ASSERT_EQ(output, 13.4); // [2] = 33.1 overwriting EXPECT_TRUE(buf.get(2, (output))); ASSERT_EQ(output, 33.1); // [3] = was 15.5, requested to set 44.4, but not overwriting EXPECT_TRUE(buf.get(3, (output))); ASSERT_EQ(output, 15.5); // [6] = 22.1 (as set with rolling) EXPECT_TRUE(buf.get(6, (output))); ASSERT_EQ(output, 22.1); IF_HEAVY_LOGGING(cerr << "Dropping first 4 positions:\n"); buf.drop(4); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); EXPECT_TRUE(buf.get(2, (output))); // Was 6 before dropping ASSERT_EQ(output.d, 22.1); IF_HEAVY_LOGGING(cerr << "Pushing 1 aslong there is capacity:\n"); int i = 0; while (buf.push(1) != -1) { IF_HEAVY_LOGGING(cerr << "Pushed, begin=" << buf.m_xBegin << " end=" << buf.m_xEnd << endl); ++i; } IF_HEAVY_LOGGING(cerr << "Done " << i << " operations, buffer:\n"); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); IF_HEAVY_LOGGING(cerr << "Updating value at position 5:\n"); EXPECT_TRUE(buf.update(5, Add(3.33))); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); EXPECT_TRUE(buf.get(5, (output))); ASSERT_EQ(output, 4.33); int offset = 9; IF_HEAVY_LOGGING(cerr << "Forced adding at position 9 with dropping (capacity: " << buf.capacity() << "):\n"); // State we already know it has failed. Calculate drop size. int dropshift = offset - (buf.capacity() - 1); // buf.capacity()-1 is the latest position offset -= dropshift; IF_HEAVY_LOGGING(cerr << "Need to drop: " << dropshift << " New offset:" << offset << endl); ASSERT_GE(dropshift, 0); if (dropshift > 0) { buf.drop(dropshift); IF_HEAVY_LOGGING(cerr << "AFTER DROPPING:\n"); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); EXPECT_TRUE(buf.set(offset, 99.1, true)); // size() - 1 is the latest possible offset ASSERT_EQ(buf.size() - 1 + dropshift, 9); } else { IF_HEAVY_LOGGING(cerr << "NEGATIVE DROP!\n"); } IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); int size = buf.size(); IF_HEAVY_LOGGING(cerr << "Dropping rest of the items (passing " << (size) << "):\n"); // NOTE: 'drop' gets the POSITION as argument, but this position // is allowed to be past the last addressable position. When passing // the current size, it should make the container empty. buf.drop(size); EXPECT_TRUE(buf.empty()); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); IF_HEAVY_LOGGING(cerr << "DONE.\n"); } TEST(ConfigString, Setting) { using namespace std; static const auto STRSIZE = 20; StringStorage s; EXPECT_TRUE(s.empty()); EXPECT_EQ(s.size(), 0U); EXPECT_EQ(s.str(), std::string()); char example_ac1[] = "example_long"; char example_ac2[] = "short"; char example_ac3[] = "example_longer"; char example_acx[] = "example_long_excessively"; char example_ace[] = ""; // According to the standard, this array gets automatically // the number of characters + 1 for terminating 0. Get sizeof()-1 // to get the number of characters. EXPECT_TRUE(s.set(example_ac1, sizeof (example_ac1)-1)); EXPECT_EQ(s.size(), sizeof (example_ac1)-1); EXPECT_FALSE(s.empty()); EXPECT_TRUE(s.set(example_ac2, sizeof (example_ac2)-1)); EXPECT_EQ(s.size(), sizeof (example_ac2)-1); EXPECT_TRUE(s.set(example_ac3, sizeof (example_ac3)-1)); EXPECT_EQ(s.size(), sizeof (example_ac3)-1); EXPECT_FALSE(s.set(example_acx, sizeof (example_acx)-1)); EXPECT_EQ(s.size(), sizeof (example_ac3)-1); EXPECT_TRUE(s.set(example_ace, sizeof (example_ace)-1)); EXPECT_EQ(s.size(), 0U); string example_s1 = "example_long"; string example_s2 = "short"; string example_s3 = "example_longer"; string example_sx = "example_long_excessively"; string example_se = ""; EXPECT_TRUE(s.set(example_s1)); EXPECT_EQ(s.size(), example_s1.size()); EXPECT_FALSE(s.empty()); EXPECT_TRUE(s.set(example_s2)); EXPECT_EQ(s.size(), example_s2.size()); EXPECT_TRUE(s.set(example_s3)); EXPECT_EQ(s.size(), example_s3.size()); EXPECT_FALSE(s.set(example_sx)); EXPECT_EQ(s.size(), example_s3.size()); EXPECT_TRUE(s.set(example_se)); EXPECT_EQ(s.size(), 0U); EXPECT_TRUE(s.empty()); } srt-1.4.4/testing/000077500000000000000000000000001412557703600140175ustar00rootroot00000000000000srt-1.4.4/testing/README.md000066400000000000000000000006011412557703600152730ustar00rootroot00000000000000Testing ======= This directory contains applications used for testing and development only. They may contain experimental versions or not fully functioning features. Every application has its own individual Manifest file (`*.maf`), which defines of which source files particular application comprises. They may be contained either in the same directory, or in any other subproject. srt-1.4.4/testing/srt-test-file.cpp000066400000000000000000000263011412557703600172270ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #ifdef _WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apputil.hpp" #include "uriparser.hpp" #include "logsupport.hpp" #include "socketoptions.hpp" #include "verbose.hpp" #include "testmedia.hpp" #ifndef S_ISDIR #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) #endif bool Upload(UriParser& srt, UriParser& file); bool Download(UriParser& srt, UriParser& file); static size_t g_buffer_size = 1456; static bool g_skip_flushing = false; using namespace std; srt_logging::Logger applog(SRT_LOGFA_APP, srt_logger_config, "srt-file"); int main( int argc, char** argv ) { vector optargs; OptionName o_loglevel ((optargs), " Minimum severity for logs", "ll", "loglevel"), o_buffer ((optargs), " Size of the single reading operation", "b", "buffer"), o_verbose ((optargs), " Print extra verbos output", "v", "verbose"), o_noflush ((optargs), " Do not wait safely 5 seconds at the end to flush buffers", "sf", "skipflush"), o_help ((optargs), " This help", "?", "help", "-help") ; options_t params = ProcessOptions(argv, argc, optargs); bool need_help = OptionPresent(params, o_help); //* cerr << "OPTIONS (DEBUG)\n"; for (auto o: params) { cerr << "[" << o.first << "] "; copy(o.second.begin(), o.second.end(), ostream_iterator(cerr, " ")); cerr << endl; } // */ if (need_help) { cerr << "Usage:\n"; cerr << " " << argv[0] << " [options] \n"; cerr << "*** (Position of [options] is unrestricted.)\n"; cerr << "*** ( option parameters can be only terminated by a next option.)\n"; cerr << "where:\n"; cerr << " and is specified by an URI.\n"; cerr << "SUPPORTED URI SCHEMES:\n"; cerr << " srt: use SRT connection\n"; cerr << " udp: read from bound UDP socket or send to given address as UDP\n"; cerr << " file (default if scheme not specified) specified as:\n"; cerr << " - empty host/port and absolute file path in the URI\n"; cerr << " - only a filename, also as a relative path\n"; cerr << " - file://con ('con' as host): designates stdin or stdout\n"; cerr << "OPTIONS HELP SYNTAX: -option :\n"; for (auto os: optargs) cout << OptionHelpItem(*os.pid) << endl; return 1; } vector args = params[""]; if ( args.size() < 2 ) { cerr << "Usage: " << argv[0] << " \n"; return 1; } string loglevel = Option(params, "error", o_loglevel); srt_logging::LogLevel::type lev = SrtParseLogLevel(loglevel); srt::setloglevel(lev); srt::addlogfa(SRT_LOGFA_APP); bool verbo = OptionPresent(params, o_verbose); if (verbo) { Verbose::on = true; Verbose::cverb = &std::cout; } string bs = Option(params, "", o_buffer); if ( bs != "" ) { ::g_buffer_size = stoi(bs); } string sf = Option(params, "no", o_noflush); if (sf == "" || !false_names.count(sf)) ::g_skip_flushing = true; string source = args[0]; string target = args[1]; UriParser us(source), ut(target); Verb() << "SOURCE type=" << us.scheme() << ", TARGET type=" << ut.scheme(); try { if (us.scheme() == "srt") { if (ut.scheme() != "file") { cerr << "SRT to FILE should be specified\n"; return 1; } Download(us, ut); } else if (ut.scheme() == "srt") { if (us.scheme() != "file") { cerr << "FILE to SRT should be specified\n"; return 1; } Upload(ut, us); } else { cerr << "SRT URI must be one of given media.\n"; return 1; } } catch (std::exception& x) { cerr << "ERROR: " << x.what() << endl; return 1; } return 0; } tuple ExtractPath(string path) { //string& dir = r_dir; //string& fname = r_fname; string directory = path; string filename = ""; struct stat state; stat(path.c_str(), &state); if (!S_ISDIR(state.st_mode)) { // Extract directory as a butlast part of path size_t pos = path.find_last_of("/"); if ( pos == string::npos ) { filename = path; directory = "."; } else { directory = path.substr(0, pos); filename = path.substr(pos+1); } } if (directory[0] != '/') { // Glue in the absolute prefix of the current directory // to make it absolute. This is needed to properly interpret // the fixed uri. static const size_t s_max_path = 4096; // don't care how proper this is char tmppath[s_max_path]; char* gwd = getcwd(tmppath, s_max_path); if ( !gwd ) { // Don't bother with that now. We need something better for that anyway. throw std::invalid_argument("Path too long"); } string wd = gwd; directory = wd + "/" + directory; } return make_tuple(directory, filename); } bool DoUpload(UriParser& ut, string path, string filename) { SrtModel m(ut.host(), ut.portno(), ut.parameters()); string id = filename; Verb() << "Passing '" << id << "' as stream ID\n"; m.Establish((id)); // Check if the filename was changed if (id != filename) { cerr << "SRT caller has changed the filename '" << filename << "' to '" << id << "' - rejecting\n"; return false; } Verb() << "USING ID: " << id; // SrtTarget* tp = new SrtTarget; // tp->StealFrom(m); // unique_ptr target(tp); //SRTSOCKET ss = tp->Socket(); SRTSOCKET ss = m.Socket(); // Use a manual loop for reading from SRT vector buf(::g_buffer_size); ifstream ifile(path, ios::binary); if ( !ifile ) { cerr << "Error opening file: '" << path << "'"; return false; } for (;;) { size_t n = ifile.read(buf.data(), ::g_buffer_size).gcount(); size_t shift = 0; while (n > 0) { int st = srt_send(ss, buf.data()+shift, n); Verb() << "Upload: " << n << " --> " << st << (!shift ? string() : "+" + Sprint(shift)); if (st == SRT_ERROR) { cerr << "Upload: SRT error: " << srt_getlasterror_str() << endl; return false; } n -= st; shift += st; } if (ifile.eof()) break; if ( !ifile.good() ) { cerr << "ERROR while reading file\n"; return false; } } if ( !::g_skip_flushing ) { // send-flush-loop for (;;) { size_t bytes; size_t blocks; int st = srt_getsndbuffer(ss, &blocks, &bytes); if (st == SRT_ERROR) { cerr << "Error in srt_getsndbuffer: " << srt_getlasterror_str() << endl; return false; } if (bytes == 0) { Verb() << "Sending buffer DEPLETED - ok."; break; } Verb() << "Sending buffer still: bytes=" << bytes << " blocks=" << blocks; this_thread::sleep_for(chrono::milliseconds(250)); } } return true; } bool DoDownload(UriParser& us, string directory, string filename) { SrtModel m(us.host(), us.portno(), us.parameters()); string id = filename; m.Establish((id)); // Disregard the filename, unless the destination file exists. string path = directory + "/" + id; struct stat state; if ( stat(path.c_str(), &state) == -1 ) { switch ( errno ) { case ENOENT: // This is expected, go on. break; default: cerr << "Download: error '" << errno << "'when checking destination location: " << path << endl; return false; } } else { // Check if destination is a regular file, if so, allow to overwrite. // Otherwise reject. if (!S_ISREG(state.st_mode)) { cerr << "Download: target location '" << path << "' does not designate a regular file.\n"; return false; } } ofstream ofile(path, ios::out | ios::trunc | ios::binary); if ( !ofile.good() ) { cerr << "Download: can't create output file: " << path; return false; } SRTSOCKET ss = m.Socket(); Verb() << "Downloading from '" << us.uri() << "' to '" << path; vector buf(::g_buffer_size); for (;;) { int n = srt_recv(ss, buf.data(), ::g_buffer_size); if (n == SRT_ERROR) { cerr << "Download: SRT error: " << srt_getlasterror_str() << endl; return false; } if (n == 0) { Verb() << "Download COMPLETE."; break; } // Write to file any amount of data received Verb() << "Download: --> " << n; ofile.write(buf.data(), n); } return true; } bool Upload(UriParser& srt_target_uri, UriParser& fileuri) { if ( fileuri.scheme() != "file" ) { cerr << "Upload: source accepted only as a file\n"; return false; } // fileuri is source-reading file // srt_target_uri is SRT target string path = fileuri.path(); string directory, filename; tie(directory, filename) = ExtractPath(path); Verb() << "Extract path '" << path << "': directory=" << directory << " filename=" << filename; // Set ID to the filename. // Directory will be preserved. // Add some extra parameters. srt_target_uri["transtype"] = "file"; return DoUpload(srt_target_uri, path, filename); } bool Download(UriParser& srt_source_uri, UriParser& fileuri) { if (fileuri.scheme() != "file" ) { cerr << "Download: target accepted only as a file\n"; return false; } string path = fileuri.path(), directory, filename; tie(directory, filename) = ExtractPath(path); srt_source_uri["transtype"] = "file"; return DoDownload(srt_source_uri, directory, filename); } srt-1.4.4/testing/srt-test-file.maf000066400000000000000000000002701412557703600172050ustar00rootroot00000000000000 SOURCES srt-test-file.cpp testmedia.cpp ../apps/apputil.cpp ../apps/verbose.cpp ../apps/socketoptions.cpp ../apps/uriparser.cpp ../apps/logsupport.cpp ../apps/logsupport_appdefs.cpp srt-1.4.4/testing/srt-test-live.cpp000066400000000000000000000775221412557703600172620ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ // NOTE: This application uses C++11. // This program uses quite a simple architecture, which is mainly related to // the way how it's invoked: srt-test-live (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 (common/socketoptions.hpp) for a list of // all options. // MSVS likes to complain about lots of standard C functions being unsafe. #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS 1 #endif #define REQUIRE_CXX11 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apputil.hpp" #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" #include "logsupport.hpp" #include "testmedia.hpp" #include "testmedia.hpp" // requires access to SRT-dependent globals #include "verbose.hpp" // NOTE: This is without "haisrt/" because it uses an internal path // to the library. Application using the "installed" library should // use #include #include // This TEMPORARILY contains extra C++-only SRT API. #include using namespace std; srt_logging::Logger applog(SRT_LOGFA_APP, srt_logger_config, "srt-live"); map g_options; struct ForcedExit: public std::runtime_error { ForcedExit(const std::string& arg): std::runtime_error(arg) { } }; struct AlarmExit: public std::runtime_error { AlarmExit(const std::string& arg): std::runtime_error(arg) { } }; volatile bool timer_state = false; void OnINT_ForceExit(int) { cerr << "\n-------- REQUESTED INTERRUPT!\n"; transmit_int_state = true; } std::string g_interrupt_reason; void OnAlarm_Interrupt(int) { cerr << "\n---------- INTERRUPT ON TIMEOUT: hang on " << g_interrupt_reason << "!\n"; transmit_int_state = true; // JIC timer_state = true; throw AlarmExit("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); } }; bool CheckMediaSpec(const string& prefix, const vector& spec, string& w_outspec) { // This function prints error messages by itself then returns false. // Otherwise nothing is printed and true is returned. // r_outspec is for a case when a redundancy specification should be translated. if (spec.empty()) { cerr << prefix << ": Specification is empty\n"; return false; } if (spec.size() == 1) { // Then, whatever. w_outspec = spec[0]; return true; } // We have multiple items specified, check each one // it SRT, if so, craft the redundancy URI spec, // otherwise reject vector adrs; map uriparam; bool first = true; bool allow_raw_spec = false; for (auto uris: spec) { UriParser uri(uris, UriParser::EXPECT_HOST); if (!allow_raw_spec && uri.type() != UriParser::SRT) { cerr << ": Multiple media must be all with SRT scheme, or srt://* as first.\n"; return false; } if (uri.host() == "*") { allow_raw_spec = true; first = false; uriparam = uri.parameters(); // This does not specify the address, only options and URI. continue; } string aspec = uri.host() + ":" + uri.port(); if (aspec[0] == ':' || aspec[aspec.size()-1] == ':') { cerr << "Empty host or port in the address specification: " << uris << endl; return false; } if (allow_raw_spec && !uri.parameters().empty()) { bool cont = false; // Extract attributes if any and pass them there. for (UriParser::query_it i = uri.parameters().begin(); i != uri.parameters().end(); ++i) { aspec += cont ? "&" : "?"; cont = false; aspec += i->first + "=" + i->second; } } adrs.push_back(aspec); if (first) { uriparam = uri.parameters(); first = false; } } w_outspec = "srt:////group?"; if (map_getp(uriparam, "type") == nullptr) uriparam["type"] = "redundancy"; for (auto& name_value: uriparam) { string name, value; tie(name, value) = name_value; w_outspec += name + "=" + value + "&"; } w_outspec += "nodes="; for (string& a: adrs) w_outspec += a + ","; Verb() << "NOTE: " << prefix << " specification set as: " << (w_outspec); return true; } extern "C" void TestLogHandler(void* opaque, int level, const char* file, int line, const char* area, const char* message); namespace srt_logging { extern Logger glog; } #if ENABLE_EXPERIMENTAL_BONDING extern "C" int SrtCheckGroupHook(void* , SRTSOCKET acpsock, int , const sockaddr*, const char* ) { static string gtypes[] = { "undefined", // SRT_GTYPE_UNDEFINED "broadcast", "backup", "balancing", "multicast" }; int type; int size = sizeof type; srt_getsockflag(acpsock, SRTO_GROUPCONNECT, &type, &size); Verb() << "listener: @" << acpsock << " - accepting " << (type ? "GROUP" : "SINGLE") << VerbNoEOL; if (type != 0) { SRT_GROUP_TYPE gt; size = sizeof gt; if (-1 != srt_getsockflag(acpsock, SRTO_GROUPTYPE, >, &size)) { if (gt < Size(gtypes)) Verb() << " type=" << gtypes[gt] << VerbNoEOL; else Verb() << " type=" << int(gt) << VerbNoEOL; } } Verb() << " connection"; return 0; } #endif extern "C" int SrtUserPasswordHook(void* , SRTSOCKET acpsock, int hsv, const sockaddr*, const char* streamid) { if (hsv < 5) { Verb() << "SrtUserPasswordHook: HS version 4 doesn't support extended handshake"; return -1; } static const map passwd { {"admin", "thelocalmanager"}, {"user", "verylongpassword"} }; // Try the "standard interpretation" with username at key u string username; static const char stdhdr [] = "#!::"; uint32_t* pattern = (uint32_t*)stdhdr; if (strlen(streamid) > 4 && *(uint32_t*)streamid == *pattern) { vector items; Split(streamid+4, ',', back_inserter(items)); for (auto& i: items) { vector kv; Split(i, '=', back_inserter(kv)); if (kv.size() == 2 && kv[0] == "u") { username = kv[1]; } } } else { // By default the whole streamid is username username = streamid; } // This hook sets the password to the just accepted socket // depending on the user string exp_pw = passwd.at(username); srt_setsockflag(acpsock, SRTO_PASSPHRASE, exp_pw.c_str(), exp_pw.size()); return 0; } struct RejectData { int code; string streaminfo; } g_reject_data; extern "C" int SrtRejectByCodeHook(void* op, SRTSOCKET acpsock, int , const sockaddr*, const char* ) { RejectData* data = (RejectData*)op; srt_setrejectreason(acpsock, data->code); srt::setstreamid(acpsock, data->streaminfo); return -1; } int main( int argc, char** argv ) { // This is mainly required on Windows to initialize the network system, // for a case when the instance would use UDP. SRT does it on its own, independently. if ( !SysInitializeNetwork() ) throw std::runtime_error("Can't initialize network!"); srt_startup(); // Symmetrically, this does a cleanup; put into a local destructor to ensure that // it's called regardless of how this function returns. struct NetworkCleanup { ~NetworkCleanup() { SysCleanupNetwork(); srt_cleanup(); } } cleanupobj; vector optargs; OptionName o_timeout ((optargs), " Data transmission timeout", "t", "to", "timeout" ), o_chunk ((optargs), " Single reading operation buffer size", "c", "chunk"), o_bandwidth ((optargs), " Input reading speed limit", "b", "bandwidth", "bitrate"), o_report ((optargs), " Print bandwidth report periodically", "r", "bandwidth-report", "bitrate-report"), o_verbose ((optargs), "[channel=0|1|./file] Print size of every packet transferred on stdout or specified [channel]", "v", "verbose"), o_crash ((optargs), " Core-dump when connection got broken by whatever reason (developer mode)", "k", "crash"), o_loglevel ((optargs), " Minimum severity for logs (see --help logging)", "ll", "loglevel"), o_logfa ((optargs), " Enabled Functional Areas (see --help logging)", "lfa", "logfa"), o_logfile ((optargs), " File to send logs to", "lf", "logfile"), o_stats ((optargs), " How often stats should be reported", "s", "stats", "stats-report-frequency"), o_statspf ((optargs), " Format for printing statistics", "pf", "statspf", "statspformat"), o_logint ((optargs), " Use internal function for receiving logs (for testing)", "loginternal"), o_skipflush ((optargs), " Do not wait safely 5 seconds at the end to flush buffers", "sf", "skipflush"), o_stoptime ((optargs), " Time after which the application gets interrupted", "d", "stoptime"), o_hook ((optargs), " Use listener callback of given specification (internally coded)", "hook"), #if ENABLE_EXPERIMENTAL_BONDING o_group ((optargs), " Using multiple SRT connections as redundancy group", "g"), #endif o_stime ((optargs), " Pass source time explicitly to SRT output", "st", "srctime", "sourcetime"), o_retry ((optargs), " Retry connection N times if failed on timeout", "rc", "retry"), o_help ((optargs), "[special=logging] This help", "?", "help", "-help") ; options_t params = ProcessOptions(argv, argc, optargs); bool need_help = OptionPresent(params, o_help); vector args = params[""]; string source_spec, target_spec; #if ENABLE_EXPERIMENTAL_BONDING vector groupspec = Option(params, vector{}, o_group); #endif vector source_items, target_items; if (!need_help) { // You may still need help. #if ENABLE_EXPERIMENTAL_BONDING if ( !groupspec.empty() ) { // Check if you have something before -g and after -g. if (args.empty()) { // Then all items are sources, but the last one is a single target. if (groupspec.size() < 3) { cerr << "ERROR: Redundancy group: with nothing preceding -g, use -g ... (at least 3 args)\n"; need_help = true; } else { copy(groupspec.begin(), groupspec.end()-1, back_inserter(source_items)); target_items.push_back(*(groupspec.end()-1)); } } else { // Something before g, something after g. This time -g can accept also one argument. copy(args.begin(), args.end(), back_inserter(source_items)); copy(groupspec.begin(), groupspec.end(), back_inserter(target_items)); } } else #endif { if (args.size() < 2) { cerr << "ERROR: source and target URI must be specified.\n\n"; need_help = true; } else { source_items.push_back(args[0]); target_items.push_back(args[1]); } } } // Check verbose option before extracting the argument so that Verb()s // can be displayed also when they report something about option parsing. string verbose_val = Option(params, "no", o_verbose); unique_ptr pout_verb; int verbch = 1; // default cerr if (verbose_val != "no") { Verbose::on = true; if (verbose_val == "") verbch = 1; else if (verbose_val.substr(0, 2) == "./") verbch = 3; else try { verbch = stoi(verbose_val); } catch (...) { verbch = 1; } if (verbch == 1) { Verbose::cverb = &std::cout; } else if (verbch == 2) { Verbose::cverb = &std::cerr; } else if (verbch == 3) { pout_verb.reset(new ofstream(verbose_val.substr(2), ios::out | ios::trunc)); if (!pout_verb->good()) { cerr << "-v: error opening verbose output file: " << verbose_val << endl; return 1; } Verbose::cverb = pout_verb.get(); } else { cerr << "-v or -v:1 (default) or -v:2 only allowed\n"; return 1; } } if (!need_help) { // Redundancy is then simply recognized by the fact that there are // multiple specified inputs or outputs, for SRT caller only. Check // every URI in advance. if (!CheckMediaSpec("INPUT", source_items, (source_spec))) need_help = true; if (!CheckMediaSpec("OUTPUT", target_items, (target_spec))) need_help = true; } if (need_help) { string helpspec = Option(params, o_help); if (helpspec == "logging") { cerr << "Logging options:\n"; cerr << " -ll - specify minimum log level\n"; cerr << " -lfa - specify functional areas\n"; cerr << "Where:\n\n"; cerr << " : fatal error note warning debug\n\n"; cerr << "This turns on logs that are at the given log name and all on the left.\n"; cerr << "(Names from syslog, like alert, crit, emerg, err, info, panic, are also\n"; cerr << "recognized, but they are aligned to those that lie close in hierarchy.)\n\n"; cerr << " is a space-sep list of areas to turn on or ~areas to turn off.\n\n"; cerr << "The list may include 'all' to turn all on or off, beside those selected.\n"; cerr << "Example: `-lfa ~all cc` - turns off all FA, except cc\n"; cerr << "Default: all are on except haicrypt. NOTE: 'general' can't be off.\n\n"; cerr << "List of functional areas:\n"; map revmap; for (auto entry: SrtLogFAList()) revmap[entry.second] = entry.first; int en10 = 0; for (auto entry: revmap) { cerr << " " << entry.second; if (entry.first/10 != en10) { cerr << endl; en10 = entry.first/10; } } cerr << endl; return 1; } // Unrecognized helpspec is same as no helpspec, that is, general help. cerr << "Usage:\n"; cerr << " (1) " << argv[0] << " [options] \n"; cerr << " (2) " << argv[0] << " -g [options]\n"; cerr << "*** (Position of [options] is unrestricted.)\n"; cerr << "*** ( option parameters can be only terminated by a next option.)\n"; cerr << "where:\n"; cerr << " (1) Exactly one input and one output URI spec is required,\n"; cerr << " (2) Multiple SRT inputs or output as redundant links are allowed.\n"; cerr << " `URI1 URI2 -g URI3` uses 1, 2 input and 3 output\n"; cerr << " `-g URI1 URI2 URI3` like above\n"; cerr << " `URI1 -g URI2 URI3` uses 1 input and 2, 3 output\n"; cerr << "SUPPORTED URI SCHEMES:\n"; cerr << " srt: use SRT connection\n"; cerr << " udp: read from bound UDP socket or send to given address as UDP\n"; cerr << " file (default if scheme not specified) specified as:\n"; cerr << " - empty host/port and absolute file path in the URI\n"; cerr << " - only a filename, also as a relative path\n"; cerr << " - file://con ('con' as host): designates stdin or stdout\n"; cerr << "OPTIONS HELP SYNTAX: -option :\n"; for (auto os: optargs) cout << OptionHelpItem(*os.pid) << endl; return 1; } int timeout = Option(params, "30", o_timeout); size_t chunk = Option(params, "0", o_chunk); if ( chunk == 0 ) { chunk = SRT_LIVE_DEF_PLSIZE; } else { transmit_chunk_size = chunk; } transmit_use_sourcetime = OptionPresent(params, o_stime); size_t bandwidth = Option(params, "0", o_bandwidth); transmit_bw_report = Option(params, "0", o_report); bool crashonx = OptionPresent(params, o_crash); string loglevel = Option(params, "error", o_loglevel); vector logfa = Option(params, o_logfa); string logfile = Option(params, "", o_logfile); transmit_stats_report = Option(params, "0", o_stats); bool internal_log = OptionPresent(params, o_logint); bool skip_flushing = OptionPresent(params, o_skipflush); string hook = Option(params, "", o_hook); if (hook != "") { vector hargs; Split(hook, ':', back_inserter(hargs)); if (hargs[0] == "user-password") { transmit_accept_hook_fn = &SrtUserPasswordHook; transmit_accept_hook_op = nullptr; } else if (hargs[0] == "reject") { hargs.resize(3); // make sure 3 elements exist, may be empty g_reject_data.code = stoi(hargs[1]); g_reject_data.streaminfo = hargs[2]; transmit_accept_hook_op = (void*)&g_reject_data; transmit_accept_hook_fn = &SrtRejectByCodeHook; } #if ENABLE_EXPERIMENTAL_BONDING else if (hargs[0] == "groupcheck") { transmit_accept_hook_fn = &SrtCheckGroupHook; transmit_accept_hook_op = nullptr; } #endif } string pfextra; SrtStatsPrintFormat statspf = ParsePrintFormat(Option(params, "default", o_statspf), (pfextra)); if (statspf == SRTSTATS_PROFMAT_INVALID) { cerr << "Invalid stats print format\n"; return 1; } transmit_stats_writer = SrtStatsWriterFactory(statspf); if (pfextra != "") { vector options; Split(pfextra, ',', back_inserter(options)); for (auto& i: options) { vector klv; Split(i, '=', back_inserter(klv)); klv.resize(2); transmit_stats_writer->Option(klv[0], klv[1]); } } // Options that require integer conversion size_t stoptime = Option(params, "0", o_stoptime); std::ofstream logfile_stream; // leave unused if not set srt_setloglevel(SrtParseLogLevel(loglevel)); string logfa_on, logfa_off; ParseLogFASpec(logfa, (logfa_on), (logfa_off)); set fasoff = SrtParseLogFA(logfa_off); set fason = SrtParseLogFA(logfa_on); auto fa_del = [fasoff]() { for (set::iterator i = fasoff.begin(); i != fasoff.end(); ++i) srt_dellogfa(*i); }; auto fa_add = [fason]() { for (set::iterator i = fason.begin(); i != fason.end(); ++i) srt_addlogfa(*i); }; if (logfa_off == "all") { // If the spec is: // -lfa ~all control app // then we first delete all, then enable given ones fa_del(); fa_add(); } else { // Otherwise we first add all those that have to be added, // then delete those unwanted. This embraces both // -lfa control app ~cc // and // -lfa all ~cc fa_add(); fa_del(); } srt::addlogfa(SRT_LOGFA_APP); 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 { srt::setlogstream(logfile_stream); } } string retryphrase = Option(params, "", o_retry); if (retryphrase != "") { if (retryphrase[retryphrase.size()-1] == 'a') { transmit_retry_always = true; retryphrase = retryphrase.substr(0, retryphrase.size()-1); } transmit_retry_connect = stoi(retryphrase); } #ifdef _WIN32 #define alarm(argument) (void)0 if (stoptime != 0) { cerr << "ERROR: The -stoptime option (-d) is not implemented on Windows\n"; return 1; } #else signal(SIGALRM, OnAlarm_Interrupt); #endif signal(SIGINT, OnINT_ForceExit); signal(SIGTERM, OnINT_ForceExit); time_t start_time { time(0) }; time_t end_time { -1 }; if (stoptime != 0) { if (stoptime < 10) { cerr << "ERROR: -stoptime (-d) must be at least 10 seconds\n"; return 1; } alarm(stoptime); cerr << "STOPTIME: will interrupt after " << stoptime << "s\n"; if (timeout != 30) { cerr << "WARNING: -timeout (-t) option ignored due to specified -stoptime (-d)\n"; } } // XXX This could be also controlled by an option. int final_delay = 5; // In the beginning, set Alarm unique_ptr src; unique_ptr tar; try { src = Source::Create(source_spec); tar = Target::Create(target_spec); } catch(std::exception& x) { if (::transmit_int_state) { // The application was terminated by SIGINT or SIGTERM. // Don't print anything, just exit gently like ffmpeg. cerr << "Exit on request.\n"; return 255; } if (stoptime != 0 && ::timer_state) { cerr << "Exit on timeout.\n"; return 0; } Verb() << "MEDIA CREATION FAILED: " << x.what() << " - exiting."; // Don't speak anything when no -v option. // (the "requested interrupt" will be printed anyway) return 2; } catch (...) { cerr << "ERROR: UNKNOWN EXCEPTION\n"; return 2; } alarm(0); end_time = time(0); if (!src || !tar) { const string tarstate = tar ? "CREATED" : "FAILED"; const string srcstate = src ? "CREATED" : "FAILED"; cerr << "ERROR: not both media created; source:" << srcstate << " target:" << tarstate << endl; return 2; } // Now loop until broken BandwidthGuard bw(bandwidth); if (transmit_use_sourcetime && src->uri.type() != UriParser::SRT) { Verb() << "WARNING: -st option is effective only if the target type is SRT"; } Verb() << "STARTING TRANSMISSION: '" << source_spec << "' --> '" << target_spec << "'"; // After the time has been spent in the creation // (including waiting for connection) // rest of the time should be spent for transmission. if (stoptime != 0) { int elapsed = end_time - start_time; int remain = stoptime - elapsed; if (remain <= final_delay) { cerr << "NOTE: remained too little time for cleanup: " << remain << "s - exiting\n"; return 0; } cerr << "NOTE: stoptime: remaining " << remain << " seconds (setting alarm to " << (remain - final_delay) << "s)\n"; alarm(remain - final_delay); } try { for (;;) { if (stoptime == 0 && timeout != -1 ) { Verb() << "[." << VerbNoEOL; alarm(timeout); } else { alarm(0); } Verb() << " << ... " << VerbNoEOL; g_interrupt_reason = "reading"; const MediaPacket& data = src->Read(chunk); Verb() << " << " << data.payload.size() << " -> " << VerbNoEOL; if ( data.payload.empty() && src->End() ) { Verb() << "EOS"; break; } g_interrupt_reason = "writing"; tar->Write(data); if (stoptime == 0 && timeout != -1 ) { Verb() << ".] " << VerbNoEOL; alarm(0); } if ( tar->Broken() ) { Verb() << " OUTPUT broken"; break; } Verb() << "sent"; if (::transmit_int_state) { Verror() << "\n (interrupted on request)"; break; } bw.Checkpoint(chunk, transmit_bw_report); if (stoptime != 0) { int elapsed = time(0) - end_time; int remain = stoptime - final_delay - elapsed; if (remain < 0) { Verror() << "\n (interrupted on timeout: elapsed " << elapsed << "s) - waiting " << final_delay << "s for cleanup"; this_thread::sleep_for(chrono::seconds(final_delay)); break; } } } } catch (Source::ReadEOF&) { alarm(0); if (!skip_flushing) { Verror() << "(DEBUG) EOF when reading file. Looping until the sending bufer depletes.\n"; for (;;) { size_t still = tar->Still(); if (still == 0) { Verror() << "(DEBUG) DEPLETED. Done.\n"; break; } Verror() << "(DEBUG)... still " << still << " bytes (sleep 1s)\n"; this_thread::sleep_for(chrono::seconds(1)); } } } catch (std::exception& x) { // Catches TransmissionError and AlarmExit if (stoptime != 0 && ::timer_state) { Verror() << "Exit on timeout."; } else if (::transmit_int_state) { Verror() << "Exit on interrupt."; // Do nothing. } else { Verror() << "STD EXCEPTION: " << x.what(); } if ( crashonx ) throw; if (final_delay > 0) { Verror() << "Waiting " << final_delay << "s for possible cleanup..."; this_thread::sleep_for(chrono::seconds(final_delay)); } if (stoptime != 0 && ::timer_state) return 0; return 255; } catch (...) { Verror() << "UNKNOWN type of EXCEPTION"; if ( crashonx ) throw; return 1; } return 0; } // Class utilities 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 = SysLocalTime(now); size_t pos = strftime(buf, 1024, "[%c ", &local); #ifdef _MSC_VER // That's something weird that happens on Microsoft Visual Studio 2013 // Trying to keep portability, while every version of MSVS is a different plaform. // On MSVS 2015 there's already a standard-compliant snprintf, whereas _snprintf // is available on backward compatibility and it doesn't work exactly the same way. #define snprintf _snprintf #endif snprintf(buf+pos, 1024-pos, "%s:%d(%s)]{%d} %s", file, line, area, level, message); cerr << buf << endl; } srt-1.4.4/testing/srt-test-live.maf000066400000000000000000000002711412557703600172260ustar00rootroot00000000000000 SOURCES srt-test-live.cpp testmedia.cpp ../apps/apputil.cpp ../apps/verbose.cpp ../apps/socketoptions.cpp ../apps/uriparser.cpp ../apps/logsupport.cpp ../apps/logsupport_appdefs.cpp srt-1.4.4/testing/srt-test-mpbond.cpp000066400000000000000000000214741412557703600175750ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include #include #include #include #include #include #include #include #define REQUIRE_CXX11 1 #include "apputil.hpp" // CreateAddr #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" #include "logsupport.hpp" #include "testmediabase.hpp" #include "testmedia.hpp" #include "netinet_any.h" #include "threadname.h" #include "verbose.hpp" #include #include // Make the windows-nonexistent alarm an empty call #ifdef _WIN32 #define alarm(argument) (void)0 #define signal_alarm(fn) (void)0 #else #define signal_alarm(fn) signal(SIGALRM, fn) #endif srt_logging::Logger applog(SRT_LOGFA_APP, srt_logger_config, "srt-mpbond"); volatile bool mpbond_int_state = false; void OnINT_SetIntState(int) { cerr << "\n-------- REQUESTED INTERRUPT!\n"; mpbond_int_state = true; } int main( int argc, char** argv ) { // This is mainly required on Windows to initialize the network system, // for a case when the instance would use UDP. SRT does it on its own, independently. if ( !SysInitializeNetwork() ) throw std::runtime_error("Can't initialize network!"); // Symmetrically, this does a cleanup; put into a local destructor to ensure that // it's called regardless of how this function returns. struct NetworkCleanup { ~NetworkCleanup() { SysCleanupNetwork(); } } cleanupobj; signal(SIGINT, OnINT_SetIntState); signal(SIGTERM, OnINT_SetIntState); vector optargs; OptionName o_input ((optargs), " Define input to send over SRT endpoint", "i", "input"), o_output ((optargs), " Define output to send data read from SRT endpoint", "o", "output"), o_verbose ((optargs), "[channel=0|1] Print size of every packet transferred on stdout or specified [channel]", "v", "verbose"), o_loglevel ((optargs), " Minimum severity for logs", "ll", "loglevel"), o_logfa ((optargs), " Enabled Functional Areas", "lfa", "logfa"), o_help ((optargs), " This help", "?", "help", "-help") ; options_t params = ProcessOptions(argv, argc, optargs); bool need_help = OptionPresent(params, o_help); vector args = params[""]; string srtspec; if (args.empty()) need_help = true; else { for (size_t i = 0; i < args.size(); ++i) { UriParser u(args[i], UriParser::EXPECT_HOST); if (u.portno() == 0) { cerr << "ERROR: " << args[i] << " expected host:port or :port syntax.\n"; return 1; } } } if (need_help) { cerr << "Usage:\n"; cerr << " " << argv[0] << " [-i INPUT] [-o OUTPUT]\n"; cerr << "*** (Position of [options] is unrestricted.)\n"; cerr << "*** ( option parameters can be only terminated by a next option.)\n"; cerr << "where:\n"; cerr << " - : a list of host:port specs for SRT listener\n"; cerr << " - INPUT or OUTPUT: at least one of that kind must be specified\n"; cerr << "SUPPORTED URI SCHEMES:\n"; cerr << " srt: use SRT connection\n"; cerr << " udp: read from bound UDP socket or send to given address as UDP\n"; cerr << " file (default if scheme not specified) specified as:\n"; cerr << " - empty host/port and absolute file path in the URI\n"; cerr << " - only a filename, also as a relative path\n"; cerr << " - file://con ('con' as host): designates stdin or stdout\n"; cerr << "OPTIONS HELP SYNTAX: -option :\n"; for (auto os: optargs) cout << OptionHelpItem(*os.pid) << endl; return 1; } bool skip_flushing = false; // non-configurable for now bool mode_output = OptionPresent(params, o_output); string loglevel = Option(params, "error", "ll", "loglevel"); srt_logging::LogLevel::type lev = SrtParseLogLevel(loglevel); srt::setloglevel(lev); srt::addlogfa(SRT_LOGFA_APP); // Check verbose option before extracting the argument so that Verb()s // can be displayed also when they report something about option parsing. string verbose_val = Option(params, "no", o_verbose); int verbch = 1; // default cerr if (verbose_val != "no") { Verbose::on = true; try { verbch = stoi(verbose_val); } catch (...) { verbch = 1; } if (verbch != 1) { if (verbch != 2) { cerr << "-v or -v:1 (default) or -v:2 only allowed\n"; return 1; } Verbose::cverb = &std::cerr; } else { Verbose::cverb = &std::cout; } } if (OptionPresent(params, o_input) == OptionPresent(params, o_output)) { cerr << "One of -i and -o options must be specified (not both)\n"; return 1; } // Create listeners according to the parameters vector listeners; Verb() << "LISTENERS [ " << VerbNoEOL; for (size_t i = 0; i < args.size(); ++i) { UriParser u(args[i], UriParser::EXPECT_HOST); sockaddr_any sa = CreateAddr(u.host(), u.portno()); SRTSOCKET s = srt_create_socket(); //SRT_GROUPCONNTYPE gcon = SRTGC_GROUPONLY; int gcon = 1; srt_setsockflag(s, SRTO_GROUPCONNECT, &gcon, sizeof gcon); srt_bind(s, sa.get(), sizeof sa); srt_listen(s, 5); listeners.push_back(s); Verb() << u.host() << ":" << u.portno() << " " << VerbNoEOL; } Verb() << "] accept..."; SRTSOCKET conngrp = srt_accept_bond(listeners.data(), listeners.size(), -1); if (conngrp == SRT_INVALID_SOCK) { cerr << "ERROR: srt_accept_bond: " << srt_getlasterror_str() << endl; return 1; } auto s = new SrtSource; unique_ptr src; unique_ptr tar; try { // Now create input or output if (mode_output) { string outspec = Option(params, o_output); Verb() << "SRT -> " << outspec; tar = Target::Create(outspec); s->Acquire(conngrp); src.reset(s); } else { string inspec = Option(params, o_input); Verb() << "SRT <- " << inspec; src = Source::Create(inspec); auto s = new SrtTarget; s->Acquire(conngrp); tar.reset(s); } } catch (...) { return 2; } size_t chunk = SRT_LIVE_MAX_PLSIZE; // Now run the loop try { for (;;) { Verb() << " << ... " << VerbNoEOL; const MediaPacket& data = src->Read(chunk); Verb() << " << " << data.payload.size() << " -> " << VerbNoEOL; if ( data.payload.empty() && src->End() ) { Verb() << "EOS"; break; } tar->Write(data); if ( tar->Broken() ) { Verb() << " OUTPUT broken"; break; } Verb() << "sent"; if ( mpbond_int_state ) { Verror() << "\n (interrupted on request)"; break; } } } catch (Source::ReadEOF&) { alarm(0); if (!skip_flushing) { Verror() << "(DEBUG) EOF when reading file. Looping until the sending bufer depletes.\n"; for (;;) { size_t still = tar->Still(); if (still == 0) { Verror() << "(DEBUG) DEPLETED. Done.\n"; break; } Verror() << "(DEBUG)... still " << still << " bytes (sleep 1s)\n"; this_thread::sleep_for(chrono::seconds(1)); } } } catch (std::exception& x) { // Catches TransmissionError and AlarmExit if (::mpbond_int_state) { Verror() << "Exit on interrupt."; // Do nothing. } else { Verror() << "STD EXCEPTION: " << x.what(); } return 255; } catch (...) { Verror() << "UNKNOWN type of EXCEPTION"; return 1; } return 0; } srt-1.4.4/testing/srt-test-mpbond.maf000066400000000000000000000002721412557703600175470ustar00rootroot00000000000000 SOURCES srt-test-mpbond.cpp testmedia.cpp ../apps/apputil.cpp ../apps/verbose.cpp ../apps/socketoptions.cpp ../apps/uriparser.cpp ../apps/logsupport.cpp ../apps/logsupport_appdefs.cpp srt-1.4.4/testing/srt-test-multiplex.cpp000066400000000000000000000436051412557703600203410ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include #include #include #include #include #include #include #include #define REQUIRE_CXX11 1 #include "apputil.hpp" // CreateAddr #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" #include "logsupport.hpp" #include "testmediabase.hpp" #include "testmedia.hpp" #include "netinet_any.h" #include "threadname.h" #include "verbose.hpp" #include #include // Make the windows-nonexistent alarm an empty call #ifdef _WIN32 #define alarm(argument) (void)0 #define signal_alarm(fn) (void)0 #else #define signal_alarm(fn) signal(SIGALRM, fn) #endif using namespace std; // The length of the SRT payload used in srt_recvmsg call. // So far, this function must be used and up to this length of payload. const size_t DEFAULT_CHUNK = 1316; srt_logging::Logger applog(SRT_LOGFA_APP, srt_logger_config, "srt-mplex"); volatile bool siplex_int_state = false; void OnINT_SetIntState(int) { cerr << "\n-------- REQUESTED INTERRUPT!\n"; siplex_int_state = true; } volatile bool alarm_state = false; void OnALRM_SetAlarmState(int) { alarm_state = true; } map defined_streams; string file_pattern = "output%.dat"; struct MediumPair { unique_ptr src; unique_ptr tar; thread runner; size_t chunk = DEFAULT_CHUNK; volatile bool interrupted = false; volatile bool has_quit = false; bytevector initial_portion; string name; MediumPair(unique_ptr s, unique_ptr t): src(move(s)), tar(move(t)) {} void Stop() { interrupted = true; runner.join(); src.reset(); tar.reset(); } void TransmissionLoop() { struct MarkQuit { volatile bool& q; ~MarkQuit() { q = true; applog.Note() << "MediumPair: Giving it 5 seconds delay before exiting"; this_thread::sleep_for(chrono::seconds(5)); } } mq { has_quit }; applog.Note() << "STARTING TRANSMiSSION: " << name; if (!initial_portion.empty()) { tar->Write(initial_portion); if (tar->Broken()) { applog.Note() << "OUTPUT BROKEN for loop: " << name; return; } initial_portion.clear(); } try { for (;;) { ostringstream sout; alarm(1); auto data = src->Read(chunk); alarm(0); if (alarm_state) { alarm_state = false; // This means that it's just a checkpoint. if ( interrupted ) break; continue; } sout << " << " << data.payload.size() << " -> "; if ( data.payload.empty() && src->End() ) { sout << "EOS"; applog.Note() << sout.str(); break; } tar->Write(data); if (tar->Broken()) { sout << " OUTPUT broken"; applog.Note() << sout.str(); break; } sout << " sent"; if ( siplex_int_state ) { sout << " --- (interrupted on request)"; applog.Note() << sout.str(); break; } applog.Note() << sout.str(); } } catch (const Source::ReadEOF&) { applog.Note() << "EOS - closing media for loop: " << name; src->Close(); tar->Close(); applog.Note() << "CLOSED: " << name; } catch (const std::runtime_error& x) { applog.Note() << "INTERRUPTED: " << x.what(); src->Close(); tar->Close(); applog.Note() << "CLOSED: " << name; } catch (...) { applog.Note() << "UNEXPECTED EXCEPTION, rethrowing"; throw; } } }; class MediaBase { public: list media; /// Take the Source and Target and bind them for a transmission. /// This spawns a thread for transmission. /// @param src source medium /// @param tar target medium /// @param initial_portion First portion of data read from @c src for any extra checks, which /// are still meant to be delivered to @c tar MediumPair& Link(std::unique_ptr src, std::unique_ptr tar, bytevector&& initial_portion, string name, string thread_name) { media.emplace_back(move(src), move(tar)); MediumPair& med = media.back(); med.initial_portion = move(initial_portion); med.name = name; // Ok, got this, so we can start transmission. srt::ThreadName tn(thread_name); med.runner = thread( [&med]() { med.TransmissionLoop(); }); return med; } void StopAll() { for (auto& x: media) x.Stop(); } ~MediaBase() { StopAll(); } } g_media_base; string ResolveFilePattern(int number) { vector parts; Split(::file_pattern, '%', back_inserter(parts)); ostringstream os; os << parts[0]; for (auto i = parts.begin()+1; i < parts.end(); ++i) os << number << *i; return os.str(); } string SelectMedium(string id, bool mode_output) { static int number = 0; // Empty ID is incorrect. if ( id == "" ) { applog.Error() << "SelectMedium: empty id"; return ""; } string uri = map_get(defined_streams, id); // Test the URI if it is openable. UriParser u(uri); if ( u.scheme() == "file" && u.path() == "" ) { if (mode_output) { ++number; string sol = ResolveFilePattern(number); applog.Warn() << "SelectMedium: for [" << id << "] uri '" << uri << "' is file with no path - autogenerating filename: " << sol; return sol; } applog.Error() << "SelectMedium: id not found: [" << id << "]"; return ""; } applog.Note() << "SelectMedium: for [" << id << "] found medium: " << uri; return uri; } bool PrepareStreamNames(const map>& params, bool mode_output) { vector v; string flag; if (mode_output) { // You have an incoming stream over SRT and you need to // redirect it to the correct locally defined output stream. if (params.count("o") && !params.at("o").empty()) { // We have a defined list of parameters. // Check if there's just one item and it's a file pattern // Each stream needs to be defined separately, at least to have IDs // If this is a file without path, use the default file pattern. v = params.at("o"); flag = "o"; } } else { // You have some input media and you want to send them all // over SRT medium. if (params.count("i")) { v = params.at("i"); flag = "i"; } } if ( v.empty() ) return false; for (string& s: v) { UriParser u(s); string id = u["id"]; if ( id != "" ) { defined_streams[id] = s; } else { cerr << "Parameter at -" << flag << " without id: " << s << endl; return false; } } return true; } bool SelectAndLink(SrtModel& m, string id, bool mode_output) { // So, we have made a connection that is now contained in m. // For that connection we need to select appropriate stream // to send. // // XXX // Currently only one method implemented: select appropriate number from the list. // If SRT mode is caller, then SelectMedium will always return // a nonempty string that is a key in defined_streams map. // This is because in this case the id comes directly from // that map's keys. string medium = SelectMedium(id, mode_output); if ( medium == "" ) { // No medium available for that stream, ignore it. m.Close(); return false; } // Now create a medium and store. unique_ptr source; unique_ptr target; string name; ostringstream os; SRTSOCKET sock = m.Socket(); string thread_name; if ( mode_output ) { // Create Source out of SrtModel and Target from the given medium auto s = new SrtSource(); s->StealFrom(m); source.reset(s); target = Target::Create(medium); os << m.m_host << ":" << m.m_port << "[" << id << "]%" << sock << " -> " << medium; thread_name = "TL>" + medium; } else { // Create Source of given medium and Target of SrtModel. source = Source::Create(medium); auto t = new SrtTarget(); t->StealFrom(m); target.reset(t); os << medium << " -> " << m.m_host << ":" << m.m_port << "[" << id << "]%" << sock; thread_name = "TL<" + medium; } bytevector dummy_initial_portion; g_media_base.Link(move(source), move(target), move(dummy_initial_portion), os.str(), thread_name); return true; } void Stall() { // Call this function if everything is running in their own // threads and there's nothing more to run. Check periodically // if all threads are still alive, quit if all are dead. while (!siplex_int_state) { this_thread::sleep_for(chrono::seconds(1)); // Check all cars if any crashed for (auto i = g_media_base.media.begin(), i_next = i; i != g_media_base.media.end(); i = i_next) { ++i_next; if (i->has_quit) { Verb() << "Found QUIT mediumpair: " << i->name << " - removing from base"; i->Stop(); g_media_base.media.erase(i); } } if (g_media_base.media.empty()) { Verb() << "All media have quit. Marking exit."; break; } } } void Usage(string program) { cerr << "Usage: " << program << " [-i INPUT...] [-o OUTPUT...]\n"; } void Help(string program) { Usage(program); cerr << endl; cerr << "SIPLEX is a program that demonstrates two SRT features:\n" " - using one UDP outgoing port for multiple connecting SRT sockets\n" " - setting a resource ID on a socket visible on the listener side\n" "\n" "The will be input or output depending on the further -i/-o option.\n" "The URIs specified as -i INPUT... will be used for input and therefore SRT for output,\n" "and in the other way around if you use -o OUTPUT...\n" "For every such URI you must specify additionally a parameter named 'id', which will be\n" "interperted by the application and used to set resource id on an SRT socket when connecting\n" "or to match with the id extracted from the accepted socket of incoming connection.\n" "Example:\n" "\tSender: srt-multiplex srt://remhost:2000 -i udp://:5000?id=low udp://:6000?id=high\n" "\tReceiver: srt-multiplex srt://:2000 -o output-high.ts?id=high output-low.ts?id=low\n" "\nHere you create a Sender which will connect to 'remhost' port 2000 using multiple SRT\n" "sockets, all of which will be using the same outgoing port. Here the port is autoselected\n" "by the first socket when connecting, every next one will reuse that port. Alternatively you\n" "can enforce the outgoing port using 'port' parameter in the SRT URI.\n\n" "Then for every input resource a separate connection is made and appropriate resource id\n" "will be set to particular socket assigned to that resource according to the 'id' parameter.\n" "When the listener side (here Receiver) gets the socket accepted, it will have the resource\n" "id set just as the caller side did, in which case srt-multiplex will search for this id among\n" "the registered resources and match the resource (output here) with this id. If the resource is\n" "not found, the connection is closed immediately. This works the same way regardless of which\n" "direction is used by caller or listener\n"; } int main( int argc, char** argv ) { // This is mainly required on Windows to initialize the network system, // for a case when the instance would use UDP. SRT does it on its own, independently. if ( !SysInitializeNetwork() ) throw std::runtime_error("Can't initialize network!"); // Initialize signals signal_alarm(OnALRM_SetAlarmState); signal(SIGINT, OnINT_SetIntState); signal(SIGTERM, OnINT_SetIntState); // Symmetrically, this does a cleanup; put into a local destructor to ensure that // it's called regardless of how this function returns. struct NetworkCleanup { ~NetworkCleanup() { SysCleanupNetwork(); } } cleanupobj; const OptionName o_loglevel = { "ll", "loglevel" }, o_input = { "i" }, o_output = { "o" }; vector optargs = { { o_loglevel, OptionScheme::ARG_ONE }, { o_input, OptionScheme::ARG_VAR }, { o_output, OptionScheme::ARG_VAR } }; map> params = ProcessOptions(argv, argc, optargs); // The call syntax is: // // srt-multiplex -o/-i ARGS... // // SRT URI should contain: // srt://[host]:port?mode=MODE&adapter=ADAPTER&port=PORT&otherparameters... // // Extra parameters: // // mode: caller/listener/rendezvous. Default: if host empty, listener, otherwise caller. // adapter: IP to select network device for listner or rendezvous. Default: for listener taken from host, otherwise 0.0.0.0 // port: default=0. Used only for caller mode, sets the outgoing port number. If 0, system-selected (default behavior) // // Syntax cases for -i: // // Every item from ARGS... is an input URI. For every such case a new socket should be // created and the data should be transmitted through that socket. // // Syntax cases for -o: // // EMPTY ARGS...: use 'output%.dat' file patter for every stream. // PATTERN (one argument that contains % somewhere): define the output file pattern // URI...: try to match the input stream to particular URI by 'name' parameter. If none matches, ignore. if ( params.count("-help") ) { Help(argv[0]); return 1; } if ( params[""].empty() ) { Usage(argv[0]); return 1; } if (params[""].size() > 1) { cerr << "Extra parameter after the first one: " << Printable(params[""]) << endl; return 1; } // Force exist (void)params["o"]; (void)params["i"]; if (!params["o"].empty() && !params["i"].empty()) { cerr << "Input-output mixed mode not supported. Specify either -i or -o.\n"; return 1; } bool mode_output = false; if (params["i"].empty()) { mode_output = true; } if ( !PrepareStreamNames(params, mode_output)) { cerr << "Incorrect input/output specification\n"; return 1; } if ( defined_streams.empty() ) { cerr << "No streams defined\n"; return 1; } string loglevel = Option(params, "error", "ll", "loglevel"); srt_logging::LogLevel::type lev = SrtParseLogLevel(loglevel); srt::setloglevel(lev); srt::addlogfa(SRT_LOGFA_APP); string verbo = Option(params, "no", "v", "verbose"); if ( verbo == "" || !false_names.count(verbo) ) Verbose::on = true; string srt_uri = params[""][0]; UriParser up(srt_uri); if ( up.scheme() != "srt" ) { cerr << "First parameter must be a SRT-scheme URI\n"; return 1; } int iport = atoi(up.port().c_str()); if ( iport < 1024 ) { cerr << "Port value invalid: " << iport << " - must be >=1024\n"; return 1; } SrtModel m(up.host(), iport, up.parameters()); srt::ThreadName::set("main"); // Note: for input, there must be an exactly defined // number of sources. The loop rolls up to all these sources. // // For output, if you use defined output URI, roll the loop until // they are all managed. // If you use file pattern, then: // - if SRT is in listener mode, just listen infinitely // - if SRT is in caller mode, the limit number of the streams must be used. Default is 10. set ids; for (auto& mp: defined_streams) ids.insert(mp.first); try { for(;;) { string id = *ids.begin(); m.Establish((id)); // The 'id' could have been altered. // If Establish did connect(), then it gave this stream id, // in which case it will return unchanged. If it did accept(), // then it will be overwritten with the received stream id. // Whatever the result was, we need to bind the transmitter with // the local resource of this id, and if this failed, simply // close the stream and ignore it. // Select medium from parameters. if (SelectAndLink(m, id, mode_output)) { ids.erase(id); if (ids.empty()) break; } srt::ThreadName::set("main"); } applog.Note() << "All local stream definitions covered. Waiting for interrupt/broken all connections."; Stall(); } catch (std::exception& x) { cerr << "CATCH!\n" << x.what() << endl;; } } srt-1.4.4/testing/srt-test-multiplex.maf000066400000000000000000000002761412557703600203170ustar00rootroot00000000000000 SOURCES srt-test-multiplex.cpp testmedia.cpp ../apps/apputil.cpp ../apps/verbose.cpp ../apps/socketoptions.cpp ../apps/uriparser.cpp ../apps/logsupport.cpp ../apps/logsupport_appdefs.cpp srt-1.4.4/testing/srt-test-relay.cpp000077500000000000000000000351531412557703600174340ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ #include "platform_sys.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "testactivemedia.hpp" #include "apputil.hpp" #include "uriparser.hpp" #include "logsupport.hpp" #include "logging.h" #include "socketoptions.hpp" #include "verbose.hpp" #include "testmedia.hpp" #include "threadname.h" bool Upload(UriParser& srt, UriParser& file); bool Download(UriParser& srt, UriParser& file); srt_logging::Logger applog(SRT_LOGFA_APP, srt_logger_config, "srt-relay"); std::atomic g_program_established {false}; SrtModel* g_pending_model = nullptr; thread::id g_root_thread = std::this_thread::get_id(); static void OnINT_SetInterrupted(int) { Verb() << VerbLock << "SIGINT: Setting interrupt state."; ::transmit_int_state = true; // Just for a case, forcefully close all active SRT sockets. SrtModel* pm = ::g_pending_model; if (pm) { // The program is hanged on accepting a new SRT connection. // We need to check which thread we've fallen into. if (this_thread::get_id() == g_root_thread) { // Throw an exception, it will be caught in a predicted place. throw std::runtime_error("Interrupted on request"); } else { // This is some other thread, so close the listener socket. // This will cause the accept block to be interrupted. for (SRTSOCKET i: { pm->Socket(), pm->Listener() }) if (i != SRT_INVALID_SOCK) srt_close(i); } } } using namespace std; size_t g_chunksize = 0; size_t g_default_live_chunksize = 1316; size_t g_default_file_chunksize = 1456; class SrtMainLoop { UriParser m_srtspec; // Media used unique_ptr m_srt_relay; SourceMedium m_srt_source; SourceMedium m_input_medium; list> m_output_media; thread m_input_thr; std::exception_ptr m_input_xp; void InputRunner(); volatile bool m_input_running = false; public: SrtMainLoop(const string& srt_uri, bool input_echoback, const string& input_spec, const vector& output_spec); void run(); void MakeStop() { m_input_running = false; } bool IsRunning() { return m_input_running; } ~SrtMainLoop() { if (m_input_thr.joinable()) m_input_thr.join(); } }; int main( int argc, char** argv ) { OptionName o_loglevel = { "ll", "loglevel" }, o_logfa = { "lf", "logfa" }, o_verbose = {"v", "verbose" }, o_input = {"i", "input"}, o_output = {"o", "output"}, o_echo = {"e", "io", "input-echoback"}, o_chunksize = {"c", "chunk"} ; // Options that expect no arguments (ARG_NONE) need not be mentioned. vector optargs = { { o_loglevel, OptionScheme::ARG_ONE }, { o_logfa, OptionScheme::ARG_ONE }, { o_input, OptionScheme::ARG_ONE }, { o_output, OptionScheme::ARG_VAR }, { o_chunksize, OptionScheme::ARG_ONE } }; options_t params = ProcessOptions(argv, argc, optargs); /* cerr << "OPTIONS (DEBUG)\n"; for (auto o: params) { cerr << "[" << o.first << "] "; copy(o.second.begin(), o.second.end(), ostream_iterator(cerr, " ")); cerr << endl; } */ vector args = params[""]; if ( args.size() != 1 ) { cerr << "Usage: " << argv[0] << " [ -i | -e ] [ -o ]\n"; cerr << "Options:\n"; cerr << "\t-v . . . . . . . . . . Verbose mode\n"; cerr << "\t-ll . . . . . Log level for SRT\n"; cerr << "\t-lf . . . . . Log Functional Areas enabled\n"; cerr << "\t-c Single reading buffer size\n"; cerr << "\t-i . . . . . . . . Input medium spec\n"; cerr << "\t-o . . . . . . . . Output medium spec\n"; cerr << "\t-e . . . (conflicts with -i) Feed SRT output back to SRT input\n"; cerr << "\nNote: specify `transtype=file` for using TCP-like stream mode\n"; return 1; } string loglevel = Option(params, "error", o_loglevel); string logfa = Option(params, "", o_logfa); srt_logging::LogLevel::type lev = SrtParseLogLevel(loglevel); UDT::setloglevel(lev); if (logfa == "") { UDT::addlogfa(SRT_LOGFA_APP); } else { // Add only selected FAs set unknown_fas; set fas = SrtParseLogFA(logfa, &unknown_fas); UDT::resetlogfa(fas); // The general parser doesn't recognize the "app" FA, we check it here. if (unknown_fas.count("app")) UDT::addlogfa(SRT_LOGFA_APP); } string verbo = Option(params, "no", o_verbose); if ( verbo == "" || !false_names.count(verbo) ) { Verbose::on = true; int verboch = atoi(verbo.c_str()); if (verboch <= 0) { verboch = 1; } else if (verboch > 2) { cerr << "ERROR: -v option accepts value 1 (stdout, default) or 2 (stderr)\n"; return 1; } if (verboch == 1) { Verbose::cverb = &std::cout; } else { Verbose::cverb = &std::cerr; } } string chunk = Option(params, "", o_chunksize); if (chunk != "") { ::g_chunksize = stoi(chunk); } string srt_endpoint = args[0]; UriParser usrt(srt_endpoint); if (usrt.scheme() != "srt") { cerr << "ERROR: the only one freestanding parameter should be an SRT uri.\n"; cerr << "Usage: " << argv[0] << " [ -i ] [ -o ] [ -e ]\n"; return 1; } // Allowed are only one input and multiple outputs. // Input-echoback is treated as a single input. bool input_echoback = Option(params, "no", o_echo) != "no"; string input_spec = Option(params, "", o_input); if (input_spec != "" && input_echoback) { cerr << "ERROR: input-echoback is treated as input specifcation, -i can't be specified together.\n"; return 1; } vector output_spec = Option(params, vector{}, o_output); if (!input_echoback) { if (input_spec == "" || output_spec.empty()) { cerr << "ERROR: at least one input and one output must be specified (-io specifies both)\n"; return 1; } } Verb() << "SETTINGS:"; Verb() << "SRT connection: " << srt_endpoint; if (input_echoback) { Verb() << "INPUT: (from SRT connection)"; } else { Verb() << "INPUT: " << input_spec; } Verb() << "OUTPUT LIST:"; if (input_echoback) { Verb() << "\t(back to SRT connection)"; } for (auto& s: output_spec) Verb() << "\t" << s; #ifdef _MSC_VER // Replacement for sigaction, just use 'signal' // This may make this working kinda impaired and unexpected, // but still better that not compiling at all. signal(SIGINT, OnINT_SetInterrupted); #else struct sigaction sigIntHandler; sigIntHandler.sa_handler = OnINT_SetInterrupted; sigemptyset(&sigIntHandler.sa_mask); sigIntHandler.sa_flags = 0; sigaction(SIGINT, &sigIntHandler, NULL); #endif try { SrtMainLoop loop(srt_endpoint, input_echoback, input_spec, output_spec); loop.run(); } catch (std::exception& x) { cerr << "ERROR: " << x.what() << endl; return 1; } return 0; } SrtMainLoop::SrtMainLoop(const string& srt_uri, bool input_echoback, const string& input_spec, const vector& output_spec) { // Now prepare all media // They use pointers instead of real variables // so that the creation time can be delayed // up to this moment, and the parameters prepared // before passing to the constructors. // Start with output media so that they are ready when // the data come in. for (string spec: output_spec) { Verb() << "Setting up output: " << spec; unique_ptr m { new TargetMedium }; m->Setup(Target::Create(spec)); m_output_media.push_back(move(m)); } // Start with SRT. UriParser srtspec(srt_uri); string transtype = srtspec["transtype"].deflt("live"); SrtModel m(srtspec.host(), srtspec.portno(), srtspec.parameters()); // Just to keep it unchanged. string id = m_srtspec["streamid"]; Verb() << "Establishing SRT connection: " << srt_uri; ::g_pending_model = &m; m.Establish((id)); ::g_program_established = true; ::g_pending_model = nullptr; Verb() << "... Established. configuring other pipes:"; // Once it's ready, use it to initialize the medium. bool file_mode = (transtype == "file"); if (g_chunksize == 0) { if (file_mode) g_chunksize = g_default_file_chunksize; else g_chunksize = g_default_live_chunksize; Verb() << "DEFAULT CHUNKSIZE used: " << g_chunksize; } m_srt_relay.reset(new SrtRelay); m_srt_relay->StealFrom(m); m_srt_source.Setup(m_srt_relay.get(), g_chunksize); // Now check the input medium if (input_echoback) { Verb() << "SRT set up as input source and the first output target"; // Add SRT medium to output targets, and keep input medium empty. unique_ptr m { new TargetMedium }; m->Setup(m_srt_relay.get()); m_output_media.push_back(move(m)); } else { // Initialize input medium and do not add SRT medium // to the output list, as this will be fed directly // by the data from this input medium in a spearate engine. Verb() << "Setting up input: " << input_spec; m_input_medium.Setup(Source::Create(input_spec), g_chunksize); if (!file_mode) { // Also set writing to SRT non-blocking always. bool no = false; srt_setsockflag(m_srt_relay->Socket(), SRTO_SNDSYN, &no, sizeof no); } } // We're done here. Verb() << "MEDIA SUCCESSFULLY CREATED."; } void SrtMainLoop::InputRunner() { srt::ThreadName::set("InputRN"); // An extra thread with a loop that reads from the external input // and writes into the SRT medium. When echoback mode is used, // this thread isn't started at all and instead the SRT reading // serves as both SRT reading and input reading. auto on_return_set = OnReturnSet(m_input_running, false); Verb() << VerbLock << "RUNNING INPUT LOOP"; for (;;) { applog.Debug() << "SrtMainLoop::InputRunner: extracting..."; auto data = m_input_medium.Extract(); if (data.payload.empty()) { Verb() << "INPUT READING INTERRUPTED."; break; } //Verb() << "INPUT [" << data.size() << "] " << VerbNoEOL; applog.Debug() << "SrtMainLoop::InputRunner: [" << data.payload.size() << "] CLIENT -> SRT-RELAY"; m_srt_relay->Write(data); } } void SrtMainLoop::run() { // Start the media runners. Verb() << VerbLock << "STARTING OUTPUT threads:"; for (auto& o: m_output_media) o->run(); Verb() << VerbLock << "STARTING SRT INPUT LOOP"; m_srt_source.run(); Verb() << VerbLock << "STARTING INPUT "; if (m_input_medium.med) { m_input_medium.run(); m_input_running = true; std::ostringstream tns; tns << "Input:" << this; srt::ThreadName tn(tns.str()); m_input_thr = thread([this] { try { InputRunner(); } catch (...) { m_input_xp = std::current_exception(); } Verb() << "INPUT: thread exit"; }); } Verb() << VerbLock << "RUNNING SRT MEDIA LOOP"; for (;;) { applog.Debug() << "SrtMainLoop::run: SRT-RELAY: extracting..."; auto data = m_srt_source.Extract(); if (data.payload.empty()) { Verb() << "SRT READING INTERRUPTED."; break; } vector output_report; bool any = false; int no = 1; for (auto i = m_output_media.begin(), i_next = i; i != m_output_media.end(); i = i_next) { ++i_next; auto& o = *i; applog.Debug() << "SrtMainLoop::run: [" << data.payload.size() << "] SRT-RELAY: resending to output #" << no << "..."; if (!o->Schedule(data)) { if (Verbose::on) { ostringstream os; os << " --XXX-> <" << no << ">"; output_report.push_back(os.str()); } m_output_media.erase(i); continue; } if (Verbose::on) { ostringstream os; os << " --> <" << no << ">"; output_report.push_back(os.str()); } any = true; ++no; } applog.Debug() << "SrtMainLoop::run: [" << data.payload.size() << "] SRT-RELAY -> OUTPUTS: " << Printable(output_report); if (Verbose::on) { string outputs; for (auto& r: output_report) outputs += " " + r; if (!any) outputs = " --> * (no output)"; Verb() << VerbLock << "SRT [" << data.payload.size() << "] " << outputs; } } Verb() << "MEDIA LOOP EXIT"; for (auto& m : m_output_media) { m->quit(); } m_input_medium.quit(); m_srt_source.quit(); if (m_input_xp) { try { std::rethrow_exception(m_input_xp); } catch (std::exception& x) { cerr << "INPUT EXIT BY EXCEPTION: " << x.what() << endl; } catch (...) { cerr << "INPUT EXIT BY UNKNOWN EXCEPTION\n"; } } } srt-1.4.4/testing/srt-test-relay.maf000066400000000000000000000003161412557703600174030ustar00rootroot00000000000000 SOURCES srt-test-relay.cpp testmedia.cpp testactivemedia.cpp ../apps/apputil.cpp ../apps/verbose.cpp ../apps/socketoptions.cpp ../apps/uriparser.cpp ../apps/logsupport.cpp ../apps/logsupport_appdefs.cpp srt-1.4.4/testing/testactivemedia.cpp000066400000000000000000000103221412557703600176740ustar00rootroot00000000000000 #include "testactivemedia.hpp" void SourceMedium::Runner() { srt::ThreadName::set("SourceRN"); Verb() << VerbLock << "Starting SourceMedium: " << this; for (;;) { auto input = med->Read(chunksize_); if (input.payload.empty() && med->End()) { Verb() << VerbLock << "Exiting SourceMedium: " << this; return; } LOGP(applog.Debug, "SourceMedium(", typeid(*med).name(), "): [", input.payload.size(), "] MEDIUM -> BUFFER. signal(", &ready, ")"); lock_guard g(buffer_lock); buffer.push_back(input); ready.notify_one(); } } MediaPacket SourceMedium::Extract() { unique_lock g(buffer_lock); for (;;) { if (::transmit_int_state) running = false; if (!buffer.empty()) { MediaPacket top; swap(top, *buffer.begin()); buffer.pop_front(); LOGP(applog.Debug, "SourceMedium(", typeid(*med).name(), "): [", top.payload.size(), "] BUFFER -> CLIENT"); return top; } else { // Don't worry about the media status as long as you have somthing in the buffer. // Purge the buffer first, then worry about the other things. if (!running) { //LOGP(applog.Debug, "Extract(", typeid(*med).name(), "): INTERRUPTED READING"); //Verb() << "SourceMedium " << this << " not running"; return {}; } } // Block until ready //LOGP(applog.Debug, "Extract(", typeid(*med).name(), "): ", this, " wait(", &ready, ") -->"); ready.wait_for(g, chrono::seconds(1), [this] { return running && !buffer.empty(); }); // LOGP(applog.Debug, "Extract(", typeid(*med).name(), "): ", this, " <-- notified (running:" // << boolalpha << running << " buffer:" << buffer.size() << ")"); } } void TargetMedium::Runner() { srt::ThreadName::set("TargetRN"); auto on_return_set = OnReturnSet(running, false); Verb() << VerbLock << "Starting TargetMedium: " << this; for (;;) { MediaPacket val; { unique_lock lg(buffer_lock); if (buffer.empty()) { if (!running) { //LOGP(applog.Debug, "TargetMedium(", typeid(*med).name(), "): buffer empty, medium stopped, exiting."); return; } bool gotsomething = ready.wait_for(lg, chrono::seconds(1), [this] { return !running || !buffer.empty(); } ); LOGP(applog.Debug, "TargetMedium(", typeid(*med).name(), "): [", val.payload.size(), "] BUFFER update (timeout:", boolalpha, gotsomething, " running: ", running, ")"); if (::transmit_int_state || !running || !med || med->Broken()) { LOGP(applog.Debug, "TargetMedium(", typeid(*med).name(), "): buffer empty, medium ", (!::transmit_int_state ? (running ? (med ? (med->Broken() ? "broken" : "UNKNOWN") : "deleted") : "stopped") : "killed")); return; } if (!gotsomething) // exit on timeout continue; } swap(val, *buffer.begin()); LOGP(applog.Debug, "TargetMedium(", typeid(*med).name(), "): [", val.payload.size(), "] BUFFER extraction"); buffer.pop_front(); } // Check before writing if (med->Broken()) { LOGP(applog.Debug, "TargetMedium(", typeid(*med).name(), "): [", val.payload.size(), "] BUFFER -> DISCARDED (medium broken)"); running = false; return; } LOGP(applog.Debug, "TargetMedium(", typeid(*med).name(), "): [", val.payload.size(), "] BUFFER -> MEDIUM"); // You get the data to send, send them. med->Write(val); } } srt-1.4.4/testing/testactivemedia.hpp000066400000000000000000000110511412557703600177010ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "testmedia.hpp" #include "logsupport.hpp" #define SRT_ENABLE_VERBOSE_LOCK 1 #include "verbose.hpp" #include "logging.h" #include "threadname.h" extern srt_logging::Logger applog; template struct Medium { MediumDir* med = nullptr; std::unique_ptr pinned_med; std::list buffer; std::mutex buffer_lock; std::thread thr; std::condition_variable ready; std::atomic running = {false}; std::exception_ptr xp; // To catch exception thrown by a thread virtual void Runner() = 0; void RunnerBase() { try { running = true; Runner(); } catch (...) { xp = std::current_exception(); } //Verb() << "Medium: " << this << ": thread exit"; std::unique_lock g(buffer_lock); running = false; ready.notify_all(); //Verb() << VerbLock << "Medium: EXIT NOTIFIED"; } void run() { running = true; std::ostringstream tns; tns << typeid(*this).name() << ":" << this; srt::ThreadName tn(tns.str()); thr = thread( [this] { RunnerBase(); } ); } void quit() { if (!med) return; LOGP(applog.Debug, "Medium(", typeid(*med).name(), ") quit. Buffer contains ", buffer.size(), " blocks"); std::string name; if (Verbose::on) name = typeid(*med).name(); med->Close(); if (thr.joinable()) { LOGP(applog.Debug, "Medium::quit: Joining medium thread (", name, ") ..."); thr.join(); LOGP(applog.Debug, "... done"); } if (xp) { try { std::rethrow_exception(xp); } catch (TransmissionError& e) { if (Verbose::on) Verb() << VerbLock << "Medium " << this << " exited with Transmission Error:\n\t" << e.what(); else cerr << "Transmission Error: " << e.what() << endl; } catch (...) { if (Verbose::on) Verb() << VerbLock << "Medium " << this << " exited with UNKNOWN EXCEPTION:"; else cerr << "UNKNOWN EXCEPTION on medium\n"; } } // Prevent further quits from running med = nullptr; } void Setup(MediumDir* t) { med = t; // Leave pinned_med as 0 } void Setup(std::unique_ptr&& medbase) { pinned_med = std::move(medbase); med = pinned_med.get(); } virtual ~Medium() { //Verb() << "Medium: " << this << " DESTROYED. Threads quit."; quit(); } virtual void Start() { run(); } virtual void Stop() { quit(); } }; struct SourceMedium: Medium { size_t chunksize_ = 0; typedef Medium Base; // Source Runner: read payloads and put on the buffer void Runner() override; // External user: call this to get the buffer. MediaPacket Extract(); template void Setup(Arg&& medium, size_t chunksize) { chunksize_ = chunksize; return Base::Setup(std::move(medium)); } }; struct TargetMedium: Medium { void Runner() override; bool Schedule(const MediaPacket& data) { LOGP(applog.Debug, "TargetMedium::Schedule LOCK ... "); lock_guard lg(buffer_lock); LOGP(applog.Debug, "TargetMedium::Schedule LOCKED - checking: running=", running, " interrupt=", ::transmit_int_state); if (!running || ::transmit_int_state) { LOGP(applog.Debug, "TargetMedium::Schedule: not running, discarding packet"); return false; } LOGP(applog.Debug, "TargetMedium(", typeid(*med).name(), "): Schedule: [", data.payload.size(), "] CLIENT -> BUFFER"); buffer.push_back(data); ready.notify_one(); return true; } void Clear() { lock_guard lg(buffer_lock); buffer.clear(); } void Interrupt() { lock_guard lg(buffer_lock); running = false; ready.notify_one(); } ~TargetMedium() { //Verb() << "TargetMedium: DESTROYING"; Interrupt(); // ~Medium will do quit() additionally, which joins the thread } }; srt-1.4.4/testing/testmedia.cpp000077500000000000000000002746421412557703600165240ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ // Medium concretizations // Just for formality. This file should be used #include #include #include #include #include #include #include #include #include #include #include #if !defined(_WIN32) #include #endif // SRT protected includes #include "netinet_any.h" #include "common.h" #include "api.h" #include "udt.h" #include "logging.h" #include "utilities.h" #include "apputil.hpp" #include "socketoptions.hpp" #include "uriparser.hpp" #include "testmedia.hpp" #include "srt_compat.h" #include "verbose.hpp" using namespace std; using srt_logging::KmStateStr; using srt_logging::SockStatusStr; #if ENABLE_EXPERIMENTAL_BONDING using srt_logging::MemberStatusStr; #endif std::atomic transmit_throw_on_interrupt {false}; std::atomic transmit_int_state {false}; int transmit_bw_report = 0; unsigned transmit_stats_report = 0; size_t transmit_chunk_size = SRT_LIVE_DEF_PLSIZE; bool transmit_printformat_json = false; srt_listen_callback_fn* transmit_accept_hook_fn = nullptr; void* transmit_accept_hook_op = nullptr; bool transmit_use_sourcetime = false; int transmit_retry_connect = 0; bool transmit_retry_always = false; // Do not unblock. Copy this to an app that uses applog and set appropriate name. //srt_logging::Logger applog(SRT_LOGFA_APP, srt_logger_config, "srt-test"); std::shared_ptr transmit_stats_writer; string DirectionName(SRT_EPOLL_T direction) { string dir_name; if (direction & ~SRT_EPOLL_ERR) { if (direction & SRT_EPOLL_IN) { dir_name = "source"; } if (direction & SRT_EPOLL_OUT) { if (!dir_name.empty()) dir_name = "relay"; else dir_name = "target"; } if (direction & SRT_EPOLL_ERR) { dir_name += "+error"; } } else { // stupid name for a case of IPE dir_name = "stone"; } return dir_name; } template inline bytevector FileRead(FileBase& ifile, size_t chunk, const string& filename) { bytevector data(chunk); ifile.read(data.data(), chunk); size_t nread = ifile.gcount(); if (nread < data.size()) data.resize(nread); if (data.empty()) throw Source::ReadEOF(filename); return data; } class FileSource: public virtual Source { ifstream ifile; string filename_copy; public: FileSource(const string& path): ifile(path, ios::in | ios::binary), filename_copy(path) { if (!ifile) throw std::runtime_error(path + ": Can't open file for reading"); } MediaPacket Read(size_t chunk) override { return FileRead(ifile, chunk, filename_copy); } bool IsOpen() override { return bool(ifile); } bool End() override { return ifile.eof(); } //~FileSource() { ifile.close(); } }; #ifdef PLEASE_LOG #include "logging.h" #endif class FileTarget: public virtual Target { ofstream ofile; public: FileTarget(const string& path): ofile(path, ios::out | ios::trunc | ios::binary) {} void Write(const MediaPacket& data) override { ofile.write(data.payload.data(), data.payload.size()); #ifdef PLEASE_LOG applog.Debug() << "FileTarget::Write: " << data.size() << " written to a file"; #endif } bool IsOpen() override { return !!ofile; } bool Broken() override { return !ofile.good(); } //~FileTarget() { ofile.close(); } void Close() override { #ifdef PLEASE_LOG applog.Debug() << "FileTarget::Close"; #endif ofile.close(); } }; // Can't base this class on FileSource and FileTarget classes because they use two // separate fields, which makes it unable to reliably define IsOpen(). This would // require to use 'fstream' type field in some kind of FileCommon first. Not worth // a shot. class FileRelay: public Relay { fstream iofile; string filename_copy; public: FileRelay(const string& path): iofile(path, ios::in | ios::out | ios::binary), filename_copy(path) { if (!iofile) throw std::runtime_error(path + ": Can't open file for reading"); } MediaPacket Read(size_t chunk) override { return FileRead(iofile, chunk, filename_copy); } void Write(const MediaPacket& data) override { iofile.write(data.payload.data(), data.payload.size()); } bool IsOpen() override { return !!iofile; } bool End() override { return iofile.eof(); } bool Broken() override { return !iofile.good(); } void Close() override { iofile.close(); } }; template struct File; template <> struct File { typedef FileSource type; }; template <> struct File { typedef FileTarget type; }; template <> struct File { typedef FileRelay type; }; template Iface* CreateFile(const string& name) { return new typename File::type (name); } void SrtCommon::InitParameters(string host, string path, map par) { // Application-specific options: mode, blocking, timeout, adapter if ( Verbose::on && !par.empty()) { Verb() << "SRT parameters specified:\n"; for (map::iterator i = par.begin(); i != par.end(); ++i) { Verb() << "\t" << i->first << " = '" << i->second << "'\n"; } } if (path != "") { // Special case handling of an unusual specification. if (path.substr(0, 2) != "//") { Error("Path specification not supported for SRT (use // in front for special cases)"); } path = path.substr(2); #if ENABLE_EXPERIMENTAL_BONDING if (path == "group") { // Group specified, check type. m_group_type = par["type"]; if (m_group_type == "") { Error("With //group, the group 'type' must be specified."); } vector parts; Split(m_group_type, '/', back_inserter(parts)); if (parts.size() == 0 || parts.size() > 2) { Error("Invalid specification for 'type' parameter"); } if (parts.size() == 2) { m_group_type = parts[0]; m_group_config = parts[1]; } vector nodes; Split(par["nodes"], ',', back_inserter(nodes)); if (nodes.empty()) { Error("With //group, 'nodes' must specify comma-separated host:port specs."); } int token = 1; // Check if correctly specified for (string& hostport: nodes) { if (hostport == "") continue; // The attribute string, as it was embedded in another URI, // must have had replaced the & character with another ?, so // now all ? character, except the first one, must be now // restored so that UriParser interprets them correctly. size_t atq = hostport.find('?'); if (atq != string::npos) { while (atq+1 < hostport.size()) { size_t next = hostport.find('?', atq+1); if (next == string::npos) break; hostport[next] = '&'; atq = next; } } UriParser check(hostport, UriParser::EXPECT_HOST); if (check.host() == "" || check.port() == "") { Error("With //group, 'nodes' must specify comma-separated host:port specs."); } if (check.portno() <= 1024) { Error("With //group, every node in 'nodes' must have port >1024"); } Connection cc(check.host(), check.portno()); if (check.parameters().count("weight")) { cc.weight = stoi(check.queryValue("weight")); } if (check.parameters().count("source")) { UriParser sourcehp(check.queryValue("source"), UriParser::EXPECT_HOST); cc.source = CreateAddr(sourcehp.host(), sourcehp.portno()); } // Check if there's a key with 'srto.' prefix. UriParser::query_it start = check.parameters().lower_bound("srto."); SRT_SOCKOPT_CONFIG* config = nullptr; bool all_clear = true; vector fails; map options; if (start != check.parameters().end()) { for (; start != check.parameters().end(); ++start) { auto& y = *start; if (y.first.substr(0, 5) != "srto.") break; options[y.first.substr(5)] = y.second; } } if (!options.empty()) { config = srt_create_config(); for (auto o: srt_options) { if (!options.count(o.name)) continue; string value = options.at(o.name); bool ok = o.apply(config, value); if ( !ok ) { fails.push_back(o.name); all_clear = false; } } if (!all_clear) { srt_delete_config(config); Error("With //group, failed to set options: " + Printable(fails)); } cc.options = config; } cc.token = token++; m_group_nodes.push_back(move(cc)); } par.erase("type"); par.erase("nodes"); // For a group-connect specification, it's // always the caller mode. // XXX change it here if maybe rendezvous is also // possible in future. par["mode"] = "caller"; } #endif } string adapter; if (par.count("adapter")) { adapter = par.at("adapter"); } m_mode = "default"; if (par.count("mode")) { m_mode = par.at("mode"); } SocketOption::Mode mode = SrtInterpretMode(m_mode, host, adapter); if (mode == SocketOption::FAILURE) { Error("Invalid mode"); } if (!m_group_nodes.empty() && mode != SocketOption::CALLER) { Error("Group node specification is only available in caller mode"); } // Fix the mode name after successful interpretation m_mode = SocketOption::mode_names[mode]; par.erase("mode"); if (par.count("blocking")) { m_blocking_mode = !false_names.count(par.at("blocking")); par.erase("blocking"); } if (par.count("timeout")) { m_timeout = stoi(par.at("timeout"), 0, 0); par.erase("timeout"); } if (par.count("adapter")) { m_adapter = adapter; par.erase("adapter"); } else if (m_mode == "listener") { // For listener mode, adapter is taken from host, // if 'adapter' parameter is not given m_adapter = host; } if (par.count("tsbpd") && false_names.count(par.at("tsbpd"))) { m_tsbpdmode = false; } if (par.count("port")) { m_outgoing_port = stoi(par.at("port"), 0, 0); par.erase("port"); } // That's kinda clumsy, but it must rely on the defaults. // Default mode is live, so check if the file mode was enforced if (par.count("transtype") == 0 || par["transtype"] != "file") { // If the Live chunk size was nondefault, enforce the size. if (transmit_chunk_size != SRT_LIVE_DEF_PLSIZE) { if (transmit_chunk_size > SRT_LIVE_MAX_PLSIZE) throw std::runtime_error("Chunk size in live mode exceeds 1456 bytes; this is not supported"); par["payloadsize"] = Sprint(transmit_chunk_size); } } // Assigning group configuration from a special "groupconfig" attribute. // This is the only way how you can set up this configuration at the listener side. if (par.count("groupconfig")) { m_group_config = par.at("groupconfig"); par.erase("groupconfig"); } // Fix Minversion, if specified as string if (par.count("minversion")) { string v = par["minversion"]; if (v.find('.') != string::npos) { int version = SrtParseVersion(v.c_str()); if (version == 0) { throw std::runtime_error(Sprint("Value for 'minversion' doesn't specify a valid version: ", v)); } par["minversion"] = Sprint(version); Verb() << "\tFIXED: minversion = 0x" << std::hex << std::setfill('0') << std::setw(8) << version << std::dec; } } // Assign the others here. m_options = par; m_options["mode"] = m_mode; } void SrtCommon::PrepareListener(string host, int port, int backlog) { m_bindsock = srt_create_socket(); if (m_bindsock == SRT_ERROR) Error("srt_create_socket"); int stat = ConfigurePre(m_bindsock); if (stat == SRT_ERROR) Error("ConfigurePre"); if (!m_blocking_mode) { srt_conn_epoll = AddPoller(m_bindsock, SRT_EPOLL_OUT); } auto sa = CreateAddr(host, port); Verb() << "Binding a server on " << host << ":" << port << " ..."; stat = srt_bind(m_bindsock, sa.get(), sizeof sa); if (stat == SRT_ERROR) { srt_close(m_bindsock); Error("srt_bind"); } Verb() << " listen... " << VerbNoEOL; stat = srt_listen(m_bindsock, backlog); if (stat == SRT_ERROR) { srt_close(m_bindsock); Error("srt_listen"); } } void SrtCommon::StealFrom(SrtCommon& src) { // This is used when SrtCommon class designates a listener // object that is doing Accept in appropriate direction class. // The new object should get the accepted socket. m_direction = src.m_direction; m_blocking_mode = src.m_blocking_mode; m_timeout = src.m_timeout; m_tsbpdmode = src.m_tsbpdmode; m_options = src.m_options; m_bindsock = SRT_INVALID_SOCK; // no listener m_sock = src.m_sock; src.m_sock = SRT_INVALID_SOCK; // STEALING } void SrtCommon::AcceptNewClient() { sockaddr_any scl; ::transmit_throw_on_interrupt = true; if (!m_blocking_mode) { Verb() << "[ASYNC] (conn=" << srt_conn_epoll << ")"; int len = 2; SRTSOCKET ready[2]; while (srt_epoll_wait(srt_conn_epoll, 0, 0, ready, &len, 1000, 0, 0, 0, 0) == -1) { if (::transmit_int_state) Error("srt_epoll_wait for srt_accept: interrupt"); if (srt_getlasterror(NULL) == SRT_ETIMEOUT) continue; Error("srt_epoll_wait(srt_conn_epoll)"); } Verb() << "[EPOLL: " << len << " sockets] " << VerbNoEOL; } Verb() << " accept..." << VerbNoEOL; m_sock = srt_accept(m_bindsock, (scl.get()), (&scl.len)); if (m_sock == SRT_INVALID_SOCK) { srt_close(m_bindsock); m_bindsock = SRT_INVALID_SOCK; Error("srt_accept"); } #if ENABLE_EXPERIMENTAL_BONDING if (m_sock & SRTGROUP_MASK) { m_listener_group = true; if (m_group_config != "") { int stat = srt_group_configure(m_sock, m_group_config.c_str()); if (stat == SRT_ERROR) { // Don't break the connection basing on this, just ignore. Verb() << " (error setting config: '" << m_group_config << "') " << VerbNoEOL; } } // There might be added a poller, remove it. // We need it work different way. #ifndef SRT_OLD_APP_READER if (srt_epoll != -1) { Verb() << "(Group: erasing epoll " << srt_epoll << ") " << VerbNoEOL; srt_epoll_release(srt_epoll); } // Don't add any sockets, they will have to be added // anew every time again. srt_epoll = srt_epoll_create(); #endif // Group data must have a size of at least 1 // otherwise the srt_group_data() call will fail if (m_group_data.empty()) m_group_data.resize(1); Verb() << " connected(group epoll " << srt_epoll <<")."; } else #endif { sockaddr_any peeraddr(AF_INET6); string peer = ""; if (-1 != srt_getpeername(m_sock, (peeraddr.get()), (&peeraddr.len))) { peer = peeraddr.str(); } sockaddr_any agentaddr(AF_INET6); string agent = ""; if (-1 != srt_getsockname(m_sock, (agentaddr.get()), (&agentaddr.len))) { agent = agentaddr.str(); } Verb() << " connected [" << agent << "] <-- " << peer; } ::transmit_throw_on_interrupt = false; // ConfigurePre is done on bindsock, so any possible Pre flags // are DERIVED by sock. ConfigurePost is done exclusively on sock. int stat = ConfigurePost(m_sock); if (stat == SRT_ERROR) Error("ConfigurePost"); } static string PrintEpollEvent(int events, int et_events) { static pair const namemap [] = { make_pair(SRT_EPOLL_IN, "R"), make_pair(SRT_EPOLL_OUT, "W"), make_pair(SRT_EPOLL_ERR, "E"), make_pair(SRT_EPOLL_UPDATE, "U") }; ostringstream os; int N = Size(namemap); for (int i = 0; i < N; ++i) { if (events & namemap[i].first) { os << "["; if (et_events & namemap[i].first) os << "^"; os << namemap[i].second << "]"; } } return os.str(); } void SrtCommon::Init(string host, int port, string path, map par, SRT_EPOLL_OPT dir) { m_direction = dir; InitParameters(host, path, par); int backlog = 1; if (m_mode == "listener" && par.count("groupconnect") && true_names.count(par["groupconnect"])) { backlog = 10; } Verb() << "Opening SRT " << DirectionName(dir) << " " << m_mode << "(" << (m_blocking_mode ? "" : "non-") << "blocking," << " backlog=" << backlog << ") on " << host << ":" << port; try { if (m_mode == "caller") { if (m_group_nodes.empty()) { OpenClient(host, port); } #if ENABLE_EXPERIMENTAL_BONDING else { OpenGroupClient(); // Source data are in the fields already. } #endif } else if (m_mode == "listener") OpenServer(m_adapter, port, backlog); else if (m_mode == "rendezvous") OpenRendezvous(m_adapter, host, port); else { throw std::invalid_argument("Invalid 'mode'. Use 'client' or 'server'"); } } catch (...) { // This is an in-constructor-called function, so // when the exception is thrown, the destructor won't // close the sockets. This intercepts the exception // to close them. Verb() << "Open FAILED - closing SRT sockets"; if (m_bindsock != SRT_INVALID_SOCK) srt_close(m_bindsock); if (m_sock != SRT_INVALID_SOCK) srt_close(m_sock); m_sock = m_bindsock = SRT_INVALID_SOCK; throw; } int pbkeylen = 0; SRT_KM_STATE kmstate, snd_kmstate, rcv_kmstate; int len = sizeof (int); srt_getsockflag(m_sock, SRTO_PBKEYLEN, &pbkeylen, &len); srt_getsockflag(m_sock, SRTO_KMSTATE, &kmstate, &len); srt_getsockflag(m_sock, SRTO_SNDKMSTATE, &snd_kmstate, &len); srt_getsockflag(m_sock, SRTO_RCVKMSTATE, &rcv_kmstate, &len); Verb() << "ENCRYPTION status: " << KmStateStr(kmstate) << " (SND:" << KmStateStr(snd_kmstate) << " RCV:" << KmStateStr(rcv_kmstate) << ") PBKEYLEN=" << pbkeylen; // Display some selected options on the socket. if (Verbose::on) { int64_t bandwidth = 0; int latency = 0; bool blocking_snd = false, blocking_rcv = false; int dropdelay = 0; int size_int = sizeof (int), size_int64 = sizeof (int64_t), size_bool = sizeof (bool); char packetfilter[100] = ""; int packetfilter_size = 100; srt_getsockflag(m_sock, SRTO_MAXBW, &bandwidth, &size_int64); srt_getsockflag(m_sock, SRTO_RCVLATENCY, &latency, &size_int); srt_getsockflag(m_sock, SRTO_RCVSYN, &blocking_rcv, &size_bool); srt_getsockflag(m_sock, SRTO_SNDSYN, &blocking_snd, &size_bool); srt_getsockflag(m_sock, SRTO_SNDDROPDELAY, &dropdelay, &size_int); srt_getsockflag(m_sock, SRTO_PACKETFILTER, (packetfilter), (&packetfilter_size)); Verb() << "OPTIONS: maxbw=" << bandwidth << " rcvlatency=" << latency << boolalpha << " blocking{rcv=" << blocking_rcv << " snd=" << blocking_snd << "} snddropdelay=" << dropdelay << " packetfilter=" << packetfilter; } if (!m_blocking_mode) { // Don't add new epoll if already created as a part // of group management: if (srt_epoll == -1)... if (m_mode == "caller") dir = (dir | SRT_EPOLL_UPDATE); Verb() << "NON-BLOCKING MODE - SUB FOR " << PrintEpollEvent(dir, 0); srt_epoll = AddPoller(m_sock, dir); } } int SrtCommon::AddPoller(SRTSOCKET socket, int modes) { int pollid = srt_epoll_create(); if (pollid == -1) throw std::runtime_error("Can't create epoll in nonblocking mode"); Verb() << "EPOLL: creating eid=" << pollid << " and adding @" << socket << " in " << DirectionName(SRT_EPOLL_OPT(modes)) << " mode"; srt_epoll_add_usock(pollid, socket, &modes); return pollid; } int SrtCommon::ConfigurePost(SRTSOCKET sock) { bool yes = m_blocking_mode; int result = 0; if (m_direction & SRT_EPOLL_OUT) { Verb() << "Setting SND blocking mode: " << boolalpha << yes << " timeout=" << m_timeout; result = srt_setsockopt(sock, 0, SRTO_SNDSYN, &yes, sizeof yes); if (result == -1) { #ifdef PLEASE_LOG extern srt_logging::Logger applog; applog.Error() << "ERROR SETTING OPTION: SRTO_SNDSYN"; #endif return result; } if (m_timeout) result = srt_setsockopt(sock, 0, SRTO_SNDTIMEO, &m_timeout, sizeof m_timeout); if (result == -1) { #ifdef PLEASE_LOG extern srt_logging::Logger applog; applog.Error() << "ERROR SETTING OPTION: SRTO_SNDTIMEO"; #endif return result; } } if (m_direction & SRT_EPOLL_IN) { Verb() << "Setting RCV blocking mode: " << boolalpha << yes << " timeout=" << m_timeout; 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); else { int timeout = 1000; result = srt_setsockopt(sock, 0, SRTO_RCVTIMEO, &timeout, sizeof timeout); } if (result == -1) return result; } // host is only checked for emptiness and depending on that the connection mode is selected. // Here we are not exactly interested with that information. vector failures; SrtConfigurePost(sock, m_options, &failures); if (!failures.empty()) { if (Verbose::on) { Verb() << "WARNING: failed to set options: "; copy(failures.begin(), failures.end(), ostream_iterator(*Verbose::cverb, ", ")); Verb(); } } return 0; } int SrtCommon::ConfigurePre(SRTSOCKET sock) { int result = 0; int no = 0; if (!m_tsbpdmode) { result = srt_setsockopt(sock, 0, SRTO_TSBPDMODE, &no, sizeof no); if (result == -1) return result; } // Let's pretend async mode is set this way. // This is for asynchronous connect. int maybe = m_blocking_mode; result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &maybe, sizeof maybe); if (result == -1) return result; // host is only checked for emptiness and depending on that the connection mode is selected. // Here we are not exactly interested with that information. vector failures; // NOTE: here host = "", so the 'connmode' will be returned as LISTENER always, // but it doesn't matter here. We don't use 'connmode' for anything else than // checking for failures. SocketOption::Mode conmode = SrtConfigurePre(sock, "", m_options, &failures); if (conmode == SocketOption::FAILURE) { if (Verbose::on ) { Verb() << "WARNING: failed to set options: "; copy(failures.begin(), failures.end(), ostream_iterator(*Verbose::cverb, ", ")); Verb(); } return SRT_ERROR; } return 0; } void SrtCommon::SetupAdapter(const string& host, int port) { auto lsa = CreateAddr(host, port); int stat = srt_bind(m_sock, lsa.get(), sizeof lsa); if (stat == SRT_ERROR) Error("srt_bind"); } void SrtCommon::OpenClient(string host, int port) { PrepareClient(); if (m_outgoing_port) { SetupAdapter("", m_outgoing_port); } ConnectClient(host, port); } void SrtCommon::PrepareClient() { m_sock = srt_create_socket(); if (m_sock == SRT_ERROR) Error("srt_create_socket"); int stat = ConfigurePre(m_sock); if (stat == SRT_ERROR) Error("ConfigurePre"); if (!m_blocking_mode) { srt_conn_epoll = AddPoller(m_sock, SRT_EPOLL_CONNECT | SRT_EPOLL_ERR); } } #if ENABLE_EXPERIMENTAL_BONDING void TransmitGroupSocketConnect(void* srtcommon, SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token) { SrtCommon* that = (SrtCommon*)srtcommon; if (error == SRT_SUCCESS) { return; // nothing to do for a successful socket } #ifdef PLEASE_LOG applog.Debug("connect callback: error on @", sock, " erc=", error, " token=", token); #endif /* Example: identify by target address sockaddr_any peersa = peer; sockaddr_any agentsa; bool haveso = (srt_getsockname(sock, agentsa.get(), &agentsa.len) != -1); */ for (auto& n: that->m_group_nodes) { if (n.token != -1 && n.token == token) { n.error = error; n.reason = srt_getrejectreason(sock); return; } /* bool isso = haveso && !n.source.empty(); if (n.target == peersa && (!isso || n.source.equal_address(agentsa))) { Verb() << " (by target)" << VerbNoEOL; n.error = error; n.reason = srt_getrejectreason(sock); return; } */ } Verb() << " IPE: LINK NOT FOUND???]"; } void SrtCommon::OpenGroupClient() { SRT_GROUP_TYPE type = SRT_GTYPE_UNDEFINED; // Resolve group type. if (m_group_type == "broadcast") type = SRT_GTYPE_BROADCAST; else if (m_group_type == "backup") type = SRT_GTYPE_BACKUP; else if (m_group_type == "balancing") type = SRT_GTYPE_BALANCING; else { Error("With //group, type='" + m_group_type + "' undefined"); } m_sock = srt_create_group(type); if (m_sock == -1) Error("srt_create_group"); srt_connect_callback(m_sock, &TransmitGroupSocketConnect, this); int stat = -1; if (m_group_config != "") { stat = srt_group_configure(m_sock, m_group_config.c_str()); if (stat == SRT_ERROR) Error("srt_group_configure"); } stat = ConfigurePre(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePre"); if (!m_blocking_mode) { // Note: here the GROUP is added to the poller. srt_conn_epoll = AddPoller(m_sock, SRT_EPOLL_CONNECT | SRT_EPOLL_ERR); } // Don't check this. Should this fail, the above would already. // XXX Now do it regardless whether it's blocking or non-blocking // mode - reading from group is currently manually from every socket. srt_epoll = srt_epoll_create(); // ConnectClient can't be used here, the code must // be more-less repeated. In this case the situation // that not all connections can be established is tolerated, // the only case of error is when none of the connections // can be established. bool any_node = false; Verb() << "REDUNDANT connections with " << m_group_nodes.size() << " nodes:"; if (m_group_data.empty()) m_group_data.resize(1); vector targets; int namelen = sizeof (sockaddr_any); Verb() << "Connecting to nodes:"; int i = 1; for (Connection& c: m_group_nodes) { auto sa = CreateAddr(c.host, c.port); c.target = sa; Verb() << "\t[" << c.token << "] " << c.host << ":" << c.port << VerbNoEOL; vector extras; if (c.weight) extras.push_back(Sprint("weight=", c.weight)); if (!c.source.empty()) extras.push_back("source=" + c.source.str()); if (!extras.empty()) { Verb() << "?" << extras[0] << VerbNoEOL; for (size_t i = 1; i < extras.size(); ++i) Verb() << "&" << extras[i] << VerbNoEOL; } Verb(); ++i; const sockaddr* source = c.source.empty() ? nullptr : c.source.get(); SRT_SOCKGROUPCONFIG gd = srt_prepare_endpoint(source, sa.get(), namelen); gd.weight = c.weight; gd.config = c.options; targets.push_back(gd); } ::transmit_throw_on_interrupt = true; for (;;) // REPEATABLE BLOCK { Connect_Again: Verb() << "Waiting for group connection... " << VerbNoEOL; int fisock = srt_connect_group(m_sock, targets.data(), targets.size()); if (fisock == SRT_ERROR) { // Complete the error information for every member ostringstream out; set reasons; for (Connection& c: m_group_nodes) { if (c.error != SRT_SUCCESS) { out << "[" << c.token << "] " << c.host << ":" << c.port; if (!c.source.empty()) out << "[[" << c.source.str() << "]]"; out << ": " << srt_strerror(c.error, 0) << ": " << srt_rejectreason_str(c.reason) << endl; } reasons.insert(c.reason); } if (transmit_retry_connect && (transmit_retry_always || (reasons.size() == 1 && *reasons.begin() == SRT_REJ_TIMEOUT))) { if (transmit_retry_connect != -1) --transmit_retry_connect; Verb() << "...all links timeout, retrying (" << transmit_retry_connect << ")..."; continue; } Error("srt_connect_group, nodes:\n" + out.str()); } else { Verb() << "[ASYNC] will wait..." << VerbNoEOL; } break; } if (m_blocking_mode) { Verb() << "SUCCESSFUL"; } else { Verb() << "INITIATED [ASYNC]"; } // Configuration change applied on a group should // spread the setting on all sockets. ConfigurePost(m_sock); for (size_t i = 0; i < targets.size(); ++i) { // As m_group_nodes is simply transformed into 'targets', // one index can be used to index them all. You don't // have to check if they have equal addresses because they // are equal by definition. if (targets[i].id != -1 && targets[i].errorcode == SRT_SUCCESS) { m_group_nodes[i].socket = targets[i].id; } } // Now check which sockets were successful, only those // should be added to epoll. size_t size = m_group_data.size(); stat = srt_group_data(m_sock, m_group_data.data(), &size); if (stat == -1 && size > m_group_data.size()) { // Just too small buffer. Resize and continue. m_group_data.resize(size); stat = srt_group_data(m_sock, m_group_data.data(), &size); } if (stat == -1) { Error("srt_group_data"); } m_group_data.resize(size); for (size_t i = 0; i < m_group_nodes.size(); ++i) { SRTSOCKET insock = m_group_nodes[i].socket; if (insock == -1) { Verb() << "TARGET '" << sockaddr_any(targets[i].peeraddr).str() << "' connection failed."; continue; } // Have socket, store it into the group socket array. any_node = true; } if (!any_node) Error("All connections failed"); // Wait for REAL connected state if nonblocking mode, for AT LEAST one node. if (!m_blocking_mode) { Verb() << "[ASYNC] " << VerbNoEOL; // SPIN-WAITING version. Don't use it unless you know what you're doing. // SpinWaitAsync(); // Socket readiness for connection is checked by polling on WRITE allowed sockets. int len1 = 2, len2 = 2; SRTSOCKET ready_conn[2], ready_err[2]; if (srt_epoll_wait(srt_conn_epoll, ready_err, &len2, ready_conn, &len1, -1, // Wait infinitely NULL, NULL, NULL, NULL) != -1) { Verb() << "[C]" << VerbNoEOL; for (int i = 0; i < len1; ++i) Verb() << " " << ready_conn[i] << VerbNoEOL; Verb() << "[E]" << VerbNoEOL; for (int i = 0; i < len2; ++i) Verb() << " " << ready_err[i] << VerbNoEOL; Verb() << ""; // We are waiting for one entity to be ready so it's either // in one or the other if (find(ready_err, ready_err+len2, m_sock) != ready_err+len2) { Verb() << "[EPOLL: " << len2 << " entities FAILED]"; // Complete the error information for every member ostringstream out; set reasons; for (Connection& c: m_group_nodes) { if (c.error != SRT_SUCCESS) { out << "[" << c.token << "] " << c.host << ":" << c.port; if (!c.source.empty()) out << "[[" << c.source.str() << "]]"; out << ": " << srt_strerror(c.error, 0) << ": " << srt_rejectreason_str(c.reason) << endl; } reasons.insert(c.reason); } if (transmit_retry_connect && (transmit_retry_always || (reasons.size() == 1 && *reasons.begin() == SRT_REJ_TIMEOUT))) { if (transmit_retry_connect != -1) --transmit_retry_connect; Verb() << "...all links timeout, retrying NOW (" << transmit_retry_connect << ")..."; goto Connect_Again; } Error("srt_connect_group, nodes:\n" + out.str()); } else if (find(ready_conn, ready_conn+len1, m_sock) != ready_conn+len1) { Verb() << "[EPOLL: " << len1 << " entities] " << VerbNoEOL; } else { Error("Group: SPURIOUS epoll readiness"); } } else { Error("srt_epoll_wait"); } } stat = ConfigurePost(m_sock); if (stat == -1) { // This kind of error must reject the whole operation. // Usually you'll get this error on the first socket, // and doing this on the others would result in the same. Error("ConfigurePost"); } ::transmit_throw_on_interrupt = false; Verb() << "Group connection report:"; for (auto& d: m_group_data) { // id, status, result, peeraddr Verb() << "@" << d.id << " <" << SockStatusStr(d.sockstate) << "> (=" << d.result << ") PEER:" << sockaddr_any((sockaddr*)&d.peeraddr, sizeof d.peeraddr).str(); } // Prepare group data for monitoring the group status. m_group_data.resize(m_group_nodes.size()); } #endif /* This may be used sometimes for testing, but it's nonportable. void SrtCommon::SpinWaitAsync() { static string udt_status_names [] = { "INIT" , "OPENED", "LISTENING", "CONNECTING", "CONNECTED", "BROKEN", "CLOSING", "CLOSED", "NONEXIST" }; for (;;) { SRT_SOCKSTATUS state = srt_getsockstate(m_sock); if (int(state) < SRTS_CONNECTED) { if (Verbose::on) Verb() << state; usleep(250000); continue; } else if (int(state) > SRTS_CONNECTED) { Error("UDT::connect status=" + udt_status_names[state]); } return; } } */ struct TransmitErrorReason { int error; int reason; }; static std::map transmit_error_storage; static void TransmitConnectCallback(void*, SRTSOCKET socket, int errorcode, const sockaddr* /*peer*/, int /*token*/) { int reason = srt_getrejectreason(socket); transmit_error_storage[socket] = TransmitErrorReason { errorcode, reason }; Verb() << "[Connection error reported on @" << socket << "]"; } void SrtCommon::ConnectClient(string host, int port) { auto sa = CreateAddr(host, port); Verb() << "Connecting to " << host << ":" << port << " ... " << VerbNoEOL; if (!m_blocking_mode) { srt_connect_callback(m_sock, &TransmitConnectCallback, 0); } int stat = -1; for (;;) { ::transmit_throw_on_interrupt = true; stat = srt_connect(m_sock, sa.get(), sizeof sa); ::transmit_throw_on_interrupt = false; if (stat == SRT_ERROR) { int reason = srt_getrejectreason(m_sock); #if PLEASE_LOG LOGP(applog.Error, "ERROR reported by srt_connect - closing socket @", m_sock); #endif if (transmit_retry_connect && (transmit_retry_always || reason == SRT_REJ_TIMEOUT)) { if (transmit_retry_connect != -1) --transmit_retry_connect; Verb() << "...timeout, retrying (" << transmit_retry_connect << ")..."; continue; } srt_close(m_sock); Error("srt_connect", reason); } break; } // Wait for REAL connected state if nonblocking mode if (!m_blocking_mode) { Verb() << "[ASYNC] " << VerbNoEOL; // SPIN-WAITING version. Don't use it unless you know what you're doing. // SpinWaitAsync(); // Socket readiness for connection is checked by polling on WRITE allowed sockets. int lenc = 2, lene = 2; SRTSOCKET ready_connect[2], ready_error[2]; if (srt_epoll_wait(srt_conn_epoll, ready_error, &lene, ready_connect, &lenc, -1, 0, 0, 0, 0) != -1) { // We should have just one socket, so check whatever socket // is in the transmit_error_storage. if (!transmit_error_storage.empty()) { Verb() << "[CALLBACK(error): " << VerbNoEOL; int error, reason; bool failed = false; for (pair& e: transmit_error_storage) { Verb() << "{@" << e.first << " error=" << e.second.error << " reason=" << e.second.reason << "} " << VerbNoEOL; error = e.second.error; reason = e.second.reason; if (error != SRT_SUCCESS) failed = true; } Verb() << "]"; transmit_error_storage.clear(); if (failed) Error("srt_connect(async/cb)", reason, error); } if (lene > 0) { Verb() << "[EPOLL(error): " << lene << " sockets]"; int reason = srt_getrejectreason(ready_error[0]); Error("srt_connect(async)", reason, SRT_ECONNREJ); } Verb() << "[EPOLL: " << lenc << " sockets] " << VerbNoEOL; } else { transmit_error_storage.clear(); Error("srt_epoll_wait(srt_conn_epoll)"); } transmit_error_storage.clear(); } Verb() << " connected."; stat = ConfigurePost(m_sock); if (stat == SRT_ERROR) Error("ConfigurePost"); } void SrtCommon::Error(string src, int reason, int force_result) { int errnov = 0; const int result = force_result == 0 ? srt_getlasterror(&errnov) : force_result; if (result == SRT_SUCCESS) { cerr << "\nERROR (app): " << src << endl; throw std::runtime_error(src); } string message = srt_strerror(result, errnov); if (result == SRT_ECONNREJ) { if ( Verbose::on ) Verb() << "FAILURE\n" << src << ": [" << result << "] " << "Connection rejected: [" << int(reason) << "]: " << srt_rejectreason_str(reason); else cerr << "\nERROR #" << result << ": Connection rejected: [" << int(reason) << "]: " << srt_rejectreason_str(reason); } else { if ( Verbose::on ) Verb() << "FAILURE\n" << src << ": [" << result << "." << errnov << "] " << message; else cerr << "\nERROR #" << result << "." << errnov << ": " << message << endl; } throw TransmissionError("error: " + src + ": " + message); } void SrtCommon::SetupRendezvous(string adapter, string host, int port) { sockaddr_any target = CreateAddr(host, port); if (target.family() == AF_UNSPEC) { Error("Unable to resolve target host: " + host); } bool yes = true; srt_setsockopt(m_sock, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); const int outport = m_outgoing_port ? m_outgoing_port : port; // Prefer the same IPv as target host auto localsa = CreateAddr(adapter, outport, target.family()); string showhost = adapter; if (showhost == "") showhost = "ANY"; if (target.family() == AF_INET6) showhost = "[" + showhost + "]"; Verb() << "Binding rendezvous: " << showhost << ":" << outport << " ..."; int stat = srt_bind(m_sock, localsa.get(), localsa.size()); if (stat == SRT_ERROR) { srt_close(m_sock); Error("srt_bind"); } } void SrtCommon::Close() { #if PLEASE_LOG extern srt_logging::Logger applog; LOGP(applog.Error, "CLOSE requested - closing socket @", m_sock); #endif bool any = false; bool yes = true; if (m_sock != SRT_INVALID_SOCK) { Verb() << "SrtCommon: DESTROYING CONNECTION, closing socket (rt%" << m_sock << ")..."; srt_setsockflag(m_sock, SRTO_SNDSYN, &yes, sizeof yes); srt_close(m_sock); any = true; } if (m_bindsock != SRT_INVALID_SOCK) { Verb() << "SrtCommon: DESTROYING SERVER, closing socket (ls%" << m_bindsock << ")..."; // Set sndsynchro to the socket to synch-close it. srt_setsockflag(m_bindsock, SRTO_SNDSYN, &yes, sizeof yes); srt_close(m_bindsock); any = true; } if (any) Verb() << "SrtCommon: ... done."; } SrtCommon::~SrtCommon() { Close(); } #if ENABLE_EXPERIMENTAL_BONDING void SrtCommon::UpdateGroupStatus(const SRT_SOCKGROUPDATA* grpdata, size_t grpdata_size) { if (!grpdata) { // This happens when you passed too small array. Treat this as error and stop. cerr << "ERROR: broadcast group update reports " << grpdata_size << " existing sockets, but app registerred only " << m_group_nodes.size() << endl; Error("Too many unpredicted sockets in the group"); } // Clear the active flag in all nodes so that they are reactivated // if they are in the group list, REGARDLESS OF THE STATUS. We need to // see all connections that are in the nodes, but not in the group, // and this one would have to be activated. const SRT_SOCKGROUPDATA* gend = grpdata + grpdata_size; for (auto& n: m_group_nodes) { bool active = (find_if(grpdata, gend, [&n] (const SRT_SOCKGROUPDATA& sg) { return sg.id == n.socket; }) != gend); if (!active) n.socket = SRT_INVALID_SOCK; } // Note: sockets are not necessarily in the same order. Find // the socket by id. for (size_t i = 0; i < grpdata_size; ++i) { const SRT_SOCKGROUPDATA& d = grpdata[i]; SRTSOCKET id = d.id; SRT_SOCKSTATUS status = d.sockstate; int result = d.result; SRT_MEMBERSTATUS mstatus = d.memberstate; if (result != -1 && status == SRTS_CONNECTED) { // Short report with the state. Verb() << "G@" << id << "<" << MemberStatusStr(mstatus) << "> " << VerbNoEOL; continue; } // id, status, result, peeraddr Verb() << "\n\tG@" << id << " <" << SockStatusStr(status) << "/" << MemberStatusStr(mstatus) << "> (=" << result << ") PEER:" << sockaddr_any((sockaddr*)&d.peeraddr, sizeof d.peeraddr).str() << VerbNoEOL; if (status >= SRTS_BROKEN) { Verb() << "NOTE: socket @" << id << " is pending for destruction, waiting for it."; } } // This was only informative. Now we check all nodes if they // are not active int i = 1; for (auto& n: m_group_nodes) { if (n.error != SRT_SUCCESS) { Verb() << "[" << i << "] CONNECTION FAILURE to '" << n.host << ":" << n.port << "': " << srt_strerror(n.error, 0) << ":" << srt_rejectreason_str(n.reason); } // Check which nodes are no longer active and activate them. if (n.socket != SRT_INVALID_SOCK) continue; auto sa = CreateAddr(n.host, n.port); Verb() << "[" << i << "] RECONNECTING to node " << n.host << ":" << n.port << " ... " << VerbNoEOL; ++i; n.error = SRT_SUCCESS; n.reason = SRT_REJ_UNKNOWN; const sockaddr* source = n.source.empty() ? nullptr : n.source.get(); SRT_SOCKGROUPCONFIG gd = srt_prepare_endpoint(source, sa.get(), sa.size()); gd.weight = n.weight; gd.config = n.options; gd.token = n.token; int fisock = srt_connect_group(m_sock, &gd, 1); if (fisock == SRT_ERROR) { // Whatever. Skip the node. Verb() << "FAILED: "; } else { // Have socket, store it into the group socket array. n.socket = gd.id; } } } #endif SrtSource::SrtSource(string host, int port, std::string path, const map& par) { Init(host, port, path, par, SRT_EPOLL_IN); ostringstream os; os << host << ":" << port; hostport_copy = os.str(); } static void PrintSrtStats(SRTSOCKET sock, bool clr, bool bw, bool stats) { CBytePerfMon perf; // clear only if stats report is to be read srt_bstats(sock, &perf, clr); if (bw) cout << transmit_stats_writer->WriteBandwidth(perf.mbpsBandwidth); if (stats) cout << transmit_stats_writer->WriteStats(sock, perf); } #ifdef SRT_OLD_APP_READER // NOTE: 'output' is expected to be EMPTY here. bool SrtSource::GroupCheckPacketAhead(bytevector& output) { bool status = false; vector past_ahead; // This map no longer maps only ahead links. // Here are all links, and whether ahead, it's defined by the sequence. for (auto i = m_group_positions.begin(); i != m_group_positions.end(); ++i) { // i->first: socket ID // i->second: ReadPos { sequence, packet } // We are not interested with the socket ID because we // aren't going to read from it - we have the packet already. ReadPos& a = i->second; int seqdiff = CSeqNo::seqcmp(a.sequence, m_group_seqno); if ( seqdiff == 1) { // The very next packet. Return it. m_group_seqno = a.sequence; Verb() << " (SRT group: ahead delivery %" << a.sequence << " from @" << i->first << ")"; swap(output, a.packet); status = true; } else if (seqdiff < 1 && !a.packet.empty()) { Verb() << " (@" << i->first << " dropping collected ahead %" << a.sequence << ")"; a.packet.clear(); } // In case when it's >1, keep it in ahead } return status; } static string DisplayEpollResults(const std::set& sockset, std::string prefix) { typedef set fset_t; ostringstream os; os << prefix << " "; for (fset_t::const_iterator i = sockset.begin(); i != sockset.end(); ++i) { os << "@" << *i << " "; } return os.str(); } bytevector SrtSource::GroupRead(size_t chunk) { // Read the current group status. m_sock is here the group id. bytevector output; // Later iteration over it might be less efficient than // by vector, but we'll also often try to check a single id // if it was ever seen broken, so that it's skipped. set broken; RETRY_READING: size_t size = m_group_data.size(); int stat = srt_group_data(m_sock, m_group_data.data(), &size); if (stat == -1 && size > m_group_data.size()) { // Just too small buffer. Resize and continue. m_group_data.resize(size); stat = srt_group_data(m_sock, m_group_data.data(), &size); } else { // Downsize if needed. m_group_data.resize(size); } if (stat == -1) // Also after the above fix { Error(UDT::getlasterror(), "FAILURE when reading group data"); } if (size == 0) { Error("No sockets in the group - disconnected"); } bool connected = false; for (auto& d: m_group_data) { if (d.status == SRTS_CONNECTED) { connected = true; break; } } if (!connected) { Error("All sockets in the group disconnected"); } if (Verbose::on) { for (auto& d: m_group_data) { if (d.status != SRTS_CONNECTED) // id, status, result, peeraddr Verb() << "@" << d.id << " <" << SockStatusStr(d.status) << "> (=" << d.result << ") PEER:" << sockaddr_any((sockaddr*)&d.peeraddr, sizeof d.peeraddr).str(); } } // Check first the ahead packets if you have any to deliver. if (m_group_seqno != -1 && !m_group_positions.empty()) { bytevector ahead_packet; // This function also updates the group sequence pointer. if (GroupCheckPacketAhead(ahead_packet)) return move(ahead_packet); } // LINK QUALIFICATION NAMES: // // HORSE: Correct link, which delivers the very next sequence. // Not necessarily this link is currently active. // // KANGAROO: Got some packets dropped and the sequence number // of the packet jumps over the very next sequence and delivers // an ahead packet. // // ELEPHANT: Is not ready to read, while others are, or reading // up to the current latest delivery sequence number does not // reach this sequence and the link becomes non-readable earlier. // The above condition has ruled out one kangaroo and turned it // into a horse. // Below there's a loop that will try to extract packets. Kangaroos // will be among the polled ones because skipping them risks that // the elephants will take over the reading. Links already known as // elephants will be also polled in an attempt to revitalize the // connection that experienced just a short living choking. // // After polling we attempt to read from every link that reported // read-readiness and read at most up to the sequence equal to the // current delivery sequence. // Links that deliver a packet below that sequence will be retried // until they deliver no more packets or deliver the packet of // expected sequence. Links that don't have a record in m_group_positions // and report readiness will be always read, at least to know what // sequence they currently stand on. // // Links that are already known as kangaroos will be polled, but // no reading attempt will be done. If after the reading series // it will turn out that we have no more horses, the slowest kangaroo // will be "advanced to a horse" (the ahead link with a sequence // closest to the current delivery sequence will get its sequence // set as current delivered and its recorded ahead packet returned // as the read packet). // If we find at least one horse, the packet read from that link // will be delivered. All other link will be just ensured update // up to this sequence number, or at worst all available packets // will be read. In this case all kangaroos remain kangaroos, // until the current delivery sequence m_group_seqno will be lifted // to the sequence recorded for these links in m_group_positions, // during the next time ahead check, after which they will become // horses. Verb() << "E(" << srt_epoll << ") " << VerbNoEOL; for (size_t i = 0; i < size; ++i) { SRT_SOCKGROUPDATA& d = m_group_data[i]; if (d.status == SRTS_CONNECTING) { Verb() << "@" << d.id << " " << VerbNoEOL; int modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; srt_epoll_add_usock(srt_epoll, d.id, &modes); continue; // don't read over a failed or pending socket } if (d.status >= SRTS_BROKEN) { broken.insert(d.id); } if (broken.count(d.id)) { Verb() << "@" << d.id << " " << VerbNoEOL; continue; } if (d.status != SRTS_CONNECTED) { Verb() << "@" << d.id << " " << VerbNoEOL; // Sockets in this state are ignored. We are waiting until it // achieves CONNECTING state, then it's added to write. continue; } // Don't skip packets that are ahead because if we have a situation // that all links are either "elephants" (do not report read readiness) // and "kangaroos" (have already delivered an ahead packet) then // omiting kangaroos will result in only elephants to be polled for // reading. Elephants, due to the strict timing requirements and // ensurance that TSBPD on every link will result in exactly the same // delivery time for a packet of given sequence, having an elephant // and kangaroo in one cage means that the elephant is simply a broken // or half-broken link (the data are not delivered, but it will get // repaired soon, enough for SRT to maintain the connection, but it // will still drop packets that didn't arrive in time), in both cases // it may potentially block the reading for an indefinite time, while // simultaneously a kangaroo might be a link that got some packets // dropped, but then it's still capable to deliver packets on time. // Note also that about the fact that some links turn out to be // elephants we'll learn only after we try to poll and read them. // Note that d.id might be a socket that was previously being polled // on write, when it's attempting to connect, but now it's connected. // This will update the socket with the new event set. int modes = SRT_EPOLL_IN | SRT_EPOLL_ERR; srt_epoll_add_usock(srt_epoll, d.id, &modes); Verb() << "@" << d.id << "[READ] " << VerbNoEOL; } Verb() << ""; // Here we need to make an additional check. // There might be a possibility that all sockets that // were added to the reader group, are ahead. At least // surely we don't have a situation that any link contains // an ahead-read subsequent packet, because GroupCheckPacketAhead // already handled that case. // // What we can have is that every link has: // - no known seq position yet (is not registered in the position map yet) // - the position equal to the latest delivered sequence // - the ahead position // Now the situation is that we don't have any packets // waiting for delivery so we need to wait for any to report one. // XXX We support blocking mode only at the moment. // The non-blocking mode would need to simply check the readiness // with only immediate report, and read-readiness would have to // be done in background. SrtPollState sready; // Poll on this descriptor until reading is available, indefinitely. if (UDT::epoll_swait(srt_epoll, sready, -1) == SRT_ERROR) { Error(UDT::getlasterror(), "UDT::epoll_swait(srt_epoll, group)"); } if (Verbose::on) { Verb() << "RDY: {" << DisplayEpollResults(sready.rd(), "[R]") << DisplayEpollResults(sready.wr(), "[W]") << DisplayEpollResults(sready.ex(), "[E]") << "} " << VerbNoEOL; } LOGC(applog.Debug, log << "epoll_swait: " << DisplayEpollResults(sready.rd(), "[R]") << DisplayEpollResults(sready.wr(), "[W]") << DisplayEpollResults(sready.ex(), "[E]")); typedef set fset_t; // Handle sockets of pending connection and with errors. broken = sready.ex(); // We don't do anything about sockets that have been configured to // poll on writing (that is, pending for connection). What we need // is that the epoll_swait call exit on that fact. Probably if this // was the only socket reported, no broken and no read-ready, this // will later check on output if still empty, if so, repeat the whole // function. This write-ready socket will be there already in the // connected state and will be added to read-polling. // Ok, now we need to have some extra qualifications: // 1. If a socket has no registry yet, we read anyway, just // to notify the current position. We read ONLY ONE PACKET this time, // we'll worry later about adjusting it to the current group sequence // position. // 2. If a socket is already position ahead, DO NOT read from it, even // if it is ready. // The state of things whether we were able to extract the very next // sequence will be simply defined by the fact that `output` is nonempty. int32_t next_seq = m_group_seqno; // If this set is empty, it won't roll even once, therefore output // will be surely empty. This will be checked then same way as when // reading from every socket resulted in error. for (fset_t::const_iterator i = sready.rd().begin(); i != sready.rd().end(); ++i) { // Check if this socket is in aheads // If so, don't read from it, wait until the ahead is flushed. SRTSOCKET id = *i; ReadPos* p = nullptr; auto pe = m_group_positions.find(id); if (pe != m_group_positions.end()) { p = &pe->second; // Possible results of comparison: // x < 0: the sequence is in the past, the socket should be adjusted FIRST // x = 0: the socket should be ready to get the exactly next packet // x = 1: the case is already handled by GroupCheckPacketAhead. // x > 1: AHEAD. DO NOT READ. int seqdiff = CSeqNo::seqcmp(p->sequence, m_group_seqno); if (seqdiff > 1) { Verb() << "EPOLL: @" << id << " %" << p->sequence << " AHEAD, not reading."; continue; } } // Read from this socket stubbornly, until: // - reading is no longer possible (AGAIN) // - the sequence difference is >= 1 int fi = 1; // marker for Verb to display flushing for (;;) { bytevector data(chunk); SRT_MSGCTRL mctrl = srt_msgctrl_default; stat = srt_recvmsg2(id, data.data(), chunk, &mctrl); if (stat == SRT_ERROR) { if (fi == 0) { if (Verbose::on) { if (p) { int32_t pktseq = p->sequence; int seqdiff = CSeqNo::seqcmp(p->sequence, m_group_seqno); Verb() << ". %" << pktseq << " " << seqdiff << ")"; } else { Verb() << ".)"; } } fi = 1; } int err = srt_getlasterror(0); if (err == SRT_EASYNCRCV) { // Do not treat this as spurious, just stop reading. break; } Verb() << "Error @" << id << ": " << srt_getlasterror_str(); broken.insert(id); break; } // NOTE: checks against m_group_seqno and decisions based on it // must NOT be done if m_group_seqno is -1, which means that we // are about to deliver the very first packet and we take its // sequence number as a good deal. // The order must be: // - check discrepancy // - record the sequence // - check ordering. // The second one must be done always, but failed discrepancy // check should exclude the socket from any further checks. // That's why the common check for m_group_seqno != -1 can't // embrace everything below. // We need to first qualify the sequence, just for a case if (m_group_seqno != -1 && abs(m_group_seqno - mctrl.pktseq) > CSeqNo::m_iSeqNoTH) { // This error should be returned if the link turns out // to be the only one, or set to the group data. // err = SRT_ESECFAIL; if (fi == 0) { Verb() << ".)"; fi = 1; } Verb() << "Error @" << id << ": SEQUENCE DISCREPANCY: base=%" << m_group_seqno << " vs pkt=%" << mctrl.pktseq << ", setting ESECFAIL"; broken.insert(id); break; } // Rewrite it to the state for a case when next reading // would not succeed. Do not insert the buffer here because // this is only required when the sequence is ahead; for that // it will be fixed later. if (!p) { p = &(m_group_positions[id] = ReadPos { mctrl.pktseq, {} }); } else { p->sequence = mctrl.pktseq; } if (m_group_seqno != -1) { // Now we can safely check it. int seqdiff = CSeqNo::seqcmp(mctrl.pktseq, m_group_seqno); if (seqdiff <= 0) { if (fi == 1) { Verb() << "(@" << id << " FLUSH:" << VerbNoEOL; fi = 0; } Verb() << "." << VerbNoEOL; // The sequence is recorded, the packet has to be discarded. // That's all. continue; } // Finish flush reporting if fallen into here if (fi == 0) { Verb() << ". %" << mctrl.pktseq << " " << (-seqdiff) << ")"; fi = 1; } // Now we have only two possibilities: // seqdiff == 1: The very next sequence, we want to read and return the packet. // seqdiff > 1: The packet is ahead - record the ahead packet, but continue with the others. if (seqdiff > 1) { Verb() << "@" << id << " %" << mctrl.pktseq << " AHEAD"; p->packet = move(data); break; // Don't read from that socket anymore. } } // We have seqdiff = 1, or we simply have the very first packet // which's sequence is taken as a good deal. Update the sequence // and record output. if (!output.empty()) { Verb() << "@" << id << " %" << mctrl.pktseq << " REDUNDANT"; break; } Verb() << "@" << id << " %" << mctrl.pktseq << " DELIVERING"; output = move(data); // Record, but do not update yet, until all sockets are handled. next_seq = mctrl.pktseq; break; } } // ready_len is only the length of currently reported // ready sockets, NOT NECESSARILY containing all sockets from the group. if (broken.size() == size) { // All broken Error("All sockets broken"); } if (Verbose::on && !broken.empty()) { Verb() << "BROKEN: " << Printable(broken) << " - removing"; } // Now remove all broken sockets from aheads, if any. // Even if they have already delivered a packet. for (SRTSOCKET d: broken) { m_group_positions.erase(d); srt_close(d); } // May be required to be re-read. broken.clear(); if (!output.empty()) { // We have extracted something, meaning that we have the sequence shift. // Update it now and don't do anything else with the sockets. // Sanity check if (next_seq == -1) { Error("IPE: next_seq not set after output extracted!"); } m_group_seqno = next_seq; return output; } // Check if we have any sockets left :D // Here we surely don't have any more HORSES, // only ELEPHANTS and KANGAROOS. Qualify them and // attempt to at least take advantage of KANGAROOS. // In this position all links are either: // - updated to the current position // - updated to the newest possible possition available // - not yet ready for extraction (not present in the group) // If we haven't extracted the very next sequence position, // it means that we might only have the ahead packets read, // that is, the next sequence has been dropped by all links. if (!m_group_positions.empty()) { // This might notify both lingering links, which didn't // deliver the required sequence yet, and links that have // the sequence ahead. Review them, and if you find at // least one packet behind, just wait for it to be ready. // Use again the waiting function because we don't want // the general waiting procedure to skip others. set elephants; // const because it's `typename decltype(m_group_positions)::value_type` pair* slowest_kangaroo = nullptr; for (auto& sock_rp: m_group_positions) { // NOTE that m_group_seqno in this place wasn't updated // because we haven't successfully extracted anything. int seqdiff = CSeqNo::seqcmp(sock_rp.second.sequence, m_group_seqno); if (seqdiff < 0) { elephants.insert(sock_rp.first); } // If seqdiff == 0, we have a socket ON TRACK. else if (seqdiff > 0) { if (!slowest_kangaroo) { slowest_kangaroo = &sock_rp; } else { // Update to find the slowest kangaroo. int seqdiff = CSeqNo::seqcmp(slowest_kangaroo->second.sequence, sock_rp.second.sequence); if (seqdiff > 0) { slowest_kangaroo = &sock_rp; } } } } // Note that if no "slowest_kangaroo" was found, it means // that we don't have kangaroos. if (slowest_kangaroo) { // We have a slowest kangaroo. Elephants must be ignored. // Best case, they will get revived, worst case they will be // soon broken. // // As we already have the packet delivered by the slowest // kangaroo, we can simply return it. m_group_seqno = slowest_kangaroo->second.sequence; Verb() << "@" << slowest_kangaroo->first << " %" << m_group_seqno << " KANGAROO->HORSE"; swap(output, slowest_kangaroo->second.packet); return output; } // Here ALL LINKS ARE ELEPHANTS, stating that we still have any. if (Verbose::on) { if (!elephants.empty()) { // If we don't have kangaroos, then simply reattempt to // poll all elephants again anyway (at worst they are all // broken and we'll learn about it soon). Verb() << "ALL LINKS ELEPHANTS. Re-polling."; } else { Verb() << "ONLY BROKEN WERE REPORTED. Re-polling."; } } goto RETRY_READING; } // We have checked so far only links that were ready to poll. // Links that are not ready should be re-checked. // Links that were not ready at the entrance should be checked // separately, and probably here is the best moment to do it. // After we make sure that at least one link is ready, we can // reattempt to read a packet from it. // Ok, so first collect all sockets that are in // connecting state, make a poll for connection. srt_epoll_clear_usocks(srt_epoll); bool have_connectors = false, have_ready = false; for (auto& d: m_group_data) { if (d.status < SRTS_CONNECTED) { // Not sure anymore if IN or OUT signals the connect-readiness, // but no matter. The signal will be cleared once it is used, // while it will be always on when there's anything ready to read. int modes = SRT_EPOLL_IN | SRT_EPOLL_OUT; srt_epoll_add_usock(srt_epoll, d.id, &modes); have_connectors = true; } else if (d.status == SRTS_CONNECTED) { have_ready = true; } } if (have_ready || have_connectors) { Verb() << "(still have: " << (have_ready ? "+" : "-") << "ready, " << (have_connectors ? "+" : "-") << "conenctors)."; goto RETRY_READING; } if (have_ready) { Verb() << "(connected in the meantime)"; // Some have connected in the meantime, don't // waste time on the pending ones. goto RETRY_READING; } if (have_connectors) { Verb() << "(waiting for pending connectors to connect)"; // Wait here for them to be connected. vector sready; sready.resize(m_group_data.size()); int ready_len = m_group_data.size(); if (srt_epoll_wait(srt_epoll, sready.data(), &ready_len, 0, 0, -1, 0, 0, 0, 0) == SRT_ERROR) { Error("All sockets in the group disconnected"); } goto RETRY_READING; } Error("No data extracted"); return output; // Just a marker - this above function throws an exception } #endif MediaPacket SrtSource::Read(size_t chunk) { static size_t counter = 1; bool have_group SRT_ATR_UNUSED = !m_group_nodes.empty(); bytevector data(chunk); // EXPERIMENTAL #ifdef SRT_OLD_APP_READER if (have_group || m_listener_group) { data = GroupRead(chunk); } if (have_group) { // This is to be done for caller mode only UpdateGroupStatus(m_group_data.data(), m_group_data.size()); } #else SRT_MSGCTRL mctrl = srt_msgctrl_default; bool ready = true; int stat; do { #if ENABLE_EXPERIMENTAL_BONDING if (have_group || m_listener_group) { mctrl.grpdata = m_group_data.data(); mctrl.grpdata_size = m_group_data.size(); } #endif if (::transmit_int_state) Error("srt_recvmsg2: interrupted"); ::transmit_throw_on_interrupt = true; stat = srt_recvmsg2(m_sock, data.data(), chunk, &mctrl); ::transmit_throw_on_interrupt = false; if (stat != SRT_ERROR) { ready = true; } else { int syserr = 0; int err = srt_getlasterror(&syserr); if (!m_blocking_mode) { // EAGAIN for SRT READING if (err == SRT_EASYNCRCV) { Epoll_again: Verb() << "AGAIN: - waiting for data by epoll(" << srt_epoll << ")..."; // Poll on this descriptor until reading is available, indefinitely. int len = 2; SRT_EPOLL_EVENT sready[2]; len = srt_epoll_uwait(srt_epoll, sready, len, -1); if (len != -1) { Verb() << "... epoll reported ready " << len << " sockets"; // If the event was SRT_EPOLL_UPDATE, report it, and still wait. bool any_read_ready = false; vector errored; for (int i = 0; i < len; ++i) { if (sready[i].events & SRT_EPOLL_UPDATE) { Verb() << "... [BROKEN CONNECTION reported on @" << sready[i].fd << "]"; } if (sready[i].events & SRT_EPOLL_IN) any_read_ready = true; if (sready[i].events & SRT_EPOLL_ERR) { errored.push_back(sready[i].fd); } } if (!any_read_ready) { Verb() << " ... [NOT READ READY - AGAIN (" << errored.size() << " errored: " << Printable(errored) << ")]"; goto Epoll_again; } continue; } // If was -1, then passthru. } } else { // In blocking mode it uses a minimum of 1s timeout, // and continues only if interrupt not requested. if (!::transmit_int_state && (err == SRT_EASYNCRCV || err == SRT_ETIMEOUT)) { ready = false; continue; } } Error("srt_recvmsg2"); } if (stat == 0) { throw ReadEOF(hostport_copy); } #if PLEASE_LOG extern srt_logging::Logger applog; LOGC(applog.Debug, log << "recv: #" << mctrl.msgno << " %" << mctrl.pktseq << " " << BufferStamp(data.data(), stat) << " BELATED: " << ((CTimer::getTime()-mctrl.srctime)/1000.0) << "ms"); #endif Verb() << "(#" << mctrl.msgno << " %" << mctrl.pktseq << " " << BufferStamp(data.data(), stat) << ") " << VerbNoEOL; } while (!ready); chunk = size_t(stat); if (chunk < data.size()) data.resize(chunk); const bool need_bw_report = transmit_bw_report && int(counter % transmit_bw_report) == transmit_bw_report - 1; const bool need_stats_report = transmit_stats_report && counter % transmit_stats_report == transmit_stats_report - 1; #if ENABLE_EXPERIMENTAL_BONDING if (have_group) // Means, group with caller mode { UpdateGroupStatus(mctrl.grpdata, mctrl.grpdata_size); if (transmit_stats_writer && (need_stats_report || need_bw_report)) { PrintSrtStats(m_sock, need_stats_report, need_bw_report, need_stats_report); for (size_t i = 0; i < mctrl.grpdata_size; ++i) PrintSrtStats(mctrl.grpdata[i].id, need_stats_report, need_bw_report, need_stats_report); } } else #endif { if (transmit_stats_writer && (need_stats_report || need_bw_report)) { PrintSrtStats(m_sock, need_stats_report, need_bw_report, need_stats_report); } } #endif ++counter; return MediaPacket(data, mctrl.srctime); } SrtTarget::SrtTarget(std::string host, int port, std::string path, const std::map& par) { Init(host, port, path, par, SRT_EPOLL_OUT); } int SrtTarget::ConfigurePre(SRTSOCKET sock) { int result = SrtCommon::ConfigurePre(sock); if (result == -1) return result; int yes = 1; // This is for the HSv4 compatibility; if both parties are HSv5 // (min. version 1.2.1), then this setting simply does nothing. // In HSv4 this setting is obligatory; otherwise the SRT handshake // extension will not be done at all. result = srt_setsockopt(sock, 0, SRTO_SENDER, &yes, sizeof yes); if (result == -1) return result; return 0; } void SrtTarget::Write(const MediaPacket& data) { static int counter = 1; ::transmit_throw_on_interrupt = true; // Check first if it's ready to write. // If not, wait indefinitely. if (!m_blocking_mode) { Epoll_again: int len = 2; SRT_EPOLL_EVENT sready[2]; len = srt_epoll_uwait(srt_epoll, sready, len, -1); if (len != -1) { bool any_write_ready = false; for (int i = 0; i < len; ++i) { if (sready[i].events & SRT_EPOLL_UPDATE) { Verb() << "... [BROKEN CONNECTION reported on @" << sready[i].fd << "]"; } if (sready[i].events & SRT_EPOLL_OUT) any_write_ready = true; } if (!any_write_ready) { Verb() << " ... [NOT WRITE READY - AGAIN]"; goto Epoll_again; } // Pass on, write ready. } else { Error("srt_epoll_uwait"); } } SRT_MSGCTRL mctrl = srt_msgctrl_default; #if ENABLE_EXPERIMENTAL_BONDING bool have_group = !m_group_nodes.empty(); if (have_group || m_listener_group) { mctrl.grpdata = m_group_data.data(); mctrl.grpdata_size = m_group_data.size(); } #endif if (transmit_use_sourcetime) { mctrl.srctime = data.time; } int stat = srt_sendmsg2(m_sock, data.payload.data(), data.payload.size(), &mctrl); // For a socket group, the error is reported only // if ALL links from the group have failed to perform // the operation. If only one did, the result will be // visible in the status array. if (stat == SRT_ERROR) Error("srt_sendmsg"); ::transmit_throw_on_interrupt = false; const bool need_bw_report = transmit_bw_report && int(counter % transmit_bw_report) == transmit_bw_report - 1; const bool need_stats_report = transmit_stats_report && counter % transmit_stats_report == transmit_stats_report - 1; #if ENABLE_EXPERIMENTAL_BONDING if (have_group) { // For listener group this is not necessary. The group information // is updated in mctrl. UpdateGroupStatus(mctrl.grpdata, mctrl.grpdata_size); if (transmit_stats_writer && (need_stats_report || need_bw_report)) { PrintSrtStats(m_sock, need_stats_report, need_bw_report, need_stats_report); for (size_t i = 0; i < mctrl.grpdata_size; ++i) PrintSrtStats(mctrl.grpdata[i].id, need_stats_report, need_bw_report, need_stats_report); } } else #endif { if (transmit_stats_writer && (need_stats_report || need_bw_report)) { PrintSrtStats(m_sock, need_stats_report, need_bw_report, need_stats_report); } } Verb() << "(#" << mctrl.msgno << " %" << mctrl.pktseq << " " << BufferStamp(data.payload.data(), data.payload.size()) << ") " << VerbNoEOL; ++counter; } SrtRelay::SrtRelay(std::string host, int port, std::string path, const std::map& par) { Init(host, port, path, par, SRT_EPOLL_IN | SRT_EPOLL_OUT); } SrtModel::SrtModel(string host, int port, map par) { InitParameters(host, "", par); if (m_mode == "caller") is_caller = true; else if (m_mode == "rendezvous") is_rend = true; else if (m_mode != "listener") throw std::invalid_argument("Wrong 'mode' attribute; expected: caller, listener, rendezvous"); m_host = host; m_port = port; } void SrtModel::Establish(std::string& w_name) { // This does connect or accept. // When this returned true, the caller should create // a new SrtSource or SrtTaget then call StealFrom(*this) on it. // If this is a connector and the peer doesn't have a corresponding // medium, it should send back a single byte with value 0. This means // that agent should stop connecting. if (is_rend) { OpenRendezvous(m_adapter, m_host, m_port); } else if (is_caller) { // Establish a connection PrepareClient(); if (w_name != "") { Verb() << "Connect with requesting stream [" << w_name << "]"; srt::setstreamid(m_sock, w_name); } else { Verb() << "NO STREAM ID for SRT connection"; } if (m_outgoing_port) { Verb() << "Setting outgoing port: " << m_outgoing_port; SetupAdapter("", m_outgoing_port); } ConnectClient(m_host, m_port); if (m_outgoing_port == 0) { // Must rely on a randomly selected one. Extract the port // so that it will be reused next time. sockaddr_any s(AF_INET); int namelen = s.size(); if (srt_getsockname(Socket(), (s.get()), (&namelen)) == SRT_ERROR) { Error("srt_getsockname"); } m_outgoing_port = s.hport(); Verb() << "Extracted outgoing port: " << m_outgoing_port; } } else { // Listener - get a socket by accepting. // Check if the listener is already created first if (Listener() == SRT_INVALID_SOCK) { Verb() << "Setting up listener: port=" << m_port << " backlog=5"; PrepareListener(m_adapter, m_port, 5); } Verb() << "Accepting a client..."; AcceptNewClient(); // This rewrites m_sock with a new SRT socket ("accepted" socket) w_name = UDT::getstreamid(m_sock); Verb() << "... GOT CLIENT for stream [" << w_name << "]"; } } template struct Srt; template <> struct Srt { typedef SrtSource type; }; template <> struct Srt { typedef SrtTarget type; }; template <> struct Srt { typedef SrtRelay type; }; template Iface* CreateSrt(const string& host, int port, std::string path, const map& par) { return new typename Srt::type (host, port, path, par); } MediaPacket ConsoleRead(size_t chunk) { bytevector data(chunk); bool st = cin.read(data.data(), chunk).good(); chunk = cin.gcount(); if (chunk == 0 && !st) return bytevector(); int64_t stime = 0; if (transmit_use_sourcetime) stime = srt_time_now(); if (chunk < data.size()) data.resize(chunk); if (data.empty()) throw Source::ReadEOF("CONSOLE device"); return MediaPacket(data, stime); } class ConsoleSource: public virtual Source { public: ConsoleSource() { } MediaPacket Read(size_t chunk) override { return ConsoleRead(chunk); } bool IsOpen() override { return cin.good(); } bool End() override { return cin.eof(); } }; class ConsoleTarget: public virtual Target { public: ConsoleTarget() { } void Write(const MediaPacket& data) override { cout.write(data.payload.data(), data.payload.size()); } bool IsOpen() override { return cout.good(); } bool Broken() override { return cout.eof(); } }; class ConsoleRelay: public Relay, public ConsoleSource, public ConsoleTarget { public: ConsoleRelay() = default; bool IsOpen() override { return cin.good() && cout.good(); } }; template struct Console; template <> struct Console { typedef ConsoleSource type; }; template <> struct Console { typedef ConsoleTarget type; }; template <> struct Console { typedef ConsoleRelay type; }; template Iface* CreateConsole() { return new typename Console::type (); } // More options can be added in future. SocketOption udp_options [] { { "iptos", IPPROTO_IP, IP_TOS, SocketOption::PRE, SocketOption::INT, nullptr }, // IP_TTL and IP_MULTICAST_TTL are handled separately by a common option, "ttl". { "mcloop", IPPROTO_IP, IP_MULTICAST_LOOP, SocketOption::PRE, SocketOption::INT, nullptr } }; static inline bool IsMulticast(in_addr adr) { unsigned char* abytes = (unsigned char*)&adr.s_addr; unsigned char c = abytes[0]; return c >= 224 && c <= 239; } void UdpCommon::Setup(string host, int port, map attr) { m_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (m_sock == -1) Error(SysError(), "UdpCommon::Setup: socket"); int yes = 1; ::setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof yes); sadr = CreateAddr(host, port); bool is_multicast = false; if (sadr.family() == AF_INET) { if (attr.count("multicast")) { if (!IsMulticast(sadr.sin.sin_addr)) { throw std::runtime_error("UdpCommon: requested multicast for a non-multicast-type IP address"); } is_multicast = true; } else if (IsMulticast(sadr.sin.sin_addr)) { is_multicast = true; } if (is_multicast) { ip_mreq_source mreq_ssm; ip_mreq mreq; sockaddr_any maddr; int opt_name; void* mreq_arg_ptr; socklen_t mreq_arg_size; adapter = attr.count("adapter") ? attr.at("adapter") : string(); if (adapter == "") { Verb() << "Multicast: home address: INADDR_ANY:" << port; maddr.sin.sin_family = AF_INET; maddr.sin.sin_addr.s_addr = htonl(INADDR_ANY); maddr.sin.sin_port = htons(port); // necessary for temporary use } else { Verb() << "Multicast: home address: " << adapter << ":" << port; maddr = CreateAddr(adapter, port); } if (attr.count("source")) { /* this is an ssm. we need to use the right struct and opt */ opt_name = IP_ADD_SOURCE_MEMBERSHIP; mreq_ssm.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; mreq_ssm.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; inet_pton(AF_INET, attr.at("source").c_str(), &mreq_ssm.imr_sourceaddr); mreq_arg_size = sizeof(mreq_ssm); mreq_arg_ptr = &mreq_ssm; } else { opt_name = IP_ADD_MEMBERSHIP; mreq.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; mreq.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; mreq_arg_size = sizeof(mreq); mreq_arg_ptr = &mreq; } #ifdef _WIN32 const char* mreq_arg = (const char*)mreq_arg_ptr; const auto status_error = SOCKET_ERROR; #else const void* mreq_arg = mreq_arg_ptr; const auto status_error = -1; #endif #if defined(_WIN32) || defined(__CYGWIN__) // On Windows it somehow doesn't work when bind() // is called with multicast address. Write the address // that designates the network device here. // Also, sets port sharing when working with multicast sadr = maddr; int reuse = 1; int shareAddrRes = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuse), sizeof(reuse)); if (shareAddrRes == status_error) { throw runtime_error("marking socket for shared use failed"); } Verb() << "Multicast(Windows): will bind to home address"; #else Verb() << "Multicast(POSIX): will bind to IGMP address: " << host; #endif int res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg, mreq_arg_size); if (res == status_error) { Error(errno, "adding to multicast membership failed"); } attr.erase("multicast"); attr.erase("adapter"); } } // The "ttl" options is handled separately, it maps to both IP_TTL // and IP_MULTICAST_TTL so that TTL setting works for both uni- and multicast. if (attr.count("ttl")) { int ttl = stoi(attr.at("ttl")); int res = setsockopt(m_sock, IPPROTO_IP, IP_TTL, (const char*)&ttl, sizeof ttl); if (res == -1) Verb() << "WARNING: failed to set 'ttl' (IP_TTL) to " << ttl; res = setsockopt(m_sock, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof ttl); if (res == -1) Verb() << "WARNING: failed to set 'ttl' (IP_MULTICAST_TTL) to " << ttl; attr.erase("ttl"); } 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 (!ok) Verb() << "WARNING: failed to set '" << o.name << "' to " << value; } } } void UdpCommon::Error(int err, string src) { char buf[512]; string message = SysStrError(err, buf, 512u); if (Verbose::on) Verb() << "FAILURE\n" << src << ": [" << err << "] " << message; else cerr << "\nERROR #" << err << ": " << message << endl; throw TransmissionError("error: " + src + ": " + message); } UdpCommon::~UdpCommon() { #ifdef _WIN32 if (m_sock != -1) { shutdown(m_sock, SD_BOTH); closesocket(m_sock); m_sock = -1; } #else close(m_sock); #endif } UdpSource::UdpSource(string host, int port, const map& attr) { Setup(host, port, attr); int stat = ::bind(m_sock, sadr.get(), sadr.size()); if (stat == -1) Error(SysError(), "Binding address for UDP"); eof = false; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; if (::setsockopt(m_sock, SOL_SOCKET, SO_RCVTIMEO, (const char*) &tv, sizeof(tv)) < 0) Error(SysError(), "Setting timeout for UDP"); } MediaPacket UdpSource::Read(size_t chunk) { bytevector data(chunk); sockaddr_any sa(sadr.family()); int64_t srctime = 0; AGAIN: int stat = recvfrom(m_sock, data.data(), (int) chunk, 0, sa.get(), &sa.syslen()); int err = SysError(); if (transmit_use_sourcetime) { srctime = srt_time_now(); } if (stat == -1) { if (!::transmit_int_state && err == SysAGAIN) goto AGAIN; Error(SysError(), "UDP Read/recvfrom"); } if (stat < 1) { eof = true; return bytevector(); } chunk = size_t(stat); if (chunk < data.size()) data.resize(chunk); return MediaPacket(data, srctime); } UdpTarget::UdpTarget(string host, int port, const map& attr) { Setup(host, port, attr); if (adapter != "") { auto maddr = CreateAddr(adapter, 0); in_addr addr = maddr.sin.sin_addr; int res = setsockopt(m_sock, IPPROTO_IP, IP_MULTICAST_IF, reinterpret_cast(&addr), sizeof(addr)); if (res == -1) { Error(SysError(), "setsockopt/IP_MULTICAST_IF: " + adapter); } } } void UdpTarget::Write(const MediaPacket& data) { int stat = sendto(m_sock, data.payload.data(), data.payload.size(), 0, (sockaddr*)&sadr, sizeof sadr); if (stat == -1) Error(SysError(), "UDP Write/sendto"); } template struct Udp; template <> struct Udp { typedef UdpSource type; }; template <> struct Udp { typedef UdpTarget type; }; template <> struct Udp { typedef UdpRelay type; }; template Iface* CreateUdp(const string& host, int port, const map& par) { return new typename Udp::type (host, port, par); } template inline bool IsOutput() { return false; } template<> inline bool IsOutput() { return true; } template extern unique_ptr CreateMedium(const string& uri) { unique_ptr ptr; UriParser u(uri); int iport = 0; switch ( u.type() ) { default: break; // do nothing, return nullptr case UriParser::FILE: if (u.host() == "con" || u.host() == "console") { if ( IsOutput() && ( (Verbose::on && Verbose::cverb == &cout) || transmit_bw_report || transmit_stats_report) ) { cerr << "ERROR: file://con with -v or -r or -s 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: ptr.reset( CreateSrt(u.host(), u.portno(), u.path(), 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; } if (ptr) ptr->uri = move(u); return ptr; } std::unique_ptr Source::Create(const std::string& url) { return CreateMedium(url); } std::unique_ptr Target::Create(const std::string& url) { return CreateMedium(url); } srt-1.4.4/testing/testmedia.hpp000066400000000000000000000235441412557703600165170ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_COMMON_TRANSMITMEDIA_HPP #define INC_SRT_COMMON_TRANSMITMEDIA_HPP #include #include #include #include #include #include "apputil.hpp" #include "testmediabase.hpp" #include // Needs access to CUDTException #include extern srt_listen_callback_fn* transmit_accept_hook_fn; extern void* transmit_accept_hook_op; extern std::atomic transmit_int_state; extern std::shared_ptr transmit_stats_writer; using namespace std; const srt_logging::LogFA SRT_LOGFA_APP = 10; extern srt_logging::Logger applog; // Trial version of an exception. Try to implement later an official // interruption mechanism in SRT using this. struct TransmissionError: public std::runtime_error { TransmissionError(const std::string& arg): std::runtime_error(arg) { } }; class SrtCommon { int srt_conn_epoll = -1; void SpinWaitAsync(); protected: friend void TransmitGroupSocketConnect(void* srtcommon, SRTSOCKET sock, int error, const sockaddr* peer, int token); struct ConnectionBase { string host; int port; int weight = 0; SRTSOCKET socket = SRT_INVALID_SOCK; sockaddr_any source; sockaddr_any target; int token = -1; ConnectionBase(string h, int p): host(h), port(p), source(AF_INET) {} }; struct Connection: ConnectionBase { #if ENABLE_EXPERIMENTAL_BONDING SRT_SOCKOPT_CONFIG* options = nullptr; #endif int error = SRT_SUCCESS; int reason = SRT_REJ_UNKNOWN; Connection(string h, int p): ConnectionBase(h, p) {} Connection(Connection&& old): ConnectionBase(old) { #if ENABLE_EXPERIMENTAL_BONDING if (old.options) { options = old.options; old.options = nullptr; } #endif } ~Connection() { #if ENABLE_EXPERIMENTAL_BONDING srt_delete_config(options); #endif } }; int srt_epoll = -1; SRT_EPOLL_T m_direction = SRT_EPOLL_OPT_NONE; //< 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_direction int m_timeout = 0; //< enforces using SRTO_SNDTIMEO or SRTO_RCVTIMEO, depending on @a m_direction bool m_tsbpdmode = true; int m_outgoing_port = 0; string m_mode; string m_adapter; map m_options; // All other options, as provided in the URI vector m_group_nodes; string m_group_type; string m_group_config; #if ENABLE_EXPERIMENTAL_BONDING vector m_group_data; #ifdef SRT_OLD_APP_READER int32_t m_group_seqno = -1; struct ReadPos { int32_t sequence; bytevector packet; }; map m_group_positions; SRTSOCKET m_group_active; // The link from which the last packet was delivered #endif #endif SRTSOCKET m_sock = SRT_INVALID_SOCK; SRTSOCKET m_bindsock = SRT_INVALID_SOCK; bool m_listener_group = false; 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 UpdateGroupStatus(const SRT_SOCKGROUPDATA* grpdata, size_t grpdata_size); public: void InitParameters(string host, string path, map par); void PrepareListener(string host, int port, int backlog); void StealFrom(SrtCommon& src); void AcceptNewClient(); SRTSOCKET Socket() const { return m_sock; } SRTSOCKET Listener() const { return m_bindsock; } void Acquire(SRTSOCKET s) { m_sock = s; if (s & SRTGROUP_MASK) m_listener_group = true; } virtual void Close(); protected: void Error(string src, int reason = SRT_REJ_UNKNOWN, int force_result = 0); void Init(string host, int port, string path, map par, SRT_EPOLL_OPT dir); int AddPoller(SRTSOCKET socket, int modes); virtual int ConfigurePost(SRTSOCKET sock); virtual int ConfigurePre(SRTSOCKET sock); void OpenClient(string host, int port); #if ENABLE_EXPERIMENTAL_BONDING void OpenGroupClient(); #endif void PrepareClient(); void SetupAdapter(const std::string& host, int port); void ConnectClient(string host, int port); void SetupRendezvous(string adapter, string host, int port); void OpenServer(string host, int port, int backlog = 1) { PrepareListener(host, port, backlog); if (transmit_accept_hook_fn) { srt_listen_callback(m_bindsock, transmit_accept_hook_fn, transmit_accept_hook_op); } AcceptNewClient(); } void OpenRendezvous(string adapter, string host, int port) { PrepareClient(); SetupRendezvous(adapter, host, port); ConnectClient(host, port); } virtual ~SrtCommon(); }; class SrtSource: public virtual Source, public virtual SrtCommon { std::string hostport_copy; public: SrtSource(std::string host, int port, std::string path, const std::map& par); SrtSource() { // Do nothing - create just to prepare for use } MediaPacket Read(size_t chunk) override; bytevector GroupRead(size_t chunk); bool GroupCheckPacketAhead(bytevector& output); /* In this form this isn't needed. Unblock if any extra settings have to be made. virtual int ConfigurePre(UDTSOCKET sock) override { int result = SrtCommon::ConfigurePre(sock); if ( result == -1 ) return result; return 0; } */ bool IsOpen() override { return IsUsable(); } bool End() override { return IsBroken(); } void Close() override { return SrtCommon::Close(); } }; class SrtTarget: public virtual Target, public virtual SrtCommon { public: SrtTarget(std::string host, int port, std::string path, const std::map& par); SrtTarget() {} int ConfigurePre(SRTSOCKET sock) override; void Write(const MediaPacket& data) override; bool IsOpen() override { return IsUsable(); } bool Broken() override { return IsBroken(); } void Close() override { return SrtCommon::Close(); } size_t Still() override { size_t bytes; int st = srt_getsndbuffer(m_sock, nullptr, &bytes); if (st == -1) return 0; return bytes; } }; class SrtRelay: public Relay, public SrtSource, public SrtTarget { public: SrtRelay(std::string host, int port, std::string path, const std::map& par); SrtRelay() {} int ConfigurePre(SRTSOCKET sock) override { // This overrides the change introduced in SrtTarget, // which sets the SRTO_SENDER flag. For a bidirectional transmission // this flag should not be set, as the connection should be checked // for being 1.3.0 clients only. return SrtCommon::ConfigurePre(sock); } // Override separately overridden methods by SrtSource and SrtTarget bool IsOpen() override { return IsUsable(); } void Close() override { return SrtCommon::Close(); } }; // This class is used when we don't know yet whether the given URI // designates an effective listener or caller. So we create it, initialize, // then we know what mode we'll be using. // // When caller, then we will do connect() using this object, then clone out // a new object - of a direction specific class - which will steal the socket // from this one and then roll the data. After this, this object is ready // to connect again, and will create its own socket for that occasion, and // the whole procedure repeats. // // When listener, then this object will be doing accept() and with every // successful acceptation it will clone out a new object - of a direction // specific class - which will steal just the connection socket from this // object. This object will still live on and accept new connections and // so on. class SrtModel: public SrtCommon { public: bool is_caller = false; bool is_rend = false; string m_host; int m_port = 0; SrtModel(string host, int port, map par); void Establish(std::string& w_name); void Close() { if (m_sock != SRT_INVALID_SOCK) { srt_close(m_sock); m_sock = SRT_INVALID_SOCK; } } }; class UdpCommon { protected: int m_sock = -1; sockaddr_any sadr; std::string adapter; std::map m_options; void Setup(std::string host, int port, std::map attr); void Error(int err, std::string src); ~UdpCommon(); }; class UdpSource: public virtual Source, public virtual UdpCommon { bool eof = true; public: UdpSource(string host, int port, const map& attr); MediaPacket Read(size_t chunk) override; bool IsOpen() override { return m_sock != -1; } bool End() override { return eof; } }; class UdpTarget: public virtual Target, public virtual UdpCommon { public: UdpTarget(string host, int port, const map& attr); void Write(const MediaPacket& data) override; bool IsOpen() override { return m_sock != -1; } bool Broken() override { return false; } }; class UdpRelay: public Relay, public UdpSource, public UdpTarget { public: UdpRelay(string host, int port, const map& attr): UdpSource(host, port, attr), UdpTarget(host, port, attr) { } bool IsOpen() override { return m_sock != -1; } }; #endif srt-1.4.4/testing/testmediabase.hpp000066400000000000000000000043101412557703600173400ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #ifndef INC_SRT_COMMON_TRANMITBASE_HPP #define INC_SRT_COMMON_TRANMITBASE_HPP #include #include #include #include #include #include #include "uriparser.hpp" typedef std::vector bytevector; extern std::atomic transmit_throw_on_interrupt; extern int transmit_bw_report; extern unsigned transmit_stats_report; extern size_t transmit_chunk_size; extern bool transmit_printformat_json; extern bool transmit_use_sourcetime; extern int transmit_retry_connect; extern bool transmit_retry_always; struct MediaPacket { bytevector payload; int64_t time = 0; MediaPacket(bytevector&& src): payload(std::move(src)) {} MediaPacket(bytevector&& src, int64_t stime): payload(std::move(src)), time(stime) {} MediaPacket(const bytevector& src): payload(src) {} MediaPacket(const bytevector& src, int64_t stime): payload(src), time(stime) {} MediaPacket() {} }; class Location { public: UriParser uri; Location() {} virtual bool IsOpen() = 0; virtual void Close() {} }; class Source: public virtual Location { public: virtual MediaPacket Read(size_t chunk) = 0; virtual bool End() = 0; static std::unique_ptr Create(const std::string& url); virtual ~Source() {} class ReadEOF: public std::runtime_error { public: ReadEOF(const std::string& fn): std::runtime_error( "EOF while reading file: " + fn ) { } }; }; class Target: public virtual Location { public: virtual void Write(const MediaPacket& portion) = 0; virtual bool Broken() = 0; virtual size_t Still() { return 0; } static std::unique_ptr Create(const std::string& url); virtual ~Target() {} }; class Relay: public virtual Source, public virtual Target, public virtual Location { public: static std::unique_ptr Create(const std::string& url); virtual ~Relay() {} }; #endif srt-1.4.4/testing/uriparser-test.maf000066400000000000000000000000371412557703600174750ustar00rootroot00000000000000 SOURCES ../apps/uriparser.cpp srt-1.4.4/testing/utility-test.cpp000066400000000000000000000042331412557703600172050ustar00rootroot00000000000000/* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include #include #include #include #include #include #include #include void ShowDistance(int32_t s1, int32_t s2) { using namespace std; cout << "s1=" << s1 << "s2=" << s2 << " DISTANCE:\n"; cout << "seqcmp -> " << CSeqNo::seqcmp(s1, s2) << endl; cout << "seqoff -> " << CSeqNo::seqoff(s2, s1) << endl; } int main() { using namespace std; cout << "PacketBoundary: " << hex << MSGNO_PACKET_BOUNDARY::mask << endl; cout << "PB_FIRST: " << hex << PacketBoundaryBits(PB_FIRST) << endl; cout << "PB_LAST: " << hex << PacketBoundaryBits(PB_LAST) << endl; cout << "PB_SOLO: " << hex << PacketBoundaryBits(PB_SOLO) << endl; 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; cout << "Creating array of bytes: 10, 11, 20, 25 - FormatBinaryString: "; uint8_t array[4] = { 10, 11, 20, 25 }; cout << FormatBinaryString(array, 4) << endl; cout << "-------------------------------\n"; cout << "SEQUENCES:\n"; int32_t s1 = 100, s2 = 200; ShowDistance(s1, s2); cout << "GO BACK BY -150:\n"; s1 = CSeqNo::decseq(s1, 150); s2 = CSeqNo::decseq(s2, 150); ShowDistance(s1, s2); return 0; } srt-1.4.4/testing/utility-test.maf000066400000000000000000000000321412557703600171570ustar00rootroot00000000000000 SOURCES utility-test.cpp