diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d7cf3d882e..d865ceb261 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -6,6 +6,7 @@ labels: ["bug", "needs-triage"] assignees: [] body: - type: checkboxes + id: confirm attributes: label: Please confirm you have already done the following options: @@ -13,6 +14,14 @@ body: - label: I have all the details the issue requires validations: required: true + - type: checkboxes + id: prompt + attributes: + label: Please answer the following prompt + options: + - label: This issue is replicable using the unmodified sample application + validations: + required: false - type: textarea id: description attributes: @@ -37,6 +46,9 @@ body: Please include full errors, uncaught exceptions, stack traces, and relevant VERBOSE logs. To get relevant VERBOSE logs from the SDK, you can retrieve by running `export AWS_KVS_LOG_LEVEL=1` + + If you are reporting a memory leak, please provide sufficient evidence such as a `valgrind` output. + Note that the CI for this repository uses [Google Sanitizers](https://github.com/google/sanitizers) to minimize software issues and vulnerabilities. validations: required: true - type: textarea @@ -44,7 +56,10 @@ body: attributes: label: Reproduction Steps description: | - Provide a self-contained, concise snippet of code that can be used to reproduce the issue. + Provide a self-contained, concise snippet of code that can be used to + reproduce the issue, or the command used to run the unmodified sample application. Please share + which SDK you are using as master and as viewer, and also any timing-related information, if + applicable. For more complex issues provide a repo with the smallest sample that reproduces the bug. Avoid including business logic or unrelated code, it makes diagnosis more difficult. validations: @@ -55,6 +70,12 @@ body: label: WebRTC C SDK version being used validations: required: true + - type: input + id: sdk-previous-version + attributes: + label: If it was working in a previous version, which one? + validations: + required: false - type: input id: compiler-version attributes: diff --git a/.github/ISSUE_TEMPLATE/questions-help.md b/.github/ISSUE_TEMPLATE/questions-help.md deleted file mode 100644 index 83d1501847..0000000000 --- a/.github/ISSUE_TEMPLATE/questions-help.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Questions/Help -about: Describe this issue template's purpose here. -title: "[QUESTION]" -labels: question,needs-triage -assignees: '' - ---- - -A one liner description about the use case and what you are trying to achieve - -** Logging ** -Add relevent SDK logging. IMPORTANT NOTE: Please make sure to NOT share AWS access credentials under any circumstance! Please make sure they are not in the logs. - -** Any design considerations/constraints ** -Explain in detail how you would like to integrate our SDK into your solution - -** If you would not like to open an issue to discuss your solution in open-platform, please email your question to kinesis-video-support@amazon.com ** diff --git a/.github/build_windows.bat b/.github/build_windows.bat deleted file mode 100644 index 66fc5f2093..0000000000 --- a/.github/build_windows.bat +++ /dev/null @@ -1,6 +0,0 @@ -call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat" amd64 -mkdir build -cd build -cmd.exe /c cmake -G "NMake Makefiles" .. -cmake -G "NMake Makefiles" -DBUILD_TEST=TRUE .. -nmake diff --git a/.github/build_windows_openssl.bat b/.github/build_windows_openssl.bat index 3fb65e786d..f067cc0e43 100644 --- a/.github/build_windows_openssl.bat +++ b/.github/build_windows_openssl.bat @@ -2,5 +2,5 @@ call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Buil mkdir build cd build cmd.exe /c cmake -G "NMake Makefiles" .. -cmake -G "NMake Makefiles" -DBUILD_TEST=TRUE -DENABLE_AWS_SDK_IN_TESTS=OFF -DEXT_PTHREAD_INCLUDE_DIR="C:/tools/pthreads-w32-2-9-1-release/Pre-built.2/include/" -DEXT_PTHREAD_LIBRARIES="C:/tools/pthreads-w32-2-9-1-release/Pre-built.2/lib/x64/libpthreadGC2.a" .. +cmake -G "NMake Makefiles" -DBUILD_TEST=TRUE -DENABLE_AWS_SDK_IN_TESTS=OFF -DPKG_CONFIG_EXECUTABLE="D:\\gstreamer\\1.0\\x86_64\\bin\\pkg-config.exe" -DEXT_PTHREAD_INCLUDE_DIR="C:/tools/pthreads-w32-2-9-1-release/Pre-built.2/include/" -DEXT_PTHREAD_LIBRARIES="C:/tools/pthreads-w32-2-9-1-release/Pre-built.2/lib/x64/libpthreadGC2.a" .. nmake \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 280659f4c7..0f4380f213 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: - master jobs: clang-format-check: - runs-on: macos-11 + runs-on: macos-latest steps: - name: Clone repository uses: actions/checkout@v3 @@ -74,6 +74,31 @@ jobs: run: | cd build ./tst/webrtc_client_test + mac-os-m1-build-clang: + runs-on: macos-13-xlarge + env: + AWS_KVS_LOG_LEVEL: 2 + permissions: + id-token: write + contents: read + steps: + - name: Clone repository + uses: actions/checkout@v3 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: ${{ secrets.AWS_REGION }} + - name: Build repository + run: | + brew unlink openssl + mkdir build && cd build + sh -c 'cmake .. -DBUILD_TEST=TRUE -DCMAKE_C_COMPILER=$(brew --prefix llvm@15)/bin/clang -DCMAKE_CXX_COMPILER=$(brew --prefix llvm@15)/bin/clang++' + make + - name: Run tests + run: | + cd build + ./tst/webrtc_client_test static-build-mac: runs-on: macos-11 env: @@ -137,7 +162,7 @@ jobs: undefined-behavior-sanitizer: runs-on: ubuntu-20.04 env: - UBSAN_OPTIONS: halt_on_error=1 + UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 CC: clang CXX: clang++ AWS_KVS_LOG_LEVEL: 2 @@ -356,6 +381,39 @@ jobs: run: | cd build timeout --signal=SIGABRT 60m ./tst/webrtc_client_test + + mbedtls-ubuntu-gcc-4_4-build: + runs-on: ubuntu-20.04 + env: + AWS_KVS_LOG_LEVEL: 2 + CC: gcc-4.4 + permissions: + id-token: write + contents: read + steps: + - name: Clone repository + uses: actions/checkout@v3 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: ${{ secrets.AWS_REGION }} + - name: Install deps + run: | + sudo apt clean && sudo apt update + sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu/ trusty main' + sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu/ trusty universe' + sudo apt-get -q update + sudo apt-get -y install gcc-4.4 + sudo apt-get -y install gdb + - name: Build repository + run: | + mkdir build && cd build + cmake .. -DUSE_OPENSSL=OFF -DUSE_MBEDTLS=ON + make + mbedtls-ubuntu-clang: runs-on: ubuntu-20.04 env: @@ -391,7 +449,7 @@ jobs: timeout --signal=SIGABRT 60m ./tst/webrtc_client_test sample-check: if: github.repository == 'awslabs/amazon-kinesis-video-streams-webrtc-sdk-c' - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest env: AWS_KVS_LOG_LEVEL: 2 permissions: @@ -399,19 +457,47 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} + role-duration-seconds: 10800 - name: Build repository run: | sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' mkdir build && cd build cmake .. make - cd .. + - name: Sample check + run: | + ./scripts/check-sample.sh + sample-check-no-data-channel: + if: github.repository == 'awslabs/amazon-kinesis-video-streams-webrtc-sdk-c' + runs-on: ubuntu-latest + env: + AWS_KVS_LOG_LEVEL: 2 + permissions: + id-token: write + contents: read + steps: + - name: Clone repository + uses: actions/checkout@v4 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: ${{ secrets.AWS_REGION }} + role-duration-seconds: 10800 + - name: Build repository + run: | + sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' + mkdir build && cd build + cmake .. -DENABLE_DATA_CHANNEL=OFF + make + - name: Sample check without data channel + run: | ./scripts/check-sample.sh ubuntu-os-build: runs-on: ubuntu-20.04 @@ -493,8 +579,8 @@ jobs: - name: Run tests shell: powershell run: | - $env:Path += ';C:\webrtc\open-source\bin;C:\tools\pthreads-w32-2-9-1-release\Pre-built.2\dll\x64' - & "C:\webrtc\build\tst\webrtc_client_test.exe" --gtest_filter="-DataChannelFunctionalityTest.*:IceApiTest.*:IceFunctionalityTest.*:PeerConnectionFunctionalityTest.*:SignalingApiFunctionalityTest.*:TurnConnectionFunctionalityTest.*:RtpFunctionalityTest.marshallUnmarshallH264Data:RtpFunctionalityTest.packingUnpackingVerifySameH264Frame:RtcpFunctionalityTest.onRtcpPacketCompound:RtcpFunctionalityTest.twcc3" + $env:Path += ';C:\webrtc\open-source\bin;C:\tools\pthreads-w32-2-9-1-release\Pre-built.2\dll\x64;C:\webrtc\build' + & "C:\webrtc\build\tst\webrtc_client_test.exe" --gtest_filter="-SignalingApiFunctionalityTest.receivingIceConfigOffer_SlowClockSkew:SignalingApiFunctionalityTest.iceServerConfigRefreshConnectedAuthExpiration:SignalingApiFunctionalityTest.receivingIceConfigOffer_FastClockSkew:SignalingApiFunctionalityTest.receivingIceConfigOffer_FastClockSkew_VerifyOffsetRemovedWhenClockFixed:DataChannelFunctionalityTest.*:DtlsApiTest.*:IceApiTest.*:IceFunctionalityTest.*:PeerConnectionFunctionalityTest.*:TurnConnectionFunctionalityTest.*:RtpFunctionalityTest.marshallUnmarshallH264Data:RtpFunctionalityTest.packingUnpackingVerifySameH264Frame:RtcpFunctionalityTest.onRtcpPacketCompound:RtcpFunctionalityTest.twcc3" # windows-msvc-mbedtls: # runs-on: windows-2022 # env: @@ -576,4 +662,4 @@ jobs: sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' mkdir build && cd build cmake .. -DBUILD_OPENSSL=TRUE -DBUILD_OPENSSL_PLATFORM=linux-generic32 -DBUILD_LIBSRTP_HOST_PLATFORM=x86_64-unknown-linux-gnu -DBUILD_LIBSRTP_DESTINATION_PLATFORM=arm-unknown-linux-uclibcgnueabi - make \ No newline at end of file + make diff --git a/CMake/Dependencies/libawscpp-CMakeLists.txt b/CMake/Dependencies/libawscpp-CMakeLists.txt index 062e4110a8..60f6bee6f8 100644 --- a/CMake/Dependencies/libawscpp-CMakeLists.txt +++ b/CMake/Dependencies/libawscpp-CMakeLists.txt @@ -4,11 +4,11 @@ include(ExternalProject) ExternalProject_Add(libawscpp-download GIT_REPOSITORY https://github.com/aws/aws-sdk-cpp.git - GIT_TAG 1.11.157 + GIT_TAG 1.11.217 LIST_SEPARATOR "|" CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DBUILD_ONLY=kinesisvideo|kinesis-video-webrtc-storage -DCMAKE_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX} BUILD_ALWAYS TRUE TEST_COMMAND "" -) \ No newline at end of file +) diff --git a/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt b/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt index 08ae3908db..c8cfa179a8 100644 --- a/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt +++ b/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt @@ -6,7 +6,7 @@ include(ExternalProject) ExternalProject_Add(libkvsCommonLws-download GIT_REPOSITORY https://github.com/awslabs/amazon-kinesis-video-streams-producer-c.git - GIT_TAG 178109a5dbfc5288ba5cf7fab1dc1afd5e2e182b + GIT_TAG v1.5.2 PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX} diff --git a/CMake/Dependencies/libmbedtls-CMakeLists.txt b/CMake/Dependencies/libmbedtls-CMakeLists.txt index aad9516994..2aa6f7ba72 100644 --- a/CMake/Dependencies/libmbedtls-CMakeLists.txt +++ b/CMake/Dependencies/libmbedtls-CMakeLists.txt @@ -26,7 +26,7 @@ ExternalProject_Add( CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX} -DUSE_SHARED_MBEDTLS_LIBRARY=${BUILD_SHARED} - -DCMAKE_BUILD_TYPE=Release + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_MACOSX_RPATH=${CMAKE_MACOSX_RPATH} -DENABLE_TESTING=OFF -DENABLE_PROGRAMS=OFF diff --git a/CMake/Dependencies/libusrsctp-CMakeLists.txt b/CMake/Dependencies/libusrsctp-CMakeLists.txt index 7ccc63f5bf..ab99cf4c15 100644 --- a/CMake/Dependencies/libusrsctp-CMakeLists.txt +++ b/CMake/Dependencies/libusrsctp-CMakeLists.txt @@ -8,7 +8,8 @@ ExternalProject_Add(project_libusrsctp GIT_REPOSITORY https://github.com/sctplab/usrsctp.git GIT_TAG 1ade45cbadfd19298d2c47dc538962d4425ad2dd PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} -fPIC" -Dsctp_werror=0 BUILD_ALWAYS TRUE diff --git a/CMake/Dependencies/libwebsockets-CMakeLists.txt b/CMake/Dependencies/libwebsockets-CMakeLists.txt index 90bec7d553..8e0f068ca1 100644 --- a/CMake/Dependencies/libwebsockets-CMakeLists.txt +++ b/CMake/Dependencies/libwebsockets-CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.6.3) project(libwebsocket-download NONE) -SET(PATCH_COMMAND git apply --ignore-whitespace ${CMAKE_CURRENT_LIST_DIR}/libwebsockets-old-gcc-fix-cast-cmakelists.patch ${CMAKE_CURRENT_LIST_DIR}/libwebsockets-leak-pipe-fix.patch) +SET(PATCH_COMMAND git apply --verbose --ignore-whitespace ${CMAKE_CURRENT_LIST_DIR}/libwebsockets-old-gcc-fix-cast-cmakelists.patch) include(ExternalProject) if (BUILD_STATIC_LIBS) @@ -30,13 +30,14 @@ endif() ExternalProject_Add(project_libwebsockets GIT_REPOSITORY https://github.com/warmcat/libwebsockets.git - GIT_TAG v4.2.2 + GIT_TAG v4.3.3 PATCH_COMMAND ${PATCH_COMMAND} PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build LIST_SEPARATOR | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DLWS_WITH_HTTP2=1 -DLWS_HAVE_HMAC_CTX_new=1 -DLWS_HAVE_SSL_EXTRA_CHAIN_CERTS=1 diff --git a/CMake/Dependencies/libwebsockets-old-gcc-fix-cast-cmakelists.patch b/CMake/Dependencies/libwebsockets-old-gcc-fix-cast-cmakelists.patch index 61e25e8a35..6fd33a5bef 100644 --- a/CMake/Dependencies/libwebsockets-old-gcc-fix-cast-cmakelists.patch +++ b/CMake/Dependencies/libwebsockets-old-gcc-fix-cast-cmakelists.patch @@ -11,31 +11,22 @@ index 68629e6f..6ef628b8 100644 prev = h2n->hpack_pos; h2n->hpack_pos = (uint16_t)huftable_decode( (int)h2n->hpack_pos, b); -diff --git a/lib/tls/CMakeLists.txt b/lib/tls/CMakeLists.txt -index b214df75..06eaf255 100644 ---- a/lib/tls/CMakeLists.txt -+++ b/lib/tls/CMakeLists.txt -@@ -57,10 +57,10 @@ if (LWS_WITH_BORINGSSL) - endif() +diff --git a/lib/core/lws_map.c b/lib/core/lws_map.c +index d149d86752..b319d79f49 100644 +--- a/lib/core/lws_map.c ++++ b/lib/core/lws_map.c +@@ -29,11 +29,11 @@ typedef struct lws_map_hashtable { + lws_dll2_owner_t ho; + } lws_map_hashtable_t; - if (LWS_WITH_SSL AND NOT LWS_WITH_WOLFSSL AND NOT LWS_WITH_MBEDTLS) -- if ("${LWS_OPENSSL_LIBRARIES}" STREQUAL "" OR "${LWS_OPENSSL_INCLUDE_DIRS}" STREQUAL "") -+ if (("${LWS_OPENSSL_LIBRARIES}" STREQUAL "" AND ("${LWS_OPENSSL_SSL_LIBRARY}" STREQUAL "" OR "${LWS_OPENSSL_CRYPTO_LIBRARY}" STREQUAL "")) OR "${LWS_OPENSSL_INCLUDE_DIRS}" STREQUAL "") - else() - if (NOT LWS_PLAT_FREERTOS) -- set(OPENSSL_LIBRARIES ${LWS_OPENSSL_LIBRARIES}) -+ list(APPEND OPENSSL_LIBRARIES ${LWS_OPENSSL_LIBRARIES} ${LWS_OPENSSL_SSL_LIBRARY} ${LWS_OPENSSL_CRYPTO_LIBRARY}) - endif() - set(OPENSSL_INCLUDE_DIRS ${LWS_OPENSSL_INCLUDE_DIRS}) - set(OPENSSL_FOUND 1) -@@ -248,7 +248,9 @@ if (LWS_WITH_SSL) - find_package(PkgConfig QUIET) - pkg_check_modules(PC_OPENSSL openssl QUIET) - find_package(OpenSSL REQUIRED) -- list(APPEND OPENSSL_LIBRARIES ${PC_OPENSSL_LIBRARIES}) -+ if (NOT LWS_WITH_STATIC) -+ list(APPEND OPENSSL_LIBRARIES ${PC_OPENSSL_LIBRARIES}) -+ endif() - set(OPENSSL_LIBRARIES ${OPENSSL_LIBRARIES} PARENT_SCOPE) - endif() - set(OPENSSL_INCLUDE_DIRS "${OPENSSL_INCLUDE_DIR}") +-typedef struct lws_map { ++struct lws_map { + lws_map_info_t info; + + /* array of info.modulo x lws_map_hashtable_t overallocated */ +-} lws_map_t; ++}; + + typedef struct lws_map_item { + lws_dll2_t list; /* owned by hashtable */ + diff --git a/CMakeLists.txt b/CMakeLists.txt index 96aa15ce33..33f9b92aca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,9 @@ include(CheckIncludeFiles) include(CheckFunctionExists) # The version MUST be updated before every release -project(KinesisVideoWebRTCClient VERSION 1.8.1 LANGUAGES C) +project(KinesisVideoWebRTCClient VERSION 1.10.0 LANGUAGES C) + + # User Flags @@ -19,6 +21,7 @@ option(BUILD_LIBSRTP_HOST_PLATFORM "If buildng LibSRTP what is the current platf option(BUILD_LIBSRTP_DESTINATION_PLATFORM "If buildng LibSRTP what is the destination platform" OFF) option(BUILD_SAMPLE "Build available samples" ON) option(ENABLE_DATA_CHANNEL "Enable support for data channel" ON) +option(ENABLE_KVS_THREADPOOL "Enable support for KVS thread pool in signaling" ON) option(INSTRUMENTED_ALLOCATORS "Enable memory instrumentation" OFF) option(ENABLE_AWS_SDK_IN_TESTS "Enable support for compiling AWS SDKs for tests" ON) @@ -38,6 +41,10 @@ if(WIN32) set(EXT_PTHREAD_LIBRARIES "" CACHE FILEPATH "Path to PThread libraries") endif() +if(NOT CMAKE_BUILD_TYPE) + message(STATUS "Setting CMAKE_BUILD_TYPE to Release by default") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) +endif() execute_process( COMMAND git rev-parse HEAD @@ -101,6 +108,10 @@ message(STATUS "dependencies install path is ${OPEN_SRC_INSTALL_PREFIX}") add_definitions(-DKVS_CA_CERT_PATH="${CMAKE_SOURCE_DIR}/certs/cert.pem") add_definitions(-DCMAKE_DETECTED_CACERT_PATH) +if (ENABLE_KVS_THREADPOOL) + add_definitions(-DENABLE_KVS_THREADPOOL) +endif() + if(USE_OPENSSL) add_definitions(-DKVS_USE_OPENSSL) elseif(USE_MBEDTLS) @@ -110,10 +121,16 @@ elseif(USE_MBEDTLS) message(STATUS "Detected clang") set(CMAKE_C_FLAGS "-I${CMAKE_CURRENT_SOURCE_DIR}/configs -DMBEDTLS_USER_CONFIG_FILE=\"\" ${CMAKE_C_FLAGS}") set(CMAKE_CXX_FLAGS "-I${CMAKE_CURRENT_SOURCE_DIR}/configs -DMBEDTLS_USER_CONFIG_FILE=\"\" ${CMAKE_CXX_FLAGS}") - else() + elseif("${CMAKE_C_COMPILER_ID}" MATCHES "GNU") message(STATUS "Detected gcc") - set(CMAKE_C_FLAGS "-Wno-error=stringop-overflow -I${CMAKE_CURRENT_SOURCE_DIR}/configs -DMBEDTLS_USER_CONFIG_FILE=\"\" ${CMAKE_C_FLAGS}") - set(CMAKE_CXX_FLAGS "-Wno-error=stringop-overflow -I${CMAKE_CURRENT_SOURCE_DIR}/configs -DMBEDTLS_USER_CONFIG_FILE=\"\" ${CMAKE_CXX_FLAGS}") + set(CMAKE_C_FLAGS "-I${CMAKE_CURRENT_SOURCE_DIR}/configs -DMBEDTLS_USER_CONFIG_FILE=\"\" ${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS "-I${CMAKE_CURRENT_SOURCE_DIR}/configs -DMBEDTLS_USER_CONFIG_FILE=\"\" ${CMAKE_CXX_FLAGS}") + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER "7.0") + set(CMAKE_C_FLAGS "-Wno-error=stringop-overflow ${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS "-Wno-error=stringop-overflow ${CMAKE_CXX_FLAGS}") + endif() + else() + message(FATAL_ERROR "Detected unsupported compiler: ${CMAKE_C_COMPILER_ID}") endif() endif() @@ -137,14 +154,16 @@ if(BUILD_DEPENDENCIES) if (USE_OPENSSL) set(BUILD_ARGS -DBUILD_STATIC_LIBS=${BUILD_STATIC_LIBS} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBUILD_OPENSSL_PLATFORM=${BUILD_OPENSSL_PLATFORM} -DOPENSSL_EXTRA=${OPENSSL_EXTRA}) build_dependency(openssl ${BUILD_ARGS}) set(OPENSSL_ROOT_DIR ${OPEN_SRC_INSTALL_PREFIX}) elseif(USE_MBEDTLS) set(BUILD_ARGS -DBUILD_STATIC_LIBS=${BUILD_STATIC_LIBS} - "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} -std=c99") - build_dependency(mbedtls ${BUILD_ARGS}) + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} -std=c99") + build_dependency(mbedtls ${BUILD_ARGS}) endif() @@ -155,8 +174,9 @@ if(BUILD_DEPENDENCIES) set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY}) string(REPLACE ";" "|" OPENSSL_LIBRARIES_ALT_SEP "${OPENSSL_LIBRARIES}") set(BUILD_ARGS -DBUILD_STATIC_LIBS=${BUILD_STATIC_LIBS} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DUSE_OPENSSL=${USE_OPENSSL} - -DUSE_MBEDTLS=${USE_MBEDTLS} + -DUSE_MBEDTLS=${USE_MBEDTLS} -DLWS_EXT_PTHREAD_INCLUDE_DIR=${EXT_PTHREAD_INCLUDE_DIR} -DLWS_EXT_PTHREAD_LIBRARIES=${EXT_PTHREAD_LIBRARIES} -DLWS_OPENSSL_INCLUDE_DIRS=${OPENSSL_INCLUDE_DIRS} @@ -164,6 +184,7 @@ if(BUILD_DEPENDENCIES) -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}) else() set(BUILD_ARGS -DBUILD_STATIC_LIBS=${BUILD_STATIC_LIBS} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DOPENSSL_DIR=${OPEN_SRC_INSTALL_PREFIX} -DUSE_OPENSSL=${USE_OPENSSL} -DUSE_MBEDTLS=${USE_MBEDTLS} @@ -176,16 +197,18 @@ if(BUILD_DEPENDENCIES) set(BUILD_ARGS -DBUILD_STATIC_LIBS=${BUILD_STATIC_LIBS} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DOPENSSL_DIR=${OPEN_SRC_INSTALL_PREFIX} -DBUILD_LIBSRTP_HOST_PLATFORM=${BUILD_LIBSRTP_HOST_PLATFORM} -DBUILD_LIBSRTP_DESTINATION_PLATFORM=${BUILD_LIBSRTP_DESTINATION_PLATFORM} -DUSE_OPENSSL=${USE_OPENSSL} -DUSE_MBEDTLS=${USE_MBEDTLS} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}) - + build_dependency(srtp ${BUILD_ARGS}) set(BUILD_ARGS + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}) build_dependency(usrsctp ${BUILD_ARGS}) @@ -349,7 +372,6 @@ file(GLOB WEBRTC_SIGNALING_CLIENT_SOURCE_FILES "src/source/Signaling/*.c") include_directories(${OPEN_SRC_INCLUDE_DIRS}) include_directories(${OPEN_SRC_INSTALL_PREFIX}/include) include_directories(${KINESIS_VIDEO_WEBRTC_CLIENT_SRC}/src/include) -include_directories(${KINESIS_VIDEO_WEBRTC_CLIENT_SRC}/src/ice) add_library(kvsWebrtcClient ${LINKAGE} ${WEBRTC_CLIENT_SOURCE_FILES} ${DATACHANNEL_SRC}) @@ -357,6 +379,13 @@ if(USE_MBEDTLS) target_compile_definitions(kvsWebrtcClient PRIVATE LWS_WITH_MBEDTLS) endif() +if(ENABLE_KVS_THREADPOOL) + file(GLOB THREADPOOL_SOURCE_FILES "src/source/Threadpool/*.c") + add_library(kvsWebRtcThreadpool ${LINKAGE} ${THREADPOOL_SOURCE_FILES}) + target_link_libraries(kvsWebRtcThreadpool PRIVATE kvspicUtils) + set(EXTRA_DEPS ${EXTRA_DEPS} kvsWebRtcThreadpool) +endif() + target_link_libraries( kvsWebrtcClient PRIVATE kvspicUtils @@ -420,7 +449,9 @@ endif() if(BUILD_TEST) # adding ZLIB because aws sdk static link seems to be broken when zlib is needed - find_package(ZLIB REQUIRED) + if(NOT WIN32) + find_package(ZLIB REQUIRED) + endif() add_subdirectory(tst) endif() diff --git a/README.md b/README.md index ab08aee972..51dca36d64 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Please refer to the release notes in [Releases](https://github.com/awslabs/amazo - G.711 PCM (ยต-law) * Developer Controlled Media Pipeline - Raw Media for Input/Output - - Callbacks for [Congestion Control](https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c/pull/201), FIR and PLI (set on RtcRtpTransceiver) + - Callbacks for [Congestion Control](https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c/pull/201), FIR and PLI (set on [RtcRtpTransceiver](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/structRtcInboundRtpStreamStats.html)) * DataChannels * NACKs * STUN/TURN Support @@ -59,7 +59,8 @@ To download run the following command: You will also need to install `pkg-config` and `CMake` and a build environment -### Configure +### Configuring on Ubuntu / Unix + Create a build directory in the newly checked out repository, and execute CMake from it. `mkdir -p amazon-kinesis-video-streams-webrtc-sdk-c/build; cd amazon-kinesis-video-streams-webrtc-sdk-c/build; cmake .. ` @@ -68,12 +69,52 @@ We have provided an example of using GStreamer to capture/encode video, and then GStreamer is installed on your system. On Ubuntu and Raspberry Pi OS you can get the libraries by running +```shell +sudo apt-get install cmake m4 pkg-config libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools +``` + +By default we download all the libraries from GitHub and build them locally, so should require nothing to be installed ahead of time. If you do wish to link to existing libraries you can use the following flags to customize your build. + +### Configuring on Windows + +Install [MS Visual Studio Community / Enterprise](https://visualstudio.microsoft.com/vs/community/), [Strawberry perl](https://strawberryperl.com/), and [Chocolatey](https://chocolatey.org/install) if not installed already + +Get the libraries by running the following in powershell +```shell +choco install gstreamer +choco install gstreamer-devel +curl.exe -o C:\tools\pthreads-w32-2-9-1-release.zip ftp://sourceware.org/pub/pthreads-win32/pthreads-w32-2-9-1-release.zip +mkdir C:\tools\pthreads-w32-2-9-1-release\ +Expand-Archive -Path C:\tools\pthreads-w32-2-9-1-release.zip -DestinationPath C:\tools\pthreads-w32-2-9-1-release +``` + +Modify the path to the downloaded and unzipped PThreads in cmake in `build_windows_openssl.bat` if needed / unzipped at a path other than the one mentioned above +```shell +cmake -G "NMake Makefiles" -DBUILD_TEST=TRUE -DEXT_PTHREAD_INCLUDE_DIR="C:/tools/pthreads-w32-2-9-1-release/Pre-built.2/include/" -DEXT_PTHREAD_LIBRARIES="C:/tools/pthreads-w32-2-9-1-release/Pre-built.2/lib/x64/libpthreadGC2.a" .. +``` +Modify the path to MSVC as well in the `build_windows_openssl.bat` if needed / installed a different version / location + +```shell +call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 +``` + +Allow long paths before we start the build +```shell +git config --system core.longpaths true ``` -$ sudo apt-get install libssl-dev libcurl4-openssl-dev liblog4cplus-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools + +Note that if the paths are still too long (which can cause the build to fail unfortunately), we recommend renaming the folders to use shorter names and moving them to `C:/` + +Build the SDK + +```shell +.github\build_windows_openssl.bat ``` -By default we download all the libraries from GitHub and build them locally, so should require nothing to be installed ahead of time. -If you do wish to link to existing libraries you can use the following flags to customize your build. +To run the sample application, make sure that you've exported the following paths and appended them to env:Path for powershell +```shell +$env:Path += ';C:\webrtc\open-source\bin;C:\tools\pthreads-w32-2-9-1-release\Pre-built.2\dll\x64;C:\webrtc\build' +``` ### Dependency requirements @@ -108,13 +149,15 @@ You can pass the following options to `cmake ..`. * `-DMEMORY_SANITIZER` -- Build with MemorySanitizer * `-DTHREAD_SANITIZER` -- Build with ThreadSanitizer * `-DUNDEFINED_BEHAVIOR_SANITIZER` -- Build with UndefinedBehaviorSanitizer +* `-DCMAKE_BUILD_TYPE` -- Build Release/Debug libraries. By default, the SDK generates Release build. The standard options are listed [here](https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#default-and-custom-configurations) * `-DLINK_PROFILER` -- Link with gperftools (available profiler options are listed [here](https://github.com/gperftools/gperftools)) +* `-DPKG_CONFIG_EXECUTABLE` -- Set pkg config path. This might be required to find gstreamer's pkg config specifically on Windows. To clean up the `open-source` and `build` folders from previous build, use `cmake --build . --target clean` from the `build` folder For windows builds, you will have to include additional flags for libwebsockets CMake. Add the following flags to your cmake command, or edit the CMake file in ./CMake/Dependencies/libwebsockets-CMakeLists.txt with the following: -``` +```shell cmake .. -DLWS_HAVE_PTHREAD_H=1 -DLWS_EXT_PTHREAD_INCLUDE_DIR="C:\Program Files (x86)\pthreads\include" -DLWS_EXT_PTHREAD_LIBRARIES="C:\Program Files (x86)\pthreads\lib\x64\libpthreadGC2.a" -DLWS_WITH_MINIMAL_EXAMPLES=1 ``` @@ -136,12 +179,12 @@ On MacOS: `brew install srtp libusrsctp libwebsockets ` To use OpenSSL: -``` +```shell cmake .. -DBUILD_DEPENDENCIES=OFF -DUSE_OPENSSL=ON ``` To use MBedTLS: -``` +```shell cmake .. -DBUILD_DEPENDENCIES=OFF -DUSE_OPENSSL=OFF -DUSE_MBEDTLS=ON ``` @@ -152,21 +195,21 @@ If the versions are not satisfied, this option would not work and enabling the S ### Setup your environment with your AWS account credentials and AWS region: * First set the appropriate environment variables so you can connect to KVS. If you want to use IoT certificate instead, check Setup IoT. -``` -export AWS_ACCESS_KEY_ID= -export AWS_SECRET_ACCESS_KEY= +```shell +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= ``` * Optionally, set AWS_SESSION_TOKEN if integrating with temporary token -``` +```shell export AWS_SESSION_TOKEN= ``` * Region is optional, if not being set, then us-west-2 will be used as default region. -``` -export AWS_DEFAULT_REGION= +```shell +export AWS_DEFAULT_REGION= ``` ### Setup logging: @@ -181,13 +224,13 @@ Set up the desired log level. The log levels and corresponding values currently 8. `LOG_LEVEL_PROFILE` ---- 8 To set a log level, run the following command: -``` -export AWS_KVS_LOG_LEVEL = +```shell +export AWS_KVS_LOG_LEVEL= ``` -For example: -``` -export AWS_KVS_LOG_LEVEL = 2 switches on DEBUG level logs while runnning the samples +For example, the following command switches on `DEBUG` level logs while runnning the samples. +```shell +export AWS_KVS_LOG_LEVEL=2 ``` Note: The default log level is `LOG_LEVEL_WARN`. @@ -195,15 +238,25 @@ Note: The default log level is `LOG_LEVEL_WARN`. Starting v1.8.0, by default, the SDK creates a log file that would have execution timing details of certain steps in connection establishment. It would be stored in the `build` directory as `kvsFileLogFilter.x`. In case you do not want to use defaults, you can modify certain parameters such as log file directory, log file size and file rotation index in the `createFileLoggerWithLevelFiltering` function in the samples. In addition to these logs, if you would like to have other level logs in a file as well, run: -``` +```shell export AWS_ENABLE_FILE_LOGGING=TRUE ``` +The SDK also tracks entry and exit of functions which increases the verbosity of the logs. This will be useful when you want to track the transitions within the codebase. To do so, you need to set log level to `LOG_LEVEL_VERBOSE` and add the following to the CMakeLists.txt file: +`add_definitions(-DLOG_STREAMING)` +Note: This log level is extremely VERBOSE and could flood the files if using file based logging strategy. + +
+ Time-to-first-frame breakdown metrics + +There is a flag in the sample application which (pSampleConfiguration->enableSendingMetricsToViewerViaDc) can be set to TRUE to send metrics from the master to the [JS viewer](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-js/examples/index.html). This helps get a detailed breakdown of time-to-first-frame and all the processes and API calls on master and the viewer both. This is intended to be used with the KVS WebRTC C SDK running as the master and the JS SDK as the viewer. The master sends peer, ice-agent, signaling and data-channel metrics to the viewer which are plotted ~ 20 seconds after the viewer is started. Since the timeline plot is intended to understand the time-to-first-frame, the sample web page needs to be refreshed and the master needs to be restarted if a new / updated plot is needed. While using the SDK in this mode, it is expected that all datachannel messages are JSON messages. This feature is only meant to be used for a single viewer at a time. +
+ ### Set path to SSL CA certificate (**Optional**) If you have a custom CA certificate path to set, you can set it using: -``` +```shell export AWS_KVS_CACERT_PATH=../certs/cert.pem ``` @@ -214,30 +267,31 @@ After executing `make` you will have sample applications in your `build/samples` #### Sample: kvsWebrtcClientMaster This application sends sample H264/Opus frames (path: `/samples/h264SampleFrames` and `/samples/opusSampleFrames`) via WebRTC. It also accepts incoming audio, if enabled in the browser. When checked in the browser, it prints the metadata of the received audio packets in your terminal. To run: -``` +```shell ./samples/kvsWebrtcClientMaster ``` To use the **Storage for WebRTC** feature, run the same command as above but with an additional command line arg to enable the feature. -``` +```shell ./samples/kvsWebrtcClientMaster 1 ``` #### Sample: kvsWebrtcClientMasterGstSample This application can send media from a GStreamer pipeline using test H264/Opus frames, device `autovideosrc` and `autoaudiosrc` input, or a received RTSP stream. It also will playback incoming audio via an `autoaudiosink`. To run: -``` +```shell ./samples/kvsWebrtcClientMasterGstSample ``` Pass the desired media and source type when running the sample. The mediaType can be `audio-video` or `video-only`. To use the **Storage For WebRTC** feature, use `audio-video-storage` as the mediaType. The source type can be `testsrc`, `devicesrc`, or `rtspsrc`. Specify the RTSP URI if using `rtspsrc`: -``` + +```shell ./samples/kvsWebrtcClientMasterGstSample rtspsrc rtsp:// ``` #### Sample: kvsWebrtcClientViewer This application accepts sample H264/Opus frames and prints them out. To run: -``` +```shell ./samples/kvsWebrtcClientViewer ``` @@ -257,7 +311,7 @@ Then choose Start Viewer to start live video streaming of the sample H264/Opus f * To use IoT certificate to authenticate with KVS signaling, please refer to [Controlling Access to Kinesis Video Streams Resources Using AWS IoT](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/how-iot.html) for provisioning details. * A sample IAM policy for the IoT role looks like below, policy can be modified based on your permission requirement. -``` +```json { "Version":"2012-10-17", "Statement":[ @@ -276,39 +330,42 @@ Then choose Start Viewer to start live video streaming of the sample H264/Opus f } ``` +We recommend following [best practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) while setting up the IAM policy and not allow access to all channels in the account, but allow access to only the REQUIRED channel names if the use case demands it. KVS recommendation is to use iot thing name as channel name as per public docs. +https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/how-iot.html + Note: "kinesisvideo:CreateSignalingChannel" can be removed if you are running with existing KVS signaling channels. Viewer sample requires "kinesisvideo:ConnectAsViewer" permission. Integration test requires both "kinesisvideo:ConnectAsViewer" and "kinesisvideo:DeleteSignalingChannel" permission. * With the IoT certificate, IoT credentials provider endpoint (Note: it is not the endpoint on IoT AWS Console!), public key and private key ready, you can replace the static credentials provider createStaticCredentialProvider() and freeStaticCredentialProvider() with IoT credentials provider like below, the credentials provider for [samples](https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c/blob/master/samples/Common.c) is in createSampleConfiguration(): -``` +```c createLwsIotCredentialProvider( "coxxxxxxxx168.credentials.iot.us-west-2.amazonaws.com", // IoT credentials endpoint "/Users/username/Downloads/iot-signaling/certificate.pem", // path to iot certificate "/Users/username/Downloads/iot-signaling/private.pem.key", // path to iot private key "/Users/username/Downloads/iot-signaling/cacert.pem", // path to CA cert "KinesisVideoSignalingCameraIoTRoleAlias", // IoT role alias - channelName, // iot thing name, recommended to be same as your channel name + "IoTThingName", // iot thing name, recommended to be same as your channel name &pSampleConfiguration->pCredentialProvider)); freeIotCredentialProvider(&pSampleConfiguration->pCredentialProvider); ``` ## Use Pre-generated Certificates -The certificate generating function (createCertificateAndKey) in createDtlsSession() can take between 5 - 15 seconds in low performance embedded devices, it is called for every peer connection creation when KVS WebRTC receives an offer. To avoid this extra start-up latency, certificate can be pre-generated and passed in when offer comes. +The certificate generating function ([createCertificateAndKey](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/Dtls__openssl_8c.html#a451c48525b0c0a8919a880d6834c1f7f)) in createDtlsSession() can take between 5 - 15 seconds in low performance embedded devices, it is called for every peer connection creation when KVS WebRTC receives an offer. To avoid this extra start-up latency, certificate can be pre-generated and passed in when offer comes. **Important Note: It is recommended to rotate the certificates often - preferably for every peer connection to avoid a compromised client weakening the security of the new connections.** -Take kvsWebRTCClientMaster as sample, add RtcCertificate certificates[CERT_COUNT]; to **SampleConfiguration** in Samples.h. -Then pass in the pre-generated certificate in initializePeerConnection() in Common.c. +Take `kvsWebRTCClientMaster` as sample, add `RtcCertificate certificates[CERT_COUNT];` to **SampleConfiguration** in [Samples.h](./samples/Samples.h). +Then pass in the pre-generated certificate in initializePeerConnection() in [Common.c](./samples/Common.c). -``` +```c configuration.certificates[0].pCertificate = pSampleConfiguration->certificates[0].pCertificate; configuration.certificates[0].pPrivateKey = pSampleConfiguration->certificates[0].pPrivateKey; +``` -where, `configuration` is of type `RtcConfiguration` in the function that calls `initializePeerConnection()`. +where, `configuration` is of type [`RtcConfiguration`](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/structRtcConfiguration.html) in the function that calls `initializePeerConnection()`. -Doing this will make sure that `createCertificateAndKey() would not execute since a certificate is already available.` -``` +Doing this will make sure that [`createCertificateAndKey()`](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/Dtls__openssl_8c.html#a451c48525b0c0a8919a880d6834c1f7f) would not execute since a certificate is already available. ## Provide Hardware Entropy Source @@ -368,10 +425,32 @@ When building on MacOS M1, if the build fails while trying to build OpenSSL or W To build on a 32-bit Raspbian GNU/Linux 11 on 64-bit hardware, the OpenSSL library must be manually configured. This is due to the OpenSSL autoconfiguration script detecting 64-bit hardware and emitting 64-bit ARM assembly instructions which are not allowed in 32-bit executables. A 32-bit ARM version of OpenSSL can be configured by setting 32-bit ARM platform: `cmake .. -DBUILD_OPENSSL_PLATFORM=linux-armv4` -### Threadpool for Signaling Channel messages -The threadpool is enabled by default, and starts with 3 threads that it can increase up to 5 if all 3 are actively in use. To change these values to better match the resources of your use case -please edit samples/Samples.h defines `KVS_SIGNALING_THREADPOOL_MIN` and `KVS_SIGNALING_THREADPOOL_MAX`. You can also disable the threadpool to instead create and detach each thread -to handle signaling messages by commenting out `KVS_USE_SIGNALING_CHANNEL_THREADPOOL`. +### Threadpool for the SDK +The threadpool is enabled by default, and starts with 3 threads that it can increase up to 10 if all are actively in use. To change these values to better match the resources of your use case you can set the environment variables to do so: +1. `export AWS_KVS_WEBRTC_THREADPOOL_MIN_THREADS=` +2. `export AWS_KVS_WEBRTC_THREADPOOL_MAX_THREADS=` + +To disable threadpool, run `cmake .. -DENABLE_KVS_THREADPOOL=OFF` + +Starting version 1.10.0, threadpool usage provides latency improvements in connection establishment. Note, that increasing the number of minimum threads can increase stack memory usage. So, ensure to increase with caution. + +### Setting ICE related timeouts + +There are some default timeout values set for different steps in ICE in the [KvsRtcConfiguration](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/structKvsRtcConfiguration.html). These are configurable in the application. While the defaults are generous, there could be applications that might need more flexibility to improve chances of connection establishment because of poor network. + +You can find the default setting in the logs: +``` +2024-01-08 19:43:44.433 INFO iceAgentValidateKvsRtcConfig(): + iceLocalCandidateGatheringTimeout: 10000 ms + iceConnectionCheckTimeout: 12000 ms + iceCandidateNominationTimeout: 12000 ms + iceConnectionCheckPollingInterval: 50 ms +``` +Let us look into when each of these could be changed: +1. `iceCandidateNominationTimeout`: Say the connection with host/srflx could not be established and TURN seems to be the only resort. Let us assume it takes about 15 seconds to gather the first local relay candidate, the application could set the timeout to a value more than 15 seconds to ensure candidate pairs with the local relay candidate are tried for success. If the value is set to less than 15 seconds in this case, the SDK would lose out on trying a potential candidate pair leading to connection establishment failure +2. `iceLocalCandidateGatheringTimeout`: Say the host candidates would not work and srflx/relay candidates need to be tried. Due to poor network, it is anticipated the candidates are gathered slowly and the application does not want to spend more than 20 seconds on this step. The goal is to try all possible candidate pairs. Increasing the timeout helps in giving some more time to gather more potential candidates to try for connection. Also note, this parameter increase would not make a difference in the situation unless `iceCandidateNominationTimeout` > `iceLocalCandidateGatheringTimeout` since nomination step should also be given time to work with the new candidates +3. `iceConnectionCheckTimeout`: It is useful to increase this timeout in unstable/slow network where the packet exchange takes time and hence the binding request/response. Essentially, increasing it will allow atleast one candidate pair to be tried for nomination by the other peer. +4. `iceConnectionCheckPollingInterval`: This value is set to a default of 50 ms per [spec](https://datatracker.ietf.org/doc/html/rfc8445#section-14.2). Changing this would change the frequency of connectivity checks and essentially, the ICE state machine transitions. Decreasing the value could help in faster connection establishment in a reliable high performant network setting with good system resources. Increasing the value could help in reducing the network load, however, the connection establishment could slow down. Unless there is a strong reasoning, it is **NOT** recommended to deviate from spec/default. ## Documentation All Public APIs are documented in our [Include.h](https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c/blob/master/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h), we also generate a [Doxygen](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/) each commit for easier navigation. diff --git a/configs/config_mbedtls.h b/configs/config_mbedtls.h index 52cc0ab10f..a642be21dc 100644 --- a/configs/config_mbedtls.h +++ b/configs/config_mbedtls.h @@ -19,6 +19,8 @@ extern "C" { #undef MBEDTLS_ECP_DP_SECP224K1_ENABLED #undef MBEDTLS_ECP_DP_SECP256K1_ENABLED +#undef MBEDTLS_SSL_ALPN + /** * \def MBEDTLS_ENTROPY_HARDWARE_ALT * diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index aaecb4dd1f..112c2c21d5 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -6,6 +6,18 @@ project(KinesisVideoWebRTCClientSamples LANGUAGES C) message("OPEN_SRC_INSTALL_PREFIX=${OPEN_SRC_INSTALL_PREFIX}") +if (WIN32) + if(NOT DEFINED PKG_CONFIG_EXECUTABLE) + if(EXISTS "C:\\gstreamer\\1.0\\x86_64\\bin\\pkg-config.exe") + set(PKG_CONFIG_EXECUTABLE "C:\\gstreamer\\1.0\\x86_64\\bin\\pkg-config.exe") + endif() + elseif(DEFINED PKG_CONFIG_EXECUTABLE) + message(STATUS "Gstreamer pkg-config path set to ${PKG_CONFIG_EXECUTABLE}") + else() + message(FATAL_ERROR "Gstreamer not found in default path. Set the appropriate path with -DPKG_CONFIG_EXECUTABLE=") + endif() +endif() + find_package(PkgConfig REQUIRED) pkg_check_modules(GST gstreamer-1.0) @@ -49,18 +61,18 @@ add_executable( kvsWebrtcClientMaster Common.c kvsWebRTCClientMaster.c) -target_link_libraries(kvsWebrtcClientMaster kvsWebrtcClient kvsWebrtcSignalingClient kvsCommonLws kvspicUtils websockets) +target_link_libraries(kvsWebrtcClientMaster kvsWebrtcClient kvsWebrtcSignalingClient ${EXTRA_DEPS} kvsCommonLws kvspicUtils websockets) add_executable( kvsWebrtcClientViewer Common.c kvsWebRTCClientViewer.c) -target_link_libraries(kvsWebrtcClientViewer kvsWebrtcClient kvsWebrtcSignalingClient kvsCommonLws kvspicUtils websockets) +target_link_libraries(kvsWebrtcClientViewer kvsWebrtcClient kvsWebrtcSignalingClient ${EXTRA_DEPS} kvsCommonLws kvspicUtils websockets) add_executable( discoverNatBehavior discoverNatBehavior.c) -target_link_libraries(discoverNatBehavior kvsWebrtcClient) +target_link_libraries(discoverNatBehavior kvsWebrtcClient ${EXTRA_DEPS}) if(GST_FOUND) add_executable( @@ -68,7 +80,7 @@ if(GST_FOUND) Common.c kvsWebrtcClientMasterGstSample.c ) - target_link_libraries(kvsWebrtcClientMasterGstSample kvsWebrtcClient kvsWebrtcSignalingClient ${GST_SAMPLE_LIBRARIES} kvsCommonLws kvspicUtils websockets) + target_link_libraries(kvsWebrtcClientMasterGstSample kvsWebrtcClient kvsWebrtcSignalingClient ${EXTRA_DEPS} ${GST_SAMPLE_LIBRARIES} kvsCommonLws kvspicUtils websockets) install(TARGETS kvsWebrtcClientMasterGstSample RUNTIME DESTINATION bin diff --git a/samples/Common.c b/samples/Common.c index dbc12b5c99..2deb9d6175 100644 --- a/samples/Common.c +++ b/samples/Common.c @@ -32,28 +32,6 @@ STATUS signalingCallFailed(STATUS status) STATUS_SIGNALING_DESCRIBE_MEDIA_CALL_FAILED == status); } -VOID onDataChannelMessage(UINT64 customData, PRtcDataChannel pDataChannel, BOOL isBinary, PBYTE pMessage, UINT32 pMessageLen) -{ - UNUSED_PARAM(customData); - if (isBinary) { - DLOGI("DataChannel Binary Message"); - } else { - DLOGI("DataChannel String Message: %.*s\n", pMessageLen, pMessage); - } - // Send a response to the message sent by the viewer - STATUS retStatus = STATUS_SUCCESS; - retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) MASTER_DATA_CHANNEL_MESSAGE, STRLEN(MASTER_DATA_CHANNEL_MESSAGE)); - if (retStatus != STATUS_SUCCESS) { - DLOGI("[KVS Master] dataChannelSend(): operation returned status code: 0x%08x \n", retStatus); - } -} - -VOID onDataChannel(UINT64 customData, PRtcDataChannel pRtcDataChannel) -{ - DLOGI("New DataChannel has been opened %s \n", pRtcDataChannel->name); - dataChannelOnMessage(pRtcDataChannel, customData, onDataChannelMessage); -} - VOID onConnectionStateChange(UINT64 customData, RTC_PEER_CONNECTION_STATE newState) { STATUS retStatus = STATUS_SUCCESS; @@ -68,6 +46,8 @@ VOID onConnectionStateChange(UINT64 customData, RTC_PEER_CONNECTION_STATE newSta ATOMIC_STORE_BOOL(&pSampleConfiguration->connected, TRUE); CVAR_BROADCAST(pSampleConfiguration->cvar); + pSampleStreamingSession->peerConnectionMetrics.peerConnectionStats.peerConnectionConnectedTime = + GETTIME() / HUNDREDS_OF_NANOS_IN_A_MILLISECOND; CHK_STATUS(peerConnectionGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->peerConnectionMetrics)); CHK_STATUS(iceAgentGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->iceMetrics)); @@ -360,13 +340,51 @@ VOID onIceCandidateHandler(UINT64 customData, PCHAR candidateJson) CHK_LOG_ERR(retStatus); } +PVOID asyncGetIceConfigInfo(PVOID args) +{ + STATUS retStatus = STATUS_SUCCESS; + AsyncGetIceStruct* data = (AsyncGetIceStruct*) args; + PIceConfigInfo pIceConfigInfo = NULL; + UINT32 uriCount = 0; + UINT32 i = 0, maxTurnServer = 1; + + if (data != NULL) { + /* signalingClientGetIceConfigInfoCount can return more than one turn server. Use only one to optimize + * candidate gathering latency. But user can also choose to use more than 1 turn server. */ + for (uriCount = 0, i = 0; i < maxTurnServer; i++) { + /* + * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port?transport=udp" then ICE will try TURN over UDP + * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port?transport=tcp" then ICE will try TURN over TCP/TLS + * if configuration.iceServers[uriCount + 1].urls is "turns:ip:port?transport=udp", it's currently ignored because sdk dont do TURN + * over DTLS yet. if configuration.iceServers[uriCount + 1].urls is "turns:ip:port?transport=tcp" then ICE will try TURN over TCP/TLS + * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port" then ICE will try both TURN over UDP and TCP/TLS + * + * It's recommended to not pass too many TURN iceServers to configuration because it will slow down ice gathering in non-trickle mode. + */ + CHK_STATUS(signalingClientGetIceConfigInfo(data->signalingClientHandle, i, &pIceConfigInfo)); + CHECK(uriCount < MAX_ICE_SERVERS_COUNT); + uriCount += pIceConfigInfo->uriCount; + CHK_STATUS(addConfigToServerList(&(data->pRtcPeerConnection), pIceConfigInfo)); + } + } + *(data->pUriCount) += uriCount; + +CleanUp: + SAFE_MEMFREE(data); + CHK_LOG_ERR(retStatus); + return NULL; +} + STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcPeerConnection* ppRtcPeerConnection) { ENTERS(); STATUS retStatus = STATUS_SUCCESS; RtcConfiguration configuration; - UINT32 i, j, iceConfigCount, uriCount = 0, maxTurnServer = 1; +#ifndef ENABLE_KVS_THREADPOOL + UINT32 i, j, maxTurnServer = 1; PIceConfigInfo pIceConfigInfo; + UINT32 uriCount = 0; +#endif UINT64 data; PRtcCertificate pRtcCertificate = NULL; @@ -389,37 +407,6 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP SNPRINTF(configuration.iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, pSampleConfiguration->channelInfo.pRegion, pKinesisVideoStunUrlPostFix); - if (pSampleConfiguration->useTurn) { - // Set the URIs from the configuration - CHK_STATUS(signalingClientGetIceConfigInfoCount(pSampleConfiguration->signalingClientHandle, &iceConfigCount)); - - /* signalingClientGetIceConfigInfoCount can return more than one turn server. Use only one to optimize - * candidate gathering latency. But user can also choose to use more than 1 turn server. */ - for (uriCount = 0, i = 0; i < maxTurnServer; i++) { - CHK_STATUS(signalingClientGetIceConfigInfo(pSampleConfiguration->signalingClientHandle, i, &pIceConfigInfo)); - for (j = 0; j < pIceConfigInfo->uriCount; j++) { - CHECK(uriCount < MAX_ICE_SERVERS_COUNT); - /* - * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port?transport=udp" then ICE will try TURN over UDP - * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port?transport=tcp" then ICE will try TURN over TCP/TLS - * if configuration.iceServers[uriCount + 1].urls is "turns:ip:port?transport=udp", it's currently ignored because sdk dont do TURN - * over DTLS yet. if configuration.iceServers[uriCount + 1].urls is "turns:ip:port?transport=tcp" then ICE will try TURN over TCP/TLS - * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port" then ICE will try both TURN over UDP and TCP/TLS - * - * It's recommended to not pass too many TURN iceServers to configuration because it will slow down ice gathering in non-trickle mode. - */ - - STRNCPY(configuration.iceServers[uriCount + 1].urls, pIceConfigInfo->uris[j], MAX_ICE_CONFIG_URI_LEN); - STRNCPY(configuration.iceServers[uriCount + 1].credential, pIceConfigInfo->password, MAX_ICE_CONFIG_CREDENTIAL_LEN); - STRNCPY(configuration.iceServers[uriCount + 1].username, pIceConfigInfo->userName, MAX_ICE_CONFIG_USER_NAME_LEN); - - uriCount++; - } - } - } - - pSampleConfiguration->iceUriCount = uriCount + 1; - // Check if we have any pregenerated certs and use them // NOTE: We are running under the config lock retStatus = stackQueueDequeue(pSampleConfiguration->pregeneratedCertificates, &data); @@ -434,6 +421,40 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP } CHK_STATUS(createPeerConnection(&configuration, ppRtcPeerConnection)); + + if (pSampleConfiguration->useTurn) { +#ifdef ENABLE_KVS_THREADPOOL + pSampleConfiguration->iceUriCount = 1; + AsyncGetIceStruct* pAsyncData = NULL; + + pAsyncData = (AsyncGetIceStruct*) MEMCALLOC(1, SIZEOF(AsyncGetIceStruct)); + pAsyncData->signalingClientHandle = pSampleConfiguration->signalingClientHandle; + pAsyncData->pRtcPeerConnection = *ppRtcPeerConnection; + pAsyncData->pUriCount = &(pSampleConfiguration->iceUriCount); + CHK_STATUS(peerConnectionAsync(asyncGetIceConfigInfo, (PVOID) pAsyncData)); +#else + + /* signalingClientGetIceConfigInfoCount can return more than one turn server. Use only one to optimize + * candidate gathering latency. But user can also choose to use more than 1 turn server. */ + for (uriCount = 0, i = 0; i < maxTurnServer; i++) { + /* + * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port?transport=udp" then ICE will try TURN over UDP + * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port?transport=tcp" then ICE will try TURN over TCP/TLS + * if configuration.iceServers[uriCount + 1].urls is "turns:ip:port?transport=udp", it's currently ignored because sdk dont do TURN + * over DTLS yet. if configuration.iceServers[uriCount + 1].urls is "turns:ip:port?transport=tcp" then ICE will try TURN over TCP/TLS + * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port" then ICE will try both TURN over UDP and TCP/TLS + * + * It's recommended to not pass too many TURN iceServers to configuration because it will slow down ice gathering in non-trickle mode. + */ + CHK_STATUS(signalingClientGetIceConfigInfo(pSampleConfiguration->signalingClientHandle, i, &pIceConfigInfo)); + CHECK(uriCount < MAX_ICE_SERVERS_COUNT); + uriCount += pIceConfigInfo->uriCount; + CHK_STATUS(addConfigToServerList(ppRtcPeerConnection, pIceConfigInfo)); + } + pSampleConfiguration->iceUriCount = uriCount + 1; +#endif + } + CleanUp: CHK_LOG_ERR(retStatus); @@ -511,14 +532,17 @@ STATUS createSampleStreamingSession(PSampleConfiguration pSampleConfiguration, P ATOMIC_STORE_BOOL(&pSampleStreamingSession->terminateFlag, FALSE); ATOMIC_STORE_BOOL(&pSampleStreamingSession->candidateGatheringDone, FALSE); + pSampleStreamingSession->peerConnectionMetrics.peerConnectionStats.peerConnectionStartTime = GETTIME() / HUNDREDS_OF_NANOS_IN_A_MILLISECOND; CHK_STATUS(initializePeerConnection(pSampleConfiguration, &pSampleStreamingSession->pPeerConnection)); CHK_STATUS(peerConnectionOnIceCandidate(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession, onIceCandidateHandler)); CHK_STATUS( peerConnectionOnConnectionStateChange(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession, onConnectionStateChange)); +#ifdef ENABLE_DATA_CHANNEL if (pSampleConfiguration->onDataChannel != NULL) { CHK_STATUS(peerConnectionOnDataChannel(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession, pSampleConfiguration->onDataChannel)); } +#endif // Declare that we support H264,Profile=42E01F,level-asymmetry-allowed=1,packetization-mode=1 and Opus CHK_STATUS(addSupportedCodec(pSampleStreamingSession->pPeerConnection, RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE)); @@ -757,18 +781,23 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE CHK(NULL != (pSampleConfiguration = (PSampleConfiguration) MEMCALLOC(1, SIZEOF(SampleConfiguration))), STATUS_NOT_ENOUGH_MEMORY); #ifdef IOT_CORE_ENABLE_CREDENTIALS - PCHAR pIotCoreCredentialEndPoint, pIotCoreCert, pIotCorePrivateKey, pIotCoreRoleAlias; + PCHAR pIotCoreCredentialEndPoint, pIotCoreCert, pIotCorePrivateKey, pIotCoreRoleAlias, pIotCoreCertificateId, pIotCoreThingName; CHK_ERR((pIotCoreCredentialEndPoint = GETENV(IOT_CORE_CREDENTIAL_ENDPOINT)) != NULL, STATUS_INVALID_OPERATION, "AWS_IOT_CORE_CREDENTIAL_ENDPOINT must be set"); CHK_ERR((pIotCoreCert = GETENV(IOT_CORE_CERT)) != NULL, STATUS_INVALID_OPERATION, "AWS_IOT_CORE_CERT must be set"); CHK_ERR((pIotCorePrivateKey = GETENV(IOT_CORE_PRIVATE_KEY)) != NULL, STATUS_INVALID_OPERATION, "AWS_IOT_CORE_PRIVATE_KEY must be set"); CHK_ERR((pIotCoreRoleAlias = GETENV(IOT_CORE_ROLE_ALIAS)) != NULL, STATUS_INVALID_OPERATION, "AWS_IOT_CORE_ROLE_ALIAS must be set"); + CHK_ERR((pIotCoreThingName = GETENV(IOT_CORE_THING_NAME)) != NULL, STATUS_INVALID_OPERATION, "AWS_IOT_CORE_THING_NAME must be set"); #else CHK_ERR((pAccessKey = GETENV(ACCESS_KEY_ENV_VAR)) != NULL, STATUS_INVALID_OPERATION, "AWS_ACCESS_KEY_ID must be set"); CHK_ERR((pSecretKey = GETENV(SECRET_KEY_ENV_VAR)) != NULL, STATUS_INVALID_OPERATION, "AWS_SECRET_ACCESS_KEY must be set"); #endif pSessionToken = GETENV(SESSION_TOKEN_ENV_VAR); + if (pSessionToken != NULL && IS_EMPTY_STRING(pSessionToken)) { + DLOGW("Session token is set but its value is empty. Ignoring."); + pSessionToken = NULL; + } // If the env is set, we generate normal log files apart from filtered profile log files // If not set, we generate only the filtered profile log files @@ -800,7 +829,7 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE #ifdef IOT_CORE_ENABLE_CREDENTIALS CHK_STATUS(createLwsIotCredentialProvider(pIotCoreCredentialEndPoint, pIotCoreCert, pIotCorePrivateKey, pSampleConfiguration->pCaCertPath, - pIotCoreRoleAlias, channelName, &pSampleConfiguration->pCredentialProvider)); + pIotCoreRoleAlias, pIotCoreThingName, &pSampleConfiguration->pCredentialProvider)); #else CHK_STATUS( createStaticCredentialProvider(pAccessKey, 0, pSecretKey, 0, pSessionToken, 0, MAX_UINT64, &pSampleConfiguration->pCredentialProvider)); @@ -818,9 +847,15 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE * not ahead of time. */ pSampleConfiguration->trickleIce = trickleIce; pSampleConfiguration->useTurn = useTurn; + pSampleConfiguration->enableSendingMetricsToViewerViaDc = FALSE; pSampleConfiguration->channelInfo.version = CHANNEL_INFO_CURRENT_VERSION; pSampleConfiguration->channelInfo.pChannelName = channelName; +#ifdef IOT_CORE_ENABLE_CREDENTIALS + if ((pIotCoreCertificateId = GETENV(IOT_CORE_CERTIFICATE_ID)) != NULL) { + pSampleConfiguration->channelInfo.pChannelName = pIotCoreCertificateId; + } +#endif pSampleConfiguration->channelInfo.pKmsKeyId = NULL; pSampleConfiguration->channelInfo.tagCount = 0; pSampleConfiguration->channelInfo.pTags = NULL; @@ -843,8 +878,6 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE pSampleConfiguration->clientInfo.loggingLevel = logLevel; pSampleConfiguration->clientInfo.cacheFilePath = NULL; // Use the default path pSampleConfiguration->clientInfo.signalingClientCreationMaxRetryAttempts = CREATE_SIGNALING_CLIENT_RETRY_ATTEMPTS_SENTINEL_VALUE; - pSampleConfiguration->clientInfo.signalingMessagesMinimumThreads = KVS_SIGNALING_THREADPOOL_MIN; - pSampleConfiguration->clientInfo.signalingMessagesMaximumThreads = KVS_SIGNALING_THREADPOOL_MAX; pSampleConfiguration->iceCandidatePairStatsTimerId = MAX_UINT32; pSampleConfiguration->pregenerateCertTimerId = MAX_UINT32; pSampleConfiguration->signalingClientMetrics.version = SIGNALING_CLIENT_METRICS_CURRENT_VERSION; @@ -1366,7 +1399,7 @@ STATUS signalingMessageReceived(UINT64 customData, PReceivedSignalingMessage pRe { STATUS retStatus = STATUS_SUCCESS; PSampleConfiguration pSampleConfiguration = (PSampleConfiguration) customData; - BOOL peerConnectionFound = FALSE, locked = FALSE, startStats = FALSE; + BOOL peerConnectionFound = FALSE, locked = FALSE, startStats = FALSE, freeStreamingSession = FALSE; UINT32 clientIdHash; UINT64 hashValue = 0; PPendingMessageQueue pPendingMessageQueue = NULL; @@ -1412,10 +1445,7 @@ STATUS signalingMessageReceived(UINT64 customData, PReceivedSignalingMessage pRe } CHK_STATUS(createSampleStreamingSession(pSampleConfiguration, pReceivedSignalingMessage->signalingMessage.peerClientId, TRUE, &pSampleStreamingSession)); - MUTEX_LOCK(pSampleConfiguration->streamingSessionListReadLock); - pSampleConfiguration->sampleStreamingSessionList[pSampleConfiguration->streamingSessionCount++] = pSampleStreamingSession; - MUTEX_UNLOCK(pSampleConfiguration->streamingSessionListReadLock); - + freeStreamingSession = TRUE; CHK_STATUS(handleOffer(pSampleConfiguration, pSampleStreamingSession, &pReceivedSignalingMessage->signalingMessage)); CHK_STATUS(hashTablePut(pSampleConfiguration->pRtcPeerConnectionForRemoteClient, clientIdHash, (UINT64) pSampleStreamingSession)); @@ -1429,6 +1459,11 @@ STATUS signalingMessageReceived(UINT64 customData, PReceivedSignalingMessage pRe pPendingMessageQueue = NULL; } + MUTEX_LOCK(pSampleConfiguration->streamingSessionListReadLock); + pSampleConfiguration->sampleStreamingSessionList[pSampleConfiguration->streamingSessionCount++] = pSampleStreamingSession; + MUTEX_UNLOCK(pSampleConfiguration->streamingSessionListReadLock); + freeStreamingSession = FALSE; + startStats = pSampleConfiguration->iceCandidatePairStatsTimerId == MAX_UINT32; break; @@ -1513,6 +1548,10 @@ STATUS signalingMessageReceived(UINT64 customData, PReceivedSignalingMessage pRe freeMessageQueue(pPendingMessageQueue); } + if (freeStreamingSession && pSampleStreamingSession != NULL) { + freeSampleStreamingSession(&pSampleStreamingSession); + } + if (locked) { MUTEX_UNLOCK(pSampleConfiguration->sampleConfigurationObjLock); } @@ -1630,3 +1669,181 @@ STATUS removeExpiredMessageQueues(PStackQueue pPendingQueue) return retStatus; } + +#ifdef ENABLE_DATA_CHANNEL +VOID onDataChannelMessage(UINT64 customData, PRtcDataChannel pDataChannel, BOOL isBinary, PBYTE pMessage, UINT32 pMessageLen) +{ + STATUS retStatus = STATUS_SUCCESS; + UINT32 i, strLen, tokenCount; + CHAR pMessageSend[MAX_DATA_CHANNEL_METRICS_MESSAGE_SIZE], errorMessage[200]; + PCHAR json; + PSampleStreamingSession pSampleStreamingSession = (PSampleStreamingSession) customData; + PSampleConfiguration pSampleConfiguration; + DataChannelMessage dataChannelMessage; + jsmn_parser parser; + jsmntok_t tokens[MAX_JSON_TOKEN_COUNT]; + + CHK(pMessage != NULL && pDataChannel != NULL, STATUS_NULL_ARG); + + if (pSampleStreamingSession == NULL) { + STRCPY(errorMessage, "Could not generate stats since the streaming session is NULL"); + retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) errorMessage, STRLEN(errorMessage)); + DLOGE("%s", errorMessage); + goto CleanUp; + } + + pSampleConfiguration = pSampleStreamingSession->pSampleConfiguration; + if (pSampleConfiguration == NULL) { + STRCPY(errorMessage, "Could not generate stats since the sample configuration is NULL"); + retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) errorMessage, STRLEN(errorMessage)); + DLOGE("%s", errorMessage); + goto CleanUp; + } + + if (pSampleConfiguration->enableSendingMetricsToViewerViaDc) { + jsmn_init(&parser); + json = (PCHAR) pMessage; + tokenCount = jsmn_parse(&parser, json, STRLEN(json), tokens, SIZEOF(tokens) / SIZEOF(jsmntok_t)); + + MEMSET(dataChannelMessage.content, '\0', SIZEOF(dataChannelMessage.content)); + MEMSET(dataChannelMessage.firstMessageFromViewerTs, '\0', SIZEOF(dataChannelMessage.firstMessageFromViewerTs)); + MEMSET(dataChannelMessage.firstMessageFromMasterTs, '\0', SIZEOF(dataChannelMessage.firstMessageFromMasterTs)); + MEMSET(dataChannelMessage.secondMessageFromViewerTs, '\0', SIZEOF(dataChannelMessage.secondMessageFromViewerTs)); + MEMSET(dataChannelMessage.secondMessageFromMasterTs, '\0', SIZEOF(dataChannelMessage.secondMessageFromMasterTs)); + MEMSET(dataChannelMessage.lastMessageFromViewerTs, '\0', SIZEOF(dataChannelMessage.lastMessageFromViewerTs)); + + if (tokenCount > 1) { + if (tokens[0].type != JSMN_OBJECT) { + STRCPY(errorMessage, "Invalid JSON received, please send a valid json as the SDK is operating in datachannel-benchmarking mode"); + retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) errorMessage, STRLEN(errorMessage)); + DLOGE("%s", errorMessage); + retStatus = STATUS_INVALID_API_CALL_RETURN_JSON; + goto CleanUp; + } + DLOGI("DataChannel json message: %.*s\n", pMessageLen, pMessage); + + for (i = 1; i < tokenCount; i++) { + if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "content")) { + strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start); + if (strLen != 0) { + STRNCPY(dataChannelMessage.content, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + } + } else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "firstMessageFromViewerTs")) { + strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start); + // parse and retain this message from the viewer to send it back again + if (strLen != 0) { + // since the length is not zero, we have already attached this timestamp to structure in the last iteration + STRNCPY(dataChannelMessage.firstMessageFromViewerTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + } + } else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "firstMessageFromMasterTs")) { + strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start); + if (strLen != 0) { + // since the length is not zero, we have already attached this timestamp to structure in the last iteration + STRNCPY(dataChannelMessage.firstMessageFromMasterTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + } else { + // if this timestamp was not assigned during the previous message session, add it now + SNPRINTF(dataChannelMessage.firstMessageFromMasterTs, 20, "%llu", GETTIME() / 10000); + break; + } + } else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "secondMessageFromViewerTs")) { + strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start); + // parse and retain this message from the viewer to send it back again + if (strLen != 0) { + STRNCPY(dataChannelMessage.secondMessageFromViewerTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + } + } else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "secondMessageFromMasterTs")) { + strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start); + if (strLen != 0) { + // since the length is not zero, we have already attached this timestamp to structure in the last iteration + STRNCPY(dataChannelMessage.secondMessageFromMasterTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + } else { + // if this timestamp was not assigned during the previous message session, add it now + SNPRINTF(dataChannelMessage.secondMessageFromMasterTs, 20, "%llu", GETTIME() / 10000); + break; + } + } else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "lastMessageFromViewerTs")) { + strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start); + if (strLen != 0) { + STRNCPY(dataChannelMessage.lastMessageFromViewerTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + } + } + } + + if (STRLEN(dataChannelMessage.lastMessageFromViewerTs) == 0) { + // continue sending the data_channel_metrics_message with new timestamps until we receive the lastMessageFromViewerTs from the viewer + SNPRINTF(pMessageSend, MAX_DATA_CHANNEL_METRICS_MESSAGE_SIZE, DATA_CHANNEL_MESSAGE_TEMPLATE, MASTER_DATA_CHANNEL_MESSAGE, + dataChannelMessage.firstMessageFromViewerTs, dataChannelMessage.firstMessageFromMasterTs, + dataChannelMessage.secondMessageFromViewerTs, dataChannelMessage.secondMessageFromMasterTs, + dataChannelMessage.lastMessageFromViewerTs); + DLOGI("Master's response: %s", pMessageSend); + + retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) pMessageSend, STRLEN(pMessageSend)); + } else { + // now that we've received the last message, send across the signaling, peerConnection, ice metrics + SNPRINTF(pSampleStreamingSession->pSignalingClientMetricsMessage, MAX_SIGNALING_CLIENT_METRICS_MESSAGE_SIZE, + SIGNALING_CLIENT_METRICS_JSON_TEMPLATE, pSampleConfiguration->signalingClientMetrics.signalingStartTime, + pSampleConfiguration->signalingClientMetrics.signalingEndTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.offerReceivedTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.answerTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.describeChannelStartTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.describeChannelEndTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.getSignalingChannelEndpointStartTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.getSignalingChannelEndpointEndTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.getIceServerConfigStartTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.getIceServerConfigEndTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.getTokenStartTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.getTokenEndTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.createChannelStartTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.createChannelEndTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.connectStartTime, + pSampleConfiguration->signalingClientMetrics.signalingClientStats.connectEndTime); + DLOGI("Sending signaling metrics to the viewer: %s", pSampleStreamingSession->pSignalingClientMetricsMessage); + + CHK_STATUS(peerConnectionGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->peerConnectionMetrics)); + SNPRINTF(pSampleStreamingSession->pPeerConnectionMetricsMessage, MAX_PEER_CONNECTION_METRICS_MESSAGE_SIZE, + PEER_CONNECTION_METRICS_JSON_TEMPLATE, + pSampleStreamingSession->peerConnectionMetrics.peerConnectionStats.peerConnectionStartTime, + pSampleStreamingSession->peerConnectionMetrics.peerConnectionStats.peerConnectionConnectedTime); + DLOGI("Sending peer-connection metrics to the viewer: %s", pSampleStreamingSession->pPeerConnectionMetricsMessage); + + CHK_STATUS(iceAgentGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->iceMetrics)); + SNPRINTF(pSampleStreamingSession->pIceAgentMetricsMessage, MAX_ICE_AGENT_METRICS_MESSAGE_SIZE, ICE_AGENT_METRICS_JSON_TEMPLATE, + pSampleStreamingSession->iceMetrics.kvsIceAgentStats.candidateGatheringStartTime, + pSampleStreamingSession->iceMetrics.kvsIceAgentStats.candidateGatheringEndTime); + DLOGI("Sending ice-agent metrics to the viewer: %s", pSampleStreamingSession->pIceAgentMetricsMessage); + + retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) pSampleStreamingSession->pSignalingClientMetricsMessage, + STRLEN(pSampleStreamingSession->pSignalingClientMetricsMessage)); + retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) pSampleStreamingSession->pPeerConnectionMetricsMessage, + STRLEN(pSampleStreamingSession->pPeerConnectionMetricsMessage)); + retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) pSampleStreamingSession->pIceAgentMetricsMessage, + STRLEN(pSampleStreamingSession->pIceAgentMetricsMessage)); + } + } else { + DLOGI("DataChannel string message: %.*s\n", pMessageLen, pMessage); + STRCPY(errorMessage, "Send a json message for benchmarking as the C SDK is operating in benchmarking mode"); + retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) errorMessage, STRLEN(errorMessage)); + } + } else { + if (isBinary) { + DLOGI("DataChannel Binary Message"); + } else { + DLOGI("DataChannel String Message: %.*s\n", pMessageLen, pMessage); + } + // Send a response to the message sent by the viewer + retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) MASTER_DATA_CHANNEL_MESSAGE, STRLEN(MASTER_DATA_CHANNEL_MESSAGE)); + } + if (retStatus != STATUS_SUCCESS) { + DLOGI("[KVS Master] dataChannelSend(): operation returned status code: 0x%08x \n", retStatus); + } + +CleanUp: + CHK_LOG_ERR(retStatus); +} + +VOID onDataChannel(UINT64 customData, PRtcDataChannel pRtcDataChannel) +{ + DLOGI("New DataChannel has been opened %s \n", pRtcDataChannel->name); + dataChannelOnMessage(pRtcDataChannel, customData, onDataChannelMessage); +} +#endif diff --git a/samples/Samples.h b/samples/Samples.h index 41c34897af..83644c0689 100644 --- a/samples/Samples.h +++ b/samples/Samples.h @@ -49,18 +49,28 @@ extern "C" { #define IOT_CORE_THING_NAME ((PCHAR) "AWS_IOT_CORE_THING_NAME") #define IOT_CORE_CERTIFICATE_ID ((PCHAR) "AWS_IOT_CORE_CERTIFICATE_ID") +/* Uncomment the following line in order to enable IoT credentials checks in the provided samples */ +// #define IOT_CORE_ENABLE_CREDENTIALS 1 + #define MASTER_DATA_CHANNEL_MESSAGE "This message is from the KVS Master" #define VIEWER_DATA_CHANNEL_MESSAGE "This message is from the KVS Viewer" -// Signaling client threadpool for handling messages -#define KVS_SIGNALING_THREADPOOL_MIN 3 -#define KVS_SIGNALING_THREADPOOL_MAX 5 - -// comment out this line to disable the feature -#define KVS_USE_SIGNALING_CHANNEL_THREADPOOL 1 - -/* Uncomment the following line in order to enable IoT credentials checks in the provided samples */ -// #define IOT_CORE_ENABLE_CREDENTIALS 1 +#define DATA_CHANNEL_MESSAGE_TEMPLATE \ + "{\"content\":\"%s\",\"firstMessageFromViewerTs\":\"%s\",\"firstMessageFromMasterTs\":\"%s\",\"secondMessageFromViewerTs\":\"%s\"," \ + "\"secondMessageFromMasterTs\":\"%s\",\"lastMessageFromViewerTs\":\"%s\" }" +#define PEER_CONNECTION_METRICS_JSON_TEMPLATE "{\"peerConnectionStartTime\": %llu, \"peerConnectionEndTime\": %llu }" +#define SIGNALING_CLIENT_METRICS_JSON_TEMPLATE \ + "{\"signalingStartTime\": %llu, \"signalingEndTime\": %llu, \"offerReceiptTime\": %llu, \"sendAnswerTime\": %llu, " \ + "\"describeChannelStartTime\": %llu, \"describeChannelEndTime\": %llu, \"getSignalingChannelEndpointStartTime\": %llu, " \ + "\"getSignalingChannelEndpointEndTime\": %llu, \"getIceServerConfigStartTime\": %llu, \"getIceServerConfigEndTime\": %llu, " \ + "\"getTokenStartTime\": %llu, \"getTokenEndTime\": %llu, \"createChannelStartTime\": %llu, \"createChannelEndTime\": %llu, " \ + "\"connectStartTime\": %llu, \"connectEndTime\": %llu }" +#define ICE_AGENT_METRICS_JSON_TEMPLATE "{\"candidateGatheringStartTime\": %llu, \"candidateGatheringEndTime\": %llu }" + +#define MAX_DATA_CHANNEL_METRICS_MESSAGE_SIZE 260 // strlen(DATA_CHANNEL_MESSAGE_TEMPLATE) + 20 * 5 +#define MAX_PEER_CONNECTION_METRICS_MESSAGE_SIZE 105 // strlen(PEER_CONNECTION_METRICS_JSON_TEMPLATE) + 20 * 2 +#define MAX_SIGNALING_CLIENT_METRICS_MESSAGE_SIZE 736 // strlen(SIGNALING_CLIENT_METRICS_JSON_TEMPLATE) + 20 * 10 +#define MAX_ICE_AGENT_METRICS_MESSAGE_SIZE 113 // strlen(ICE_AGENT_METRICS_JSON_TEMPLATE) + 20 * 2 typedef enum { SAMPLE_STREAMING_VIDEO_ONLY, @@ -119,6 +129,7 @@ typedef struct { CVAR cvar; BOOL trickleIce; BOOL useTurn; + BOOL enableSendingMetricsToViewerViaDc; BOOL enableFileLogging; UINT64 customData; PSampleStreamingSession sampleStreamingSessionList[DEFAULT_MAX_CONCURRENT_STREAMING_SESSION]; @@ -139,6 +150,15 @@ typedef struct { UINT32 logLevel; } SampleConfiguration, *PSampleConfiguration; +typedef struct { + CHAR content[100]; + CHAR firstMessageFromViewerTs[20]; + CHAR firstMessageFromMasterTs[20]; + CHAR secondMessageFromViewerTs[20]; + CHAR secondMessageFromMasterTs[20]; + CHAR lastMessageFromViewerTs[20]; +} DataChannelMessage; + typedef struct { UINT64 hashValue; UINT64 createTime; @@ -173,8 +193,20 @@ struct __SampleStreamingSession { UINT64 offerReceiveTime; PeerConnectionMetrics peerConnectionMetrics; KvsIceAgentMetrics iceMetrics; + CHAR pPeerConnectionMetricsMessage[MAX_PEER_CONNECTION_METRICS_MESSAGE_SIZE]; + CHAR pSignalingClientMetricsMessage[MAX_SIGNALING_CLIENT_METRICS_MESSAGE_SIZE]; + CHAR pIceAgentMetricsMessage[MAX_ICE_AGENT_METRICS_MESSAGE_SIZE]; }; +// TODO this should all be in a higher webrtccontext layer above PeerConnection +// Placing it here now since this is where all the current webrtccontext functions are placed +typedef struct { + SIGNALING_CLIENT_HANDLE signalingClientHandle; + PRtcPeerConnection pRtcPeerConnection; + PUINT32 pUriCount; + +} AsyncGetIceStruct; + VOID sigintHandler(INT32); STATUS readFrameFromDisk(PBYTE, PUINT32, PCHAR); PVOID sendVideoPackets(PVOID); diff --git a/samples/kvsWebRTCClientMaster.c b/samples/kvsWebRTCClientMaster.c index f4a4529685..fcc5a1835b 100644 --- a/samples/kvsWebRTCClientMaster.c +++ b/samples/kvsWebRTCClientMaster.c @@ -19,7 +19,8 @@ INT32 main(INT32 argc, CHAR* argv[]) #endif #ifdef IOT_CORE_ENABLE_CREDENTIALS - CHK_ERR((pChannelName = getenv(IOT_CORE_THING_NAME)) != NULL, STATUS_INVALID_OPERATION, "AWS_IOT_CORE_THING_NAME must be set"); + CHK_ERR((pChannelName = argc > 1 ? argv[1] : GETENV(IOT_CORE_THING_NAME)) != NULL, STATUS_INVALID_OPERATION, + "AWS_IOT_CORE_THING_NAME must be set"); #else pChannelName = argc > 1 ? argv[1] : SAMPLE_CHANNEL_NAME; #endif @@ -53,7 +54,11 @@ INT32 main(INT32 argc, CHAR* argv[]) CHK_STATUS(initKvsWebRtc()); DLOGI("[KVS Master] KVS WebRTC initialization completed successfully"); - CHK_STATUS(initSignaling(pSampleConfiguration, SAMPLE_MASTER_CLIENT_ID)); + PROFILE_CALL_WITH_START_END_T_OBJ( + retStatus = initSignaling(pSampleConfiguration, SAMPLE_MASTER_CLIENT_ID), pSampleConfiguration->signalingClientMetrics.signalingStartTime, + pSampleConfiguration->signalingClientMetrics.signalingEndTime, pSampleConfiguration->signalingClientMetrics.signalingCallTime, + "Initialize signaling client and connect to the signaling channel"); + DLOGI("[KVS Master] Channel %s set up done ", pChannelName); // Checking for termination @@ -175,6 +180,9 @@ PVOID sendVideoPackets(PVOID args) if (status != STATUS_SUCCESS) { DLOGV("writeFrame() failed with 0x%08x", status); } + } else { + // Reset file index to ensure first frame sent upon SRTP ready is a key frame. + fileIndex = 0; } } MUTEX_UNLOCK(pSampleConfiguration->streamingSessionListReadLock); @@ -238,6 +246,9 @@ PVOID sendAudioPackets(PVOID args) PROFILE_WITH_START_TIME(pSampleConfiguration->sampleStreamingSessionList[i]->offerReceiveTime, "Time to first frame"); pSampleConfiguration->sampleStreamingSessionList[i]->firstFrame = FALSE; } + } else { + // Reset file index to stay in sync with video frames. + fileIndex = 0; } } MUTEX_UNLOCK(pSampleConfiguration->streamingSessionListReadLock); diff --git a/samples/kvsWebRTCClientViewer.c b/samples/kvsWebRTCClientViewer.c index 000aa61261..bde875ce6e 100644 --- a/samples/kvsWebRTCClientViewer.c +++ b/samples/kvsWebRTCClientViewer.c @@ -51,7 +51,8 @@ INT32 main(INT32 argc, CHAR* argv[]) #endif #ifdef IOT_CORE_ENABLE_CREDENTIALS - CHK_ERR((pChannelName = getenv(IOT_CORE_THING_NAME)) != NULL, STATUS_INVALID_OPERATION, "AWS_IOT_CORE_THING_NAME must be set"); + CHK_ERR((pChannelName = argc > 1 ? argv[1] : GETENV(IOT_CORE_THING_NAME)) != NULL, STATUS_INVALID_OPERATION, + "AWS_IOT_CORE_THING_NAME must be set"); #else pChannelName = argc > 1 ? argv[1] : SAMPLE_CHANNEL_NAME; #endif @@ -82,6 +83,7 @@ INT32 main(INT32 argc, CHAR* argv[]) MEMSET(&offerSessionDescriptionInit, 0x00, SIZEOF(RtcSessionDescriptionInit)); + offerSessionDescriptionInit.useTrickleIce = pSampleStreamingSession->remoteCanTrickleIce; CHK_STATUS(setLocalDescription(pSampleStreamingSession->pPeerConnection, &offerSessionDescriptionInit)); DLOGI("[KVS Viewer] Completed setting local description"); @@ -162,12 +164,12 @@ INT32 main(INT32 argc, CHAR* argv[]) if (pSampleConfiguration != NULL) { retStatus = freeSignalingClient(&pSampleConfiguration->signalingClientHandle); if (retStatus != STATUS_SUCCESS) { - DLOGE("[KVS Master] freeSignalingClient(): operation returned status code: 0x%08x ", retStatus); + DLOGE("[KVS Viewer] freeSignalingClient(): operation returned status code: 0x%08x ", retStatus); } retStatus = freeSampleConfiguration(&pSampleConfiguration); if (retStatus != STATUS_SUCCESS) { - DLOGE("[KVS Master] freeSampleConfiguration(): operation returned status code: 0x%08x ", retStatus); + DLOGE("[KVS Viewer] freeSampleConfiguration(): operation returned status code: 0x%08x ", retStatus); } } DLOGI("[KVS Viewer] Cleanup done"); diff --git a/samples/kvsWebrtcClientMasterGstSample.c b/samples/kvsWebrtcClientMasterGstSample.c index 0ccf87b5bd..2cd75ba181 100644 --- a/samples/kvsWebrtcClientMasterGstSample.c +++ b/samples/kvsWebrtcClientMasterGstSample.c @@ -78,11 +78,13 @@ GstFlowReturn on_new_sample(GstElement* sink, gpointer data, UINT64 trackid) status = writeFrame(pRtcRtpTransceiver, &frame); if (status != STATUS_SRTP_NOT_READY_YET && status != STATUS_SUCCESS) { #ifdef VERBOSE - DLOGE("writeFrame() failed with 0x%08x", status); + DLOGE("[KVS GStreamer Master] writeFrame() failed with 0x%08x", status); #endif } else if (status == STATUS_SUCCESS && pSampleStreamingSession->firstFrame) { PROFILE_WITH_START_TIME(pSampleStreamingSession->offerReceiveTime, "Time to first frame"); pSampleStreamingSession->firstFrame = FALSE; + } else if (status == STATUS_SRTP_NOT_READY_YET) { + DLOGI("[KVS GStreamer Master] SRTP not ready yet, dropping frame"); } } MUTEX_UNLOCK(pSampleConfiguration->streamingSessionListReadLock); @@ -180,7 +182,7 @@ PVOID sendGstreamerAudioVideo(PVOID args) pSampleConfiguration->rtspUri); if (stringOutcome > RTSP_PIPELINE_MAX_CHAR_COUNT) { - printf("[KVS GStreamer Master] ERROR: rtsp uri entered exceeds maximum allowed length set by RTSP_PIPELINE_MAX_CHAR_COUNT\n"); + DLOGE("[KVS GStreamer Master] ERROR: rtsp uri entered exceeds maximum allowed length set by RTSP_PIPELINE_MAX_CHAR_COUNT"); goto CleanUp; } pipeline = gst_parse_launch(rtspPipeLineBuffer, &error); @@ -226,7 +228,7 @@ PVOID sendGstreamerAudioVideo(PVOID args) pSampleConfiguration->rtspUri); if (stringOutcome > RTSP_PIPELINE_MAX_CHAR_COUNT) { - printf("[KVS GStreamer Master] ERROR: rtsp uri entered exceeds maximum allowed length set by RTSP_PIPELINE_MAX_CHAR_COUNT\n"); + DLOGE("[KVS GStreamer Master] ERROR: rtsp uri entered exceeds maximum allowed length set by RTSP_PIPELINE_MAX_CHAR_COUNT"); goto CleanUp; } pipeline = gst_parse_launch(rtspPipeLineBuffer, &error); @@ -243,8 +245,7 @@ PVOID sendGstreamerAudioVideo(PVOID args) appsinkAudio = gst_bin_get_by_name(GST_BIN(pipeline), "appsink-audio"); if (!(appsinkVideo != NULL || appsinkAudio != NULL)) { - printf("[KVS GStreamer Master] sendGstreamerAudioVideo(): cant find appsink, operation returned status code: 0x%08x \n", - STATUS_INTERNAL_ERROR); + DLOGE("[KVS GStreamer Master] sendGstreamerAudioVideo(): cant find appsink, operation returned status code: 0x%08x", STATUS_INTERNAL_ERROR); goto CleanUp; } @@ -281,7 +282,7 @@ PVOID sendGstreamerAudioVideo(PVOID args) CleanUp: if (error != NULL) { - DLOGE("%s", error->message); + DLOGE("[KVS GStreamer Master] %s", error->message); g_clear_error(&error); } @@ -377,7 +378,7 @@ PVOID receiveGstreamerAudioVideo(PVOID args) CleanUp: if (error != NULL) { - DLOGE("%s", error->message); + DLOGE("[KVS GStreamer Master] %s", error->message); g_clear_error(&error); } @@ -396,7 +397,8 @@ INT32 main(INT32 argc, CHAR* argv[]) signal(SIGINT, sigintHandler); #ifdef IOT_CORE_ENABLE_CREDENTIALS - CHK_ERR((pChannelName = getenv(IOT_CORE_THING_NAME)) != NULL, STATUS_INVALID_OPERATION, "AWS_IOT_CORE_THING_NAME must be set"); + CHK_ERR((pChannelName = argc > 1 ? argv[1] : GETENV(IOT_CORE_THING_NAME)) != NULL, STATUS_INVALID_OPERATION, + "AWS_IOT_CORE_THING_NAME must be set"); #else pChannelName = argc > 1 ? argv[1] : SAMPLE_CHANNEL_NAME; #endif @@ -444,9 +446,9 @@ INT32 main(INT32 argc, CHAR* argv[]) } else if (STRCMP(argv[3], "rtspsrc") == 0) { DLOGI("[KVS GStreamer Master] Using RTSP source in GStreamer"); if (argc < 5) { - printf("[KVS GStreamer Master] No RTSP source URI included. Defaulting to device source"); - printf("[KVS GStreamer Master] Usage: ./kvsWebrtcClientMasterGstSample audio-video rtspsrc rtsp://\n" - "or ./kvsWebrtcClientMasterGstSample video-only rtspsrc "); + DLOGI("[KVS GStreamer Master] No RTSP source URI included. Defaulting to device source"); + DLOGI("[KVS GStreamer Master] Usage: ./kvsWebrtcClientMasterGstSample audio-video rtspsrc rtsp://" + "or ./kvsWebrtcClientMasterGstSample video-only rtspsrc "); pSampleConfiguration->srcType = DEVICE_SOURCE; } else { pSampleConfiguration->srcType = RTSP_SOURCE; @@ -456,7 +458,7 @@ INT32 main(INT32 argc, CHAR* argv[]) DLOGI("[KVS Gstreamer Master] Unrecognized source type. Defaulting to device source in GStreamer"); } } else { - printf("[KVS GStreamer Master] Using device source in GStreamer\n"); + DLOGI("[KVS GStreamer Master] Using device source in GStreamer"); } switch (pSampleConfiguration->mediaType) { diff --git a/scripts/check-sample.sh b/scripts/check-sample.sh index e5312a4f11..d4d8cdbe5b 100755 --- a/scripts/check-sample.sh +++ b/scripts/check-sample.sh @@ -1,6 +1,6 @@ #!/bin/bash -if [[ -z "$AWS_ACCESS_KEY_ID" || -z "$AWS_SECRET_ACCESS_KEY" ]] +if [[ -z "$AWS_ACCESS_KEY_ID" || -z "$AWS_SECRET_ACCESS_KEY" || -z "$AWS_SESSION_TOKEN" ]] then echo "Couldn't find AWS credentials. Very likely this build is coming from a fork. Ignoring." exit 0 diff --git a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h index dce4517605..3fcadf5693 100644 --- a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h +++ b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h @@ -46,6 +46,23 @@ extern "C" { DLOGP("[%s] Time taken: %" PRIu64 " ms", msg, (GETTIME() - (t)) / HUNDREDS_OF_NANOS_IN_A_MILLISECOND); \ } while (FALSE) +#define PROFILE_CALL_WITH_START_END_T_OBJ(f, s, e, d, msg) \ + do { \ + s = GETTIME() / HUNDREDS_OF_NANOS_IN_A_MILLISECOND; \ + f; \ + e = GETTIME() / HUNDREDS_OF_NANOS_IN_A_MILLISECOND; \ + d = ((e) - (s)); \ + DLOGP("[%s] Time taken: %" PRIu64 " ms", (msg), (d)); \ + } while (FALSE) + +#define PROFILE_WITH_START_END_TIME_OBJ(t1, t2, d, msg) \ + do { \ + t1 = (t1 / HUNDREDS_OF_NANOS_IN_A_MILLISECOND); \ + t2 = (GETTIME() / HUNDREDS_OF_NANOS_IN_A_MILLISECOND); \ + d = ((t2) - (t1)); \ + DLOGP("[%s] Time taken: %" PRIu64 " ms", (msg), (d)); \ + } while (FALSE) + #define PROFILE_WITH_START_TIME_OBJ(t1, t2, msg) \ do { \ t2 = (GETTIME() - (t1)) / HUNDREDS_OF_NANOS_IN_A_MILLISECOND; \ @@ -191,7 +208,7 @@ extern "C" { #define STATUS_SSL_PACKET_BEFORE_DTLS_READY STATUS_DTLS_BASE + 0x00000004 #define STATUS_SSL_UNKNOWN_SRTP_PROFILE STATUS_DTLS_BASE + 0x00000005 #define STATUS_SSL_INVALID_CERTIFICATE_BITS STATUS_DTLS_BASE + 0x00000006 - +#define STATUS_DTLS_SESSION_ALREADY_FREED STATUS_DTLS_BASE + 0x00000007 /*!@} */ ///////////////////////////////////////////////////// @@ -236,7 +253,10 @@ extern "C" { #define STATUS_TURN_CONNECTION_PEER_NOT_USABLE STATUS_ICE_BASE + 0x00000027 #define STATUS_ICE_SERVER_INDEX_INVALID STATUS_ICE_BASE + 0x00000028 #define STATUS_ICE_CANDIDATE_STRING_MISSING_TYPE STATUS_ICE_BASE + 0x00000029 -#define STATUS_TURN_CONNECTION_ALLOCAITON_FAILED STATUS_ICE_BASE + 0x0000002a +#define STATUS_TURN_CONNECTION_ALLOCATION_FAILED STATUS_ICE_BASE + 0x0000002a +#define STATUS_TURN_INVALID_STATE STATUS_ICE_BASE + 0x0000002b +#define STATUS_TURN_CONNECTION_GET_CREDENTIALS_FAILED STATUS_ICE_BASE + 0x0000002c + /*!@} */ ///////////////////////////////////////////////////// @@ -356,6 +376,7 @@ extern "C" { #define STATUS_PEERCONNECTION_CREATE_ANSWER_WITHOUT_REMOTE_DESCRIPTION STATUS_PEERCONNECTION_BASE + 0x00000001 #define STATUS_PEERCONNECTION_CODEC_INVALID STATUS_PEERCONNECTION_BASE + 0x00000002 #define STATUS_PEERCONNECTION_CODEC_MAX_EXCEEDED STATUS_PEERCONNECTION_BASE + 0x00000003 +#define STATUS_PEERCONNECTION_EARLY_DNS_RESOLUTION_FAILED STATUS_PEERCONNECTION_BASE + 0x00000004 /*!@} */ ///////////////////////////////////////////////////// @@ -639,6 +660,26 @@ extern "C" { */ #define SIGNALING_CONNECT_TIMEOUT (5 * HUNDREDS_OF_NANOS_IN_A_SECOND) +/** + * Default minimum number of threads in the threadpool for the SDK + */ +#define THREADPOOL_MIN_THREADS 3 + +/** + * Default maximum number of threads in the threadpool for the SDK + */ +#define THREADPOOL_MAX_THREADS 10 + +/** + * Env to set minimum number of threads in the threadpool for the KVS SDK + */ +#define WEBRTC_THREADPOOL_MIN_THREADS_ENV_VAR (PCHAR) "AWS_KVS_WEBRTC_THREADPOOL_MIN_THREADS" + +/** + * Env to set maximum number of threads in the threadpool for the SDK + */ +#define WEBRTC_THREADPOOL_MAX_THREADS_ENV_VAR (PCHAR) "AWS_KVS_WEBRTC_THREADPOOL_MAX_THREADS" + #ifdef _WIN32 /** * Default timeout for sending data @@ -686,9 +727,10 @@ extern "C" { /** * Parameterized string for KVS STUN Server */ -#define KINESIS_VIDEO_STUN_URL_POSTFIX "amazonaws.com" -#define KINESIS_VIDEO_STUN_URL_POSTFIX_CN "amazonaws.com.cn" -#define KINESIS_VIDEO_STUN_URL "stun:stun.kinesisvideo.%s.%s:443" +#define KINESIS_VIDEO_STUN_URL_POSTFIX "amazonaws.com" +#define KINESIS_VIDEO_STUN_URL_POSTFIX_CN "amazonaws.com.cn" +#define KINESIS_VIDEO_STUN_URL "stun:stun.kinesisvideo.%s.%s:443" +#define KINESIS_VIDEO_STUN_URL_WITHOUT_PORT "stun.kinesisvideo.%s.%s" /** * Default signaling SSL port @@ -1139,7 +1181,7 @@ typedef struct { //!< USE_CANDIDATE attribute. If client is ice controlled, this is the timeout for receiving binding request //!< that has USE_CANDIDATE attribute after connection check is done. Use default value if 0. - UINT32 iceConnectionCheckPollingInterval; //!< Ta in https://tools.ietf.org/html/rfc8445 + UINT32 iceConnectionCheckPollingInterval; //!< Ta in https://datatracker.ietf.org/doc/html/rfc8445#section-14.2 //!< rate at which binding request packets are sent during connection check. Use default interval if 0. INT32 generatedCertificateBits; //!< GeneratedCertificateBits controls the amount of bits the locally generated self-signed certificate uses @@ -1201,6 +1243,7 @@ typedef struct { */ typedef struct { SDP_TYPE type; //!< Indicates an offer/answer SDP type + BOOL useTrickleIce; //!< Indicates if an offer should set trickle ice CHAR sdp[MAX_SESSION_DESCRIPTION_INIT_SDP_LEN + 1]; //!< SDP Data containing media capabilities, transport addresses //!< and related metadata in a transport agnostic manner //!< @@ -1264,10 +1307,10 @@ typedef struct { //!< being used this value can be NULL or point to an EMPTY_STRING. KvsRetryStrategyCallbacks signalingRetryStrategyCallbacks; //!< Retry strategy callbacks used while creating signaling client INT32 signalingClientCreationMaxRetryAttempts; //!< Max attempts to create signaling client before returning error to the caller - UINT32 stateMachineRetryCountReadOnly; //!< Retry count of state machine. Note that this **MUST NOT** be modified by the user. It is a read only - //!< field - UINT32 signalingMessagesMinimumThreads; - UINT32 signalingMessagesMaximumThreads; + UINT32 stateMachineRetryCountReadOnly; //!< Retry count of state machine. Note that this **MUST NOT** be modified by the user. It is a read only + //!< field + UINT32 signalingMessagesMinimumThreads; //!< Unused field post v1.8.1 + UINT32 signalingMessagesMaximumThreads; //!< Unused field post v1.8.1 } SignalingClientInfo, *PSignalingClientInfo; /** @@ -1506,7 +1549,10 @@ typedef struct { * @brief SignalingStats Collection of signaling related stats. Can be expanded in the future */ typedef struct { - UINT32 version; //!< Structure version + UINT32 version; //!< Structure version + UINT64 signalingStartTime; + UINT64 signalingEndTime; + UINT64 signalingCallTime; SignalingClientStats signalingClientStats; //!< Signaling client metrics stats. Reference in Stats.h } SignalingClientMetrics, *PSignalingClientMetrics; @@ -1573,6 +1619,16 @@ typedef struct { */ PUBLIC_API STATUS createPeerConnection(PRtcConfiguration, PRtcPeerConnection*); +/** + * @brief Give peer connection an ice config to add to its server list + * + * @param[in] PRtcPeerConnection* initialized RtcPeerConnection + * @param[in] PIceConfigInfo Ice config info to add to this peer connection + * + * @return STATUS code of the execution. STATUS_SUCCESS on success + */ +PUBLIC_API STATUS addConfigToServerList(PRtcPeerConnection*, PIceConfigInfo); + /** * @brief Free a RtcPeerConnection * @@ -1649,6 +1705,16 @@ PUBLIC_API STATUS peerConnectionGetLocalDescription(PRtcPeerConnection, PRtcSess */ PUBLIC_API STATUS peerConnectionGetCurrentLocalDescription(PRtcPeerConnection, PRtcSessionDescriptionInit); +/** + * Allows use of internal threadpool + * + * @param[in] startRoutine function pointer to execute in threadpool + * @param[in] PVOID void pointer to pass to function pointer + * + * @return STATUS code of the execution. STATUS_SUCCESS on success + */ +PUBLIC_API STATUS peerConnectionAsync(startRoutine fn, PVOID data); + /** * @brief Populate the provided answer that contains an RFC 3264 offer * with the supported configurations for the session. diff --git a/src/include/com/amazonaws/kinesis/video/webrtcclient/Stats.h b/src/include/com/amazonaws/kinesis/video/webrtcclient/Stats.h index 56b1f6711a..72fffb67ba 100644 --- a/src/include/com/amazonaws/kinesis/video/webrtcclient/Stats.h +++ b/src/include/com/amazonaws/kinesis/video/webrtcclient/Stats.h @@ -256,6 +256,8 @@ typedef struct { UINT64 iceCandidatePairNominationTime; UINT64 candidateGatheringTime; UINT64 iceAgentSetUpTime; + UINT64 candidateGatheringStartTime; + UINT64 candidateGatheringEndTime; } KvsIceAgentStats, *PKvsIceAgentStats; /** @@ -571,6 +573,18 @@ typedef struct { * @brief SignalingClientMetrics Represent the stats related to the KVS WebRTC SDK signaling client */ typedef struct { + UINT64 describeChannelStartTime; + UINT64 describeChannelEndTime; + UINT64 getSignalingChannelEndpointStartTime; + UINT64 getSignalingChannelEndpointEndTime; + UINT64 getIceServerConfigStartTime; + UINT64 getIceServerConfigEndTime; + UINT64 getTokenStartTime; + UINT64 getTokenEndTime; + UINT64 createChannelStartTime; + UINT64 createChannelEndTime; + UINT64 connectStartTime; + UINT64 connectEndTime; UINT64 cpApiCallLatency; //!< Latency (in 100 ns) incurred per backend API call for the control plane APIs UINT64 dpApiCallLatency; //!< Latency (in 100 ns) incurred per backend API call for the data plane APIs UINT64 signalingClientUptime; //!< Client uptime (in 100 ns). Timestamp will be recorded at every SIGNALING_CLIENT_STATE_CONNECTED @@ -606,15 +620,20 @@ typedef struct { fetchClientTime; //!< Total time (ms) taken to fetch signaling client which includes describe, create, get endpoint and get ICE server config UINT64 connectClientTime; //!< Total time (ms) taken to connect the signaling client which includes connecting to the signaling channel UINT64 offerToAnswerTime; + UINT64 offerReceivedTime; + UINT64 answerTime; UINT64 joinSessionToOfferRecvTime; //!< Total time (ms) taken from joinSession call until offer is received } SignalingClientStats, *PSignalingClientStats; typedef struct { + UINT64 peerConnectionStartTime; + UINT64 peerConnectionConnectedTime; UINT64 peerConnectionCreationTime; //!< Time taken (ms) for peer connection object creation time UINT64 dtlsSessionSetupTime; //!< Time taken (ms) for DTLS handshake to complete UINT64 iceHolePunchingTime; //!< Time taken (ms) for ICE agent set up to complete UINT64 closePeerConnectionTime; //!< Time taken (ms) to close the peer connection UINT64 freePeerConnectionTime; //!< Time taken (ms) to free the peer connection object + UINT64 stunDnsResolutionTime; //!< Time taken (ms) to complete STUN DNS resolution on the thread } PeerConnectionStats, *PPeerConnectionStats; /** diff --git a/src/source/Crypto/Dtls.h b/src/source/Crypto/Dtls.h index f932a8c37c..7f0fbd9532 100644 --- a/src/source/Crypto/Dtls.h +++ b/src/source/Crypto/Dtls.h @@ -43,6 +43,13 @@ typedef enum { RTC_DTLS_TRANSPORT_STATE_FAILED, /* The transport has failed as the result of an error */ } RTC_DTLS_TRANSPORT_STATE; +typedef enum { + DTLS_STATE_HANDSHAKE_NEW, + DTLS_STATE_HANDSHAKE_IN_PROGRESS, + DTLS_STATE_HANDSHAKE_COMPLETED, + DTLS_STATE_HANDSHAKE_ERROR, +} DTLS_HANDSHAKE_STATE; + /* Callback that is fired when Dtls Server wishes to send packet */ typedef VOID (*DtlsSessionOutboundPacketFunc)(UINT64, PBYTE, UINT32); @@ -98,7 +105,8 @@ typedef struct { typedef struct __DtlsSession DtlsSession, *PDtlsSession; struct __DtlsSession { volatile ATOMIC_BOOL isStarted; - volatile ATOMIC_BOOL shutdown; + volatile ATOMIC_BOOL isShutdown; + volatile ATOMIC_BOOL isCleanUp; UINT32 certificateCount; DtlsSessionCallbacks dtlsSessionCallbacks; TIMER_QUEUE_HANDLE timerQueueHandle; @@ -106,10 +114,13 @@ struct __DtlsSession { UINT64 dtlsSessionStartTime; UINT64 dtlsSessionSetupTime; RTC_DTLS_TRANSPORT_STATE state; + DTLS_HANDSHAKE_STATE handshakeState; MUTEX sslLock; #ifdef KVS_USE_OPENSSL volatile ATOMIC_BOOL sslInitFinished; + volatile SIZE_T objRefCount; + CVAR receivePacketCvar; // dtls message must fit into a UDP packet BYTE outgoingDataBuffer[MAX_UDP_PACKET_SIZE]; UINT32 outgoingDataLen; @@ -168,6 +179,7 @@ STATUS dtlsSessionShutdown(PDtlsSession); STATUS dtlsSessionOnOutBoundData(PDtlsSession, UINT64, DtlsSessionOutboundPacketFunc); STATUS dtlsSessionOnStateChange(PDtlsSession, UINT64, DtlsSessionOnStateChange); +STATUS dtlsSessionHandshakeInThread(PDtlsSession, BOOL); /******** Internal Functions **********/ STATUS dtlsValidateRtcCertificates(PRtcCertificate, PUINT32); diff --git a/src/source/Crypto/Dtls_mbedtls.c b/src/source/Crypto/Dtls_mbedtls.c index 36bf7a3f81..29dca1a8c6 100644 --- a/src/source/Crypto/Dtls_mbedtls.c +++ b/src/source/Crypto/Dtls_mbedtls.c @@ -248,6 +248,12 @@ INT32 dtlsSessionKeyDerivationCallback(PVOID customData, const unsigned char* pM return 0; } +STATUS dtlsSessionHandshakeInThread(PDtlsSession pDtlsSession, BOOL isServer) +{ + DLOGI("Threadpool based DTLS handshake not supported for mbedtls"); + return STATUS_SUCCESS; +} + STATUS dtlsSessionStart(PDtlsSession pDtlsSession, BOOL isServer) { ENTERS(); @@ -327,7 +333,7 @@ STATUS dtlsSessionProcessPacket(PDtlsSession pDtlsSession, PBYTE pData, PINT32 p CHK(pDtlsSession != NULL && pData != NULL && pDataLen != NULL, STATUS_NULL_ARG); CHK(ATOMIC_LOAD_BOOL(&pDtlsSession->isStarted), STATUS_SSL_PACKET_BEFORE_DTLS_READY); - CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->shutdown), retStatus); + CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->isShutdown), retStatus); MUTEX_LOCK(pDtlsSession->sslLock); locked = TRUE; @@ -384,7 +390,7 @@ STATUS dtlsSessionPutApplicationData(PDtlsSession pDtlsSession, PBYTE pData, INT BOOL iterate = TRUE; CHK(pData != NULL, STATUS_NULL_ARG); - CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->shutdown), retStatus); + CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->isShutdown), retStatus); MUTEX_LOCK(pDtlsSession->sslLock); locked = TRUE; @@ -530,13 +536,13 @@ STATUS dtlsSessionShutdown(PDtlsSession pDtlsSession) MUTEX_LOCK(pDtlsSession->sslLock); locked = TRUE; - CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->shutdown), retStatus); + CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->isShutdown), retStatus); while (mbedtls_ssl_close_notify(&pDtlsSession->sslCtx) == MBEDTLS_ERR_SSL_WANT_WRITE) { // keep flushing outgoing buffer until nothing left } - ATOMIC_STORE_BOOL(&pDtlsSession->shutdown, TRUE); + ATOMIC_STORE_BOOL(&pDtlsSession->isShutdown, TRUE); CHK_STATUS(dtlsSessionChangeState(pDtlsSession, RTC_DTLS_TRANSPORT_STATE_CLOSED)); CleanUp: diff --git a/src/source/Crypto/Dtls_openssl.c b/src/source/Crypto/Dtls_openssl.c index d1fcb1b788..7b1ccf2b17 100644 --- a/src/source/Crypto/Dtls_openssl.c +++ b/src/source/Crypto/Dtls_openssl.c @@ -10,6 +10,20 @@ INT32 dtlsCertificateVerifyCallback(INT32 preverify_ok, X509_STORE_CTX* ctx) return 1; } +VOID acquireDtlsSession(PDtlsSession pDtlsSession) +{ + if (pDtlsSession != NULL) { + ATOMIC_INCREMENT(&pDtlsSession->objRefCount); + } +} + +VOID releaseDtlsSession(PDtlsSession pDtlsSession) +{ + if (pDtlsSession != NULL) { + ATOMIC_DECREMENT(&pDtlsSession->objRefCount); + } +} + STATUS dtlsCertificateFingerprint(X509* pCertificate, PCHAR pBuff) { ENTERS(); @@ -41,6 +55,7 @@ STATUS dtlsTransmissionTimerCallback(UINT32 timerID, UINT64 currentTime, UINT64 UINT64 timeoutValDefaultTimeUnit = 0; LONG dtlsTimeoutRet = 0; + acquireDtlsSession(pDtlsSession); CHK(pDtlsSession != NULL, STATUS_NULL_ARG); MEMSET(&timeout, 0x00, SIZEOF(struct timeval)); @@ -81,6 +96,7 @@ STATUS dtlsTransmissionTimerCallback(UINT32 timerID, UINT64 currentTime, UINT64 if (locked) { MUTEX_UNLOCK(pDtlsSession->sslLock); } + releaseDtlsSession(pDtlsSession); return retStatus; } @@ -276,6 +292,7 @@ STATUS createDtlsSession(PDtlsSessionCallbacks pDtlsSessionCallbacks, TIMER_QUEU PDtlsSession pDtlsSession = NULL; UINT32 i, certCount; UINT64 startTimeInMacro = 0; + BOOL acquired = FALSE; DtlsSessionCertificateInfo certInfos[MAX_RTCCONFIGURATION_CERTIFICATES]; MEMSET(certInfos, 0x00, SIZEOF(certInfos)); @@ -283,12 +300,16 @@ STATUS createDtlsSession(PDtlsSessionCallbacks pDtlsSessionCallbacks, TIMER_QUEU CHK_STATUS(dtlsValidateRtcCertificates(pRtcCertificates, &certCount)); pDtlsSession = MEMCALLOC(SIZEOF(DtlsSession), 1); + acquireDtlsSession(pDtlsSession); + acquired = TRUE; CHK(pDtlsSession != NULL, STATUS_NOT_ENOUGH_MEMORY); pDtlsSession->timerQueueHandle = timerQueueHandle; pDtlsSession->timerId = MAX_UINT32; pDtlsSession->sslLock = MUTEX_CREATE(TRUE); pDtlsSession->state = RTC_DTLS_TRANSPORT_STATE_NEW; + pDtlsSession->handshakeState = DTLS_STATE_HANDSHAKE_NEW; + pDtlsSession->receivePacketCvar = CVAR_CREATE(); ATOMIC_STORE_BOOL(&pDtlsSession->isStarted, FALSE); ATOMIC_STORE_BOOL(&pDtlsSession->sslInitFinished, FALSE); @@ -312,8 +333,8 @@ STATUS createDtlsSession(PDtlsSessionCallbacks pDtlsSessionCallbacks, TIMER_QUEU } } - CHK_STATUS(createSslCtx(certInfos, pDtlsSession->certificateCount, &pDtlsSession->pSslCtx)); - CHK_STATUS(createSsl(pDtlsSession->pSslCtx, &pDtlsSession->pSsl)); + PROFILE_CALL(CHK_STATUS(createSslCtx(certInfos, pDtlsSession->certificateCount, &pDtlsSession->pSslCtx)), "Create SSL Context"); + PROFILE_CALL(CHK_STATUS(createSsl(pDtlsSession->pSslCtx, &pDtlsSession->pSsl)), "Create SSL session"); // Generate and store the certificate fingerprints CHK_STATUS(dtlsGenerateCertificateFingerprints(pDtlsSession, certInfos)); @@ -335,6 +356,9 @@ STATUS createDtlsSession(PDtlsSessionCallbacks pDtlsSessionCallbacks, TIMER_QUEU freeDtlsSession(&pDtlsSession); } + if (acquired) { + releaseDtlsSession(pDtlsSession); + } LEAVES(); return retStatus; } @@ -357,18 +381,15 @@ STATUS dtlsGenerateCertificateFingerprints(PDtlsSession pDtlsSession, PDtlsSessi return retStatus; } -STATUS dtlsSessionStart(PDtlsSession pDtlsSession, BOOL isServer) +STATUS beginHandshakeProcess(PDtlsSession pDtlsSession, BOOL isServer, PINT32 sslRet) { ENTERS(); STATUS retStatus = STATUS_SUCCESS; - BOOL locked = FALSE; - INT32 sslRet, sslErr; - CHK(pDtlsSession != NULL && pDtlsSession != NULL, STATUS_NULL_ARG); - CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->isStarted), retStatus); + acquireDtlsSession(pDtlsSession); + CHK(pDtlsSession != NULL, STATUS_NULL_ARG); - MUTEX_LOCK(pDtlsSession->sslLock); - locked = TRUE; + CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->isStarted), retStatus); CHK_STATUS(dtlsSessionChangeState(pDtlsSession, RTC_DTLS_TRANSPORT_STATE_CONNECTING)); @@ -381,23 +402,168 @@ STATUS dtlsSessionStart(PDtlsSession pDtlsSession, BOOL isServer) } else { SSL_set_connect_state(pDtlsSession->pSsl); } - sslRet = SSL_do_handshake(pDtlsSession->pSsl); - if (sslRet <= 0) { - LOG_OPENSSL_ERROR("SSL_do_handshake"); + + if (!isServer) { + pDtlsSession->dtlsSessionStartTime = GETTIME(); } + *sslRet = SSL_do_handshake(pDtlsSession->pSsl); +CleanUp: + CHK_LOG_ERR(retStatus); + releaseDtlsSession(pDtlsSession); + LEAVES(); + return retStatus; +} +STATUS dtlsSessionStart(PDtlsSession pDtlsSession, BOOL isServer) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + BOOL locked = FALSE; + INT32 sslRet; + + acquireDtlsSession(pDtlsSession); + CHK(pDtlsSession != NULL, STATUS_NULL_ARG); + + MUTEX_LOCK(pDtlsSession->sslLock); + locked = TRUE; + + CHK_STATUS(beginHandshakeProcess(pDtlsSession, isServer, &sslRet)); pDtlsSession->dtlsSessionStartTime = GETTIME(); CHK_STATUS(timerQueueAddTimer(pDtlsSession->timerQueueHandle, DTLS_SESSION_TIMER_START_DELAY, DTLS_TRANSMISSION_INTERVAL, dtlsTransmissionTimerCallback, (UINT64) pDtlsSession, &pDtlsSession->timerId)); - CleanUp: - CHK_LOG_ERR(retStatus); - if (locked) { MUTEX_UNLOCK(pDtlsSession->sslLock); } + releaseDtlsSession(pDtlsSession); + LEAVES(); + return retStatus; +} + +STATUS dtlsSessionHandshakeInThread(PDtlsSession pDtlsSession, BOOL isServer) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + BOOL locked = FALSE; + INT32 sslRet, sslErr; + struct timeval timeout; + int dtlsTimeoutRet = 0, dtlsHandleTimeoutRet = 0; + BOOL firstMsg = TRUE; + UINT64 waitTime = 1 * HUNDREDS_OF_NANOS_IN_A_SECOND; + BOOL dtlsHandshakeErrored = FALSE; + BOOL timedOut = FALSE; + MEMSET(&timeout, 0x00, SIZEOF(struct timeval)); + acquireDtlsSession(pDtlsSession); + CHK(pDtlsSession != NULL, STATUS_NULL_ARG); + + MUTEX_LOCK(pDtlsSession->sslLock); + locked = TRUE; + + CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->isCleanUp), STATUS_DTLS_SESSION_ALREADY_FREED); + CHK_STATUS(beginHandshakeProcess(pDtlsSession, isServer, &sslRet)); + while (!(ATOMIC_LOAD_BOOL(&pDtlsSession->sslInitFinished)) && !dtlsHandshakeErrored && !(ATOMIC_LOAD_BOOL(&pDtlsSession->isCleanUp))) { + switch (pDtlsSession->handshakeState) { + case DTLS_STATE_HANDSHAKE_NEW: + if (sslRet <= 0) { + sslErr = SSL_get_error(pDtlsSession->pSsl, sslRet); + if (sslErr == SSL_ERROR_WANT_READ || sslErr == SSL_ERROR_WANT_WRITE) { + // If OpenSSL wants to read or write, it's an indication we should check the BIO + DLOGD("Handshake want READ/WRITE"); + CHK_STATUS(dtlsCheckOutgoingDataBuffer(pDtlsSession)); + } else { + DLOGI("Failed to complete handshake..but let it go on"); + // Handle other errors + LOG_OPENSSL_ERROR("SSL_do_handshake"); + } + pDtlsSession->handshakeState = DTLS_STATE_HANDSHAKE_IN_PROGRESS; + } else { + pDtlsSession->handshakeState = DTLS_STATE_HANDSHAKE_COMPLETED; + ATOMIC_STORE_BOOL(&pDtlsSession->sslInitFinished, TRUE); + CHK_STATUS(dtlsSessionChangeState(pDtlsSession, RTC_DTLS_TRANSPORT_STATE_CONNECTED)); + } + pDtlsSession->handshakeState = DTLS_STATE_HANDSHAKE_IN_PROGRESS; + break; + case DTLS_STATE_HANDSHAKE_IN_PROGRESS: + if (SSL_is_init_finished(pDtlsSession->pSsl)) { + pDtlsSession->handshakeState = DTLS_STATE_HANDSHAKE_COMPLETED; + ATOMIC_STORE_BOOL(&pDtlsSession->sslInitFinished, TRUE); + CHK_STATUS(dtlsSessionChangeState(pDtlsSession, RTC_DTLS_TRANSPORT_STATE_CONNECTED)); + } else { + // We check for timeout here. If the timeout is 0, it is likely it + // is in Server mode at which point it is basically waiting on the first message + // from DTLS client. So, we need to wait on the CVAR. Even if timeout is 0, there is no + // guarantee that handshake was complete. It just means that no retransmission is required + // We always rely on sslInitFinished to be set to truly confirm handshake was complete + + // DTLSv1_handle_timeout: https://www.openssl.org/docs/manmaster/man3/DTLSv1_handle_timeout.html + // DTLSv1_get_timeout: https://www.openssl.org/docs/manmaster/man3/DTLSv1_get_timeout.html + dtlsTimeoutRet = DTLSv1_get_timeout(pDtlsSession->pSsl, &timeout); + if (dtlsTimeoutRet == 0) { + // Listening in on fatal errors only: https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html + if (sslErr == SSL_ERROR_SYSCALL || sslErr == SSL_ERROR_SSL) { + DLOGW("FATAL ERROR encountered while getting timeout"); + pDtlsSession->handshakeState = DTLS_STATE_HANDSHAKE_ERROR; + dtlsHandshakeErrored = TRUE; + } else { + DLOGI("No timeout is active, no retransmissions to handle"); + } + } else { + waitTime = timeout.tv_sec * HUNDREDS_OF_NANOS_IN_A_SECOND + timeout.tv_usec * HUNDREDS_OF_NANOS_IN_A_MICROSECOND; + } + if (!dtlsHandshakeErrored) { + timedOut = (CVAR_WAIT(pDtlsSession->receivePacketCvar, pDtlsSession->sslLock, waitTime) == STATUS_OPERATION_TIMED_OUT); + if (timedOut) { + DLOGD("DTLS handshake timeout event occurred, going to retransmit"); + dtlsHandleTimeoutRet = DTLSv1_handle_timeout(pDtlsSession->pSsl); + if (dtlsHandleTimeoutRet > 0) { + DLOGI("Timeout handled successfully, packet retransmitted"); + } else if (dtlsHandleTimeoutRet == 0) { + DLOGI("No pending timeout event to handle"); + } else { + sslErr = SSL_get_error(pDtlsSession->pSsl, sslRet); + if (sslErr == SSL_ERROR_SYSCALL || sslErr == SSL_ERROR_SSL) { + DLOGE("A fatal error was encountered while handling timeout"); + pDtlsSession->handshakeState = DTLS_STATE_HANDSHAKE_ERROR; + dtlsHandshakeErrored = TRUE; + } else { + DLOGW("Non fatal error while handling timeout, will retry next time"); + } + } + } + // We start calculating start of handshake DTLS handshake time taken in server mode only after clientHello + // is received, until then, we are only waiting, so we should not count that time into handshake latency + // calculation + if (isServer && firstMsg) { + pDtlsSession->dtlsSessionStartTime = GETTIME(); + firstMsg = FALSE; + } + CHK_STATUS(dtlsCheckOutgoingDataBuffer(pDtlsSession)); + } + } + break; + case DTLS_STATE_HANDSHAKE_COMPLETED: + // We would not hit this state because we would exit the while loop in the next iteration. But maintaining this state for + // completeness + DLOGI("Handshake completed"); + break; + case DTLS_STATE_HANDSHAKE_ERROR: + DLOGI("DTLS handshake could not be completed. Time outs in the handshake process"); + // We would not hit this state because we would exit the while loop in the next iteration. But maintaining this state for + // completeness + break; + default: + break; + } + } + DLOGI("Done with handshake, exiting from this thread"); +CleanUp: + CHK_LOG_ERR(retStatus); + if (locked) { + MUTEX_UNLOCK(pDtlsSession->sslLock); + } + releaseDtlsSession(pDtlsSession); LEAVES(); return retStatus; } @@ -414,10 +580,21 @@ STATUS freeDtlsSession(PDtlsSession* ppDtlsSession) CHK(pDtlsSession != NULL, retStatus); + DLOGI("Freeing the DTLS session"); + ATOMIC_STORE_BOOL(&pDtlsSession->isCleanUp, TRUE); + + // Wait until refCount drops to 0 or add a timeout mechanism to avoid indefinite waits + while (ATOMIC_LOAD(&pDtlsSession->objRefCount) > 0) { + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); + } if (pDtlsSession->timerId != MAX_UINT32) { timerQueueCancelTimer(pDtlsSession->timerQueueHandle, pDtlsSession->timerId, (UINT64) pDtlsSession); } + // Lock SSL free as an additional protection to ensure SSL contexts are not being used in the callbacks + // when actively freeing it + MUTEX_LOCK(pDtlsSession->sslLock); + if (pDtlsSession->pSsl != NULL) { SSL_free(pDtlsSession->pSsl); } @@ -425,8 +602,13 @@ STATUS freeDtlsSession(PDtlsSession* ppDtlsSession) SSL_CTX_free(pDtlsSession->pSslCtx); } if (IS_VALID_MUTEX_VALUE(pDtlsSession->sslLock)) { + CVAR_BROADCAST(pDtlsSession->receivePacketCvar); + MUTEX_UNLOCK(pDtlsSession->sslLock); MUTEX_FREE(pDtlsSession->sslLock); } + if (IS_VALID_CVAR_VALUE(pDtlsSession->receivePacketCvar)) { + CVAR_FREE(pDtlsSession->receivePacketCvar); + } SAFE_MEMFREE(pDtlsSession); *ppDtlsSession = NULL; @@ -445,38 +627,45 @@ STATUS dtlsSessionProcessPacket(PDtlsSession pDtlsSession, PBYTE pData, PINT32 p INT32 sslRet = 0, sslErr; INT32 dataLen = 0; - CHK(pDtlsSession != NULL && pDtlsSession != NULL && pDataLen != NULL, STATUS_NULL_ARG); + acquireDtlsSession(pDtlsSession); + CHK(pDtlsSession != NULL && pDataLen != NULL, STATUS_NULL_ARG); CHK(ATOMIC_LOAD_BOOL(&pDtlsSession->isStarted), STATUS_SSL_PACKET_BEFORE_DTLS_READY); MUTEX_LOCK(pDtlsSession->sslLock); locked = TRUE; - sslRet = BIO_write(SSL_get_rbio(pDtlsSession->pSsl), pData, *pDataLen); - if (sslRet <= 0) { - LOG_OPENSSL_ERROR("BIO_write"); - } + CVAR_BROADCAST(pDtlsSession->receivePacketCvar); - // should clear error before SSL_read: https://stackoverflow.com/a/47218133 - ERR_clear_error(); - sslRet = SSL_read(pDtlsSession->pSsl, pData, *pDataLen); + if (!ATOMIC_LOAD_BOOL(&pDtlsSession->isCleanUp)) { + sslRet = BIO_write(SSL_get_rbio(pDtlsSession->pSsl), pData, *pDataLen); + if (sslRet <= 0) { + LOG_OPENSSL_ERROR("BIO_write"); + } - if (sslRet == 0 && SSL_get_error(pDtlsSession->pSsl, sslRet) == SSL_ERROR_ZERO_RETURN) { - DLOGI("Detected DTLS close_notify alert"); - isClosed = TRUE; - } else if (sslRet <= 0) { - LOG_OPENSSL_ERROR("SSL_read"); - } + // should clear error before SSL_read: https://stackoverflow.com/a/47218133 + ERR_clear_error(); + sslRet = SSL_read(pDtlsSession->pSsl, pData, *pDataLen); - if (!ATOMIC_LOAD_BOOL(&pDtlsSession->sslInitFinished)) { - CHK_STATUS(dtlsCheckOutgoingDataBuffer(pDtlsSession)); - } + if (sslRet == 0 && SSL_get_error(pDtlsSession->pSsl, sslRet) == SSL_ERROR_ZERO_RETURN) { + DLOGI("Detected DTLS close_notify alert"); + isClosed = TRUE; + } else if (sslRet <= 0) { + LOG_OPENSSL_ERROR("SSL_read"); + } + + if (!ATOMIC_LOAD_BOOL(&pDtlsSession->sslInitFinished)) { + CHK_STATUS(dtlsCheckOutgoingDataBuffer(pDtlsSession)); + } - /* if SSL_read failed then set to 0 */ - dataLen = sslRet < 0 ? 0 : sslRet; + /* if SSL_read failed then set to 0 */ + dataLen = sslRet < 0 ? 0 : sslRet; - if (isClosed) { - ATOMIC_STORE_BOOL(&pDtlsSession->shutdown, TRUE); - CHK_STATUS(dtlsSessionChangeState(pDtlsSession, RTC_DTLS_TRANSPORT_STATE_CLOSED)); + if (isClosed) { + ATOMIC_STORE_BOOL(&pDtlsSession->isShutdown, TRUE); + CHK_STATUS(dtlsSessionChangeState(pDtlsSession, RTC_DTLS_TRANSPORT_STATE_CLOSED)); + } + } else { + DLOGW("DTLS session being cleaned up...ignoring the incoming packet"); } CleanUp: @@ -490,6 +679,7 @@ STATUS dtlsSessionProcessPacket(PDtlsSession pDtlsSession, PBYTE pData, PINT32 p if (locked) { MUTEX_UNLOCK(pDtlsSession->sslLock); } + releaseDtlsSession(pDtlsSession); LEAVES(); return retStatus; @@ -505,10 +695,14 @@ STATUS dtlsSessionPutApplicationData(PDtlsSession pDtlsSession, PBYTE pData, INT SIZE_T pending; BOOL locked = FALSE; + acquireDtlsSession(pDtlsSession); CHK(pDtlsSession != NULL && pData != NULL, STATUS_NULL_ARG); + MUTEX_LOCK(pDtlsSession->sslLock); locked = TRUE; - CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->shutdown), retStatus); + + CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->isShutdown), retStatus); + CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->isCleanUp), retStatus); if ((amountWritten = SSL_write(pDtlsSession->pSsl, pData, dataLen)) != dataLen && SSL_get_error(pDtlsSession->pSsl, amountWritten) == SSL_ERROR_SSL) { @@ -526,7 +720,7 @@ STATUS dtlsSessionPutApplicationData(PDtlsSession pDtlsSession, PBYTE pData, INT if (locked) { MUTEX_UNLOCK(pDtlsSession->sslLock); } - + releaseDtlsSession(pDtlsSession); LEAVES(); return retStatus; } @@ -536,16 +730,17 @@ STATUS dtlsSessionShutdown(PDtlsSession pDtlsSession) STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; + acquireDtlsSession(pDtlsSession); CHK(pDtlsSession != NULL, STATUS_NULL_ARG); MUTEX_LOCK(pDtlsSession->sslLock); locked = TRUE; - CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->shutdown), retStatus); + CHK(!ATOMIC_LOAD_BOOL(&pDtlsSession->isShutdown), retStatus); CHK(ATOMIC_LOAD_BOOL(&pDtlsSession->sslInitFinished), retStatus); SSL_shutdown(pDtlsSession->pSsl); - ATOMIC_STORE_BOOL(&pDtlsSession->shutdown, TRUE); + ATOMIC_STORE_BOOL(&pDtlsSession->isShutdown, TRUE); CHK_STATUS(dtlsCheckOutgoingDataBuffer(pDtlsSession)); CHK_STATUS(dtlsSessionChangeState(pDtlsSession, RTC_DTLS_TRANSPORT_STATE_CLOSED)); @@ -554,7 +749,7 @@ STATUS dtlsSessionShutdown(PDtlsSession pDtlsSession) if (locked) { MUTEX_UNLOCK(pDtlsSession->sslLock); } - + releaseDtlsSession(pDtlsSession); return retStatus; } @@ -565,11 +760,13 @@ STATUS dtlsCheckOutgoingDataBuffer(PDtlsSession pDtlsSession) BIO* pWriteBIO = NULL; INT32 dataLenWritten = 0, sslErr = 0; + CHK(!(ATOMIC_LOAD_BOOL(&pDtlsSession->isCleanUp)), STATUS_DTLS_SESSION_ALREADY_FREED); + pWriteBIO = SSL_get_wbio(pDtlsSession->pSsl); // proceed if write BIO is not empty CHK(BIO_ctrl_pending(pWriteBIO) > 0, retStatus); - // BIO_read removes read data + // BIO_read removes read data from the write BIO dataLenWritten = BIO_read(pWriteBIO, pDtlsSession->outgoingDataBuffer, ARRAY_SIZE(pDtlsSession->outgoingDataBuffer)); if (dataLenWritten > 0) { pDtlsSession->outgoingDataLen = (UINT32) dataLenWritten; @@ -590,6 +787,7 @@ STATUS dtlsSessionIsInitFinished(PDtlsSession pDtlsSession, PBOOL pIsConnected) STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; + acquireDtlsSession(pDtlsSession); CHK(pDtlsSession != NULL && pIsConnected != NULL, STATUS_NULL_ARG); MUTEX_LOCK(pDtlsSession->sslLock); @@ -607,7 +805,7 @@ STATUS dtlsSessionIsInitFinished(PDtlsSession pDtlsSession, PBOOL pIsConnected) if (locked) { MUTEX_UNLOCK(pDtlsSession->sslLock); } - + releaseDtlsSession(pDtlsSession); LEAVES(); return retStatus; } @@ -620,6 +818,7 @@ STATUS dtlsSessionPopulateKeyingMaterial(PDtlsSession pDtlsSession, PDtlsKeyingM BYTE keyingMaterialBuffer[MAX_SRTP_MASTER_KEY_LEN * 2 + MAX_SRTP_SALT_KEY_LEN * 2]; BOOL locked = FALSE; + acquireDtlsSession(pDtlsSession); CHK(pDtlsSession != NULL && pDtlsKeyingMaterial != NULL, STATUS_NULL_ARG); MUTEX_LOCK(pDtlsSession->sslLock); @@ -655,7 +854,7 @@ STATUS dtlsSessionPopulateKeyingMaterial(PDtlsSession pDtlsSession, PDtlsKeyingM if (locked) { MUTEX_UNLOCK(pDtlsSession->sslLock); } - + releaseDtlsSession(pDtlsSession); LEAVES(); return retStatus; } @@ -667,7 +866,9 @@ STATUS dtlsSessionGetLocalCertificateFingerprint(PDtlsSession pDtlsSession, PCHA STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; + acquireDtlsSession(pDtlsSession); CHK(pDtlsSession != NULL && pBuff != NULL, STATUS_NULL_ARG); + CHK(buffLen >= CERTIFICATE_FINGERPRINT_LENGTH, STATUS_INVALID_ARG_LEN); MUTEX_LOCK(pDtlsSession->sslLock); @@ -680,7 +881,7 @@ STATUS dtlsSessionGetLocalCertificateFingerprint(PDtlsSession pDtlsSession, PCHA if (locked) { MUTEX_UNLOCK(pDtlsSession->sslLock); } - + releaseDtlsSession(pDtlsSession); LEAVES(); return retStatus; } @@ -693,6 +894,7 @@ STATUS dtlsSessionVerifyRemoteCertificateFingerprint(PDtlsSession pDtlsSession, X509* pRemoteCertificate = NULL; BOOL locked = FALSE; + acquireDtlsSession(pDtlsSession); CHK(pDtlsSession != NULL && pExpectedFingerprint != NULL, STATUS_NULL_ARG); MUTEX_LOCK(pDtlsSession->sslLock); @@ -715,7 +917,7 @@ STATUS dtlsSessionVerifyRemoteCertificateFingerprint(PDtlsSession pDtlsSession, if (locked) { MUTEX_UNLOCK(pDtlsSession->sslLock); } - + releaseDtlsSession(pDtlsSession); LEAVES(); return retStatus; } diff --git a/src/source/Ice/IceAgent.c b/src/source/Ice/IceAgent.c index 7e3ce47c5e..c9effd4418 100644 --- a/src/source/Ice/IceAgent.c +++ b/src/source/Ice/IceAgent.c @@ -1,5 +1,5 @@ /** - * Kinesis Video Producer Callbacks Provider + * Ice Agent APIs */ #define LOG_CLASS "IceAgent" #include "../Include_i.h" @@ -61,11 +61,11 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge pIceAgent->localNetworkInterfaceCount = ARRAY_SIZE(pIceAgent->localNetworkInterfaces); pIceAgent->candidateGatheringEndTime = INVALID_TIMESTAMP_VALUE; - pIceAgent->lock = MUTEX_CREATE(FALSE); + pIceAgent->lock = MUTEX_CREATE(TRUE); // Create the state machine - CHK_STATUS(createStateMachine(ICE_AGENT_STATE_MACHINE_STATES, ICE_AGENT_STATE_MACHINE_STATE_COUNT, (UINT64) pIceAgent, iceAgentGetCurrentTime, - (UINT64) pIceAgent, &pIceAgent->pStateMachine)); + CHK_STATUS(createStateMachineWithName(ICE_AGENT_STATE_MACHINE_STATES, ICE_AGENT_STATE_MACHINE_STATE_COUNT, (UINT64) pIceAgent, + iceAgentGetCurrentTime, (UINT64) pIceAgent, ICE_STATE_MACHINE_NAME, &pIceAgent->pStateMachine)); pIceAgent->iceAgentStatus = STATUS_SUCCESS; pIceAgent->iceAgentStateTimerTask = MAX_UINT32; pIceAgent->keepAliveTimerTask = MAX_UINT32; @@ -95,6 +95,11 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge pIceAgent->iceServersCount = 0; for (i = 0; i < MAX_ICE_SERVERS_COUNT; i++) { if (pRtcConfiguration->iceServers[i].urls[0] != '\0') { + if (STRSTR(pRtcConfiguration->iceServers[i].urls, "stun")) { + pIceAgent->iceServers[pIceAgent->iceServersCount].setIpFn = pIceAgent->iceAgentCallbacks.setStunServerIpFn; + } else { + pIceAgent->iceServers[pIceAgent->iceServersCount].setIpFn = NULL; + } PROFILE_CALL_WITH_T_OBJ( retStatus = parseIceServer(&pIceAgent->iceServers[pIceAgent->iceServersCount], (PCHAR) pRtcConfiguration->iceServers[i].urls, (PCHAR) pRtcConfiguration->iceServers[i].username, (PCHAR) pRtcConfiguration->iceServers[i].credential), @@ -257,6 +262,82 @@ STATUS freeIceAgent(PIceAgent* ppIceAgent) return retStatus; } +STATUS iceAgentAddConfig(PIceAgent pIceAgent, PIceConfigInfo pIceConfigInfo) +{ + STATUS retStatus = STATUS_SUCCESS; + UINT32 i = 0; + // used in PROFILE macro + UINT64 startTimeInMacro = 0; + BOOL locked = FALSE; + + CHK(pIceAgent != NULL && pIceConfigInfo != NULL, STATUS_NULL_ARG); + + for (i = 0; i < pIceConfigInfo->uriCount; i++) { + MUTEX_LOCK(pIceAgent->lock); + locked = TRUE; + PROFILE_CALL_WITH_T_OBJ(retStatus = parseIceServer(&pIceAgent->iceServers[pIceAgent->iceServersCount], (PCHAR) pIceConfigInfo->uris[i], + (PCHAR) pIceConfigInfo->userName, (PCHAR) pIceConfigInfo->password), + pIceAgent->iceAgentProfileDiagnostics.iceServerParsingTime[i], "ICE server parsing"); + MUTEX_UNLOCK(pIceAgent->lock); + locked = FALSE; + + if (STATUS_SUCCEEDED(retStatus)) { + MUTEX_LOCK(pIceAgent->lock); + locked = TRUE; + pIceAgent->rtcIceServerDiagnostics[i].port = (INT32) getInt16(pIceAgent->iceServers[i].ipAddress.port); + switch (pIceAgent->iceServers[pIceAgent->iceServersCount].transport) { + case KVS_SOCKET_PROTOCOL_UDP: + STRCPY(pIceAgent->rtcIceServerDiagnostics[i].protocol, ICE_TRANSPORT_TYPE_UDP); + break; + case KVS_SOCKET_PROTOCOL_TCP: + STRCPY(pIceAgent->rtcIceServerDiagnostics[i].protocol, ICE_TRANSPORT_TYPE_TCP); + break; + default: + MEMSET(pIceAgent->rtcIceServerDiagnostics[i].protocol, 0, SIZEOF(pIceAgent->rtcIceServerDiagnostics[i].protocol)); + } + STRCPY(pIceAgent->rtcIceServerDiagnostics[i].url, pIceConfigInfo->uris[i]); + + MUTEX_UNLOCK(pIceAgent->lock); + locked = FALSE; + + // important to unlock iceAgent lock before calling init relay candidate, since iceAgent APIs are thread safe + // if you don't unlock this can lead to a deadlock with the timerqueue. + // init candidate && pairs + if (pIceAgent->iceServers[pIceAgent->iceServersCount].isTurn) { + if (pIceAgent->iceServers[pIceAgent->iceServersCount].transport == KVS_SOCKET_PROTOCOL_UDP || + pIceAgent->iceServers[pIceAgent->iceServersCount].transport == KVS_SOCKET_PROTOCOL_NONE) { + CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, pIceAgent->iceServersCount, KVS_SOCKET_PROTOCOL_UDP)); + } + + if (pIceAgent->iceServers[pIceAgent->iceServersCount].transport == KVS_SOCKET_PROTOCOL_TCP || + pIceAgent->iceServers[pIceAgent->iceServersCount].transport == KVS_SOCKET_PROTOCOL_NONE) { + CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, pIceAgent->iceServersCount, KVS_SOCKET_PROTOCOL_TCP)); + } + } + + MUTEX_LOCK(pIceAgent->lock); + locked = TRUE; + + pIceAgent->iceServersCount++; + + MUTEX_UNLOCK(pIceAgent->lock); + locked = FALSE; + + } else { + DLOGE("Failed to parse ICE servers"); + } + } + +CleanUp: + CHK_LOG_ERR(retStatus); + + if (locked) { + MUTEX_UNLOCK(pIceAgent->lock); + } + + return retStatus; +} + STATUS iceAgentValidateKvsRtcConfig(PKvsRtcConfiguration pKvsRtcConfiguration) { STATUS retStatus = STATUS_SUCCESS; @@ -279,7 +360,7 @@ STATUS iceAgentValidateKvsRtcConfig(PKvsRtcConfiguration pKvsRtcConfiguration) pKvsRtcConfiguration->iceConnectionCheckPollingInterval = KVS_ICE_CONNECTION_CHECK_POLLING_INTERVAL; } - DLOGD("\n\ticeLocalCandidateGatheringTimeout: %u ms" + DLOGI("\n\ticeLocalCandidateGatheringTimeout: %u ms" "\n\ticeConnectionCheckTimeout: %u ms" "\n\ticeCandidateNominationTimeout: %u ms" "\n\ticeConnectionCheckPollingInterval: %u ms", @@ -305,8 +386,6 @@ STATUS iceAgentReportNewLocalCandidate(PIceAgent pIceAgent, PIceCandidate pIceCa iceAgentLogNewCandidate(pIceCandidate); CHK_WARN(pIceAgent->iceAgentCallbacks.newLocalCandidateFn != NULL, retStatus, "newLocalCandidateFn callback not implemented"); - CHK_WARN(!ATOMIC_LOAD_BOOL(&pIceAgent->candidateGatheringFinished), retStatus, - "Cannot report new ice candidate because candidate gathering is already finished"); CHK_STATUS(iceCandidateSerialize(pIceCandidate, serializedIceCandidateBuf, &serializedIceCandidateBufLen)); pIceAgent->iceAgentCallbacks.newLocalCandidateFn(pIceAgent->iceAgentCallbacks.customData, serializedIceCandidateBuf); @@ -575,6 +654,8 @@ STATUS iceAgentStartAgent(PIceAgent pIceAgent, PCHAR remoteUsername, PCHAR remot CleanUp: + CHK_LOG_ERR(retStatus); + if (locked) { MUTEX_UNLOCK(pIceAgent->lock); } @@ -593,7 +674,6 @@ STATUS iceAgentStartGathering(PIceAgent pIceAgent) ATOMIC_STORE_BOOL(&pIceAgent->agentStartGathering, TRUE); pIceAgent->candidateGatheringStartTime = GETTIME(); - // skip gathering host candidate and srflx candidate if relay only if (pIceAgent->iceTransportPolicy != ICE_TRANSPORT_POLICY_RELAY) { // Skip getting local host candidates if transport policy is relay only @@ -607,9 +687,6 @@ STATUS iceAgentStartGathering(PIceAgent pIceAgent) "Srflx candidates setup time"); } - PROFILE_CALL_WITH_T_OBJ(CHK_STATUS(iceAgentInitRelayCandidates(pIceAgent)), pIceAgent->iceAgentProfileDiagnostics.relayCandidateSetUpTime, - "Relay candidates setup time"); - // start listening for incoming data CHK_STATUS(connectionListenerStart(pIceAgent->pConnectionListener)); @@ -908,7 +985,7 @@ STATUS iceAgentRestart(PIceAgent pIceAgent, PCHAR localIceUfrag, PCHAR localIceP * pIceAgent->pDataSendingIceCandidatePair and its ice candidates. Therefore safe to proceed freeing resources */ for (i = 0; i < localCandidateCount; ++i) { - if (localCandidates[i] != pIceAgent->pDataSendingIceCandidatePair->local) { + if (pIceAgent->pDataSendingIceCandidatePair == NULL || localCandidates[i] != pIceAgent->pDataSendingIceCandidatePair->local) { if (localCandidates[i]->iceCandidateType != ICE_CANDIDATE_TYPE_RELAYED) { CHK_STATUS(connectionListenerRemoveConnection(pIceAgent->pConnectionListener, localCandidates[i]->pSocketConnection)); CHK_STATUS(freeSocketConnection(&localCandidates[i]->pSocketConnection)); @@ -949,6 +1026,8 @@ STATUS iceAgentRestart(PIceAgent pIceAgent, PCHAR localIceUfrag, PCHAR localIceP CHK_STATUS(setStateMachineCurrentState(pIceAgent->pStateMachine, ICE_AGENT_STATE_NEW)); ATOMIC_STORE_BOOL(&pIceAgent->processStun, TRUE); + // this API does not reset servers, so re-initialize relay candidates now. + CHK_STATUS(iceAgentInitRelayCandidates(pIceAgent)); CleanUp: @@ -1398,6 +1477,10 @@ STATUS iceAgentSendSrflxCandidateRequest(PIceAgent pIceAgent) case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE: pIceServer = &(pIceAgent->iceServers[pCandidate->iceServerIndex]); if (pIceServer->ipAddress.family == pCandidate->ipAddress.family) { + // update transactionId + CHK_STATUS( + iceUtilsGenerateTransactionId(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId))); + transactionIdStoreInsert(pIceAgent->pStunBindingRequestTransactionIdStore, pBindingRequest->header.transactionId); checkSum = COMPUTE_CRC32(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId)); CHK_STATUS(iceAgentSendStunPacket(pBindingRequest, NULL, 0, pIceAgent, pCandidate, &pIceServer->ipAddress)); @@ -1599,8 +1682,8 @@ STATUS iceAgentGatherCandidateTimerCallback(UINT32 timerId, UINT64 currentTime, if (stopScheduling) { ATOMIC_STORE_BOOL(&pIceAgent->candidateGatheringFinished, TRUE); - PROFILE_WITH_START_TIME_OBJ(pIceAgent->candidateGatheringStartTime, pIceAgent->iceAgentProfileDiagnostics.candidateGatheringTime, - "Candidate gathering time"); + PROFILE_WITH_START_END_TIME_OBJ(pIceAgent->candidateGatheringStartTime, pIceAgent->candidateGatheringProcessEndTime, + pIceAgent->iceAgentProfileDiagnostics.candidateGatheringTime, "Candidate gathering time"); /* notify that candidate gathering is finished. */ if (pIceAgent->iceAgentCallbacks.newLocalCandidateFn != NULL) { pIceAgent->iceAgentCallbacks.newLocalCandidateFn(pIceAgent->iceAgentCallbacks.customData, NULL); @@ -1796,12 +1879,14 @@ STATUS turnStateFailedFn(PSocketConnection pSocketConnection, UINT64 data) PIceCandidate pNewCandidate = (PIceCandidate) data; CHK(pNewCandidate != NULL, STATUS_NULL_ARG); + MUTEX_LOCK(pNewCandidate->pIceAgent->lock); if (pNewCandidate->state == ICE_CANDIDATE_STATE_NEW) { pNewCandidate->state = ICE_CANDIDATE_STATE_INVALID; } CleanUp: + MUTEX_UNLOCK(pNewCandidate->pIceAgent->lock); return retStatus; } @@ -1845,16 +1930,17 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV callback.relayAddressAvailableFn = NULL; callback.turnStateFailedFn = turnStateFailedFn; + MUTEX_LOCK(pIceAgent->lock); + locked = TRUE; + CHK_STATUS(createTurnConnection(&pIceAgent->iceServers[iceServerIndex], pIceAgent->timerQueueHandle, TURN_CONNECTION_DATA_TRANSFER_MODE_SEND_INDIDATION, protocol, &callback, pNewCandidate->pSocketConnection, pIceAgent->pConnectionListener, &pTurnConnection)); pNewCandidate->pIceAgent = pIceAgent; pNewCandidate->pTurnConnection = pTurnConnection; - MUTEX_LOCK(pIceAgent->lock); - locked = TRUE; - CHK_STATUS(doubleListInsertItemHead(pIceAgent->localCandidates, (UINT64) pNewCandidate)); + CHK_STATUS(iceAgentReportNewLocalCandidate(pIceAgent, pNewCandidate)); pNewCandidate = NULL; /* add existing remote candidates to turn. Need to acquire lock because remoteCandidates can be mutated by @@ -2050,7 +2136,7 @@ STATUS iceAgentConnectedStateSetup(PIceAgent pIceAgent) pIceCandidatePair = (PIceCandidatePair) pCurNode->data; pCurNode = pCurNode->pNext; - if (pIceCandidatePair->state == ICE_CANDIDATE_PAIR_STATE_SUCCEEDED) { + if (pIceCandidatePair->state == ICE_CANDIDATE_PAIR_STATE_SUCCEEDED && pIceCandidatePair->nominated) { pIceAgent->pDataSendingIceCandidatePair = pIceCandidatePair; retStatus = updateSelectedLocalRemoteCandidateStats(pIceAgent); if (STATUS_FAILED(retStatus)) { @@ -2104,6 +2190,10 @@ STATUS iceAgentNominatingStateSetup(PIceAgent pIceAgent) pIceAgent->stateEndTime = GETTIME() + pIceAgent->kvsRtcConfiguration.iceCandidateNominationTimeout; + MUTEX_UNLOCK(pIceAgent->lock); + locked = FALSE; + checkIceAgentStateMachine(pIceAgent); + CleanUp: CHK_LOG_ERR(retStatus); @@ -2137,22 +2227,23 @@ STATUS iceAgentReadyStateSetup(PIceAgent pIceAgent) MUTEX_LOCK(pIceAgent->lock); locked = TRUE; - // find nominated pair - CHK_STATUS(doubleListGetHeadNode(pIceAgent->iceCandidatePairs, &pCurNode)); - while (pCurNode != NULL && pNominatedAndValidCandidatePair == NULL) { - pIceCandidatePair = (PIceCandidatePair) pCurNode->data; - pCurNode = pCurNode->pNext; - - if (pIceCandidatePair->nominated && pIceCandidatePair->state == ICE_CANDIDATE_PAIR_STATE_SUCCEEDED) { - pNominatedAndValidCandidatePair = pIceCandidatePair; - pNominatedAndValidCandidatePair->rtcIceCandidatePairDiagnostics.nominated = pIceCandidatePair->nominated; - break; + // if data sending pair already selected and is nominated, no need to find it again + if (pIceAgent->pDataSendingIceCandidatePair == NULL) { + // find nominated pair + CHK_STATUS(doubleListGetHeadNode(pIceAgent->iceCandidatePairs, &pCurNode)); + while (pCurNode != NULL && pNominatedAndValidCandidatePair == NULL) { + pIceCandidatePair = (PIceCandidatePair) pCurNode->data; + pCurNode = pCurNode->pNext; + if (pIceCandidatePair->nominated && pIceCandidatePair->state == ICE_CANDIDATE_PAIR_STATE_SUCCEEDED) { + pNominatedAndValidCandidatePair = pIceCandidatePair; + pNominatedAndValidCandidatePair->rtcIceCandidatePairDiagnostics.nominated = pIceCandidatePair->nominated; + break; + } } + CHK(pNominatedAndValidCandidatePair != NULL, STATUS_ICE_NO_NOMINATED_VALID_CANDIDATE_PAIR_AVAILABLE); + pIceAgent->pDataSendingIceCandidatePair = pNominatedAndValidCandidatePair; } - CHK(pNominatedAndValidCandidatePair != NULL, STATUS_ICE_NO_NOMINATED_VALID_CANDIDATE_PAIR_AVAILABLE); - // change the data sending ice candidate pair as the nomination ice candidate pair. - pIceAgent->pDataSendingIceCandidatePair = pNominatedAndValidCandidatePair; CHK_STATUS(getIpAddrStr(&pIceAgent->pDataSendingIceCandidatePair->local->ipAddress, ipAddrStr, ARRAY_SIZE(ipAddrStr))); DLOGP("Selected pair %s_%s, local candidate type: %s. remote candidate type: %s. Round trip time %u ms. Local candidate priority: %u, ice " "candidate pair priority: %" PRIu64, @@ -2308,7 +2399,6 @@ STATUS incomingRelayedDataHandler(UINT64 customData, PSocketConnection pSocketCo CHK(pRelayedCandidate != NULL && pSocketConnection != NULL, STATUS_NULL_ARG); - DLOGV("Candidate id: %s", pRelayedCandidate->id); CHK_STATUS(turnConnectionIncomingDataHandler(pRelayedCandidate->pTurnConnection, pBuffer, bufferLen, pSrc, pDest, turnChannelData, &turnChannelDataCount)); for (i = 0; i < turnChannelDataCount; ++i) { @@ -2360,6 +2450,9 @@ STATUS incomingDataHandler(UINT64 customData, PSocketConnection pSocketConnectio } else { if (ATOMIC_LOAD_BOOL(&pIceAgent->processStun)) { CHK_STATUS(handleStunPacket(pIceAgent, pBuffer, bufferLen, pSocketConnection, pSrc, pDest)); + MUTEX_UNLOCK(pIceAgent->lock); + locked = FALSE; + checkIceAgentStateMachine(pIceAgent); } } @@ -2464,6 +2557,7 @@ STATUS handleStunPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen, PS // before we receive the answer. CHK_STATUS(findIceCandidatePairWithLocalSocketConnectionAndRemoteAddr(pIceAgent, pSocketConnection, pSrcAddr, TRUE, &pIceCandidatePair)); CHK(pIceCandidatePair != NULL, retStatus); + DLOGD("Pair binding request! %s %s", pIceCandidatePair->local->id, pIceCandidatePair->remote->id); if (!pIceCandidatePair->nominated) { CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_USE_CANDIDATE, &pStunAttr)); @@ -2531,7 +2625,7 @@ STATUS handleStunPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen, PS "Cannot find candidate pair with local candidate %s and remote candidate %s. Dropping STUN binding success response", ipAddrStr2, ipAddrStr); } - DLOGV("Pair binding response! %s %s", pIceCandidatePair->local->id, pIceCandidatePair->remote->id); + DLOGD("Pair binding response! %s %s", pIceCandidatePair->local->id, pIceCandidatePair->remote->id); if (hashTableGet(pIceCandidatePair->requestSentTime, checkSum, &requestSentTime) == STATUS_SUCCESS) { pIceCandidatePair->roundTripTime = GETTIME() - requestSentTime; pIceCandidatePair->rtcIceCandidatePairDiagnostics.currentRoundTripTime = @@ -2617,10 +2711,10 @@ STATUS handleStunPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen, PS if (pIceCandidatePair == NULL) { CHK_STATUS(getIpAddrStr(pSrcAddr, ipAddrStr, ARRAY_SIZE(ipAddrStr))); CHK_STATUS(getIpAddrStr(&pSocketConnection->hostIpAddr, ipAddrStr2, ARRAY_SIZE(ipAddrStr2))); - CHK_WARN( - FALSE, retStatus, - "ERROR cannot find candidate pair with local candidate %s and remote candidate %s. Dropping STUN binding error response", - ipAddrStr2, ipAddrStr); + CHK_WARN(FALSE, retStatus, + "ERROR cannot find candidate pair with local candidate %s and remote candidate %s. Dropping STUN binding error " + "response", + ipAddrStr2, ipAddrStr); } DLOGW("Error binding response! %s %s", pIceCandidatePair->local->id, pIceCandidatePair->remote->id); } @@ -2673,7 +2767,8 @@ STATUS iceAgentCheckPeerReflexiveCandidate(PIceAgent pIceAgent, PKvsIpAddress pI } CHK_STATUS(doubleListGetNodeCount(pIceAgent->remoteCandidates, &candidateCount)); - CHK_WARN(candidateCount < KVS_ICE_MAX_REMOTE_CANDIDATE_COUNT, retStatus, "max remote candidate count exceeded"); // return early if limit exceeded + CHK_WARN(candidateCount < KVS_ICE_MAX_REMOTE_CANDIDATE_COUNT, retStatus, + "max remote candidate count exceeded"); // return early if limit exceeded CHK_STATUS(findCandidateWithIp(pIpAddress, pIceAgent->remoteCandidates, &pIceCandidate)); CHK(pIceCandidate == NULL, retStatus); // return early if duplicated DLOGD("New remote peer reflexive candidate found"); @@ -2843,12 +2938,14 @@ STATUS getIceAgentStats(PIceAgent pIceAgent, PKvsIceAgentMetrics pKvsIceAgentMet pKvsIceAgentMetrics->kvsIceAgentStats.hostCandidateSetUpTime = pIceAgent->iceAgentProfileDiagnostics.hostCandidateSetUpTime; pKvsIceAgentMetrics->kvsIceAgentStats.srflxCandidateSetUpTime = pIceAgent->iceAgentProfileDiagnostics.srflxCandidateSetUpTime; pKvsIceAgentMetrics->kvsIceAgentStats.relayCandidateSetUpTime = pIceAgent->iceAgentProfileDiagnostics.relayCandidateSetUpTime; - for (i = 0; i < MAX_ICE_SERVERS_COUNT; i++) { + for (i = 0; i < pIceAgent->iceServersCount; i++) { pKvsIceAgentMetrics->kvsIceAgentStats.iceServerParsingTime += pIceAgent->iceAgentProfileDiagnostics.iceServerParsingTime[i]; } pKvsIceAgentMetrics->kvsIceAgentStats.iceCandidatePairNominationTime = pIceAgent->iceAgentProfileDiagnostics.iceCandidatePairNominationTime; pKvsIceAgentMetrics->kvsIceAgentStats.candidateGatheringTime = pIceAgent->iceAgentProfileDiagnostics.candidateGatheringTime; pKvsIceAgentMetrics->kvsIceAgentStats.iceAgentSetUpTime = pIceAgent->iceAgentProfileDiagnostics.iceAgentSetUpTime; + pKvsIceAgentMetrics->kvsIceAgentStats.candidateGatheringStartTime = pIceAgent->candidateGatheringStartTime; + pKvsIceAgentMetrics->kvsIceAgentStats.candidateGatheringEndTime = pIceAgent->candidateGatheringProcessEndTime; CleanUp: return retStatus; } diff --git a/src/source/Ice/IceAgent.h b/src/source/Ice/IceAgent.h index 5d0d9e4933..b692975c1b 100644 --- a/src/source/Ice/IceAgent.h +++ b/src/source/Ice/IceAgent.h @@ -14,12 +14,16 @@ extern "C" { #define KVS_ICE_MAX_REMOTE_CANDIDATE_COUNT 100 #define KVS_ICE_MAX_LOCAL_CANDIDATE_COUNT 100 #define KVS_ICE_GATHER_REFLEXIVE_AND_RELAYED_CANDIDATE_TIMEOUT (10 * HUNDREDS_OF_NANOS_IN_A_SECOND) -#define KVS_ICE_CONNECTIVITY_CHECK_TIMEOUT (10 * HUNDREDS_OF_NANOS_IN_A_SECOND) -#define KVS_ICE_CANDIDATE_NOMINATION_TIMEOUT (10 * HUNDREDS_OF_NANOS_IN_A_SECOND) -#define KVS_ICE_SEND_KEEP_ALIVE_INTERVAL (15 * HUNDREDS_OF_NANOS_IN_A_SECOND) -#define KVS_ICE_TURN_CONNECTION_SHUTDOWN_TIMEOUT (1 * HUNDREDS_OF_NANOS_IN_A_SECOND) -#define KVS_ICE_DEFAULT_TIMER_START_DELAY (3 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND) -#define KVS_ICE_SHORT_CHECK_DELAY (50 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND) +#define KVS_ICE_CONNECTIVITY_CHECK_TIMEOUT \ + (12 * HUNDREDS_OF_NANOS_IN_A_SECOND) // This should be greater than KVS_ICE_GATHER_REFLEXIVE_AND_RELAYED_CANDIDATE_TIMEOUT to ensure there is + // buffer wait time for connectivity checks with the pairs formed with near timeout created pairs +#define KVS_ICE_CANDIDATE_NOMINATION_TIMEOUT \ + (12 * HUNDREDS_OF_NANOS_IN_A_SECOND) // This should be greater than KVS_ICE_GATHER_REFLEXIVE_AND_RELAYED_CANDIDATE_TIMEOUT to ensure there is some + // buffer for nomination with near timeout generated candidates +#define KVS_ICE_SEND_KEEP_ALIVE_INTERVAL (15 * HUNDREDS_OF_NANOS_IN_A_SECOND) +#define KVS_ICE_TURN_CONNECTION_SHUTDOWN_TIMEOUT (1 * HUNDREDS_OF_NANOS_IN_A_SECOND) +#define KVS_ICE_DEFAULT_TIMER_START_DELAY (3 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND) +#define KVS_ICE_SHORT_CHECK_DELAY (50 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND) // Ta in https://tools.ietf.org/html/rfc8445 #define KVS_ICE_CONNECTION_CHECK_POLLING_INTERVAL (50 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND) @@ -56,6 +60,9 @@ extern "C" { #define ICE_CANDIDATE_ID_LEN 8 #define STATS_NOT_APPLICABLE_STR (PCHAR) "N/A" + +#define ICE_STATE_MACHINE_NAME (PCHAR) "ICE" + typedef enum { ICE_CANDIDATE_STATE_NEW, ICE_CANDIDATE_STATE_VALID, @@ -136,6 +143,7 @@ typedef struct { IceInboundPacketFunc inboundPacketFn; IceConnectionStateChangedFunc connectionStateChangedFn; IceNewLocalCandidateFunc newLocalCandidateFn; + IceServerSetIpFunc setStunServerIpFn; } IceAgentCallbacks, *PIceAgentCallbacks; typedef struct { @@ -264,6 +272,7 @@ struct __IceAgent { PTransactionIdStore pStunBindingRequestTransactionIdStore; UINT64 candidateGatheringStartTime; + UINT64 candidateGatheringProcessEndTime; UINT64 iceAgentStartTime; }; @@ -447,6 +456,8 @@ STATUS updateSelectedLocalRemoteCandidateStats(PIceAgent); STATUS getIceAgentStats(PIceAgent, PKvsIceAgentMetrics); +STATUS iceAgentAddConfig(PIceAgent, PIceConfigInfo); + #ifdef __cplusplus } #endif diff --git a/src/source/Ice/IceAgentStateMachine.c b/src/source/Ice/IceAgentStateMachine.c index 9cef8d59d8..3204e46062 100644 --- a/src/source/Ice/IceAgentStateMachine.c +++ b/src/source/Ice/IceAgentStateMachine.c @@ -29,42 +29,72 @@ StateMachineState ICE_AGENT_STATE_MACHINE_STATES[] = { UINT32 ICE_AGENT_STATE_MACHINE_STATE_COUNT = ARRAY_SIZE(ICE_AGENT_STATE_MACHINE_STATES); +STATUS checkIceAgentStateMachine(PIceAgent pIceAgent) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + BOOL transitionReady = FALSE; + CHK(pIceAgent != NULL && pIceAgent->pStateMachine != NULL, STATUS_NULL_ARG); + + // if a state transition is ready, tell the timer to kick the timer + CHK_STATUS(checkForStateTransition(pIceAgent->pStateMachine, &transitionReady)); + if (transitionReady) { + // dangerous to have any mutexes locked by timerqueue when entering this function + CHK_STATUS(timerQueueKick(pIceAgent->timerQueueHandle, pIceAgent->iceAgentStateTimerTask)); + } + +CleanUp: + + CHK_LOG_ERR(retStatus); + + LEAVES(); + return retStatus; +} + STATUS stepIceAgentStateMachine(PIceAgent pIceAgent) { ENTERS(); STATUS retStatus = STATUS_SUCCESS; - STATUS iceAgentStatus = STATUS_SUCCESS; UINT64 oldState; + BOOL locked = FALSE; CHK(pIceAgent != NULL, STATUS_NULL_ARG); - oldState = pIceAgent->iceAgentState; + do { + oldState = pIceAgent->iceAgentState; + MUTEX_LOCK(pIceAgent->lock); + locked = TRUE; - CHK_STATUS(stepStateMachine(pIceAgent->pStateMachine)); + CHK_STATUS(stepStateMachine(pIceAgent->pStateMachine)); - MUTEX_LOCK(pIceAgent->lock); - iceAgentStatus = pIceAgent->iceAgentStatus; - MUTEX_UNLOCK(pIceAgent->lock); + // if any failure happened and state machine is not in failed state, stepStateMachine again into failed state. + if (pIceAgent->iceAgentState != ICE_AGENT_STATE_FAILED && STATUS_FAILED(pIceAgent->iceAgentStatus)) { + DLOGD("Ice agent state %s operation encountered error 0x%08x", iceAgentStateToString(pIceAgent->iceAgentState), + pIceAgent->iceAgentStatus); + CHK_STATUS(stepStateMachine(pIceAgent->pStateMachine)); + } - // if any failure happened and state machine is not in failed state, stepStateMachine again into failed state. - if (pIceAgent->iceAgentState != ICE_AGENT_STATE_FAILED && STATUS_FAILED(iceAgentStatus)) { - CHK_STATUS(stepStateMachine(pIceAgent->pStateMachine)); - } + MUTEX_UNLOCK(pIceAgent->lock); + locked = FALSE; - if (oldState != pIceAgent->iceAgentState) { - if (pIceAgent->iceAgentCallbacks.connectionStateChangedFn != NULL) { - DLOGI("Ice agent state changed from %s to %s.", iceAgentStateToString(oldState), iceAgentStateToString(pIceAgent->iceAgentState)); - pIceAgent->iceAgentCallbacks.connectionStateChangedFn(pIceAgent->iceAgentCallbacks.customData, pIceAgent->iceAgentState); + if (oldState != pIceAgent->iceAgentState) { + if (pIceAgent->iceAgentCallbacks.connectionStateChangedFn != NULL) { + DLOGI("Ice agent state changed from %s to %s.", iceAgentStateToString(oldState), iceAgentStateToString(pIceAgent->iceAgentState)); + pIceAgent->iceAgentCallbacks.connectionStateChangedFn(pIceAgent->iceAgentCallbacks.customData, pIceAgent->iceAgentState); + } + } else { + // state machine retry is not used. resetStateMachineRetryCount just to avoid + // state machine retry grace period overflow warning. + CHK_STATUS(resetStateMachineRetryCount(pIceAgent->pStateMachine)); } - } else { - // state machine retry is not used. resetStateMachineRetryCount just to avoid - // state machine retry grace period overflow warning. - CHK_STATUS(resetStateMachineRetryCount(pIceAgent->pStateMachine)); - } + } while (oldState != pIceAgent->iceAgentState); CleanUp: CHK_LOG_ERR(retStatus); + if (locked) { + MUTEX_UNLOCK(pIceAgent->lock); + } LEAVES(); return retStatus; @@ -85,6 +115,7 @@ STATUS acceptIceAgentMachineState(PIceAgent pIceAgent, UINT64 state) CHK_STATUS(acceptStateMachineState(pIceAgent->pStateMachine, state)); CleanUp: + CHK_LOG_ERR(retStatus); if (locked) { MUTEX_UNLOCK(pIceAgent->lock); @@ -200,6 +231,7 @@ STATUS executeNewIceAgentState(UINT64 customData, UINT64 time) pIceAgent->iceAgentState = ICE_AGENT_STATE_NEW; CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -247,6 +279,7 @@ STATUS fromCheckConnectionIceAgentState(UINT64 customData, PUINT64 pState) } CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { state = ICE_AGENT_STATE_FAILED; @@ -284,6 +317,7 @@ STATUS executeCheckConnectionIceAgentState(UINT64 customData, UINT64 time) CHK_STATUS(iceAgentCheckCandidatePairConnection(pIceAgent)); CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { MUTEX_LOCK(pIceAgent->lock); @@ -325,6 +359,7 @@ STATUS fromConnectedIceAgentState(UINT64 customData, PUINT64 pState) state = ICE_AGENT_STATE_NOMINATING; CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { state = ICE_AGENT_STATE_FAILED; @@ -359,6 +394,7 @@ STATUS executeConnectedIceAgentState(UINT64 customData, UINT64 time) pIceAgent->iceAgentState = ICE_AGENT_STATE_CONNECTED; CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { MUTEX_LOCK(pIceAgent->lock); @@ -415,6 +451,7 @@ STATUS fromNominatingIceAgentState(UINT64 customData, PUINT64 pState) } CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { state = ICE_AGENT_STATE_FAILED; @@ -460,6 +497,7 @@ STATUS executeNominatingIceAgentState(UINT64 customData, UINT64 time) } CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { MUTEX_LOCK(pIceAgent->lock); @@ -481,8 +519,6 @@ STATUS fromReadyIceAgentState(UINT64 customData, PUINT64 pState) PIceAgent pIceAgent = (PIceAgent) customData; UINT64 state = ICE_AGENT_STATE_READY; // original state BOOL locked = FALSE; - PDoubleListNode pCurNode = NULL, pNodeToDelete = NULL; - PIceCandidate pIceCandidate = NULL; CHK(pIceAgent != NULL && pState != NULL, STATUS_NULL_ARG); @@ -494,26 +530,11 @@ STATUS fromReadyIceAgentState(UINT64 customData, PUINT64 pState) CHK_STATUS(iceAgentStateMachineCheckDisconnection(pIceAgent, &state)); - // Free TurnConnections that are shutdown - CHK_STATUS(doubleListGetHeadNode(pIceAgent->localCandidates, &pCurNode)); - while (pCurNode != NULL) { - pIceCandidate = (PIceCandidate) pCurNode->data; - pNodeToDelete = pCurNode; - pCurNode = pCurNode->pNext; - - if (pIceCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_RELAYED && turnConnectionIsShutdownComplete(pIceCandidate->pTurnConnection)) { - MUTEX_UNLOCK(pIceAgent->lock); - CHK_LOG_ERR(freeTurnConnection(&pIceCandidate->pTurnConnection)); - MUTEX_LOCK(pIceAgent->lock); - MEMFREE(pIceCandidate); - CHK_STATUS(doubleListDeleteNode(pIceAgent->localCandidates, pNodeToDelete)); - } - } - // return early if changing to disconnected state CHK(state != ICE_AGENT_STATE_DISCONNECTED, retStatus); CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { state = ICE_AGENT_STATE_FAILED; @@ -539,7 +560,10 @@ STATUS executeReadyIceAgentState(UINT64 customData, UINT64 time) ENTERS(); UNUSED_PARAM(time); STATUS retStatus = STATUS_SUCCESS; + BOOL locked = FALSE; PIceAgent pIceAgent = (PIceAgent) customData; + PDoubleListNode pCurNode = NULL, pNodeToDelete = NULL; + PIceCandidate pIceCandidate = NULL; CHK(pIceAgent != NULL, STATUS_NULL_ARG); if (pIceAgent->iceAgentState != ICE_AGENT_STATE_READY) { @@ -547,7 +571,27 @@ STATUS executeReadyIceAgentState(UINT64 customData, UINT64 time) pIceAgent->iceAgentState = ICE_AGENT_STATE_READY; } + MUTEX_LOCK(pIceAgent->lock); + locked = TRUE; + + // Free TurnConnections that are shutdown + CHK_STATUS(doubleListGetHeadNode(pIceAgent->localCandidates, &pCurNode)); + while (pCurNode != NULL) { + pIceCandidate = (PIceCandidate) pCurNode->data; + pNodeToDelete = pCurNode; + pCurNode = pCurNode->pNext; + + if (pIceCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_RELAYED && turnConnectionIsShutdownComplete(pIceCandidate->pTurnConnection)) { + MUTEX_UNLOCK(pIceAgent->lock); + CHK_LOG_ERR(freeTurnConnection(&pIceCandidate->pTurnConnection)); + MUTEX_LOCK(pIceAgent->lock); + MEMFREE(pIceCandidate); + CHK_STATUS(doubleListDeleteNode(pIceAgent->localCandidates, pNodeToDelete)); + } + } + CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { MUTEX_LOCK(pIceAgent->lock); @@ -560,10 +604,14 @@ STATUS executeReadyIceAgentState(UINT64 customData, UINT64 time) if (pIceAgent->iceAgentStartTime != 0) { PROFILE_WITH_START_TIME_OBJ(pIceAgent->iceAgentStartTime, pIceAgent->iceAgentProfileDiagnostics.iceAgentSetUpTime, - "Time taken to get ICE Agent ready for media exchange"); + "ICE Agent ready for media exchange from check connection start"); pIceAgent->iceAgentStartTime = 0; } + if (locked) { + MUTEX_UNLOCK(pIceAgent->lock); + } + LEAVES(); return retStatus; } @@ -585,6 +633,7 @@ STATUS fromDisconnectedIceAgentState(UINT64 customData, PUINT64 pState) CHK_STATUS(pIceAgent->iceAgentStatus); CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { state = ICE_AGENT_STATE_FAILED; @@ -633,6 +682,7 @@ STATUS executeDisconnectedIceAgentState(UINT64 customData, UINT64 time) CHK_STATUS(stepStateMachine(pIceAgent->pStateMachine)); CleanUp: + CHK_LOG_ERR(retStatus); if (STATUS_FAILED(retStatus)) { MUTEX_LOCK(pIceAgent->lock); @@ -659,6 +709,7 @@ STATUS fromFailedIceAgentState(UINT64 customData, PUINT64 pState) *pState = ICE_AGENT_STATE_FAILED; CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -693,6 +744,7 @@ STATUS executeFailedIceAgentState(UINT64 customData, UINT64 time) } CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; diff --git a/src/source/Ice/IceAgentStateMachine.h b/src/source/Ice/IceAgentStateMachine.h index bd1c2f7139..a37af2ddca 100644 --- a/src/source/Ice/IceAgentStateMachine.h +++ b/src/source/Ice/IceAgentStateMachine.h @@ -41,13 +41,14 @@ extern "C" { #define ICE_AGENT_STATE_FAILED_STR (PCHAR) "ICE_AGENT_STATE_FAILED" // Whether to step the state machine +STATUS checkIceAgentStateMachine(PIceAgent); STATUS stepIceAgentStateMachine(PIceAgent); STATUS acceptIceAgentMachineState(PIceAgent, UINT64); STATUS iceAgentStateMachineCheckDisconnection(PIceAgent, PUINT64); PCHAR iceAgentStateToString(UINT64); /** - * Signaling state machine callbacks + * Ice agent state machine callbacks */ STATUS fromNewIceAgentState(UINT64, PUINT64); STATUS executeNewIceAgentState(UINT64, UINT64); diff --git a/src/source/Ice/IceUtils.c b/src/source/Ice/IceUtils.c index cc8176d505..444d2d025a 100644 --- a/src/source/Ice/IceUtils.c +++ b/src/source/Ice/IceUtils.c @@ -131,6 +131,8 @@ STATUS iceUtilsGenerateTransactionId(PBYTE pBuffer, UINT32 bufferLen) CleanUp: + CHK_LOG_ERR(retStatus); + return retStatus; } @@ -167,6 +169,19 @@ STATUS iceUtilsSendStunPacket(PStunPacket pStunPacket, PBYTE password, UINT32 pa BYTE stunPacketBuffer[STUN_PACKET_ALLOCATION_SIZE]; CHK_STATUS(iceUtilsPackageStunPacket(pStunPacket, password, passwordLen, stunPacketBuffer, &stunPacketSize)); + CHK(pDest != NULL, STATUS_NULL_ARG); + switch (pStunPacket->header.stunMessageType) { + case STUN_PACKET_TYPE_BINDING_REQUEST: + DLOGD("Sending BINDING_REQUEST to ip:%u.%u.%u.%u, port:%u", pDest->address[0], pDest->address[1], pDest->address[2], pDest->address[3], + (UINT16) getInt16(pDest->port)); + break; + case STUN_PACKET_TYPE_BINDING_RESPONSE_SUCCESS: + DLOGD("Sending BINDING_RESPONSE_SUCCESS to ip:%u.%u.%u.%u, port:%u", pDest->address[0], pDest->address[1], pDest->address[2], + pDest->address[3], (UINT16) getInt16(pDest->port)); + break; + default: + break; + } CHK_STATUS(iceUtilsSendData(stunPacketBuffer, stunPacketSize, pDest, pSocketConnection, pTurnConnection, useTurn)); CleanUp: @@ -206,6 +221,7 @@ STATUS parseIceServer(PIceServer pIceServer, PCHAR url, PCHAR username, PCHAR cr STATUS retStatus = STATUS_SUCCESS; PCHAR separator = NULL, urlNoPrefix = NULL, paramStart = NULL; UINT32 port = ICE_STUN_DEFAULT_PORT; + CHAR addressResolved[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; // username and credential is only mandatory for turn server CHK(url != NULL && pIceServer != NULL, STATUS_NULL_ARG); @@ -247,8 +263,23 @@ STATUS parseIceServer(PIceServer pIceServer, PCHAR url, PCHAR username, PCHAR cr STRNCPY(pIceServer->url, urlNoPrefix, MAX_ICE_CONFIG_URI_LEN); } - CHK_STATUS(getIpWithHostName(pIceServer->url, &pIceServer->ipAddress)); + if (pIceServer->setIpFn != NULL) { + retStatus = pIceServer->setIpFn(0, pIceServer->url, &pIceServer->ipAddress); + } + + // Adding a NULL_ARG check specifically to cover for the case where early STUN + // resolution might not be enabled + // Also cover the case where hostname is not resolved because the request was made too soon + if (retStatus == STATUS_NULL_ARG || retStatus == STATUS_PEERCONNECTION_EARLY_DNS_RESOLUTION_FAILED || pIceServer->setIpFn == NULL) { + // Reset the retStatus to ensure the appropriate status code is returned from + // getIpWithHostName + retStatus = STATUS_SUCCESS; + CHK_STATUS(getIpWithHostName(pIceServer->url, &pIceServer->ipAddress)); + } + pIceServer->ipAddress.port = (UINT16) getInt16((INT16) port); + getIpAddrStr(&pIceServer->ipAddress, addressResolved, ARRAY_SIZE(addressResolved)); + DLOGP("ICE Server address for %s: %s", pIceServer->url, addressResolved); CleanUp: diff --git a/src/source/Ice/IceUtils.h b/src/source/Ice/IceUtils.h index 4b680bdfcc..c03b1f1d19 100644 --- a/src/source/Ice/IceUtils.h +++ b/src/source/Ice/IceUtils.h @@ -58,6 +58,7 @@ typedef struct { CHAR username[MAX_ICE_CONFIG_USER_NAME_LEN + 1]; CHAR credential[MAX_ICE_CONFIG_CREDENTIAL_LEN + 1]; KVS_SOCKET_PROTOCOL transport; + IceServerSetIpFunc setIpFn; } IceServer, *PIceServer; STATUS parseIceServer(PIceServer, PCHAR, PCHAR, PCHAR); diff --git a/src/source/Ice/Network.c b/src/source/Ice/Network.c index 318e8e1a73..b5568bb97b 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -35,48 +35,51 @@ STATUS getLocalhostIpAddresses(PKvsIpAddress destIpList, PUINT32 pDestIpListLen, CHK(retWinStatus == ERROR_SUCCESS, STATUS_GET_LOCAL_IP_ADDRESSES_FAILED); for (aa = adapterAddresses; aa != NULL && ipCount < destIpListLen; aa = aa->Next) { - char ifa_name[BUFSIZ]; - memset(ifa_name, 0, BUFSIZ); - WideCharToMultiByte(CP_ACP, 0, aa->FriendlyName, wcslen(aa->FriendlyName), ifa_name, BUFSIZ, NULL, NULL); - - for (ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { - if (filter != NULL) { - DLOGI("Callback set to allow network interface filtering"); - // The callback evaluates to a FALSE if the application is interested in black listing an interface - if (filter(customData, ifa_name) == FALSE) { - filterSet = FALSE; - } else { - filterSet = TRUE; + // Skip inactive interfaces and loop back interfaces + if (aa->OperStatus == IfOperStatusUp && aa->IfType != IF_TYPE_SOFTWARE_LOOPBACK) { + char ifa_name[BUFSIZ]; + memset(ifa_name, 0, BUFSIZ); + WideCharToMultiByte(CP_ACP, 0, aa->FriendlyName, wcslen(aa->FriendlyName), ifa_name, BUFSIZ, NULL, NULL); + + for (ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { + if (filter != NULL) { + DLOGI("Callback set to allow network interface filtering"); + // The callback evaluates to a FALSE if the application is interested in black listing an interface + if (filter(customData, ifa_name) == FALSE) { + filterSet = FALSE; + } else { + filterSet = TRUE; + } } - } - - // If filter is set, ensure the details are collected for the interface - if (filterSet == TRUE) { - int family = ua->Address.lpSockaddr->sa_family; - - if (family == AF_INET) { - destIpList[ipCount].family = KVS_IP_FAMILY_TYPE_IPV4; - destIpList[ipCount].port = 0; - - pIpv4Addr = (struct sockaddr_in*) (ua->Address.lpSockaddr); - MEMCPY(destIpList[ipCount].address, &pIpv4Addr->sin_addr, IPV4_ADDRESS_LENGTH); - } else { - destIpList[ipCount].family = KVS_IP_FAMILY_TYPE_IPV6; - destIpList[ipCount].port = 0; - pIpv6Addr = (struct sockaddr_in6*) (ua->Address.lpSockaddr); - // Ignore unspecified addres: the other peer can't use this address - // Ignore link local: not very useful and will add work unnecessarily - // Ignore site local: https://tools.ietf.org/html/rfc8445#section-5.1.1.1 - if (IN6_IS_ADDR_UNSPECIFIED(&pIpv6Addr->sin6_addr) || IN6_IS_ADDR_LINKLOCAL(&pIpv6Addr->sin6_addr) || - IN6_IS_ADDR_SITELOCAL(&pIpv6Addr->sin6_addr)) { - continue; + // If filter is set, ensure the details are collected for the interface + if (filterSet == TRUE) { + int family = ua->Address.lpSockaddr->sa_family; + + if (family == AF_INET) { + destIpList[ipCount].family = KVS_IP_FAMILY_TYPE_IPV4; + destIpList[ipCount].port = 0; + + pIpv4Addr = (struct sockaddr_in*) (ua->Address.lpSockaddr); + MEMCPY(destIpList[ipCount].address, &pIpv4Addr->sin_addr, IPV4_ADDRESS_LENGTH); + } else { + destIpList[ipCount].family = KVS_IP_FAMILY_TYPE_IPV6; + destIpList[ipCount].port = 0; + + pIpv6Addr = (struct sockaddr_in6*) (ua->Address.lpSockaddr); + // Ignore unspecified addres: the other peer can't use this address + // Ignore link local: not very useful and will add work unnecessarily + // Ignore site local: https://tools.ietf.org/html/rfc8445#section-5.1.1.1 + if (IN6_IS_ADDR_UNSPECIFIED(&pIpv6Addr->sin6_addr) || IN6_IS_ADDR_LINKLOCAL(&pIpv6Addr->sin6_addr) || + IN6_IS_ADDR_SITELOCAL(&pIpv6Addr->sin6_addr)) { + continue; + } + MEMCPY(destIpList[ipCount].address, &pIpv6Addr->sin6_addr, IPV6_ADDRESS_LENGTH); } - MEMCPY(destIpList[ipCount].address, &pIpv6Addr->sin6_addr, IPV6_ADDRESS_LENGTH); - } - // in case of overfilling destIpList - ipCount++; + // in case of overfilling destIpList + ipCount++; + } } } } @@ -403,7 +406,6 @@ STATUS getIpWithHostName(PCHAR hostname, PKvsIpAddress destIp) struct in_addr inaddr; CHAR addr[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; - CHAR addressResolved[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; CHK(hostname != NULL, STATUS_NULL_ARG); DLOGI("ICE SERVER Hostname received: %s", hostname); @@ -443,12 +445,9 @@ STATUS getIpWithHostName(PCHAR hostname, PKvsIpAddress destIp) } freeaddrinfo(res); CHK_ERR(resolved, STATUS_HOSTNAME_NOT_FOUND, "Could not find network address of %s", hostname); - getIpAddrStr(destIp, addressResolved, ARRAY_SIZE(addressResolved)); - DLOGP("ICE Server address for %s with getaddrinfo: %s", hostname, addressResolved); } else { - DLOGP("ICE Server address for %s: %s", hostname, addr); inet_pton(AF_INET, addr, &inaddr); destIp->family = KVS_IP_FAMILY_TYPE_IPV4; MEMCPY(destIp->address, &inaddr, IPV4_ADDRESS_LENGTH); diff --git a/src/source/Ice/TurnConnection.c b/src/source/Ice/TurnConnection.c index 50d6ef65fc..7761b757a9 100644 --- a/src/source/Ice/TurnConnection.c +++ b/src/source/Ice/TurnConnection.c @@ -4,6 +4,9 @@ #define LOG_CLASS "TurnConnection" #include "../Include_i.h" +extern StateMachineState TURN_CONNECTION_STATE_MACHINE_STATES[]; +extern UINT32 TURN_CONNECTION_STATE_MACHINE_STATE_COUNT; + STATUS createTurnConnection(PIceServer pTurnServer, TIMER_QUEUE_HANDLE timerQueueHandle, TURN_CONNECTION_DATA_TRANSFER_MODE dataTransferMode, KVS_SOCKET_PROTOCOL protocol, PTurnConnectionCallbacks pTurnConnectionCallbacks, PSocketConnection pTurnSocket, PConnectionListener pConnectionListener, PTurnConnection* ppTurnConnection) @@ -12,6 +15,7 @@ STATUS createTurnConnection(PIceServer pTurnServer, TIMER_QUEUE_HANDLE timerQueu ENTERS(); STATUS retStatus = STATUS_SUCCESS; PTurnConnection pTurnConnection = NULL; + CHAR turnStateMachineName[MAX_STATE_MACHINE_NAME_LENGTH]; CHK(pTurnServer != NULL && ppTurnConnection != NULL && pTurnSocket != NULL, STATUS_NULL_ARG); CHK(IS_VALID_TIMER_QUEUE_HANDLE(timerQueueHandle), STATUS_INVALID_ARG); @@ -22,8 +26,7 @@ STATUS createTurnConnection(PIceServer pTurnServer, TIMER_QUEUE_HANDLE timerQueu pTurnConnection = (PTurnConnection) MEMCALLOC( 1, SIZEOF(TurnConnection) + DEFAULT_TURN_MESSAGE_RECV_CHANNEL_DATA_BUFFER_LEN * 2 + DEFAULT_TURN_MESSAGE_SEND_CHANNEL_DATA_BUFFER_LEN); CHK(pTurnConnection != NULL, STATUS_NOT_ENOUGH_MEMORY); - - pTurnConnection->lock = MUTEX_CREATE(FALSE); + pTurnConnection->lock = MUTEX_CREATE(TRUE); pTurnConnection->sendLock = MUTEX_CREATE(FALSE); pTurnConnection->freeAllocationCvar = CVAR_CREATE(); pTurnConnection->timerQueueHandle = timerQueueHandle; @@ -59,6 +62,10 @@ STATUS createTurnConnection(PIceServer pTurnServer, TIMER_QUEUE_HANDLE timerQueu pTurnConnection->nextAllocationRefreshTime = 0; pTurnConnection->currentTimerCallingPeriod = DEFAULT_TURN_TIMER_INTERVAL_BEFORE_READY; + SNPRINTF(turnStateMachineName, MAX_STATE_MACHINE_NAME_LENGTH, "%s-%p", TURN_STATE_MACHINE_NAME, (PVOID) pTurnConnection); + CHK_STATUS(createStateMachineWithName(TURN_CONNECTION_STATE_MACHINE_STATES, TURN_CONNECTION_STATE_MACHINE_STATE_COUNT, (UINT64) pTurnConnection, + turnConnectionGetTime, (UINT64) pTurnConnection, turnStateMachineName, &pTurnConnection->pStateMachine)); + CleanUp: CHK_LOG_ERR(retStatus); @@ -75,6 +82,12 @@ STATUS createTurnConnection(PIceServer pTurnServer, TIMER_QUEUE_HANDLE timerQueu return retStatus; } +UINT64 turnConnectionGetTime(UINT64 customData) +{ + UNUSED_PARAM(customData); + return GETTIME(); +} + STATUS freeTurnConnection(PTurnConnection* ppTurnConnection) { ENTERS(); @@ -125,6 +138,8 @@ STATUS freeTurnConnection(PTurnConnection* ppTurnConnection) CVAR_FREE(pTurnConnection->freeAllocationCvar); } + freeStateMachine(pTurnConnection->pStateMachine); + turnConnectionFreePreAllocatedPackets(pTurnConnection); MEMFREE(pTurnConnection); @@ -166,6 +181,7 @@ STATUS turnConnectionIncomingDataHandler(PTurnConnection pTurnConnection, PBYTE } else { CHK_STATUS(turnConnectionHandleStun(pTurnConnection, pCurrent, processedDataLen)); } + checkTurnConnectionStateMachine(pTurnConnection); } else { /* must be channel data if not stun */ CHK_STATUS(turnConnectionHandleChannelData(pTurnConnection, pCurrent, remainingDataSize, &channelDataList[totalChannelDataCount], @@ -198,6 +214,7 @@ STATUS turnConnectionHandleStun(PTurnConnection pTurnConnection, PBYTE pBuffer, PStunAttributeAddress pStunAttributeAddress = NULL; PStunAttributeLifetime pStunAttributeLifetime = NULL; PStunPacket pStunPacket = NULL; + CHAR profileDebugStr[MAX_TURN_PROFILE_LOG_DESC_LEN]; CHAR ipAddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; BOOL locked = FALSE; ATOMIC_BOOL hasAllocation = FALSE; @@ -228,12 +245,18 @@ STATUS turnConnectionHandleStun(PTurnConnection pTurnConnection, PBYTE pBuffer, // convert lifetime to 100ns and store it pTurnConnection->allocationExpirationTime = (pStunAttributeLifetime->lifetime * HUNDREDS_OF_NANOS_IN_A_SECOND) + currentTime; - DLOGD("TURN Allocation succeeded. Life time: %u seconds. Allocation expiration epoch %" PRIu64, pStunAttributeLifetime->lifetime, - pTurnConnection->allocationExpirationTime / DEFAULT_TIME_UNIT_IN_NANOS); pStunAttributeAddress = (PStunAttributeAddress) pStunAttr; pTurnConnection->relayAddress = pStunAttributeAddress->address; ATOMIC_STORE_BOOL(&pTurnConnection->hasAllocation, TRUE); + getIpAddrStr(&pTurnConnection->relayAddress, ipAddrStr, ARRAY_SIZE(ipAddrStr)); + SNPRINTF(profileDebugStr, MAX_TURN_PROFILE_LOG_DESC_LEN, "%p - %s:%d - %s", (PVOID) pTurnConnection, ipAddrStr, + pTurnConnection->relayAddress.port, "TURN allocation"); + DLOGD("[%p - %s:%d] TURN Allocation succeeded. Life time: %u seconds. Allocation expiration epoch %" PRIu64, pTurnConnection, ipAddrStr, + pTurnConnection->relayAddress.port, pStunAttributeLifetime->lifetime, + pTurnConnection->allocationExpirationTime / DEFAULT_TIME_UNIT_IN_NANOS); + PROFILE_WITH_START_TIME_OBJ(pTurnConnection->turnProfileDiagnostics.createAllocationStartTime, + pTurnConnection->turnProfileDiagnostics.createAllocationTime, profileDebugStr); if (!pTurnConnection->relayAddressReported && pTurnConnection->turnConnectionCallbacks.relayAddressAvailableFn != NULL) { pTurnConnection->relayAddressReported = TRUE; @@ -274,7 +297,13 @@ STATUS turnConnectionHandleStun(PTurnConnection pTurnConnection, PBYTE pBuffer, if (pTurnPeer->connectionState == TURN_PEER_CONN_STATE_CREATE_PERMISSION) { pTurnPeer->connectionState = TURN_PEER_CONN_STATE_BIND_CHANNEL; CHK_STATUS(getIpAddrStr(&pTurnPeer->address, ipAddrStr, ARRAY_SIZE(ipAddrStr))); - DLOGD("create permission succeeded for peer %s", ipAddrStr); + DLOGD("[%p] Create permission succeeded for peer %s:%d", pTurnConnection, ipAddrStr, pTurnPeer->address.port); + if (pTurnPeer->firstTimeCreatePermResponse) { + pTurnPeer->firstTimeCreatePermResponse = FALSE; + SNPRINTF(profileDebugStr, MAX_TURN_PROFILE_LOG_DESC_LEN, "%p - %s:%d - %s", (PVOID) pTurnConnection, ipAddrStr, + pTurnPeer->address.port, "TURN create permission"); + PROFILE_WITH_START_TIME_OBJ(pTurnPeer->createPermissionStartTime, pTurnPeer->createPermissionTime, profileDebugStr); + } } pTurnPeer->permissionExpirationTime = TURN_PERMISSION_LIFETIME + currentTime; @@ -296,8 +325,14 @@ STATUS turnConnectionHandleStun(PTurnConnection pTurnConnection, PBYTE pBuffer, pTurnPeer->connectionState = TURN_PEER_CONN_STATE_READY; CHK_STATUS(getIpAddrStr(&pTurnPeer->address, ipAddrStr, ARRAY_SIZE(ipAddrStr))); - DLOGD("Channel bind succeeded with peer %s, port: %u, channel number %u", ipAddrStr, (UINT16) getInt16(pTurnPeer->address.port), - pTurnPeer->channelNumber); + DLOGD("[%p] Channel bind succeeded with peer %s, port: %d, channel number %u", pTurnConnection, ipAddrStr, + pTurnPeer->address.port, pTurnPeer->channelNumber); + if (pTurnPeer->firstTimeBindChannelResponse) { + pTurnPeer->firstTimeBindChannelResponse = FALSE; + SNPRINTF(profileDebugStr, MAX_TURN_PROFILE_LOG_DESC_LEN, "%p - %s:%d:%u - %s", (PVOID) pTurnConnection, ipAddrStr, + pTurnPeer->address.port, pTurnPeer->channelNumber, "TURN bind channel"); + PROFILE_WITH_START_TIME_OBJ(pTurnPeer->bindChannelStartTime, pTurnPeer->bindChannelTime, profileDebugStr); + } break; } @@ -343,6 +378,7 @@ STATUS turnConnectionHandleStunError(PTurnConnection pTurnConnection, PBYTE pBuf PStunPacket pStunPacket = NULL; BOOL locked = FALSE, iterate = TRUE; PTurnPeer pTurnPeer = NULL; + CHAR profileDebugStr[MAX_TURN_PROFILE_LOG_DESC_LEN]; UINT32 i; CHK(pTurnConnection != NULL, STATUS_NULL_ARG); @@ -395,7 +431,9 @@ STATUS turnConnectionHandleStunError(PTurnConnection pTurnConnection, PBYTE pBuf pTurnConnection->turnRealm[pStunAttributeRealm->attribute.length] = '\0'; pTurnConnection->credentialObtained = TRUE; - + SNPRINTF(profileDebugStr, MAX_TURN_PROFILE_LOG_DESC_LEN, "%p - %s", (PVOID) pTurnConnection, "TURN Get Credentials"); + PROFILE_WITH_START_TIME_OBJ(pTurnConnection->turnProfileDiagnostics.getCredentialsStartTime, + pTurnConnection->turnProfileDiagnostics.getCredentialsTime, profileDebugStr); CHK_STATUS(turnConnectionUpdateNonce(pTurnConnection)); break; @@ -544,8 +582,8 @@ STATUS turnConnectionHandleChannelDataTcpMode(PTurnConnection pTurnConnection, P /* process only one channel data and return. Because channel data can be intermixed with STUN packet. * need to check remainingBufLen too because channel data could be incomplete. */ while (remainingBufLen != 0 && channelDataCount == 0) { - DLOGV("currRecvDataLen: %d", pTurnConnection->currRecvDataLen); if (pTurnConnection->currRecvDataLen != 0) { + DLOGV("currRecvDataLen: %d", pTurnConnection->currRecvDataLen); if (pTurnConnection->currRecvDataLen >= TURN_DATA_CHANNEL_SEND_OVERHEAD) { /* pTurnConnection->recvDataBuffer always has channel data start */ paddedChannelDataLen = ROUND_UP((UINT32) getInt16(*(PINT16) (pTurnConnection->recvDataBuffer + SIZEOF(channelNumber))), 4); @@ -656,6 +694,10 @@ STATUS turnConnectionAddPeer(PTurnConnection pTurnConnection, PKvsIpAddress pPee pTurnPeer->channelNumber = (UINT16) pTurnConnection->turnPeerCount + TURN_CHANNEL_BIND_CHANNEL_NUMBER_BASE; pTurnPeer->permissionExpirationTime = INVALID_TIMESTAMP_VALUE; pTurnPeer->ready = FALSE; + pTurnPeer->firstTimeCreatePermReq = TRUE; + pTurnPeer->firstTimeBindChannelReq = TRUE; + pTurnPeer->firstTimeCreatePermResponse = TRUE; + pTurnPeer->firstTimeBindChannelResponse = TRUE; CHK_STATUS(xorIpAddress(&pTurnPeer->xorAddress, NULL)); /* only work for IPv4 for now */ CHK_STATUS(createTransactionIdStore(DEFAULT_MAX_STORED_TRANSACTION_ID_COUNT, &pTurnPeer->pTransactionIdStore)); @@ -835,6 +877,7 @@ STATUS turnConnectionRefreshPermission(PTurnConnection pTurnConnection, PBOOL pN UINT64 currTime = 0; PTurnPeer pTurnPeer = NULL; BOOL needRefresh = FALSE; + CHAR ipAddr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; UINT32 i; CHK(pTurnConnection != NULL && pNeedRefresh != NULL, STATUS_NULL_ARG); @@ -846,7 +889,8 @@ STATUS turnConnectionRefreshPermission(PTurnConnection pTurnConnection, PBOOL pN pTurnPeer = &pTurnConnection->turnPeerList[i]; if (IS_VALID_TIMESTAMP(pTurnPeer->permissionExpirationTime) && currTime + DEFAULT_TURN_PERMISSION_REFRESH_GRACE_PERIOD >= pTurnPeer->permissionExpirationTime) { - DLOGD("Refreshing turn permission"); + getIpAddrStr(&pTurnPeer->address, ipAddr, ARRAY_SIZE(ipAddr)); + DLOGD("[%p] Refreshing turn permission for %s:%d", pTurnConnection, ipAddr, pTurnPeer->address.port); needRefresh = TRUE; } } @@ -889,259 +933,6 @@ STATUS turnConnectionFreePreAllocatedPackets(PTurnConnection pTurnConnection) return retStatus; } -STATUS turnConnectionStepState(PTurnConnection pTurnConnection) -{ - ENTERS(); - STATUS retStatus = STATUS_SUCCESS; - UINT32 readyPeerCount = 0, channelWithPermissionCount = 0; - UINT64 currentTime = GETTIME(); - CHAR ipAddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; - TURN_CONNECTION_STATE previousState = TURN_STATE_NEW; - BOOL refreshPeerPermission = FALSE; - UINT32 i = 0; - - CHK(pTurnConnection != NULL, STATUS_NULL_ARG); - - previousState = pTurnConnection->state; - - switch (pTurnConnection->state) { - case TURN_STATE_NEW: - // create empty turn allocation request - CHK_STATUS(turnConnectionPackageTurnAllocationRequest(NULL, NULL, NULL, 0, DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, - &pTurnConnection->pTurnPacket)); - - pTurnConnection->state = TURN_STATE_CHECK_SOCKET_CONNECTION; - pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_SOCKET_CONNECT_TIMEOUT; - break; - - case TURN_STATE_CHECK_SOCKET_CONNECTION: - if (socketConnectionIsConnected(pTurnConnection->pControlChannel)) { - /* initialize TLS once tcp connection is established */ - /* Start receiving data for TLS handshake */ - ATOMIC_STORE_BOOL(&pTurnConnection->pControlChannel->receiveData, TRUE); - - /* We dont support DTLS and TCP, so only options are TCP/TLS and UDP. */ - /* TODO: add plain TCP once it becomes available. */ - if (pTurnConnection->protocol == KVS_SOCKET_PROTOCOL_TCP && pTurnConnection->pControlChannel->pTlsSession == NULL) { - CHK_STATUS(socketConnectionInitSecureConnection(pTurnConnection->pControlChannel, FALSE)); - } - - pTurnConnection->state = TURN_STATE_GET_CREDENTIALS; - pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_GET_CREDENTIAL_TIMEOUT; - } else { - CHK(currentTime < pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_STATE_TRANSITION_TIMEOUT); - } - - // fallthrough here, missing break intended - case TURN_STATE_GET_CREDENTIALS: - - if (pTurnConnection->credentialObtained) { - DLOGV("Updated turn allocation request credential after receiving 401"); - - // update turn allocation packet with credentials - CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnPacket)); - CHK_STATUS(turnConnectionGetLongTermKey(pTurnConnection->turnServer.username, pTurnConnection->turnRealm, - pTurnConnection->turnServer.credential, pTurnConnection->longTermKey, - SIZEOF(pTurnConnection->longTermKey))); - CHK_STATUS(turnConnectionPackageTurnAllocationRequest(pTurnConnection->turnServer.username, pTurnConnection->turnRealm, - pTurnConnection->turnNonce, pTurnConnection->nonceLen, - DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket)); - - pTurnConnection->state = TURN_STATE_ALLOCATION; - pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_ALLOCATION_TIMEOUT; - pTurnConnection->stateTryCountMax = DEFAULT_TURN_ALLOCATION_MAX_TRY_COUNT; - pTurnConnection->stateTryCount = 0; - } else { - CHK(currentTime < pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_STATE_TRANSITION_TIMEOUT); - } - break; - - case TURN_STATE_ALLOCATION: - - if (ATOMIC_LOAD_BOOL(&pTurnConnection->hasAllocation)) { - CHK_STATUS(getIpAddrStr(&pTurnConnection->relayAddress, ipAddrStr, ARRAY_SIZE(ipAddrStr))); - DLOGD("Relay address received: %s, port: %u", ipAddrStr, (UINT16) getInt16(pTurnConnection->relayAddress.port)); - - if (pTurnConnection->pTurnCreatePermissionPacket != NULL) { - CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnCreatePermissionPacket)); - } - CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_CREATE_PERMISSION, NULL, &pTurnConnection->pTurnCreatePermissionPacket)); - // use host address as placeholder. hostAddress should have the same family as peer address - CHK_STATUS(appendStunAddressAttribute(pTurnConnection->pTurnCreatePermissionPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS, - &pTurnConnection->hostAddress)); - CHK_STATUS(appendStunUsernameAttribute(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->turnServer.username)); - CHK_STATUS(appendStunRealmAttribute(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->turnRealm)); - CHK_STATUS( - appendStunNonceAttribute(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->turnNonce, pTurnConnection->nonceLen)); - - // create channel bind packet here too so for each peer as soon as permission is created, it can start - // sending chaneel bind request - if (pTurnConnection->pTurnChannelBindPacket != NULL) { - CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnChannelBindPacket)); - } - CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_CHANNEL_BIND_REQUEST, NULL, &pTurnConnection->pTurnChannelBindPacket)); - // use host address as placeholder - CHK_STATUS(appendStunAddressAttribute(pTurnConnection->pTurnChannelBindPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS, - &pTurnConnection->hostAddress)); - CHK_STATUS(appendStunChannelNumberAttribute(pTurnConnection->pTurnChannelBindPacket, 0)); - CHK_STATUS(appendStunUsernameAttribute(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->turnServer.username)); - CHK_STATUS(appendStunRealmAttribute(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->turnRealm)); - CHK_STATUS(appendStunNonceAttribute(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->turnNonce, pTurnConnection->nonceLen)); - - if (pTurnConnection->pTurnAllocationRefreshPacket != NULL) { - CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnAllocationRefreshPacket)); - } - CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_REFRESH, NULL, &pTurnConnection->pTurnAllocationRefreshPacket)); - CHK_STATUS(appendStunLifetimeAttribute(pTurnConnection->pTurnAllocationRefreshPacket, DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS)); - CHK_STATUS(appendStunUsernameAttribute(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->turnServer.username)); - CHK_STATUS(appendStunRealmAttribute(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->turnRealm)); - CHK_STATUS( - appendStunNonceAttribute(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->turnNonce, pTurnConnection->nonceLen)); - - pTurnConnection->state = TURN_STATE_CREATE_PERMISSION; - pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT; - - } else { - pTurnConnection->stateTryCount++; - CHK(pTurnConnection->stateTryCount < pTurnConnection->stateTryCountMax, STATUS_TURN_CONNECTION_ALLOCAITON_FAILED); - CHK(currentTime < pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_STATE_TRANSITION_TIMEOUT); - } - break; - - case TURN_STATE_CREATE_PERMISSION: - - for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { - // As soon as create permission succeeded, we start sending channel bind message. - // So connectionState could've already advanced to ready state. - if (pTurnConnection->turnPeerList[i].connectionState == TURN_PEER_CONN_STATE_BIND_CHANNEL || - pTurnConnection->turnPeerList[i].connectionState == TURN_PEER_CONN_STATE_READY) { - channelWithPermissionCount++; - } - } - - // push back timeout if no peer is available yet - if (pTurnConnection->turnPeerCount == 0) { - pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT; - CHK(FALSE, retStatus); - } - - if (currentTime >= pTurnConnection->stateTimeoutTime || channelWithPermissionCount == pTurnConnection->turnPeerCount) { - CHK(channelWithPermissionCount > 0, STATUS_TURN_CONNECTION_FAILED_TO_CREATE_PERMISSION); - - // go to next state if we have at least one ready peer - pTurnConnection->state = TURN_STATE_BIND_CHANNEL; - pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_BIND_CHANNEL_TIMEOUT; - } - break; - - case TURN_STATE_BIND_CHANNEL: - - for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { - if (pTurnConnection->turnPeerList[i].connectionState == TURN_PEER_CONN_STATE_READY) { - readyPeerCount++; - } - } - - if (currentTime >= pTurnConnection->stateTimeoutTime || readyPeerCount == pTurnConnection->turnPeerCount) { - CHK(readyPeerCount > 0, STATUS_TURN_CONNECTION_FAILED_TO_BIND_CHANNEL); - // go to next state if we have at least one ready peer - pTurnConnection->state = TURN_STATE_READY; - } - break; - - case TURN_STATE_READY: - - CHK_STATUS(turnConnectionRefreshPermission(pTurnConnection, &refreshPeerPermission)); - if (refreshPeerPermission) { - // reset pTurnPeer->connectionState to make them go through create permission and channel bind again - for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { - pTurnConnection->turnPeerList[i].connectionState = TURN_PEER_CONN_STATE_CREATE_PERMISSION; - } - - pTurnConnection->currentTimerCallingPeriod = DEFAULT_TURN_TIMER_INTERVAL_BEFORE_READY; - CHK_STATUS(timerQueueUpdateTimerPeriod(pTurnConnection->timerQueueHandle, (UINT64) pTurnConnection, - (UINT32) ATOMIC_LOAD(&pTurnConnection->timerCallbackId), - pTurnConnection->currentTimerCallingPeriod)); - pTurnConnection->state = TURN_STATE_CREATE_PERMISSION; - pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT; - } else if (pTurnConnection->currentTimerCallingPeriod != DEFAULT_TURN_TIMER_INTERVAL_AFTER_READY) { - // use longer timer interval as now it just needs to check disconnection and permission expiration. - pTurnConnection->currentTimerCallingPeriod = DEFAULT_TURN_TIMER_INTERVAL_AFTER_READY; - CHK_STATUS(timerQueueUpdateTimerPeriod(pTurnConnection->timerQueueHandle, (UINT64) pTurnConnection, - (UINT32) ATOMIC_LOAD(&pTurnConnection->timerCallbackId), - pTurnConnection->currentTimerCallingPeriod)); - } - break; - - case TURN_STATE_CLEAN_UP: - /* start cleanning up even if we dont receive allocation freed response in time, or if connection is already closed, - * since we already sent multiple STUN refresh packets with 0 lifetime. */ - if (socketConnectionIsClosed(pTurnConnection->pControlChannel) || !ATOMIC_LOAD_BOOL(&pTurnConnection->hasAllocation) || - currentTime >= pTurnConnection->stateTimeoutTime) { - // clean transactionId store for each turn peer, preserving the peers - for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { - transactionIdStoreClear(pTurnConnection->turnPeerList[i].pTransactionIdStore); - } - - CHK_STATUS(turnConnectionFreePreAllocatedPackets(pTurnConnection)); - if (pTurnConnection != NULL) { - CHK_STATUS(socketConnectionClosed(pTurnConnection->pControlChannel)); - } - pTurnConnection->state = STATUS_SUCCEEDED(pTurnConnection->errorStatus) ? TURN_STATE_NEW : TURN_STATE_FAILED; - ATOMIC_STORE_BOOL(&pTurnConnection->shutdownComplete, TRUE); - } - - break; - - case TURN_STATE_FAILED: - DLOGW("TurnConnection in TURN_STATE_FAILED due to 0x%08x. Aborting TurnConnection", pTurnConnection->errorStatus); - /* Since we are aborting, not gonna do cleanup */ - ATOMIC_STORE_BOOL(&pTurnConnection->hasAllocation, FALSE); - /* If we haven't done cleanup, go to cleanup state which will do the cleanup then go to failed state again. */ - if (!ATOMIC_LOAD_BOOL(&pTurnConnection->shutdownComplete)) { - pTurnConnection->state = TURN_STATE_CLEAN_UP; - pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CLEAN_UP_TIMEOUT; - } - - break; - - default: - break; - } - -CleanUp: - - CHK_LOG_ERR(retStatus); - - if (STATUS_SUCCEEDED(retStatus) && ATOMIC_LOAD_BOOL(&pTurnConnection->stopTurnConnection) && pTurnConnection->state != TURN_STATE_CLEAN_UP && - pTurnConnection->state != TURN_STATE_NEW) { - pTurnConnection->state = TURN_STATE_CLEAN_UP; - pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CLEAN_UP_TIMEOUT; - } - - /* move to failed state if retStatus is failed status and state is not yet TURN_STATE_FAILED */ - if (STATUS_FAILED(retStatus) && pTurnConnection->state != TURN_STATE_FAILED) { - pTurnConnection->errorStatus = retStatus; - pTurnConnection->state = TURN_STATE_FAILED; - - if (pTurnConnection->turnConnectionCallbacks.turnStateFailedFn != NULL) { - pTurnConnection->turnConnectionCallbacks.turnStateFailedFn(pTurnConnection->pControlChannel, - pTurnConnection->turnConnectionCallbacks.customData); - } - - /* fix up state to trigger transition into TURN_STATE_FAILED */ - retStatus = STATUS_SUCCESS; - } - - if (pTurnConnection != NULL && previousState != pTurnConnection->state) { - DLOGD("TurnConnection state changed from %s to %s", turnConnectionGetStateStr(previousState), - turnConnectionGetStateStr(pTurnConnection->state)); - } - - LEAVES(); - return retStatus; -} - STATUS turnConnectionUpdateNonce(PTurnConnection pTurnConnection) { STATUS retStatus = STATUS_SUCCESS; @@ -1234,130 +1025,97 @@ BOOL turnConnectionGetRelayAddress(PTurnConnection pTurnConnection, PKvsIpAddres return FALSE; } -STATUS turnConnectionTimerCallback(UINT32 timerId, UINT64 currentTime, UINT64 customData) +STATUS checkTurnPeerConnections(PTurnConnection pTurnConnection) { - UNUSED_PARAM(timerId); - UNUSED_PARAM(currentTime); STATUS retStatus = STATUS_SUCCESS, sendStatus = STATUS_SUCCESS; - PTurnConnection pTurnConnection = (PTurnConnection) customData; - BOOL locked = FALSE, stopScheduling = FALSE; PTurnPeer pTurnPeer = NULL; PStunAttributeAddress pStunAttributeAddress = NULL; PStunAttributeChannelNumber pStunAttributeChannelNumber = NULL; - PStunAttributeLifetime pStunAttributeLifetime = NULL; UINT32 i = 0; + // turn mutex is assumed to be locked. CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { + pTurnPeer = &pTurnConnection->turnPeerList[i]; - MUTEX_LOCK(pTurnConnection->lock); - locked = TRUE; - - switch (pTurnConnection->state) { - case TURN_STATE_GET_CREDENTIALS: - sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, NULL, 0, &pTurnConnection->turnServer.ipAddress, + if (pTurnPeer->connectionState == TURN_PEER_CONN_STATE_CREATE_PERMISSION) { + if (pTurnPeer->firstTimeCreatePermReq) { + pTurnPeer->createPermissionStartTime = GETTIME(); + pTurnPeer->firstTimeCreatePermReq = FALSE; + } + // update peer address; + CHK_STATUS(getStunAttribute(pTurnConnection->pTurnCreatePermissionPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS, + (PStunAttributeHeader*) &pStunAttributeAddress)); + CHK_WARN(pStunAttributeAddress != NULL, STATUS_INTERNAL_ERROR, "xor peer address attribute not found"); + pStunAttributeAddress->address = pTurnPeer->address; + + CHK_STATUS(iceUtilsGenerateTransactionId(pTurnConnection->pTurnCreatePermissionPacket->header.transactionId, + ARRAY_SIZE(pTurnConnection->pTurnCreatePermissionPacket->header.transactionId))); + + CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); + transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnCreatePermissionPacket->header.transactionId); + sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->longTermKey, + ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, pTurnConnection->pControlChannel, NULL, FALSE); - break; - - case TURN_STATE_ALLOCATION: - sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, pTurnConnection->longTermKey, ARRAY_SIZE(pTurnConnection->longTermKey), - &pTurnConnection->turnServer.ipAddress, pTurnConnection->pControlChannel, NULL, FALSE); - break; - - case TURN_STATE_CREATE_PERMISSION: - // explicit fall-through - case TURN_STATE_BIND_CHANNEL: - // explicit fall-through - case TURN_STATE_READY: - for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { - pTurnPeer = &pTurnConnection->turnPeerList[i]; - if (pTurnPeer->connectionState == TURN_PEER_CONN_STATE_CREATE_PERMISSION) { - // update peer address; - CHK_STATUS(getStunAttribute(pTurnConnection->pTurnCreatePermissionPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS, - (PStunAttributeHeader*) &pStunAttributeAddress)); - CHK_WARN(pStunAttributeAddress != NULL, STATUS_INTERNAL_ERROR, "xor peer address attribute not found"); - pStunAttributeAddress->address = pTurnPeer->address; - - CHK_STATUS(iceUtilsGenerateTransactionId(pTurnConnection->pTurnCreatePermissionPacket->header.transactionId, - ARRAY_SIZE(pTurnConnection->pTurnCreatePermissionPacket->header.transactionId))); - - CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); - transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnCreatePermissionPacket->header.transactionId); - sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->longTermKey, - ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, - pTurnConnection->pControlChannel, NULL, FALSE); - - } else if (pTurnPeer->connectionState == TURN_PEER_CONN_STATE_BIND_CHANNEL) { - // update peer address; - CHK_STATUS(getStunAttribute(pTurnConnection->pTurnChannelBindPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS, - (PStunAttributeHeader*) &pStunAttributeAddress)); - CHK_WARN(pStunAttributeAddress != NULL, STATUS_INTERNAL_ERROR, "xor peer address attribute not found"); - pStunAttributeAddress->address = pTurnPeer->address; - - // update channel number - CHK_STATUS(getStunAttribute(pTurnConnection->pTurnChannelBindPacket, STUN_ATTRIBUTE_TYPE_CHANNEL_NUMBER, - (PStunAttributeHeader*) &pStunAttributeChannelNumber)); - CHK_WARN(pStunAttributeChannelNumber != NULL, STATUS_INTERNAL_ERROR, "channel number attribute not found"); - pStunAttributeChannelNumber->channelNumber = pTurnPeer->channelNumber; - - CHK_STATUS(iceUtilsGenerateTransactionId(pTurnConnection->pTurnChannelBindPacket->header.transactionId, - ARRAY_SIZE(pTurnConnection->pTurnChannelBindPacket->header.transactionId))); - - CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); - transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnChannelBindPacket->header.transactionId); - sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->longTermKey, - ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, - pTurnConnection->pControlChannel, NULL, FALSE); - } + } else if (pTurnPeer->connectionState == TURN_PEER_CONN_STATE_BIND_CHANNEL) { + if (pTurnPeer->firstTimeBindChannelReq) { + pTurnPeer->bindChannelStartTime = GETTIME(); + pTurnPeer->firstTimeBindChannelReq = FALSE; } + // update peer address; + CHK_STATUS(getStunAttribute(pTurnConnection->pTurnChannelBindPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS, + (PStunAttributeHeader*) &pStunAttributeAddress)); + CHK_WARN(pStunAttributeAddress != NULL, STATUS_INTERNAL_ERROR, "xor peer address attribute not found"); + pStunAttributeAddress->address = pTurnPeer->address; + + // update channel number + CHK_STATUS(getStunAttribute(pTurnConnection->pTurnChannelBindPacket, STUN_ATTRIBUTE_TYPE_CHANNEL_NUMBER, + (PStunAttributeHeader*) &pStunAttributeChannelNumber)); + CHK_WARN(pStunAttributeChannelNumber != NULL, STATUS_INTERNAL_ERROR, "channel number attribute not found"); + pStunAttributeChannelNumber->channelNumber = pTurnPeer->channelNumber; + + CHK_STATUS(iceUtilsGenerateTransactionId(pTurnConnection->pTurnChannelBindPacket->header.transactionId, + ARRAY_SIZE(pTurnConnection->pTurnChannelBindPacket->header.transactionId))); + + CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); + transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnChannelBindPacket->header.transactionId); + sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->longTermKey, + ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, + pTurnConnection->pControlChannel, NULL, FALSE); + } + } - CHK_STATUS(turnConnectionRefreshAllocation(pTurnConnection)); - break; + CHK_STATUS(turnConnectionRefreshAllocation(pTurnConnection)); - case TURN_STATE_CLEAN_UP: - if (ATOMIC_LOAD_BOOL(&pTurnConnection->hasAllocation)) { - CHK_STATUS(getStunAttribute(pTurnConnection->pTurnAllocationRefreshPacket, STUN_ATTRIBUTE_TYPE_LIFETIME, - (PStunAttributeHeader*) &pStunAttributeLifetime)); - CHK(pStunAttributeLifetime != NULL, STATUS_INTERNAL_ERROR); - pStunAttributeLifetime->lifetime = 0; - sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->longTermKey, - ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, - pTurnConnection->pControlChannel, NULL, FALSE); - pTurnConnection->deallocatePacketSent = TRUE; - } +CleanUp: - break; + CHK_LOG_ERR(retStatus); - case TURN_STATE_FAILED: - stopScheduling = ATOMIC_LOAD_BOOL(&pTurnConnection->shutdownComplete); - break; + return retStatus; +} - default: - break; - } +STATUS turnConnectionTimerCallback(UINT32 timerId, UINT64 currentTime, UINT64 customData) +{ + UNUSED_PARAM(timerId); + UNUSED_PARAM(currentTime); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + BOOL locked = FALSE, stopScheduling = FALSE; - if (sendStatus == STATUS_SOCKET_CONNECTION_CLOSED_ALREADY) { - DLOGE("TurnConnection socket %d closed unexpectedly", pTurnConnection->pControlChannel->localSocket); - turnConnectionFatalError(pTurnConnection, sendStatus); - } + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + + MUTEX_LOCK(pTurnConnection->lock); + locked = TRUE; - /* drive the state machine. */ - CHK_STATUS(turnConnectionStepState(pTurnConnection)); + CHK_STATUS(stepTurnConnectionStateMachine(pTurnConnection)); - /* after turnConnectionStepState(), turn state is TURN_STATE_NEW only if TURN_STATE_CLEAN_UP is completed. Thus - * we can stop the timer. */ - if (pTurnConnection->state == TURN_STATE_NEW) { - stopScheduling = TRUE; - } + stopScheduling = ATOMIC_LOAD_BOOL(&pTurnConnection->shutdownComplete); CleanUp: CHK_LOG_ERR(retStatus); - if (locked) { - MUTEX_UNLOCK(pTurnConnection->lock); - } - if (stopScheduling) { retStatus = STATUS_TIMER_QUEUE_STOP_SCHEDULING; if (pTurnConnection != NULL) { @@ -1365,6 +1123,10 @@ STATUS turnConnectionTimerCallback(UINT32 timerId, UINT64 currentTime, UINT64 cu } } + if (locked) { + MUTEX_UNLOCK(pTurnConnection->lock); + } + return retStatus; } @@ -1372,12 +1134,14 @@ STATUS turnConnectionGetLongTermKey(PCHAR username, PCHAR realm, PCHAR password, { STATUS retStatus = STATUS_SUCCESS; CHAR stringBuffer[STUN_MAX_USERNAME_LEN + MAX_ICE_CONFIG_CREDENTIAL_LEN + STUN_MAX_REALM_LEN + 2]; // 2 for two ":" between each value + INT32 amountWritten = 0; CHK(username != NULL && realm != NULL && password != NULL && pBuffer != NULL, STATUS_NULL_ARG); CHK(username[0] != '\0' && realm[0] != '\0' && password[0] != '\0' && bufferLen >= KVS_MD5_DIGEST_LENGTH, STATUS_INVALID_ARG); CHK((STRLEN(username) + STRLEN(realm) + STRLEN(password)) <= ARRAY_SIZE(stringBuffer) - 2, STATUS_INVALID_ARG); - SPRINTF(stringBuffer, "%s:%s:%s", username, realm, password); + amountWritten = SNPRINTF(stringBuffer, SIZEOF(stringBuffer), "%s:%s:%s", username, realm, password); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "SNPRINTF error: Failed to generate the long term key with username, realm and password"); // TODO: Return back the error check KVS_MD5_DIGEST((PBYTE) stringBuffer, STRLEN(stringBuffer), pBuffer); @@ -1422,31 +1186,6 @@ STATUS turnConnectionPackageTurnAllocationRequest(PCHAR username, PCHAR realm, P return retStatus; } -PCHAR turnConnectionGetStateStr(TURN_CONNECTION_STATE state) -{ - switch (state) { - case TURN_STATE_NEW: - return TURN_STATE_NEW_STR; - case TURN_STATE_CHECK_SOCKET_CONNECTION: - return TURN_STATE_CHECK_SOCKET_CONNECTION_STR; - case TURN_STATE_GET_CREDENTIALS: - return TURN_STATE_GET_CREDENTIALS_STR; - case TURN_STATE_ALLOCATION: - return TURN_STATE_ALLOCATION_STR; - case TURN_STATE_CREATE_PERMISSION: - return TURN_STATE_CREATE_PERMISSION_STR; - case TURN_STATE_BIND_CHANNEL: - return TURN_STATE_BIND_CHANNEL_STR; - case TURN_STATE_READY: - return TURN_STATE_READY_STR; - case TURN_STATE_CLEAN_UP: - return TURN_STATE_CLEAN_UP_STR; - case TURN_STATE_FAILED: - return TURN_STATE_FAILED_STR; - } - return TURN_STATE_UNKNOWN_STR; -} - PTurnPeer turnConnectionGetPeerWithChannelNumber(PTurnConnection pTurnConnection, UINT16 channelNumber) { PTurnPeer pTurnPeer = NULL; diff --git a/src/source/Ice/TurnConnection.h b/src/source/Ice/TurnConnection.h index cca70b6072..6d73d8b336 100644 --- a/src/source/Ice/TurnConnection.h +++ b/src/source/Ice/TurnConnection.h @@ -21,11 +21,10 @@ extern "C" { #define DEFAULT_TURN_SEND_REFRESH_INVERVAL (1 * HUNDREDS_OF_NANOS_IN_A_SECOND) // turn state timeouts -#define DEFAULT_TURN_SOCKET_CONNECT_TIMEOUT (5 * HUNDREDS_OF_NANOS_IN_A_SECOND) #define DEFAULT_TURN_GET_CREDENTIAL_TIMEOUT (5 * HUNDREDS_OF_NANOS_IN_A_SECOND) #define DEFAULT_TURN_ALLOCATION_TIMEOUT (5 * HUNDREDS_OF_NANOS_IN_A_SECOND) -#define DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT (2 * HUNDREDS_OF_NANOS_IN_A_SECOND) -#define DEFAULT_TURN_BIND_CHANNEL_TIMEOUT (3 * HUNDREDS_OF_NANOS_IN_A_SECOND) +#define DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT (5 * HUNDREDS_OF_NANOS_IN_A_SECOND) +#define DEFAULT_TURN_BIND_CHANNEL_TIMEOUT (5 * HUNDREDS_OF_NANOS_IN_A_SECOND) #define DEFAULT_TURN_CLEAN_UP_TIMEOUT (10 * HUNDREDS_OF_NANOS_IN_A_SECOND) #define DEFAULT_TURN_ALLOCATION_REFRESH_GRACE_PERIOD (30 * HUNDREDS_OF_NANOS_IN_A_SECOND) @@ -36,8 +35,7 @@ extern "C" { #define DEFAULT_TURN_MESSAGE_RECV_CHANNEL_DATA_BUFFER_LEN MAX_TURN_CHANNEL_DATA_MESSAGE_SIZE #define DEFAULT_TURN_CHANNEL_DATA_BUFFER_SIZE 512 #define DEFAULT_TURN_MAX_PEER_COUNT 32 - -#define DEFAULT_TURN_ALLOCATION_MAX_TRY_COUNT 3 +#define MAX_TURN_PROFILE_LOG_DESC_LEN 256 // all turn channel numbers must be greater than 0x4000 and less than 0x7FFF #define TURN_CHANNEL_BIND_CHANNEL_NUMBER_BASE (UINT16) 0x4000 @@ -46,6 +44,8 @@ extern "C" { #define TURN_DATA_CHANNEL_SEND_OVERHEAD 4 #define TURN_DATA_CHANNEL_MSG_FIRST_BYTE 0x40 +#define TURN_STATE_MACHINE_NAME (PCHAR) "TURN" + #define TURN_STATE_NEW_STR (PCHAR) "TURN_STATE_NEW" #define TURN_STATE_CHECK_SOCKET_CONNECTION_STR (PCHAR) "TURN_STATE_CHECK_SOCKET_CONNECTION" #define TURN_STATE_GET_CREDENTIALS_STR (PCHAR) "TURN_STATE_GET_CREDENTIALS" @@ -60,18 +60,6 @@ extern "C" { typedef STATUS (*RelayAddressAvailableFunc)(UINT64, PKvsIpAddress, PSocketConnection); typedef STATUS (*TurnStateFailedFunc)(PSocketConnection, UINT64); -typedef enum { - TURN_STATE_NEW, - TURN_STATE_CHECK_SOCKET_CONNECTION, - TURN_STATE_GET_CREDENTIALS, - TURN_STATE_ALLOCATION, - TURN_STATE_CREATE_PERMISSION, - TURN_STATE_BIND_CHANNEL, - TURN_STATE_READY, - TURN_STATE_CLEAN_UP, - TURN_STATE_FAILED, -} TURN_CONNECTION_STATE; - typedef enum { TURN_PEER_CONN_STATE_CREATE_PERMISSION, TURN_PEER_CONN_STATE_BIND_CHANNEL, @@ -110,8 +98,23 @@ typedef struct { UINT16 channelNumber; UINT64 permissionExpirationTime; BOOL ready; + BOOL firstTimeCreatePermReq; + BOOL firstTimeCreatePermResponse; + UINT64 createPermissionStartTime; + UINT64 createPermissionTime; + BOOL firstTimeBindChannelReq; + BOOL firstTimeBindChannelResponse; + UINT64 bindChannelStartTime; + UINT64 bindChannelTime; } TurnPeer, *PTurnPeer; +typedef struct { + UINT64 getCredentialsStartTime; + UINT64 getCredentialsTime; + UINT64 createAllocationStartTime; + UINT64 createAllocationTime; +} TurnProfileDiagnostics, *PTurnProfileDiagnostics; + typedef struct __TurnConnection TurnConnection; struct __TurnConnection { volatile ATOMIC_BOOL stopTurnConnection; @@ -141,11 +144,9 @@ struct __TurnConnection { MUTEX sendLock; CVAR freeAllocationCvar; - TURN_CONNECTION_STATE state; + UINT64 state; UINT64 stateTimeoutTime; - UINT32 stateTryCount; - UINT32 stateTryCountMax; STATUS errorStatus; @@ -180,6 +181,8 @@ struct __TurnConnection { UINT64 currentTimerCallingPeriod; BOOL deallocatePacketSent; + TurnProfileDiagnostics turnProfileDiagnostics; + PStateMachine pStateMachine; }; typedef struct __TurnConnection* PTurnConnection; @@ -196,12 +199,13 @@ STATUS turnConnectionRefreshAllocation(PTurnConnection); STATUS turnConnectionRefreshPermission(PTurnConnection, PBOOL); STATUS turnConnectionFreePreAllocatedPackets(PTurnConnection); -STATUS turnConnectionStepState(PTurnConnection); +// used for state machine +UINT64 turnConnectionGetTime(UINT64); + STATUS turnConnectionUpdateNonce(PTurnConnection); STATUS turnConnectionTimerCallback(UINT32, UINT64, UINT64); STATUS turnConnectionGetLongTermKey(PCHAR, PCHAR, PCHAR, PBYTE, UINT32); STATUS turnConnectionPackageTurnAllocationRequest(PCHAR, PCHAR, PBYTE, UINT16, UINT32, PStunPacket*); -PCHAR turnConnectionGetStateStr(TURN_CONNECTION_STATE); STATUS turnConnectionIncomingDataHandler(PTurnConnection, PBYTE, UINT32, PKvsIpAddress, PKvsIpAddress, PTurnChannelData, PUINT32); @@ -214,6 +218,8 @@ VOID turnConnectionFatalError(PTurnConnection, STATUS); PTurnPeer turnConnectionGetPeerWithChannelNumber(PTurnConnection, UINT16); PTurnPeer turnConnectionGetPeerWithIp(PTurnConnection, PKvsIpAddress); +STATUS checkTurnPeerConnections(PTurnConnection); + #ifdef __cplusplus } #endif diff --git a/src/source/Ice/TurnConnectionStateMachine.c b/src/source/Ice/TurnConnectionStateMachine.c new file mode 100644 index 0000000000..42da265c85 --- /dev/null +++ b/src/source/Ice/TurnConnectionStateMachine.c @@ -0,0 +1,771 @@ + +/** + * Implementation of a turn connection states machine callbacks + */ +#define LOG_CLASS "TurnConnectionState" +#include "../Include_i.h" + +/** + * Static definitions of the states + */ +StateMachineState TURN_CONNECTION_STATE_MACHINE_STATES[] = { + {TURN_STATE_NEW, TURN_STATE_NEW | TURN_STATE_CLEAN_UP, fromNewTurnState, executeNewTurnState, NULL, INFINITE_RETRY_COUNT_SENTINEL, + STATUS_TURN_INVALID_STATE}, + {TURN_STATE_CHECK_SOCKET_CONNECTION, TURN_STATE_NEW | TURN_STATE_CHECK_SOCKET_CONNECTION, fromCheckSocketConnectionTurnState, + executeCheckSocketConnectionTurnState, NULL, INFINITE_RETRY_COUNT_SENTINEL, STATUS_TURN_INVALID_STATE}, + {TURN_STATE_GET_CREDENTIALS, TURN_STATE_CHECK_SOCKET_CONNECTION | TURN_STATE_GET_CREDENTIALS, fromGetCredentialsTurnState, + executeGetCredentialsTurnState, NULL, INFINITE_RETRY_COUNT_SENTINEL, STATUS_TURN_INVALID_STATE}, + {TURN_STATE_ALLOCATION, TURN_STATE_ALLOCATION | TURN_STATE_GET_CREDENTIALS, fromAllocationTurnState, executeAllocationTurnState, NULL, + INFINITE_RETRY_COUNT_SENTINEL, STATUS_TURN_INVALID_STATE}, + {TURN_STATE_CREATE_PERMISSION, TURN_STATE_CREATE_PERMISSION | TURN_STATE_ALLOCATION | TURN_STATE_READY, fromCreatePermissionTurnState, + executeCreatePermissionTurnState, NULL, INFINITE_RETRY_COUNT_SENTINEL, STATUS_TURN_INVALID_STATE}, + {TURN_STATE_BIND_CHANNEL, TURN_STATE_BIND_CHANNEL | TURN_STATE_CREATE_PERMISSION, fromBindChannelTurnState, executeBindChannelTurnState, NULL, + INFINITE_RETRY_COUNT_SENTINEL, STATUS_TURN_INVALID_STATE}, + {TURN_STATE_READY, TURN_STATE_READY | TURN_STATE_BIND_CHANNEL, fromReadyTurnState, executeReadyTurnState, NULL, INFINITE_RETRY_COUNT_SENTINEL, + STATUS_TURN_INVALID_STATE}, + {TURN_STATE_CLEAN_UP, + TURN_STATE_CLEAN_UP | TURN_STATE_FAILED | TURN_STATE_CHECK_SOCKET_CONNECTION | TURN_STATE_GET_CREDENTIALS | TURN_STATE_ALLOCATION | + TURN_STATE_CREATE_PERMISSION | TURN_STATE_BIND_CHANNEL | TURN_STATE_READY, + fromCleanUpTurnState, executeCleanUpTurnState, NULL, INFINITE_RETRY_COUNT_SENTINEL, STATUS_TURN_INVALID_STATE}, + {TURN_STATE_FAILED, + TURN_STATE_CLEAN_UP | TURN_STATE_FAILED | TURN_STATE_CHECK_SOCKET_CONNECTION | TURN_STATE_GET_CREDENTIALS | TURN_STATE_ALLOCATION | + TURN_STATE_CREATE_PERMISSION | TURN_STATE_BIND_CHANNEL | TURN_STATE_READY, + fromFailedTurnState, executeFailedTurnState, NULL, INFINITE_RETRY_COUNT_SENTINEL, STATUS_TURN_INVALID_STATE}, +}; + +UINT32 TURN_CONNECTION_STATE_MACHINE_STATE_COUNT = ARRAY_SIZE(TURN_CONNECTION_STATE_MACHINE_STATES); + +PCHAR turnConnectionGetStateStr(UINT64 state) +{ + switch (state) { + case TURN_STATE_NEW: + return TURN_STATE_NEW_STR; + case TURN_STATE_CHECK_SOCKET_CONNECTION: + return TURN_STATE_CHECK_SOCKET_CONNECTION_STR; + case TURN_STATE_GET_CREDENTIALS: + return TURN_STATE_GET_CREDENTIALS_STR; + case TURN_STATE_ALLOCATION: + return TURN_STATE_ALLOCATION_STR; + case TURN_STATE_CREATE_PERMISSION: + return TURN_STATE_CREATE_PERMISSION_STR; + case TURN_STATE_BIND_CHANNEL: + return TURN_STATE_BIND_CHANNEL_STR; + case TURN_STATE_READY: + return TURN_STATE_READY_STR; + case TURN_STATE_CLEAN_UP: + return TURN_STATE_CLEAN_UP_STR; + case TURN_STATE_FAILED: + return TURN_STATE_FAILED_STR; + } + return TURN_STATE_UNKNOWN_STR; +} + +STATUS checkTurnConnectionStateMachine(PTurnConnection pTurnConnection) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + BOOL transitionReady = FALSE; + + CHK(pTurnConnection != NULL && pTurnConnection->pStateMachine != NULL, STATUS_NULL_ARG); + + // if a state transition is ready, tell the timer to kick the timer + CHK_STATUS(checkForStateTransition(pTurnConnection->pStateMachine, &transitionReady)); + + if (transitionReady) { + // dangerous to have any mutexes locked by timerqueue when entering this function + CHK_STATUS(timerQueueKick(pTurnConnection->timerQueueHandle, pTurnConnection->timerCallbackId)); + } + +CleanUp: + + CHK_LOG_ERR(retStatus); + + LEAVES(); + return retStatus; +} + +STATUS stepTurnConnectionStateMachine(PTurnConnection pTurnConnection) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + UINT64 oldState; + UINT64 currentTime; + + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + + do { + oldState = pTurnConnection->state; + + retStatus = stepStateMachine(pTurnConnection->pStateMachine); + + if (STATUS_SUCCEEDED(retStatus) && ATOMIC_LOAD_BOOL(&pTurnConnection->stopTurnConnection) && pTurnConnection->state != TURN_STATE_NEW && + pTurnConnection->state != TURN_STATE_CLEAN_UP) { + currentTime = GETTIME(); + pTurnConnection->state = TURN_STATE_CLEAN_UP; + pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CLEAN_UP_TIMEOUT; + + /* fix up state to trigger transition into TURN_STATE_CLEAN_UP */ + retStatus = STATUS_SUCCESS; + CHK_STATUS(stepStateMachine(pTurnConnection->pStateMachine)); + } else if (STATUS_FAILED(retStatus) && pTurnConnection->state != TURN_STATE_FAILED) { + pTurnConnection->errorStatus = retStatus; + pTurnConnection->state = TURN_STATE_FAILED; + + /* There is data race condition when editing the candidate state without holding + * the IceAgent lock. However holding the turn lock and then locking the ice agent lock + * can result in a dead lock. Ice must always be locked first, and then turn. + */ + + MUTEX_UNLOCK(pTurnConnection->lock); + if (pTurnConnection->turnConnectionCallbacks.turnStateFailedFn != NULL) { + pTurnConnection->turnConnectionCallbacks.turnStateFailedFn(pTurnConnection->pControlChannel, + pTurnConnection->turnConnectionCallbacks.customData); + } + MUTEX_LOCK(pTurnConnection->lock); + + /* fix up state to trigger transition into TURN_STATE_FAILED */ + retStatus = STATUS_SUCCESS; + CHK_STATUS(stepStateMachine(pTurnConnection->pStateMachine)); + } + + if (oldState != pTurnConnection->state) { + DLOGD("[%p] Turn connection state changed from %s to %s.", (PVOID) pTurnConnection, turnConnectionGetStateStr(oldState), + turnConnectionGetStateStr(pTurnConnection->state)); + } else { + // state machine retry is not used. resetStateMachineRetryCount just to avoid + // state machine retry grace period overflow warning. + CHK_STATUS(resetStateMachineRetryCount(pTurnConnection->pStateMachine)); + } + } while (oldState != pTurnConnection->state); + +CleanUp: + + CHK_LOG_ERR(retStatus); + + LEAVES(); + return retStatus; +} + +/////////////////////////////////////////////////////////////////////////// +// State machine callback functions +/////////////////////////////////////////////////////////////////////////// +STATUS fromNewTurnState(UINT64 customData, PUINT64 pState) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 state = TURN_STATE_NEW; + + CHK(pTurnConnection != NULL && pState != NULL, STATUS_NULL_ARG); + + state = TURN_STATE_CHECK_SOCKET_CONNECTION; + *pState = state; + +CleanUp: + + LEAVES(); + return retStatus; +} + +STATUS executeNewTurnState(UINT64 customData, UINT64 time) +{ + ENTERS(); + UNUSED_PARAM(time); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + pTurnConnection->state = TURN_STATE_NEW; + +CleanUp: + + LEAVES(); + return retStatus; +} + +STATUS fromCheckSocketConnectionTurnState(UINT64 customData, PUINT64 pState) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 state = TURN_STATE_CHECK_SOCKET_CONNECTION; + BOOL locked = FALSE; + UINT64 currentTime; + + CHK(pTurnConnection != NULL && pState != NULL, STATUS_NULL_ARG); + + MUTEX_LOCK(pTurnConnection->lock); + locked = TRUE; + if (pTurnConnection->state == TURN_STATE_CLEAN_UP || pTurnConnection->state == TURN_STATE_FAILED) { + *pState = pTurnConnection->state; + CHK(FALSE, STATUS_SUCCESS); + } + + currentTime = GETTIME(); + if (socketConnectionIsConnected(pTurnConnection->pControlChannel)) { + state = TURN_STATE_GET_CREDENTIALS; + pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_GET_CREDENTIAL_TIMEOUT; + } + + *pState = state; + +CleanUp: + + if (locked) { + MUTEX_UNLOCK(pTurnConnection->lock); + } + + LEAVES(); + return retStatus; +} + +STATUS executeCheckSocketConnectionTurnState(UINT64 customData, UINT64 time) +{ + ENTERS(); + UNUSED_PARAM(time); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + + if (pTurnConnection->state != TURN_STATE_CHECK_SOCKET_CONNECTION) { + pTurnConnection->state = TURN_STATE_CHECK_SOCKET_CONNECTION; + CHK_STATUS( + turnConnectionPackageTurnAllocationRequest(NULL, NULL, NULL, 0, DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket)); + } +CleanUp: + + LEAVES(); + return retStatus; +} + +STATUS fromGetCredentialsTurnState(UINT64 customData, PUINT64 pState) +{ + ENTERS(); + UINT64 currentTime; + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 state = TURN_STATE_GET_CREDENTIALS; + BOOL locked = FALSE; + + CHK(pTurnConnection != NULL && pState != NULL, STATUS_NULL_ARG); + + MUTEX_LOCK(pTurnConnection->lock); + locked = TRUE; + + if (pTurnConnection->state == TURN_STATE_CLEAN_UP || pTurnConnection->state == TURN_STATE_FAILED) { + *pState = pTurnConnection->state; + CHK(FALSE, STATUS_SUCCESS); + } + + currentTime = GETTIME(); + + if (pTurnConnection->credentialObtained) { + state = TURN_STATE_ALLOCATION; + pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_ALLOCATION_TIMEOUT; + } + + *pState = state; + +CleanUp: + if (locked) { + MUTEX_UNLOCK(pTurnConnection->lock); + } + + LEAVES(); + return retStatus; +} + +STATUS executeGetCredentialsTurnState(UINT64 customData, UINT64 time) +{ + ENTERS(); + UNUSED_PARAM(time); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 currentTime; + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + + currentTime = GETTIME(); + + if (pTurnConnection->state != TURN_STATE_GET_CREDENTIALS) { + pTurnConnection->turnProfileDiagnostics.getCredentialsStartTime = currentTime; + /* initialize TLS once tcp connection is established */ + /* Start receiving data for TLS handshake */ + ATOMIC_STORE_BOOL(&pTurnConnection->pControlChannel->receiveData, TRUE); + + /* We dont support DTLS and TCP, so only options are TCP/TLS and UDP. */ + /* TODO: add plain TCP once it becomes available. */ + if (pTurnConnection->protocol == KVS_SOCKET_PROTOCOL_TCP && pTurnConnection->pControlChannel->pTlsSession == NULL) { + CHK_STATUS(socketConnectionInitSecureConnection(pTurnConnection->pControlChannel, FALSE)); + } + pTurnConnection->state = TURN_STATE_GET_CREDENTIALS; + } else { + CHK(currentTime <= pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_GET_CREDENTIALS_FAILED); + } + CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, NULL, 0, &pTurnConnection->turnServer.ipAddress, pTurnConnection->pControlChannel, + NULL, FALSE)); + +CleanUp: + + LEAVES(); + return retStatus; +} + +STATUS fromAllocationTurnState(UINT64 customData, PUINT64 pState) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 state = TURN_STATE_ALLOCATION; + UINT64 currentTime; + BOOL locked = FALSE; + + CHK(pTurnConnection != NULL && pState != NULL, STATUS_NULL_ARG); + MUTEX_LOCK(pTurnConnection->lock); + locked = TRUE; + + if (pTurnConnection->state == TURN_STATE_CLEAN_UP || pTurnConnection->state == TURN_STATE_FAILED) { + *pState = pTurnConnection->state; + CHK(FALSE, STATUS_SUCCESS); + } + + if (ATOMIC_LOAD_BOOL(&pTurnConnection->hasAllocation)) { + state = TURN_STATE_CREATE_PERMISSION; + currentTime = GETTIME(); + pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT; + } + + *pState = state; + +CleanUp: + if (locked) { + MUTEX_UNLOCK(pTurnConnection->lock); + } + + LEAVES(); + return retStatus; +} + +STATUS executeAllocationTurnState(UINT64 customData, UINT64 time) +{ + ENTERS(); + UNUSED_PARAM(time); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 currentTime; + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + + currentTime = GETTIME(); + if (pTurnConnection->state != TURN_STATE_ALLOCATION) { + DLOGV("Updated turn allocation request credential after receiving 401"); + pTurnConnection->turnProfileDiagnostics.createAllocationStartTime = GETTIME(); + // update turn allocation packet with credentials + CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnPacket)); + CHK_STATUS(turnConnectionGetLongTermKey(pTurnConnection->turnServer.username, pTurnConnection->turnRealm, + pTurnConnection->turnServer.credential, pTurnConnection->longTermKey, + SIZEOF(pTurnConnection->longTermKey))); + CHK_STATUS(turnConnectionPackageTurnAllocationRequest(pTurnConnection->turnServer.username, pTurnConnection->turnRealm, + pTurnConnection->turnNonce, pTurnConnection->nonceLen, + DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket)); + pTurnConnection->state = TURN_STATE_ALLOCATION; + } else { + CHK(currentTime <= pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_ALLOCATION_FAILED); + } + CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, pTurnConnection->longTermKey, ARRAY_SIZE(pTurnConnection->longTermKey), + &pTurnConnection->turnServer.ipAddress, pTurnConnection->pControlChannel, NULL, FALSE)); + +CleanUp: + + LEAVES(); + return retStatus; +} + +STATUS fromCreatePermissionTurnState(UINT64 customData, PUINT64 pState) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 state = TURN_STATE_CREATE_PERMISSION, currentTime; + UINT32 channelWithPermissionCount = 0, i = 0; + BOOL locked = FALSE; + + CHK(pTurnConnection != NULL && pState != NULL, STATUS_NULL_ARG); + + MUTEX_LOCK(pTurnConnection->lock); + locked = TRUE; + + *pState = state; + + if (pTurnConnection->state == TURN_STATE_CLEAN_UP || pTurnConnection->state == TURN_STATE_FAILED) { + *pState = pTurnConnection->state; + CHK(FALSE, STATUS_SUCCESS); + } + + currentTime = GETTIME(); + + for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { + // As soon as create permission succeeded, we start sending channel bind message. + // So connectionState could've already advanced to ready state. + if (pTurnConnection->turnPeerList[i].connectionState == TURN_PEER_CONN_STATE_BIND_CHANNEL || + pTurnConnection->turnPeerList[i].connectionState == TURN_PEER_CONN_STATE_READY) { + channelWithPermissionCount++; + } + } + + // push back timeout if no peer is available yet + if (pTurnConnection->turnPeerCount == 0) { + pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT; + CHK(FALSE, retStatus); + } + + if (currentTime > pTurnConnection->stateTimeoutTime || channelWithPermissionCount == pTurnConnection->turnPeerCount) { + CHK(channelWithPermissionCount > 0, STATUS_TURN_CONNECTION_FAILED_TO_CREATE_PERMISSION); + + // go to next state if we have at least one ready peer + state = TURN_STATE_BIND_CHANNEL; + pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_BIND_CHANNEL_TIMEOUT; + } + *pState = state; + +CleanUp: + + if (locked) { + MUTEX_UNLOCK(pTurnConnection->lock); + } + + LEAVES(); + return retStatus; +} + +STATUS executeCreatePermissionTurnState(UINT64 customData, UINT64 time) +{ + ENTERS(); + UNUSED_PARAM(time); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + CHAR ipAddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; + UINT64 currentTime; + + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + + if (pTurnConnection->state != TURN_STATE_CREATE_PERMISSION) { + CHK_STATUS(getIpAddrStr(&pTurnConnection->relayAddress, ipAddrStr, ARRAY_SIZE(ipAddrStr))); + DLOGD("Relay address received: %s, port: %u", ipAddrStr, (UINT16) getInt16(pTurnConnection->relayAddress.port)); + if (pTurnConnection->pTurnCreatePermissionPacket != NULL) { + CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnCreatePermissionPacket)); + } + CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_CREATE_PERMISSION, NULL, &pTurnConnection->pTurnCreatePermissionPacket)); + // use host address as placeholder. hostAddress should have the same family as peer address + CHK_STATUS(appendStunAddressAttribute(pTurnConnection->pTurnCreatePermissionPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS, + &pTurnConnection->hostAddress)); + CHK_STATUS(appendStunUsernameAttribute(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->turnServer.username)); + CHK_STATUS(appendStunRealmAttribute(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->turnRealm)); + CHK_STATUS(appendStunNonceAttribute(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->turnNonce, pTurnConnection->nonceLen)); + + // create channel bind packet here too so for each peer as soon as permission is created, it can start + // sending channel bind request + if (pTurnConnection->pTurnChannelBindPacket != NULL) { + CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnChannelBindPacket)); + } + CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_CHANNEL_BIND_REQUEST, NULL, &pTurnConnection->pTurnChannelBindPacket)); + // use host address as placeholder + CHK_STATUS( + appendStunAddressAttribute(pTurnConnection->pTurnChannelBindPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS, &pTurnConnection->hostAddress)); + CHK_STATUS(appendStunChannelNumberAttribute(pTurnConnection->pTurnChannelBindPacket, 0)); + CHK_STATUS(appendStunUsernameAttribute(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->turnServer.username)); + CHK_STATUS(appendStunRealmAttribute(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->turnRealm)); + CHK_STATUS(appendStunNonceAttribute(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->turnNonce, pTurnConnection->nonceLen)); + + if (pTurnConnection->pTurnAllocationRefreshPacket != NULL) { + CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnAllocationRefreshPacket)); + } + CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_REFRESH, NULL, &pTurnConnection->pTurnAllocationRefreshPacket)); + CHK_STATUS(appendStunLifetimeAttribute(pTurnConnection->pTurnAllocationRefreshPacket, DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS)); + CHK_STATUS(appendStunUsernameAttribute(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->turnServer.username)); + CHK_STATUS(appendStunRealmAttribute(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->turnRealm)); + CHK_STATUS(appendStunNonceAttribute(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->turnNonce, pTurnConnection->nonceLen)); + + pTurnConnection->state = TURN_STATE_CREATE_PERMISSION; + } + + CHK_STATUS(checkTurnPeerConnections(pTurnConnection)); + + // push back timeout if no peer is available yet + if (pTurnConnection->turnPeerCount == 0) { + currentTime = GETTIME(); + pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT; + CHK(FALSE, retStatus); + } + +CleanUp: + + LEAVES(); + return retStatus; +} + +STATUS fromBindChannelTurnState(UINT64 customData, PUINT64 pState) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 state = TURN_STATE_BIND_CHANNEL; + UINT64 currentTime; + BOOL locked = FALSE; + UINT32 readyPeerCount = 0, i = 0; + + CHK(pTurnConnection != NULL && pState != NULL, STATUS_NULL_ARG); + + MUTEX_LOCK(pTurnConnection->lock); + locked = TRUE; + + if (pTurnConnection->state == TURN_STATE_CLEAN_UP || pTurnConnection->state == TURN_STATE_FAILED) { + *pState = pTurnConnection->state; + CHK(FALSE, STATUS_SUCCESS); + } + + currentTime = GETTIME(); + for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { + if (pTurnConnection->turnPeerList[i].connectionState == TURN_PEER_CONN_STATE_READY) { + readyPeerCount++; + } + } + if (currentTime > pTurnConnection->stateTimeoutTime || readyPeerCount == pTurnConnection->turnPeerCount) { + CHK(readyPeerCount > 0, STATUS_TURN_CONNECTION_FAILED_TO_BIND_CHANNEL); + // go to next state if we have at least one ready peer + state = TURN_STATE_READY; + } + *pState = state; + +CleanUp: + if (locked) { + MUTEX_UNLOCK(pTurnConnection->lock); + } + + LEAVES(); + return retStatus; +} + +STATUS executeBindChannelTurnState(UINT64 customData, UINT64 time) +{ + ENTERS(); + UNUSED_PARAM(time); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + if (pTurnConnection->state != TURN_STATE_BIND_CHANNEL) { + pTurnConnection->state = TURN_STATE_BIND_CHANNEL; + } + CHK_STATUS(checkTurnPeerConnections(pTurnConnection)); + +CleanUp: + + LEAVES(); + return retStatus; +} + +STATUS fromReadyTurnState(UINT64 customData, PUINT64 pState) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 state = TURN_STATE_READY; + BOOL refreshPeerPermission = FALSE; + UINT64 currentTime; + UINT32 i; + BOOL locked = FALSE; + + CHK(pTurnConnection != NULL && pState != NULL, STATUS_NULL_ARG); + + MUTEX_LOCK(pTurnConnection->lock); + locked = TRUE; + + if (pTurnConnection->state == TURN_STATE_CLEAN_UP || pTurnConnection->state == TURN_STATE_FAILED) { + *pState = pTurnConnection->state; + CHK(FALSE, STATUS_SUCCESS); + } + + CHK_STATUS(turnConnectionRefreshPermission(pTurnConnection, &refreshPeerPermission)); + currentTime = GETTIME(); + if (refreshPeerPermission) { + // reset pTurnPeer->connectionState to make them go through create permission and channel bind again + for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { + pTurnConnection->turnPeerList[i].connectionState = TURN_PEER_CONN_STATE_CREATE_PERMISSION; + } + + pTurnConnection->currentTimerCallingPeriod = DEFAULT_TURN_TIMER_INTERVAL_BEFORE_READY; + state = TURN_STATE_CREATE_PERMISSION; + pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT; + MUTEX_UNLOCK(pTurnConnection->lock); + locked = FALSE; + CHK_STATUS(timerQueueUpdateTimerPeriod(pTurnConnection->timerQueueHandle, (UINT64) pTurnConnection, + (UINT32) ATOMIC_LOAD(&pTurnConnection->timerCallbackId), pTurnConnection->currentTimerCallingPeriod)); + } else if (pTurnConnection->currentTimerCallingPeriod != DEFAULT_TURN_TIMER_INTERVAL_AFTER_READY) { + // use longer timer interval as now it just needs to check disconnection and permission expiration. + pTurnConnection->currentTimerCallingPeriod = DEFAULT_TURN_TIMER_INTERVAL_AFTER_READY; + MUTEX_UNLOCK(pTurnConnection->lock); + locked = FALSE; + CHK_STATUS(timerQueueUpdateTimerPeriod(pTurnConnection->timerQueueHandle, (UINT64) pTurnConnection, + (UINT32) ATOMIC_LOAD(&pTurnConnection->timerCallbackId), pTurnConnection->currentTimerCallingPeriod)); + } + + *pState = state; + +CleanUp: + + if (locked) { + MUTEX_UNLOCK(pTurnConnection->lock); + } + + LEAVES(); + return retStatus; +} + +STATUS executeReadyTurnState(UINT64 customData, UINT64 time) +{ + ENTERS(); + UNUSED_PARAM(time); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + + pTurnConnection->state = TURN_STATE_READY; + CHK_STATUS(checkTurnPeerConnections(pTurnConnection)); + +CleanUp: + + LEAVES(); + return retStatus; +} + +STATUS fromCleanUpTurnState(UINT64 customData, PUINT64 pState) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 state = TURN_STATE_CLEAN_UP; + UINT64 currentTime; + UINT32 i = 0; + BOOL locked = FALSE; + + CHK(pTurnConnection != NULL && pState != NULL, STATUS_NULL_ARG); + MUTEX_LOCK(pTurnConnection->lock); + locked = TRUE; + + if (pTurnConnection->state == TURN_STATE_FAILED) { + *pState = pTurnConnection->state; + CHK(FALSE, STATUS_SUCCESS); + } + + /* start cleaning up even if we dont receive allocation freed response in time, or if connection is already closed, + * since we already sent multiple STUN refresh packets with 0 lifetime. */ + currentTime = GETTIME(); + if (socketConnectionIsClosed(pTurnConnection->pControlChannel) || !ATOMIC_LOAD_BOOL(&pTurnConnection->hasAllocation) || + currentTime > pTurnConnection->stateTimeoutTime) { + // clean transactionId store for each turn peer, preserving the peers + for (i = 0; i < pTurnConnection->turnPeerCount; ++i) { + transactionIdStoreClear(pTurnConnection->turnPeerList[i].pTransactionIdStore); + } + + CHK_STATUS(turnConnectionFreePreAllocatedPackets(pTurnConnection)); + if (pTurnConnection != NULL) { + CHK_STATUS(socketConnectionClosed(pTurnConnection->pControlChannel)); + } + state = STATUS_SUCCEEDED(pTurnConnection->errorStatus) ? TURN_STATE_CLEAN_UP : TURN_STATE_FAILED; + ATOMIC_STORE_BOOL(&pTurnConnection->shutdownComplete, TRUE); + } + *pState = state; + +CleanUp: + if (locked) { + MUTEX_UNLOCK(pTurnConnection->lock); + } + + LEAVES(); + return retStatus; +} + +STATUS executeCleanUpTurnState(UINT64 customData, UINT64 time) +{ + ENTERS(); + UNUSED_PARAM(time); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + PStunAttributeLifetime pStunAttributeLifetime = NULL; + + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + + pTurnConnection->state = TURN_STATE_CLEAN_UP; + if (ATOMIC_LOAD_BOOL(&pTurnConnection->hasAllocation)) { + CHK_STATUS(getStunAttribute(pTurnConnection->pTurnAllocationRefreshPacket, STUN_ATTRIBUTE_TYPE_LIFETIME, + (PStunAttributeHeader*) &pStunAttributeLifetime)); + CHK(pStunAttributeLifetime != NULL, STATUS_INTERNAL_ERROR); + pStunAttributeLifetime->lifetime = 0; + CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->longTermKey, + ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, + pTurnConnection->pControlChannel, NULL, FALSE)); + pTurnConnection->deallocatePacketSent = TRUE; + } + +CleanUp: + + LEAVES(); + return retStatus; +} + +STATUS fromFailedTurnState(UINT64 customData, PUINT64 pState) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + UINT64 state = TURN_STATE_FAILED; + UINT64 currentTime; + BOOL locked = FALSE; + + CHK(pTurnConnection != NULL && pState != NULL, STATUS_NULL_ARG); + MUTEX_LOCK(pTurnConnection->lock); + locked = TRUE; + + if (pTurnConnection->state == TURN_STATE_CLEAN_UP) { + *pState = pTurnConnection->state; + CHK(FALSE, STATUS_SUCCESS); + } + + /* If we haven't done cleanup, go to cleanup state which will do the cleanup then go to failed state again. */ + if (!ATOMIC_LOAD_BOOL(&pTurnConnection->shutdownComplete)) { + currentTime = GETTIME(); + state = TURN_STATE_CLEAN_UP; + pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CLEAN_UP_TIMEOUT; + } + + *pState = state; + +CleanUp: + if (locked) { + MUTEX_UNLOCK(pTurnConnection->lock); + } + + LEAVES(); + return retStatus; +} + +STATUS executeFailedTurnState(UINT64 customData, UINT64 time) +{ + ENTERS(); + UNUSED_PARAM(time); + STATUS retStatus = STATUS_SUCCESS; + PTurnConnection pTurnConnection = (PTurnConnection) customData; + + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); + + pTurnConnection->state = TURN_STATE_FAILED; + DLOGW("TurnConnection in TURN_STATE_FAILED due to 0x%08x. Aborting TurnConnection", pTurnConnection->errorStatus); + /* Since we are aborting, not gonna do cleanup */ + ATOMIC_STORE_BOOL(&pTurnConnection->hasAllocation, FALSE); + +CleanUp: + + LEAVES(); + return retStatus; +} diff --git a/src/source/Ice/TurnConnectionStateMachine.h b/src/source/Ice/TurnConnectionStateMachine.h new file mode 100644 index 0000000000..7ec545387a --- /dev/null +++ b/src/source/Ice/TurnConnectionStateMachine.h @@ -0,0 +1,82 @@ +/*************************************************** +TURN Connections State Machine internal include file +***************************************************/ +#ifndef __KINESIS_VIDEO_WEBRTC_TURN_STATE_MACHINE__ +#define __KINESIS_VIDEO_WEBRTC_TURN_STATE_MACHINE__ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * TURN states definitions + * + * TURN_STATE_NONE: Dummy state + * TURN_STATE_NEW: State at creation + * TURN_STATE_CHECK_SOCKET_CONNECTION: + * TURN_STATE_GET_CREDENTIALS: + * TURN_STATE_ALLOCATION: + * TURN_STATE_CREATE_PERMISSION: + * TURN_STATE_BIND_CHANNEL: + * TURN_STATE_READY: + * TURN_STATE_CLEAN_UP: + * TURN_STATE_FAILED: + */ + +#define TURN_STATE_NONE ((UINT64) 0) // 0x000000000 +#define TURN_STATE_NEW ((UINT64) (1 << 0)) // 0x000000001 +#define TURN_STATE_CHECK_SOCKET_CONNECTION ((UINT64) (1 << 1)) // 0x000000002 +#define TURN_STATE_GET_CREDENTIALS ((UINT64) (1 << 2)) // 0x000000004 +#define TURN_STATE_ALLOCATION ((UINT64) (1 << 3)) // 0x000000008 +#define TURN_STATE_CREATE_PERMISSION ((UINT64) (1 << 4)) // 0x000000010 +#define TURN_STATE_BIND_CHANNEL ((UINT64) (1 << 5)) // 0x000000020 +#define TURN_STATE_READY ((UINT64) (1 << 6)) // 0x000000040 +#define TURN_STATE_CLEAN_UP ((UINT64) (1 << 7)) // 0x000000080 +#define TURN_STATE_FAILED ((UINT64) (1 << 8)) // 0x000000100 + +#define TURN_STATE_NONE_STR (PCHAR) "TURN_STATE_NONE" +#define TURN_STATE_NEW_STR (PCHAR) "TURN_STATE_NEW" +#define TURN_STATE_CHECK_SOCKET_CONNECTION_STR (PCHAR) "TURN_STATE_CHECK_SOCKET_CONNECTION" +#define TURN_STATE_GET_CREDENTIALS_STR (PCHAR) "TURN_STATE_GET_CREDENTIALS" +#define TURN_STATE_ALLOCATION_STR (PCHAR) "TURN_STATE_ALLOCATION" +#define TURN_STATE_CREATE_PERMISSION_STR (PCHAR) "TURN_STATE_CREATE_PERMISSION" +#define TURN_STATE_BIND_CHANNEL_STR (PCHAR) "TURN_STATE_BIND_CHANNEL" +#define TURN_STATE_READY_STR (PCHAR) "TURN_STATE_READY" +#define TURN_STATE_CLEAN_UP_STR (PCHAR) "TURN_STATE_CLEAN_UP" +#define TURN_STATE_FAILED_STR (PCHAR) "TURN_STATE_FAILED" +#define TURN_STATE_UNKNOWN_STR (PCHAR) "TURN_STATE_UNKNOWN" + +// Whether to step the state machine +STATUS stepTurnConnectionStateMachine(PTurnConnection); +STATUS acceptTurnMachineState(PTurnConnection, UINT64); +STATUS checkTurnConnectionStateMachine(PTurnConnection); +PCHAR turnStateGetStateStr(UINT64 state); + +/** + * Turn state machine callbacks + */ +STATUS fromNewTurnState(UINT64, PUINT64); +STATUS executeNewTurnState(UINT64, UINT64); +STATUS fromCheckSocketConnectionTurnState(UINT64, PUINT64); +STATUS executeCheckSocketConnectionTurnState(UINT64, UINT64); +STATUS fromGetCredentialsTurnState(UINT64, PUINT64); +STATUS executeGetCredentialsTurnState(UINT64, UINT64); +STATUS fromAllocationTurnState(UINT64, PUINT64); +STATUS executeAllocationTurnState(UINT64, UINT64); +STATUS fromCreatePermissionTurnState(UINT64, PUINT64); +STATUS executeCreatePermissionTurnState(UINT64, UINT64); +STATUS fromBindChannelTurnState(UINT64, PUINT64); +STATUS executeBindChannelTurnState(UINT64, UINT64); +STATUS fromReadyTurnState(UINT64, PUINT64); +STATUS executeReadyTurnState(UINT64, UINT64); +STATUS fromCleanUpTurnState(UINT64, PUINT64); +STATUS executeCleanUpTurnState(UINT64, UINT64); +STATUS fromFailedTurnState(UINT64, PUINT64); +STATUS executeFailedTurnState(UINT64, UINT64); + +#ifdef __cplusplus +} +#endif +#endif /* __KINESIS_VIDEO_WEBRTC_TURN_STATE_MACHINE__ */ diff --git a/src/source/Include_i.h b/src/source/Include_i.h index d767ed668a..84fec5a69a 100644 --- a/src/source/Include_i.h +++ b/src/source/Include_i.h @@ -108,6 +108,9 @@ typedef struct { // Used for ensuring alignment #define ALIGN_UP_TO_MACHINE_WORD(x) ROUND_UP((x), SIZEOF(SIZE_T)) +typedef STATUS (*IceServerSetIpFunc)(UINT64, PCHAR, PKvsIpAddress); +STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen); + //////////////////////////////////////////////////// // Project forward declarations //////////////////////////////////////////////////// @@ -118,6 +121,7 @@ STATUS generateJSONSafeString(PCHAR, UINT32); //////////////////////////////////////////////////// // Project internal includes //////////////////////////////////////////////////// +#include "Threadpool/ThreadpoolContext.h" #include "Crypto/IOBuffer.h" #include "Crypto/Crypto.h" #include "Crypto/Dtls.h" @@ -131,9 +135,15 @@ STATUS generateJSONSafeString(PCHAR, UINT32); #include "Ice/IceAgent.h" #include "Ice/TurnConnection.h" #include "Ice/IceAgentStateMachine.h" +#include "Ice/TurnConnectionStateMachine.h" #include "Ice/NatBehaviorDiscovery.h" #include "Srtp/SrtpSession.h" #include "Sctp/Sctp.h" +#include "Signaling/FileCache.h" +#include "Signaling/Signaling.h" +#include "Signaling/ChannelInfo.h" +#include "Signaling/StateMachine.h" +#include "Signaling/LwsApiCalls.h" #include "Rtp/RtpPacket.h" #include "Rtcp/RtcpPacket.h" #include "Rtcp/RollingBuffer.h" @@ -149,11 +159,6 @@ STATUS generateJSONSafeString(PCHAR, UINT32); #include "Rtp/Codecs/RtpH264Payloader.h" #include "Rtp/Codecs/RtpOpusPayloader.h" #include "Rtp/Codecs/RtpG711Payloader.h" -#include "Signaling/FileCache.h" -#include "Signaling/Signaling.h" -#include "Signaling/ChannelInfo.h" -#include "Signaling/StateMachine.h" -#include "Signaling/LwsApiCalls.h" #include "Metrics/Metrics.h" //////////////////////////////////////////////////// diff --git a/src/source/PeerConnection/PeerConnection.c b/src/source/PeerConnection/PeerConnection.c index 3d10070a3c..5a3c7eef91 100644 --- a/src/source/PeerConnection/PeerConnection.c +++ b/src/source/PeerConnection/PeerConnection.c @@ -4,6 +4,46 @@ static volatile ATOMIC_BOOL gKvsWebRtcInitialized = (SIZE_T) FALSE; +// Function to get access to the Singleton instance +PWebRtcClientContext getWebRtcClientInstance() +{ + static WebRtcClientContext w = {.pStunIpAddrCtx = NULL, .stunCtxlock = INVALID_MUTEX_VALUE, .contextRefCnt = 0, .isContextInitialized = FALSE}; + ATOMIC_INCREMENT(&w.contextRefCnt); + return &w; +} + +VOID releaseHoldOnInstance(PWebRtcClientContext pWebRtcClientContext) +{ + ATOMIC_DECREMENT(&pWebRtcClientContext->contextRefCnt); +} + +STATUS createWebRtcClientInstance() +{ + PWebRtcClientContext pWebRtcClientContext = getWebRtcClientInstance(); + STATUS retStatus = STATUS_SUCCESS; + BOOL locked = FALSE; + + CHK_WARN(!ATOMIC_LOAD_BOOL(&pWebRtcClientContext->isContextInitialized), retStatus, "WebRtc client context already initialized, nothing to do"); + CHK_ERR(!IS_VALID_MUTEX_VALUE(pWebRtcClientContext->stunCtxlock), retStatus, "Mutex seems to have been created already"); + + pWebRtcClientContext->stunCtxlock = MUTEX_CREATE(TRUE); + CHK_ERR(IS_VALID_MUTEX_VALUE(pWebRtcClientContext->stunCtxlock), STATUS_NULL_ARG, "Mutex creation failed"); + MUTEX_LOCK(pWebRtcClientContext->stunCtxlock); + locked = TRUE; + CHK_WARN(pWebRtcClientContext->pStunIpAddrCtx == NULL, STATUS_INVALID_OPERATION, "STUN object already allocated"); + pWebRtcClientContext->pStunIpAddrCtx = (PStunIpAddrContext) MEMCALLOC(1, SIZEOF(StunIpAddrContext)); + CHK_ERR(pWebRtcClientContext->pStunIpAddrCtx != NULL, STATUS_NULL_ARG, "Memory allocation for WebRtc client object failed"); + pWebRtcClientContext->pStunIpAddrCtx->expirationDuration = 2 * HUNDREDS_OF_NANOS_IN_AN_HOUR; + ATOMIC_STORE_BOOL(&pWebRtcClientContext->isContextInitialized, TRUE); + DLOGI("Initialized WebRTC Client instance"); +CleanUp: + if (locked) { + MUTEX_UNLOCK(pWebRtcClientContext->stunCtxlock); + } + releaseHoldOnInstance(pWebRtcClientContext); + return retStatus; +} + STATUS allocateSrtp(PKvsPeerConnection pKvsPeerConnection) { DtlsKeyingMaterial dtlsKeyingMaterial; @@ -253,7 +293,7 @@ STATUS sendPacketToRtpReceiver(PKvsPeerConnection pKvsPeerConnection, PBYTE pBuf pTransceiver->inboundStats.headerBytesReceived += headerBytesReceived; pTransceiver->inboundStats.bytesReceived += bytesReceived; pTransceiver->inboundStats.received.jitter = pTransceiver->pJitterBuffer->jitter / pTransceiver->pJitterBuffer->clockRate; - pTransceiver->inboundStats.received.packetsDiscarded = packetsDiscarded; + pTransceiver->inboundStats.received.packetsDiscarded += packetsDiscarded; MUTEX_UNLOCK(pTransceiver->statsLock); } if (!ownedByJitterBuffer) { @@ -402,6 +442,17 @@ STATUS onFrameDroppedFunc(UINT64 customData, UINT16 startIndex, UINT16 endIndex, return retStatus; } +PVOID dtlsSessionStartThread(PVOID args) +{ + PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) args; + if (pKvsPeerConnection != NULL) { + dtlsSessionHandshakeInThread(pKvsPeerConnection->pDtlsSession, pKvsPeerConnection->dtlsIsServer); + } else { + DLOGE("Peer connection object NULL, cannot start DTLS handshake"); + } + return NULL; +} + VOID onIceConnectionStateChange(UINT64 customData, UINT64 connectionState) { STATUS retStatus = STATUS_SUCCESS; @@ -423,7 +474,8 @@ VOID onIceConnectionStateChange(UINT64 customData, UINT64 connectionState) case ICE_AGENT_STATE_CONNECTED: /* explicit fall-through */ case ICE_AGENT_STATE_NOMINATING: - /* explicit fall-through */ + newConnectionState = RTC_PEER_CONNECTION_STATE_CONNECTING; + break; case ICE_AGENT_STATE_READY: /* start dtlsSession as soon as ice is connected */ newConnectionState = RTC_PEER_CONNECTION_STATE_CONNECTING; @@ -455,7 +507,11 @@ VOID onIceConnectionStateChange(UINT64 customData, UINT64 connectionState) // wait until DTLS state changes to CONNECTED. // // Reference: https://w3c.github.io/webrtc-pc/#rtcpeerconnectionstate-enum +#if defined(ENABLE_KVS_THREADPOOL) && defined(KVS_USE_OPENSSL) + CHK_STATUS(threadpoolContextPush(dtlsSessionStartThread, (PVOID) pKvsPeerConnection)); +#else CHK_STATUS(dtlsSessionStart(pKvsPeerConnection->pDtlsSession, pKvsPeerConnection->dtlsIsServer)); +#endif } } @@ -466,6 +522,17 @@ VOID onIceConnectionStateChange(UINT64 customData, UINT64 connectionState) CHK_LOG_ERR(retStatus); } +#ifdef ENABLE_KVS_THREADPOOL +STATUS peerConnectionAsync(startRoutine fn, PVOID data) +{ + STATUS retStatus = STATUS_SUCCESS; + CHK_STATUS(threadpoolContextPush(fn, data)); +CleanUp: + + return retStatus; +} +#endif + VOID onNewIceLocalCandidate(UINT64 customData, PCHAR candidateSdpStr) { STATUS retStatus = STATUS_SUCCESS; @@ -694,6 +761,135 @@ STATUS rtcpReportsCallback(UINT32 timerId, UINT64 currentTime, UINT64 customData return retStatus; } +// Not thread safe +STATUS getStunAddr(PStunIpAddrContext pStunIpAddrCtx) +{ + INT32 errCode; + STATUS retStatus = STATUS_SUCCESS; + struct addrinfo *rp, *res; + struct sockaddr_in* ipv4Addr; + BOOL resolved = FALSE; + + errCode = getaddrinfo(pStunIpAddrCtx->hostname, NULL, NULL, &res); + if (errCode != 0) { + DLOGI("Failed to resolve hostname with errcode: %d", errCode); + retStatus = STATUS_RESOLVE_HOSTNAME_FAILED; + } else { + for (rp = res; rp != NULL && !resolved; rp = rp->ai_next) { + if (rp->ai_family == AF_INET) { + ipv4Addr = (struct sockaddr_in*) rp->ai_addr; + pStunIpAddrCtx->kvsIpAddr.family = KVS_IP_FAMILY_TYPE_IPV4; + pStunIpAddrCtx->kvsIpAddr.port = 0; + MEMCPY(pStunIpAddrCtx->kvsIpAddr.address, &ipv4Addr->sin_addr, IPV4_ADDRESS_LENGTH); + resolved = TRUE; + } + } + freeaddrinfo(res); + } + if (!resolved) { + retStatus = STATUS_RESOLVE_HOSTNAME_FAILED; + } + return retStatus; +} + +STATUS onSetStunServerIp(UINT64 customData, PCHAR url, PKvsIpAddress pIpAddr) +{ + UNUSED_PARAM(customData); + STATUS retStatus = STATUS_SUCCESS; + BOOL locked = FALSE; + PWebRtcClientContext pWebRtcClientContext = getWebRtcClientInstance(); + CHK_WARN(ATOMIC_LOAD_BOOL(&pWebRtcClientContext->isContextInitialized), STATUS_NULL_ARG, "WebRTC Client object Object not initialized"); + + UINT64 currentTime = GETTIME(); + + MUTEX_LOCK(pWebRtcClientContext->stunCtxlock); + locked = TRUE; + + // This covers a situation where say we receive a URL that is not the default STUN or the hostname is not populated + // pWebRtcClientContext->pStunIpAddrCtx->status needs to be set to ensure we do not go ahead with resolution on thread + // in case we receive the request early on + if (STRCMP(url, pWebRtcClientContext->pStunIpAddrCtx->hostname) != 0) { + retStatus = STATUS_PEERCONNECTION_EARLY_DNS_RESOLUTION_FAILED; + // This is to ensure we do not go ahead with STUN resolution if this call is already made + pWebRtcClientContext->pStunIpAddrCtx->status = STATUS_PEERCONNECTION_EARLY_DNS_RESOLUTION_FAILED; + } else { + if (pWebRtcClientContext->pStunIpAddrCtx->isIpInitialized) { + DLOGI("Initialized successfully"); + if (currentTime > (pWebRtcClientContext->pStunIpAddrCtx->startTime + pWebRtcClientContext->pStunIpAddrCtx->expirationDuration)) { + DLOGI("Expired...need to refresh STUN address"); + // Reset start time + pWebRtcClientContext->pStunIpAddrCtx->startTime = 0; + CHK_ERR(getStunAddr(pWebRtcClientContext->pStunIpAddrCtx) == STATUS_SUCCESS, retStatus, "Failed to resolve after cache expiry"); + } + MEMCPY(pIpAddr, &pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddr, SIZEOF(pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddr)); + } else { + DLOGE("Initialization failed"); + } + } +CleanUp: + if (locked) { + MUTEX_UNLOCK(pWebRtcClientContext->stunCtxlock); + } + DLOGD("Exiting from stun server IP callback"); + releaseHoldOnInstance(pWebRtcClientContext); + return retStatus; +} + +PVOID resolveStunIceServerIp(PVOID args) +{ + UNUSED_PARAM(args); + PWebRtcClientContext pWebRtcClientContext = getWebRtcClientInstance(); + BOOL locked = FALSE; + CHAR addressResolved[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; + PCHAR pRegion; + PCHAR pHostnamePostfix; + UINT64 stunDnsResolutionStartTime = 0; + + if (ATOMIC_LOAD_BOOL(&pWebRtcClientContext->isContextInitialized)) { + MUTEX_LOCK(pWebRtcClientContext->stunCtxlock); + locked = TRUE; + if (pWebRtcClientContext->pStunIpAddrCtx == NULL) { + DLOGE("Failed to resolve STUN IP address because webrtc client instance was not created"); + } else { + if (pWebRtcClientContext->pStunIpAddrCtx->status != STATUS_PEERCONNECTION_EARLY_DNS_RESOLUTION_FAILED) { + if ((pRegion = GETENV(DEFAULT_REGION_ENV_VAR)) == NULL) { + pRegion = DEFAULT_AWS_REGION; + } + + pHostnamePostfix = KINESIS_VIDEO_STUN_URL_POSTFIX; + // If region is in CN, add CN region uri postfix + if (STRSTR(pRegion, "cn-")) { + pHostnamePostfix = KINESIS_VIDEO_STUN_URL_POSTFIX_CN; + } + + SNPRINTF(pWebRtcClientContext->pStunIpAddrCtx->hostname, SIZEOF(pWebRtcClientContext->pStunIpAddrCtx->hostname), + KINESIS_VIDEO_STUN_URL_WITHOUT_PORT, pRegion, pHostnamePostfix); + stunDnsResolutionStartTime = GETTIME(); + if (getStunAddr(pWebRtcClientContext->pStunIpAddrCtx) == STATUS_SUCCESS) { + getIpAddrStr(&pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddr, addressResolved, ARRAY_SIZE(addressResolved)); + DLOGI("ICE Server address for %s with getaddrinfo: %s", pWebRtcClientContext->pStunIpAddrCtx->hostname, addressResolved); + pWebRtcClientContext->pStunIpAddrCtx->isIpInitialized = TRUE; + } else { + DLOGE("Failed to resolve %s", pWebRtcClientContext->pStunIpAddrCtx->hostname); + } + pWebRtcClientContext->pStunIpAddrCtx->startTime = GETTIME(); + } else { + DLOGW("Request already received to get the URL before resolution could even start...allowing higher layers to handle resolution"); + } + PROFILE_WITH_START_TIME_OBJ(stunDnsResolutionStartTime, pWebRtcClientContext->pStunIpAddrCtx->stunDnsResolutionTime, + "STUN DNS resolution time taken"); + } + if (locked) { + MUTEX_UNLOCK(pWebRtcClientContext->stunCtxlock); + } + } else { + DLOGW("STUN DNS thread invoked without context being initialized"); + } + releaseHoldOnInstance(pWebRtcClientContext); + DLOGD("Exiting from stun server IP resolution thread"); + return NULL; +} + STATUS createPeerConnection(PRtcConfiguration pConfiguration, PRtcPeerConnection* ppPeerConnection) { ENTERS(); @@ -703,6 +899,7 @@ STATUS createPeerConnection(PRtcConfiguration pConfiguration, PRtcPeerConnection DtlsSessionCallbacks dtlsSessionCallbacks; PConnectionListener pConnectionListener = NULL; UINT64 startTime = 0; + UINT64 startTimeInMacro = 0; CHK(pConfiguration != NULL && ppPeerConnection != NULL, STATUS_NULL_ARG); @@ -720,9 +917,10 @@ STATUS createPeerConnection(PRtcConfiguration pConfiguration, PRtcPeerConnection CHK_STATUS(generateJSONSafeString(pKvsPeerConnection->localIcePwd, LOCAL_ICE_PWD_LEN)); CHK_STATUS(generateJSONSafeString(pKvsPeerConnection->localCNAME, LOCAL_CNAME_LEN)); - CHK_STATUS(createDtlsSession( - &dtlsSessionCallbacks, pKvsPeerConnection->timerQueueHandle, pConfiguration->kvsRtcConfiguration.generatedCertificateBits, - pConfiguration->kvsRtcConfiguration.generateRSACertificate, pConfiguration->certificates, &pKvsPeerConnection->pDtlsSession)); + PROFILE_CALL(CHK_STATUS(createDtlsSession( + &dtlsSessionCallbacks, pKvsPeerConnection->timerQueueHandle, pConfiguration->kvsRtcConfiguration.generatedCertificateBits, + pConfiguration->kvsRtcConfiguration.generateRSACertificate, pConfiguration->certificates, &pKvsPeerConnection->pDtlsSession)), + "Create DTLS Session object"); CHK_STATUS(dtlsSessionOnOutBoundData(pKvsPeerConnection->pDtlsSession, (UINT64) pKvsPeerConnection, onDtlsOutboundPacket)); CHK_STATUS(dtlsSessionOnStateChange(pKvsPeerConnection->pDtlsSession, (UINT64) pKvsPeerConnection, onDtlsStateChange)); @@ -745,10 +943,13 @@ STATUS createPeerConnection(PRtcConfiguration pConfiguration, PRtcPeerConnection iceAgentCallbacks.inboundPacketFn = onInboundPacket; iceAgentCallbacks.connectionStateChangedFn = onIceConnectionStateChange; iceAgentCallbacks.newLocalCandidateFn = onNewIceLocalCandidate; - CHK_STATUS(createConnectionListener(&pConnectionListener)); + iceAgentCallbacks.setStunServerIpFn = onSetStunServerIp; + + PROFILE_CALL(CHK_STATUS(createConnectionListener(&pConnectionListener)), "Create connection listener"); // IceAgent will own the lifecycle of pConnectionListener; - CHK_STATUS(createIceAgent(pKvsPeerConnection->localIceUfrag, pKvsPeerConnection->localIcePwd, &iceAgentCallbacks, pConfiguration, - pKvsPeerConnection->timerQueueHandle, pConnectionListener, &pKvsPeerConnection->pIceAgent)); + PROFILE_CALL(CHK_STATUS(createIceAgent(pKvsPeerConnection->localIceUfrag, pKvsPeerConnection->localIcePwd, &iceAgentCallbacks, pConfiguration, + pKvsPeerConnection->timerQueueHandle, pConnectionListener, &pKvsPeerConnection->pIceAgent)), + "Create ICE agent object"); NULLABLE_SET_EMPTY(pKvsPeerConnection->canTrickleIce); @@ -814,7 +1015,6 @@ STATUS freePeerConnection(PRtcPeerConnection* ppPeerConnection) #ifdef ENABLE_DATA_CHANNEL CHK_LOG_ERR(freeSctpSession(&pKvsPeerConnection->pSctpSession)); #endif - CHK_LOG_ERR(freeIceAgent(&pKvsPeerConnection->pIceAgent)); // free transceivers CHK_LOG_ERR(doubleListGetHeadNode(pKvsPeerConnection->pTransceivers, &pCurNode)); @@ -840,6 +1040,9 @@ STATUS freePeerConnection(PRtcPeerConnection* ppPeerConnection) // free rest of structs CHK_LOG_ERR(freeSrtpSession(&pKvsPeerConnection->pSrtpSession)); CHK_LOG_ERR(freeDtlsSession(&pKvsPeerConnection->pDtlsSession)); + // Since ICE agent has a callback invoked from DTLS during handshake, + // it is safer to free the ICE agent after DTLS session + CHK_LOG_ERR(freeIceAgent(&pKvsPeerConnection->pIceAgent)); CHK_LOG_ERR(doubleListFree(pKvsPeerConnection->pTransceivers)); CHK_LOG_ERR(doubleListFree(pKvsPeerConnection->pFakeTransceivers)); CHK_LOG_ERR(doubleListFree(pKvsPeerConnection->pAnswerTransceivers)); @@ -900,6 +1103,24 @@ STATUS peerConnectionOnIceCandidate(PRtcPeerConnection pRtcPeerConnection, UINT6 return retStatus; } +STATUS addConfigToServerList(PRtcPeerConnection* ppPeerConnection, PIceConfigInfo pIceConfigInfo) +{ + STATUS retStatus = STATUS_SUCCESS; + PKvsPeerConnection pKvsPeerConnection = NULL; + + CHK(ppPeerConnection != NULL && pIceConfigInfo != NULL, STATUS_NULL_ARG); + + pKvsPeerConnection = (PKvsPeerConnection) *ppPeerConnection; + + CHK(pKvsPeerConnection != NULL, STATUS_NULL_ARG); + + CHK_STATUS(iceAgentAddConfig(pKvsPeerConnection->pIceAgent, pIceConfigInfo)); + +CleanUp: + + return retStatus; +} + STATUS peerConnectionOnDataChannel(PRtcPeerConnection pRtcPeerConnection, UINT64 customData, RtcOnDataChannel rtcOnDataChannel) { ENTERS(); @@ -1175,19 +1396,19 @@ STATUS createOffer(PRtcPeerConnection pPeerConnection, PRtcSessionDescriptionIni CHK(NULL != (pSessionDescription = (PSessionDescription) MEMCALLOC(1, SIZEOF(SessionDescription))), STATUS_NOT_ENOUGH_MEMORY); pSessionDescriptionInit->type = SDP_TYPE_OFFER; pKvsPeerConnection->isOffer = TRUE; + if (pSessionDescriptionInit->useTrickleIce) { + NULLABLE_SET_VALUE(pKvsPeerConnection->canTrickleIce, TRUE); + } #ifdef ENABLE_DATA_CHANNEL ATOMIC_STORE_BOOL(&pKvsPeerConnection->sctpIsEnabled, TRUE); #endif CHK_STATUS(setPayloadTypesForOffer(pKvsPeerConnection->pCodecTable)); - CHK_STATUS(populateSessionDescription(pKvsPeerConnection, &(pKvsPeerConnection->remoteSessionDescription), pSessionDescription)); CHK_STATUS(serializeSessionDescription(pSessionDescription, NULL, &serializeLen)); CHK(serializeLen <= MAX_SESSION_DESCRIPTION_INIT_SDP_LEN, STATUS_NOT_ENOUGH_MEMORY); - CHK_STATUS(serializeSessionDescription(pSessionDescription, pSessionDescriptionInit->sdp, &serializeLen)); - // If embedded SDK acts as the viewer if (NULL != GETENV(DEBUG_LOG_SDP)) { DLOGD("LOCAL_SDP:%s", pSessionDescriptionInit->sdp); @@ -1214,7 +1435,6 @@ STATUS createAnswer(PRtcPeerConnection pPeerConnection, PRtcSessionDescriptionIn pKvsPeerConnection->isOffer = FALSE; CHK_STATUS(peerConnectionGetCurrentLocalDescription(pPeerConnection, pSessionDescriptionInit)); - // If embedded SDK acts as the master if (NULL != GETENV(DEBUG_LOG_SDP)) { DLOGD("LOCAL_SDP:%s", pSessionDescriptionInit->sdp); @@ -1424,7 +1644,7 @@ STATUS initKvsWebRtc(VOID) ENTERS(); STATUS retStatus = STATUS_SUCCESS; CHK(!ATOMIC_LOAD_BOOL(&gKvsWebRtcInitialized), retStatus); - + DLOGI("Initializing WebRTC library..."); SRAND(GETTIME()); CHK(srtp_init() == srtp_err_status_ok, STATUS_SRTP_INIT_FAILED); @@ -1435,10 +1655,16 @@ STATUS initKvsWebRtc(VOID) KVS_CRYPTO_INIT(); LOG_GIT_HASH(); + SET_INSTRUMENTED_ALLOCATORS(); #ifdef ENABLE_DATA_CHANNEL CHK_STATUS(initSctpSession()); #endif - +#ifdef ENABLE_KVS_THREADPOOL + DLOGI("KVS WebRtc library using thread pool"); + CHK_STATUS(createWebRtcClientInstance()); + CHK_STATUS(createThreadPoolContext()); + CHK_STATUS(threadpoolContextPush(resolveStunIceServerIp, NULL)); +#endif ATOMIC_STORE_BOOL(&gKvsWebRtcInitialized, TRUE); CleanUp: @@ -1447,6 +1673,48 @@ STATUS initKvsWebRtc(VOID) return retStatus; } +STATUS cleanupWebRtcClientInstance() +{ + STATUS retStatus = STATUS_SUCCESS; + + // Stun object cleanup + PWebRtcClientContext pWebRtcClientContext = getWebRtcClientInstance(); + + DLOGD("Releasing webrtc client context instance from cleanupWebRtcClientInstance"); + releaseHoldOnInstance(pWebRtcClientContext); + + CHK_WARN(ATOMIC_LOAD_BOOL(&pWebRtcClientContext->isContextInitialized), STATUS_INVALID_OPERATION, + "WebRtc context not initialized, nothing to clean up"); + + ATOMIC_STORE_BOOL(&pWebRtcClientContext->isContextInitialized, FALSE); + + while (ATOMIC_LOAD(&pWebRtcClientContext->contextRefCnt) > 0) { + DLOGV("Waiting on all references to be returned...%d", pWebRtcClientContext->contextRefCnt); + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); + } + + /* Start of handling STUN object */ + // Need this check to ensure we do not clean up the object in the next + // step while the resolve thread is ongoing + CHK_WARN(pWebRtcClientContext->pStunIpAddrCtx != NULL, STATUS_NULL_ARG, "Destroying STUN object without setting up"); + MUTEX_LOCK(pWebRtcClientContext->stunCtxlock); + SAFE_MEMFREE(pWebRtcClientContext->pStunIpAddrCtx); + pWebRtcClientContext->pStunIpAddrCtx = NULL; + DLOGI("Destroyed STUN IP object"); + MUTEX_UNLOCK(pWebRtcClientContext->stunCtxlock); + /* End of handling STUN object */ + + if (IS_VALID_MUTEX_VALUE(pWebRtcClientContext->stunCtxlock)) { + MUTEX_FREE(pWebRtcClientContext->stunCtxlock); + pWebRtcClientContext->stunCtxlock = INVALID_MUTEX_VALUE; + } + + DLOGI("Destroyed WebRtc client context"); + +CleanUp: + return retStatus; +} + STATUS deinitKvsWebRtc(VOID) { ENTERS(); @@ -1459,8 +1727,13 @@ STATUS deinitKvsWebRtc(VOID) srtp_shutdown(); +#ifdef ENABLE_KVS_THREADPOOL + cleanupWebRtcClientInstance(); + destroyThreadPoolContext(); + DLOGI("Destroyed threadpool"); + RESET_INSTRUMENTED_ALLOCATORS(); +#endif ATOMIC_STORE_BOOL(&gKvsWebRtcInitialized, FALSE); - CleanUp: LEAVES(); @@ -1519,11 +1792,23 @@ STATUS peerConnectionGetMetrics(PRtcPeerConnection pPeerConnection, PPeerConnect { STATUS retStatus = STATUS_SUCCESS; PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) pPeerConnection; + PWebRtcClientContext pWebRtcClientContext = getWebRtcClientInstance(); + CHK(pKvsPeerConnection != NULL && pPeerConnectionMetrics != NULL, STATUS_NULL_ARG); if (pPeerConnectionMetrics->version > PEER_CONNECTION_METRICS_CURRENT_VERSION) { DLOGW("Peer connection metrics object version invalid..setting to highest default version %d", PEER_CONNECTION_METRICS_CURRENT_VERSION); pPeerConnectionMetrics->version = PEER_CONNECTION_METRICS_CURRENT_VERSION; } +#ifdef ENABLE_KVS_THREADPOOL + MUTEX_LOCK(pWebRtcClientContext->stunCtxlock); + if (pWebRtcClientContext->isContextInitialized) { + if (pWebRtcClientContext->pStunIpAddrCtx->isIpInitialized) { + pPeerConnectionMetrics->peerConnectionStats.stunDnsResolutionTime = pWebRtcClientContext->pStunIpAddrCtx->stunDnsResolutionTime; + } + } + MUTEX_UNLOCK(pWebRtcClientContext->stunCtxlock); +#endif + pPeerConnectionMetrics->peerConnectionStats.peerConnectionCreationTime = pKvsPeerConnection->peerConnectionDiagnostics.peerConnectionCreationTime; pPeerConnectionMetrics->peerConnectionStats.dtlsSessionSetupTime = pKvsPeerConnection->peerConnectionDiagnostics.dtlsSessionSetupTime; pPeerConnectionMetrics->peerConnectionStats.iceHolePunchingTime = pKvsPeerConnection->peerConnectionDiagnostics.iceHolePunchingTime; @@ -1531,6 +1816,7 @@ STATUS peerConnectionGetMetrics(PRtcPeerConnection pPeerConnection, PPeerConnect pPeerConnectionMetrics->peerConnectionStats.closePeerConnectionTime = pKvsPeerConnection->peerConnectionDiagnostics.closePeerConnectionTime; pPeerConnectionMetrics->peerConnectionStats.freePeerConnectionTime = pKvsPeerConnection->peerConnectionDiagnostics.freePeerConnectionTime; CleanUp: + releaseHoldOnInstance(pWebRtcClientContext); return retStatus; } diff --git a/src/source/PeerConnection/PeerConnection.h b/src/source/PeerConnection/PeerConnection.h index d473809da5..9fc3638bce 100644 --- a/src/source/PeerConnection/PeerConnection.h +++ b/src/source/PeerConnection/PeerConnection.h @@ -38,6 +38,8 @@ extern "C" { // Environment variable to display SDPs #define DEBUG_LOG_SDP ((PCHAR) "DEBUG_LOG_SDP") +#define MAX_ACCESS_THREADS_WEBRTC_CLIENT_CONTEXT 50 + typedef enum { RTC_RTX_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE = 1, RTC_RTX_CODEC_VP8 = 2, @@ -150,6 +152,25 @@ typedef struct { PHashTable unkeyedDataChannels; } AllocateSctpSortDataChannelsData, *PAllocateSctpSortDataChannelsData; +typedef struct { + CHAR hostname[MAX_ICE_CONFIG_URI_LEN + 1]; + KvsIpAddress kvsIpAddr; + BOOL isIpInitialized; + UINT64 startTime; + UINT64 stunDnsResolutionTime; + UINT64 expirationDuration; + STATUS status; +} StunIpAddrContext, *PStunIpAddrContext; + +// Declare the structure of the Singleton +// Members of the singleton are responsible for their own sync mechanisms. +typedef struct { + PStunIpAddrContext pStunIpAddrCtx; + volatile ATOMIC_BOOL isContextInitialized; + volatile SIZE_T contextRefCnt; + MUTEX stunCtxlock; +} WebRtcClientContext, *PWebRtcClientContext; + STATUS onFrameReadyFunc(UINT64, UINT16, UINT16, UINT32); STATUS onFrameDroppedFunc(UINT64, UINT16, UINT16, UINT32); VOID onSctpSessionOutboundPacket(UINT64, PBYTE, UINT32); diff --git a/src/source/PeerConnection/Rtcp.c b/src/source/PeerConnection/Rtcp.c index bbf6e73927..00cb71263a 100644 --- a/src/source/PeerConnection/Rtcp.c +++ b/src/source/PeerConnection/Rtcp.c @@ -340,7 +340,6 @@ STATUS onRtcpPacket(PKvsPeerConnection pKvsPeerConnection, PBYTE pBuff, UINT32 b UINT32 currentOffset = 0; CHK(pKvsPeerConnection != NULL && pBuff != NULL, STATUS_NULL_ARG); - DLOGD("**rtcp**"); while (currentOffset < buffLen) { CHK_STATUS(setRtcpPacketFromBytes(pBuff + currentOffset, buffLen - currentOffset, &rtcpPacket)); diff --git a/src/source/PeerConnection/SessionDescription.c b/src/source/PeerConnection/SessionDescription.c index e14f2150fe..6781a6a902 100644 --- a/src/source/PeerConnection/SessionDescription.c +++ b/src/source/PeerConnection/SessionDescription.c @@ -311,16 +311,21 @@ PCHAR fmtpForPayloadType(UINT64 payloadType, PSessionDescription pSessionDescrip UINT32 currentMedia, currentAttribute; PSdpMediaDescription pMediaDescription = NULL; CHAR payloadStr[MAX_SDP_ATTRIBUTE_VALUE_LENGTH]; + INT32 amountWritten = 0; MEMSET(payloadStr, 0x00, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); - SPRINTF(payloadStr, "%" PRId64, payloadType); + amountWritten = SNPRINTF(payloadStr, SIZEOF(payloadStr), "%" PRId64, payloadType); - for (currentMedia = 0; currentMedia < pSessionDescription->mediaCount; currentMedia++) { - pMediaDescription = &(pSessionDescription->mediaDescriptions[currentMedia]); - for (currentAttribute = 0; currentAttribute < pMediaDescription->mediaAttributesCount; currentAttribute++) { - if (STRCMP(pMediaDescription->sdpAttributes[currentAttribute].attributeName, "fmtp") == 0 && - STRNCMP(pMediaDescription->sdpAttributes[currentAttribute].attributeValue, payloadStr, STRLEN(payloadStr)) == 0) { - return pMediaDescription->sdpAttributes[currentAttribute].attributeValue + STRLEN(payloadStr) + 1; + if (amountWritten < 0) { + DLOGE("Internal error: Full payload type for fmtp could not be written"); + } else { + for (currentMedia = 0; currentMedia < pSessionDescription->mediaCount; currentMedia++) { + pMediaDescription = &(pSessionDescription->mediaDescriptions[currentMedia]); + for (currentAttribute = 0; currentAttribute < pMediaDescription->mediaAttributesCount; currentAttribute++) { + if (STRCMP(pMediaDescription->sdpAttributes[currentAttribute].attributeName, "fmtp") == 0 && + STRNCMP(pMediaDescription->sdpAttributes[currentAttribute].attributeValue, payloadStr, STRLEN(payloadStr)) == 0) { + return pMediaDescription->sdpAttributes[currentAttribute].attributeValue + STRLEN(payloadStr) + 1; + } } } } @@ -400,6 +405,7 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp PSdpMediaDescription pSdpMediaDescriptionRemote; PCHAR currentFmtp = NULL, rtpMapValue = NULL; CHAR remoteSdpAttributeValue[MAX_SDP_ATTRIBUTE_VALUE_LENGTH]; + INT32 amountWritten = 0; MEMSET(remoteSdpAttributeValue, 0, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); @@ -423,12 +429,18 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp containRtx = (retStatus == STATUS_SUCCESS); retStatus = STATUS_SUCCESS; if (containRtx) { - SPRINTF(pSdpMediaDescription->mediaName, "video 9 UDP/TLS/RTP/SAVPF %" PRId64 " %" PRId64, payloadType, rtxPayloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->mediaName, SIZEOF(pSdpMediaDescription->mediaName), + "video 9 UDP/TLS/RTP/SAVPF %" PRId64 " %" PRId64, payloadType, rtxPayloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full video (with rtx) media name attribute could not be written"); } else { - SPRINTF(pSdpMediaDescription->mediaName, "video 9 UDP/TLS/RTP/SAVPF %" PRId64, payloadType); + amountWritten = + SNPRINTF(pSdpMediaDescription->mediaName, SIZEOF(pSdpMediaDescription->mediaName), "video 9 UDP/TLS/RTP/SAVPF %" PRId64, payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full video media name attribute could not be written"); } } else if (pRtcMediaStreamTrack->kind == MEDIA_STREAM_TRACK_KIND_AUDIO) { - SPRINTF(pSdpMediaDescription->mediaName, "audio 9 UDP/TLS/RTP/SAVPF %" PRId64, payloadType); + amountWritten = + SNPRINTF(pSdpMediaDescription->mediaName, SIZEOF(pSdpMediaDescription->mediaName), "audio 9 UDP/TLS/RTP/SAVPF %" PRId64, payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full audio media name attribute could not be written"); } CHK_STATUS(iceAgentPopulateSdpMediaDescriptionCandidates(pKvsPeerConnection->pIceAgent, pSdpMediaDescription, MAX_SDP_ATTRIBUTE_VALUE_LENGTH, @@ -436,60 +448,82 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp if (containRtx) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "msid"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%s %sRTX", pRtcMediaStreamTrack->streamId, - pRtcMediaStreamTrack->trackId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%s %sRTX", + pRtcMediaStreamTrack->streamId, pRtcMediaStreamTrack->trackId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full msid value (with rtx) could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc-group"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "FID %u %u", pKvsRtpTransceiver->sender.ssrc, - pKvsRtpTransceiver->sender.rtxSsrc); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "FID %u %u", + pKvsRtpTransceiver->sender.ssrc, pKvsRtpTransceiver->sender.rtxSsrc); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ssrc-grp value (with rtx) could not be written"); attributeCount++; } else { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "msid"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%s %s", pRtcMediaStreamTrack->streamId, - pRtcMediaStreamTrack->trackId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%s %s", pRtcMediaStreamTrack->streamId, + pRtcMediaStreamTrack->trackId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full msid value could not be written"); attributeCount++; } STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u cname:%s", pKvsRtpTransceiver->sender.ssrc, - pKvsPeerConnection->localCNAME); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u cname:%s", + pKvsRtpTransceiver->sender.ssrc, pKvsPeerConnection->localCNAME); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ssrc cname could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u msid:%s %s", pKvsRtpTransceiver->sender.ssrc, - pRtcMediaStreamTrack->streamId, pRtcMediaStreamTrack->trackId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u msid:%s %s", + pKvsRtpTransceiver->sender.ssrc, pRtcMediaStreamTrack->streamId, pRtcMediaStreamTrack->trackId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ssrc msid could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u mslabel:%s", pKvsRtpTransceiver->sender.ssrc, - pRtcMediaStreamTrack->streamId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u mslabel:%s", + pKvsRtpTransceiver->sender.ssrc, pRtcMediaStreamTrack->streamId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ssrc mslabel could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u label:%s", pKvsRtpTransceiver->sender.ssrc, - pRtcMediaStreamTrack->trackId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u label:%s", + pKvsRtpTransceiver->sender.ssrc, pRtcMediaStreamTrack->trackId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ssrc label could not be written"); attributeCount++; if (containRtx) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u cname:%s", pKvsRtpTransceiver->sender.rtxSsrc, - pKvsPeerConnection->localCNAME); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u cname:%s", + pKvsRtpTransceiver->sender.rtxSsrc, pKvsPeerConnection->localCNAME); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ssrc cname (with rtx) could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u msid:%s %sRTX", pKvsRtpTransceiver->sender.rtxSsrc, - pRtcMediaStreamTrack->streamId, pRtcMediaStreamTrack->trackId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u msid:%s %sRTX", + pKvsRtpTransceiver->sender.rtxSsrc, pRtcMediaStreamTrack->streamId, pRtcMediaStreamTrack->trackId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ssrc msid (with rtx) could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u mslabel:%sRTX", pKvsRtpTransceiver->sender.rtxSsrc, - pRtcMediaStreamTrack->streamId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u mslabel:%sRTX", + pKvsRtpTransceiver->sender.rtxSsrc, pRtcMediaStreamTrack->streamId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ssrc mslabel (with rtx) could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u label:%sRTX", pKvsRtpTransceiver->sender.rtxSsrc, - pRtcMediaStreamTrack->trackId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u label:%sRTX", + pKvsRtpTransceiver->sender.rtxSsrc, pRtcMediaStreamTrack->trackId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ssrc label (with rtx) could not be written"); attributeCount++; } @@ -505,9 +539,11 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, pKvsPeerConnection->localIcePwd); attributeCount++; - STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ice-options"); - STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "trickle"); - attributeCount++; + if (pKvsPeerConnection->canTrickleIce.value) { + STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ice-options"); + STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "trickle"); + attributeCount++; + } STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "fingerprint"); STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "sha-256 "); @@ -531,9 +567,13 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp // If we don't have it, we loop over, create and add them if (STRLEN(remoteSdpAttributeValue) > 0) { CHK(STRLEN(remoteSdpAttributeValue) < MAX_SDP_ATTRIBUTE_VALUE_LENGTH, STATUS_BUFFER_TOO_SMALL); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%s", remoteSdpAttributeValue); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%s", remoteSdpAttributeValue); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Mid exists, but remote SDP value could not be written"); } else { - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%d", mediaSectionId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%d", mediaSectionId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full media section Id could not be written"); } attributeCount++; @@ -592,7 +632,23 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp currentFmtp = DEFAULT_H264_FMTP; } STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " H264/90000", payloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " H264/90000", payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H264 payload type could not be written"); + attributeCount++; + + STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-fb"); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " nack", payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H264 rtcp-fb nack value could not be written"); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " nack pli", payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H264 rtcp-fb nack-pli value could not be written"); + attributeCount++; + + STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-fb"); + SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " nack", payloadType); + SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " nack pli", payloadType); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-fb"); @@ -603,17 +659,26 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp // TODO: If level asymmetry is allowed, consider sending back DEFAULT_H264_FMTP instead of the received fmtp value. if (currentFmtp != NULL) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "fmtp"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " %s", payloadType, currentFmtp); + amountWritten = + SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " %s", payloadType, currentFmtp); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H264 fmtp value could not be written"); attributeCount++; } if (containRtx) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " " RTX_VALUE, rtxPayloadType); + amountWritten = + SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " " RTX_VALUE, rtxPayloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H264 rtpmap (with rtx) could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "fmtp"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " apt=%" PRId64 "", rtxPayloadType, payloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " apt=%" PRId64 "", + rtxPayloadType, payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H264 fmtp apt value (with rtx) could not be written"); attributeCount++; } } else if (pRtcMediaStreamTrack->codec == RTC_CODEC_OPUS) { @@ -622,61 +687,90 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp } STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " opus/48000/2", payloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " opus/48000/2", payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full Opus rtpmap could not be written"); attributeCount++; if (currentFmtp != NULL) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "fmtp"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " %s", payloadType, currentFmtp); + amountWritten = + SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " %s", payloadType, currentFmtp); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full Opus fmtp could not be written"); attributeCount++; } } else if (pRtcMediaStreamTrack->codec == RTC_CODEC_VP8) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " " VP8_VALUE, payloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " " VP8_VALUE, payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full VP8 rtpmap could not be written"); attributeCount++; if (containRtx) { CHK_STATUS(hashTableGet(pKvsPeerConnection->pRtxTable, RTC_RTX_CODEC_VP8, &rtxPayloadType)); STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " " RTX_VALUE, rtxPayloadType); + amountWritten = + SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " " RTX_VALUE, rtxPayloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full VP8 rtpmap payload type (with rtx) could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "fmtp"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " apt=%" PRId64 "", rtxPayloadType, payloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " apt=%" PRId64 "", + rtxPayloadType, payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full VP8 rtpmap fmtp apt value (with rtx) could not be written"); attributeCount++; } } else if (pRtcMediaStreamTrack->codec == RTC_CODEC_MULAW) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " " MULAW_VALUE, payloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " " MULAW_VALUE, payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full MULAW rtpmap could not be written"); attributeCount++; } else if (pRtcMediaStreamTrack->codec == RTC_CODEC_ALAW) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " " ALAW_VALUE, payloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " " ALAW_VALUE, payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full ALAW rtpmap could not be written"); attributeCount++; } else if (pRtcMediaStreamTrack->codec == RTC_CODEC_UNKNOWN) { CHK_STATUS(hashTableGet(pUnknownCodecRtpmapTable, unknownCodecHashTableKey, (PUINT64) &rtpMapValue)); STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " %s", payloadType, rtpMapValue); + amountWritten = + SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " %s", payloadType, rtpMapValue); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full Unknown rtpmap could not be written"); attributeCount++; } STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u cname:%s", pKvsRtpTransceiver->sender.ssrc, - pKvsPeerConnection->localCNAME); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u cname:%s", + pKvsRtpTransceiver->sender.ssrc, pKvsPeerConnection->localCNAME); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full transceiver ssrc cname could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ssrc"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%u msid:%s %s", pKvsRtpTransceiver->sender.ssrc, - pRtcMediaStreamTrack->streamId, pRtcMediaStreamTrack->trackId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%u msid:%s %s", + pKvsRtpTransceiver->sender.ssrc, pRtcMediaStreamTrack->streamId, pRtcMediaStreamTrack->trackId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full transceiver ssrc msid could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-fb"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " goog-remb", payloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " goog-remb", payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full rtcp-fb goog-remb could not be written"); attributeCount++; if (pKvsPeerConnection->twccExtId != 0) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-fb"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " " TWCC_SDP_ATTR, payloadType); + amountWritten = + SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " " TWCC_SDP_ATTR, payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full rtcp-fb twcc could not be written"); attributeCount++; } @@ -694,8 +788,11 @@ STATUS populateSessionDescriptionDataChannel(PKvsPeerConnection pKvsPeerConnecti ENTERS(); STATUS retStatus = STATUS_SUCCESS; UINT32 attributeCount = 0; + INT32 amountWritten = 0; - SPRINTF(pSdpMediaDescription->mediaName, "application 9 UDP/DTLS/SCTP webrtc-datachannel"); + amountWritten = + SNPRINTF(pSdpMediaDescription->mediaName, SIZEOF(pSdpMediaDescription->mediaName), "application 9 UDP/DTLS/SCTP webrtc-datachannel"); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full data channel media name could not be written"); CHK_STATUS(iceAgentPopulateSdpMediaDescriptionCandidates(pKvsPeerConnection->pIceAgent, pSdpMediaDescription, MAX_SDP_ATTRIBUTE_VALUE_LENGTH, &attributeCount)); @@ -712,6 +809,12 @@ STATUS populateSessionDescriptionDataChannel(PKvsPeerConnection pKvsPeerConnecti STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, pKvsPeerConnection->localIcePwd); attributeCount++; + if (pKvsPeerConnection->canTrickleIce.value) { + STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "ice-options"); + STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "trickle"); + attributeCount++; + } + STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "fingerprint"); STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "sha-256 "); STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue + 8, pCertificateFingerprint); @@ -722,11 +825,15 @@ STATUS populateSessionDescriptionDataChannel(PKvsPeerConnection pKvsPeerConnecti attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "mid"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%d", mediaSectionId); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%d", mediaSectionId); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full data channel mid media section could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "sctp-port"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "5000"); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "5000"); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full data channel sctp-port could not be written"); attributeCount++; pSdpMediaDescription->mediaAttributesCount = attributeCount; @@ -888,9 +995,7 @@ STATUS populateSessionDescription(PKvsPeerConnection pKvsPeerConnection, PSessio INT32 charsCopied; CHK(pKvsPeerConnection != NULL && pLocalSessionDescription != NULL && pRemoteSessionDescription != NULL, STATUS_NULL_ARG); - CHK_STATUS(populateSessionDescriptionMedia(pKvsPeerConnection, pRemoteSessionDescription, pLocalSessionDescription)); - MEMSET(bundleValue, 0, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); MEMSET(wmsValue, 0, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); MEMSET(remoteSdpAttributeValue, 0, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); @@ -910,6 +1015,13 @@ STATUS populateSessionDescription(PKvsPeerConnection pKvsPeerConnection, PSessio STRCPY(pLocalSessionDescription->sdpAttributes[0].attributeName, "group"); STRCPY(pLocalSessionDescription->sdpAttributes[0].attributeValue, BUNDLE_KEY); + pLocalSessionDescription->sessionAttributesCount++; + + if (pKvsPeerConnection->canTrickleIce.value) { + STRCPY(pLocalSessionDescription->sdpAttributes[pLocalSessionDescription->sessionAttributesCount].attributeName, "ice-options"); + STRCPY(pLocalSessionDescription->sdpAttributes[pLocalSessionDescription->sessionAttributesCount].attributeValue, "trickle"); + pLocalSessionDescription->sessionAttributesCount++; + } // check all session attribute lines to see if a line with BUNDLE is present. If it is present, copy its content and break for (i = 0; i < pRemoteSessionDescription->sessionAttributesCount; i++) { @@ -941,7 +1053,6 @@ STATUS populateSessionDescription(PKvsPeerConnection pKvsPeerConnection, PSessio STRCPY(pLocalSessionDescription->mediaDescriptions[i].sdpConnectionInformation.addressType, "IP4"); STRCPY(pLocalSessionDescription->mediaDescriptions[i].sdpConnectionInformation.connectionAddress, "127.0.0.1"); } - pLocalSessionDescription->sessionAttributesCount++; STRCPY(pLocalSessionDescription->sdpAttributes[pLocalSessionDescription->sessionAttributesCount].attributeName, "msid-semantic"); STRCPY(pLocalSessionDescription->sdpAttributes[pLocalSessionDescription->sessionAttributesCount].attributeValue, " WMS myKvsVideoStream"); diff --git a/src/source/Sctp/Sctp.c b/src/source/Sctp/Sctp.c index 401fce5f0f..2af922e0cd 100644 --- a/src/source/Sctp/Sctp.c +++ b/src/source/Sctp/Sctp.c @@ -59,7 +59,7 @@ STATUS initSctpSession() { STATUS retStatus = STATUS_SUCCESS; - usrsctp_init(0, &onSctpOutboundPacket, NULL); + usrsctp_init_nothreads(0, &onSctpOutboundPacket, NULL); // Disable Explicit Congestion Notification usrsctp_sysctl_set_sctp_ecn_enable(0); @@ -86,7 +86,7 @@ STATUS createSctpSession(PSctpSessionCallbacks pSctpSessionCallbacks, PSctpSessi CHK(ppSctpSession != NULL && pSctpSessionCallbacks != NULL, STATUS_NULL_ARG); - pSctpSession = (PSctpSession) MEMALLOC(SIZEOF(SctpSession)); + pSctpSession = (PSctpSession) MEMCALLOC(1, SIZEOF(SctpSession)); CHK(pSctpSession != NULL, STATUS_NOT_ENOUGH_MEMORY); MEMSET(¶ms, 0x00, SIZEOF(struct sctp_paddrparams)); @@ -317,6 +317,8 @@ STATUS handleDcepPacket(PSctpSession pSctpSession, UINT32 streamId, PBYTE data, CHK((labelLength + protocolLength + SCTP_DCEP_HEADER_LENGTH) >= length, STATUS_SCTP_INVALID_DCEP_PACKET); + CHK(SCTP_MAX_ALLOWABLE_PACKET_LENGTH >= length, STATUS_SCTP_INVALID_DCEP_PACKET); + pSctpSession->sctpSessionCallbacks.dataChannelOpenFunc(pSctpSession->sctpSessionCallbacks.customData, streamId, data + SCTP_DCEP_HEADER_LENGTH, labelLength); diff --git a/src/source/Sdp/Sdp.h b/src/source/Sdp/Sdp.h index c156e80357..6077a4e100 100644 --- a/src/source/Sdp/Sdp.h +++ b/src/source/Sdp/Sdp.h @@ -46,17 +46,47 @@ extern "C" { #define MAX_SDP_OFFSET_LENGTH 255 #define MAX_SDP_ENCRYPTION_KEY_METHOD_LENGTH 255 #define MAX_SDP_ENCRYPTION_KEY_LENGTH 255 -#define MAX_SDP_NETWORK_TYPE_LENGTH 255 -#define MAX_SDP_ADDRESS_TYPE_LENGTH 255 -#define MAX_SDP_CONNECTION_ADDRESS_LENGTH 255 -#define MAX_SDP_SESSION_USERNAME_LENGTH 255 -#define MAX_SDP_ATTRIBUTE_NAME_LENGTH 255 -#define MAX_SDP_ATTRIBUTE_VALUE_LENGTH 255 -#define MAX_SDP_MEDIA_NAME_LENGTH 255 -#define MAX_SDP_MEDIA_TITLE_LENGTH 255 -#define MAX_SDP_BANDWIDTH_LENGTH 255 + +/* https://datatracker.ietf.org/doc/html/rfc4566#section-5.7 -- the SDK hardcodes this to be IN as per spec. + * Also, this SDK is to be used in the Internet realm. Allowing for some extra buffer + */ +#define MAX_SDP_NETWORK_TYPE_LENGTH 7 + +/* https://datatracker.ietf.org/doc/html/rfc4566#section-5.7 -- Given the SDK is to operate in IP based sessions, + * the possible values are IP4/IP6 as registered with IANA. Allowing for some extra buffer + */ +#define MAX_SDP_ADDRESS_TYPE_LENGTH 7 + +/* https://datatracker.ietf.org/doc/html/rfc4566#section-5.7 -- Given the SDK is to operate in IP based sessions, + * an IPv4 address can be a maximum of characters where TTL is + * between 0 and 255. IPv6 can be a maximum of . Setting to 63 for additional padding + */ +#define MAX_SDP_CONNECTION_ADDRESS_LENGTH 63 + +// https://datatracker.ietf.org/doc/html/rfc4566#section-5.2 -- the SDK sets it to "-" and the SDK does not parse incoming username either +#define MAX_SDP_SESSION_USERNAME_LENGTH 32 + +// https://datatracker.ietf.org/doc/html/rfc4566#section-6 -- name length restricted based on current supported attribute set +#define MAX_SDP_ATTRIBUTE_NAME_LENGTH 32 + +// One of the attributes is streamId + trackId which sums up to 512 maximum characters +#define MAX_SDP_ATTRIBUTE_VALUE_LENGTH 512 + +#define MAX_SDP_MEDIA_NAME_LENGTH 255 + +/* https://tools.ietf.org/html/rfc4566#section-5.4. Given these are free-form textual strings, that is, the length could be anything. + * Although our SDK parses this information, the SDK does not use it. Leaving this attribute in if SDK uses it in + * the future, but keeping it at smaller size to ensure structure memory efficiency + */ +#define MAX_SDP_MEDIA_TITLE_LENGTH 127 + +/* https://tools.ietf.org/html/rfc4566#section-5.4. Given these are free-form textual strings, that is, the length could be anything. + * Although our SDK parses this information, the SDK does not use it. Leaving this attribute in if SDK uses it in + * the future, but keeping it at smaller size to ensure structure memory efficiency + */ +#define MAX_SDP_SESSION_INFORMATION_LENGTH 127 + #define MAX_SDP_SESSION_NAME_LENGTH 255 -#define MAX_SDP_SESSION_INFORMATION_LENGTH 255 #define MAX_SDP_SESSION_URI_LENGTH 255 #define MAX_SDP_SESSION_EMAIL_ADDRESS_LENGTH 255 #define MAX_SDP_SESSION_PHONE_NUMBER_LENGTH 255 @@ -64,7 +94,6 @@ extern "C" { #define MAX_SDP_TOKEN_LENGTH 128 #define MAX_SDP_FMTP_VALUES 64 -#define MAX_SDP_SESSION_BANDWIDTH_COUNT 2 #define MAX_SDP_SESSION_TIME_DESCRIPTION_COUNT 2 #define MAX_SDP_SESSION_TIMEZONE_COUNT 2 /** @@ -98,11 +127,6 @@ typedef struct { SdpConnectionInformation sdpConnectionInformation; } SdpOrigin, *PSdpOrigin; -typedef struct { - CHAR sdpBandwidthType[MAX_SDP_BANDWIDTH_LENGTH + 1]; - UINT64 sdpBandwidthValue; // bps -} SdpBandwidth, *PSdpBandwidth; - /* * https://tools.ietf.org/html/rfc4566#section-5.9 * https://tools.ietf.org/html/rfc4566#section-5.10 @@ -142,13 +166,13 @@ typedef struct { CHAR mediaName[MAX_SDP_MEDIA_NAME_LENGTH + 1]; // i= - // https://tools.ietf.org/html/rfc4566#section-5.4 + // https://tools.ietf.org/html/rfc4566#section-5.4. Given these are free-form strings, the length could be anything. + // Although our SDK parses this information, the SDK does not use it. Leaving this attribute in if SDK uses it in + // the future CHAR mediaTitle[MAX_SDP_MEDIA_TITLE_LENGTH + 1]; SdpConnectionInformation sdpConnectionInformation; - SdpBandwidth sdpBandwidth[MAX_SDP_MEDIA_BANDWIDTH_COUNT]; - SdpEncryptionKey sdpEncryptionKey; SdpAttributes sdpAttributes[MAX_SDP_ATTRIBUTES_COUNT]; @@ -186,8 +210,6 @@ typedef struct { SdpConnectionInformation sdpConnectionInformation; - SdpBandwidth sdpBandwidth[MAX_SDP_SESSION_BANDWIDTH_COUNT]; - SdpTimeDescription sdpTimeDescription[MAX_SDP_SESSION_TIME_DESCRIPTION_COUNT]; SdpTimeZone sdpTimeZone[MAX_SDP_SESSION_TIMEZONE_COUNT]; diff --git a/src/source/Signaling/FileCache.c b/src/source/Signaling/FileCache.c index 3f69fb9296..bf79456276 100644 --- a/src/source/Signaling/FileCache.c +++ b/src/source/Signaling/FileCache.c @@ -38,7 +38,8 @@ STATUS deserializeSignalingCacheEntries(PCHAR cachedFileContent, UINT64 fileSize pCurrent = cachedFileContent; remainingSize = (UINT32) fileSize; /* detect end of file */ - while (remainingSize > MAX_SIGNALING_CACHE_ENTRY_TIMESTAMP_STR_LEN) { + while (STRNLEN(pCurrent, MAX_SERIALIZED_SIGNALING_CACHE_ENTRY_LEN * MAX_SIGNALING_CACHE_ENTRY_COUNT) > 0 && + remainingSize > MAX_SIGNALING_CACHE_ENTRY_TIMESTAMP_STR_LEN) { nextLine = STRCHR(pCurrent, '\n'); while ((nextToken = STRCHR(pCurrent, ',')) != NULL && nextToken < nextLine) { switch (tokenCount % 10) { diff --git a/src/source/Signaling/LwsApiCalls.c b/src/source/Signaling/LwsApiCalls.c index 72c5fc1e32..b696157da7 100644 --- a/src/source/Signaling/LwsApiCalls.c +++ b/src/source/Signaling/LwsApiCalls.c @@ -2209,8 +2209,9 @@ STATUS receiveLwsMessage(PSignalingClient pSignalingClient, PCHAR pMessage, UINT DLOGW("Failed to validate the ICE server configuration received with an Offer"); } -#ifdef KVS_USE_SIGNALING_CHANNEL_THREADPOOL - CHK_STATUS(threadpoolPush(pSignalingClient->pThreadpool, receiveLwsMessageWrapper, (PVOID) pSignalingMessageWrapper)); +#ifdef ENABLE_KVS_THREADPOOL + // This would fail if threadpool was not created + CHK_STATUS(threadpoolContextPush(receiveLwsMessageWrapper, pSignalingMessageWrapper)); #else // Issue the callback on a separate thread CHK_STATUS(THREAD_CREATE(&receivedTid, receiveLwsMessageWrapper, (PVOID) pSignalingMessageWrapper)); diff --git a/src/source/Signaling/Signaling.c b/src/source/Signaling/Signaling.c index e0406e8bff..5cbdca7870 100644 --- a/src/source/Signaling/Signaling.c +++ b/src/source/Signaling/Signaling.c @@ -37,10 +37,6 @@ STATUS createSignalingSync(PSignalingClientInfoInternal pClientInfo, PChannelInf CHK_STATUS(createValidateChannelInfo(pChannelInfo, &pSignalingClient->pChannelInfo)); CHK_STATUS(validateSignalingCallbacks(pSignalingClient, pCallbacks)); CHK_STATUS(validateSignalingClientInfo(pSignalingClient, pClientInfo)); -#ifdef KVS_USE_SIGNALING_CHANNEL_THREADPOOL - CHK_STATUS(threadpoolCreate(&pSignalingClient->pThreadpool, pClientInfo->signalingClientInfo.signalingMessagesMinimumThreads, - pClientInfo->signalingClientInfo.signalingMessagesMaximumThreads)); -#endif pSignalingClient->version = SIGNALING_CLIENT_CURRENT_VERSION; // Set invalid call times pSignalingClient->describeTime = INVALID_TIMESTAMP_VALUE; @@ -91,9 +87,10 @@ STATUS createSignalingSync(PSignalingClientInfoInternal pClientInfo, PChannelInf CHK_STATUS(configureRetryStrategyForSignalingStateMachine(pSignalingClient)); // Create the state machine - CHK_STATUS(createStateMachine(SIGNALING_STATE_MACHINE_STATES, SIGNALING_STATE_MACHINE_STATE_COUNT, - CUSTOM_DATA_FROM_SIGNALING_CLIENT(pSignalingClient), signalingGetCurrentTime, - CUSTOM_DATA_FROM_SIGNALING_CLIENT(pSignalingClient), &pSignalingClient->pStateMachine)); + CHK_STATUS(createStateMachineWithName(SIGNALING_STATE_MACHINE_STATES, SIGNALING_STATE_MACHINE_STATE_COUNT, + CUSTOM_DATA_FROM_SIGNALING_CLIENT(pSignalingClient), signalingGetCurrentTime, + CUSTOM_DATA_FROM_SIGNALING_CLIENT(pSignalingClient), SIGNALING_STATE_MACHINE_NAME, + &pSignalingClient->pStateMachine)); // Prepare the signaling channel protocols array pSignalingClient->signalingProtocols[PROTOCOL_INDEX_HTTPS].name = HTTPS_SCHEME_NAME; @@ -241,10 +238,6 @@ STATUS freeSignaling(PSignalingClient* ppSignalingClient) hashTableFree(pSignalingClient->diagnostics.pEndpointToClockSkewHashMap); -#ifdef KVS_USE_SIGNALING_CHANNEL_THREADPOOL - threadpoolFree(pSignalingClient->pThreadpool); -#endif - if (IS_VALID_MUTEX_VALUE(pSignalingClient->connectedLock)) { MUTEX_FREE(pSignalingClient->connectedLock); } @@ -420,8 +413,8 @@ STATUS signalingSendMessageSync(PSignalingClient pSignalingClient, PSignalingMes if (pSignalingMessage->messageType == SIGNALING_MESSAGE_TYPE_OFFER) { pSignalingClient->offerSentTime = GETTIME(); } else if (pSignalingMessage->messageType == SIGNALING_MESSAGE_TYPE_ANSWER) { - PROFILE_WITH_START_TIME_OBJ(pSignalingClient->offerReceivedTime, pSignalingClient->diagnostics.offerToAnswerTime, - "Offer Received to Answer Sent time"); + PROFILE_WITH_START_END_TIME_OBJ(pSignalingClient->offerReceivedTime, pSignalingClient->answerTime, + pSignalingClient->diagnostics.offerToAnswerTime, "Offer Received to Answer Sent time"); } MUTEX_UNLOCK(pSignalingClient->offerSendReceiveTimeLock); // Update the internal diagnostics only after successfully sending @@ -1426,6 +1419,22 @@ STATUS signalingGetMetrics(PSignalingClient pSignalingClient, PSignalingClientMe pSignalingClientMetrics->signalingClientStats.connectClientTime = pSignalingClient->diagnostics.connectClientTime; pSignalingClientMetrics->signalingClientStats.joinSessionCallTime = pSignalingClient->diagnostics.joinSessionCallTime; pSignalingClientMetrics->signalingClientStats.offerToAnswerTime = pSignalingClient->diagnostics.offerToAnswerTime; + pSignalingClientMetrics->signalingClientStats.answerTime = pSignalingClient->answerTime; + pSignalingClientMetrics->signalingClientStats.offerReceivedTime = pSignalingClient->offerReceivedTime; + pSignalingClientMetrics->signalingClientStats.describeChannelStartTime = pSignalingClient->diagnostics.describeChannelStartTime; + pSignalingClientMetrics->signalingClientStats.describeChannelEndTime = pSignalingClient->diagnostics.describeChannelEndTime; + pSignalingClientMetrics->signalingClientStats.getSignalingChannelEndpointStartTime = + pSignalingClient->diagnostics.getSignalingChannelEndpointStartTime; + pSignalingClientMetrics->signalingClientStats.getSignalingChannelEndpointEndTime = + pSignalingClient->diagnostics.getSignalingChannelEndpointEndTime; + pSignalingClientMetrics->signalingClientStats.getIceServerConfigStartTime = pSignalingClient->diagnostics.getIceServerConfigStartTime; + pSignalingClientMetrics->signalingClientStats.getIceServerConfigEndTime = pSignalingClient->diagnostics.getIceServerConfigEndTime; + pSignalingClientMetrics->signalingClientStats.getTokenStartTime = pSignalingClient->diagnostics.getTokenStartTime; + pSignalingClientMetrics->signalingClientStats.getTokenEndTime = pSignalingClient->diagnostics.getTokenEndTime; + pSignalingClientMetrics->signalingClientStats.createChannelStartTime = pSignalingClient->diagnostics.createChannelStartTime; + pSignalingClientMetrics->signalingClientStats.createChannelEndTime = pSignalingClient->diagnostics.createChannelEndTime; + pSignalingClientMetrics->signalingClientStats.connectStartTime = pSignalingClient->diagnostics.connectStartTime; + pSignalingClientMetrics->signalingClientStats.connectEndTime = pSignalingClient->diagnostics.connectEndTime; pSignalingClientMetrics->signalingClientStats.joinSessionToOfferRecvTime = pSignalingClient->diagnostics.joinSessionToOfferRecvTime; case 0: // Fill in the data structures according to the version of the requested structure diff --git a/src/source/Signaling/Signaling.h b/src/source/Signaling/Signaling.h index 6f0aee0209..7f8bf52b89 100644 --- a/src/source/Signaling/Signaling.h +++ b/src/source/Signaling/Signaling.h @@ -89,6 +89,8 @@ extern "C" { #define DEFAULT_CREATE_SIGNALING_CLIENT_RETRY_ATTEMPTS 7 +#define SIGNALING_STATE_MACHINE_NAME (PCHAR) "SIGNALING" + static const ExponentialBackoffRetryStrategyConfig DEFAULT_SIGNALING_STATE_MACHINE_EXPONENTIAL_BACKOFF_RETRY_CONFIGURATION = { /* Exponential wait times with this config will look like following - ************************************ @@ -184,6 +186,18 @@ typedef struct { volatile SIZE_T numberOfErrors; volatile SIZE_T numberOfRuntimeErrors; volatile SIZE_T numberOfReconnects; + UINT64 describeChannelStartTime; + UINT64 describeChannelEndTime; + UINT64 getSignalingChannelEndpointStartTime; + UINT64 getSignalingChannelEndpointEndTime; + UINT64 getIceServerConfigStartTime; + UINT64 getIceServerConfigEndTime; + UINT64 getTokenStartTime; + UINT64 getTokenEndTime; + UINT64 createChannelStartTime; + UINT64 createChannelEndTime; + UINT64 connectStartTime; + UINT64 connectEndTime; UINT64 createTime; UINT64 connectTime; UINT64 cpApiLatency; @@ -358,10 +372,7 @@ typedef struct { UINT64 deleteTime; UINT64 connectTime; UINT64 describeMediaTime; - -#ifdef KVS_USE_SIGNALING_CHANNEL_THREADPOOL - PThreadpool pThreadpool; -#endif + UINT64 answerTime; UINT64 offerReceivedTime; UINT64 offerSentTime; @@ -373,7 +384,6 @@ typedef struct { // Conditional variable for join storage session wait state CVAR jssWaitCvar; - } SignalingClient, *PSignalingClient; // Public handle to and from object converters diff --git a/src/source/Signaling/StateMachine.c b/src/source/Signaling/StateMachine.c index 3c4d0d8dbf..c865a626be 100644 --- a/src/source/Signaling/StateMachine.c +++ b/src/source/Signaling/StateMachine.c @@ -38,8 +38,9 @@ StateMachineState SIGNALING_STATE_MACHINE_STATES[] = { SIGNALING_STATE_JOIN_SESSION_CONNECTED | SIGNALING_STATE_GET_ENDPOINT | SIGNALING_STATE_READY | SIGNALING_STATE_GET_ICE_CONFIG, fromGetIceConfigSignalingState, executeGetIceConfigSignalingState, defaultSignalingStateTransitionHook, SIGNALING_STATES_DEFAULT_RETRY_COUNT, STATUS_SIGNALING_GET_ICE_CONFIG_CALL_FAILED}, - {SIGNALING_STATE_READY, SIGNALING_STATE_GET_ICE_CONFIG | SIGNALING_STATE_DISCONNECTED | SIGNALING_STATE_READY, fromReadySignalingState, - executeReadySignalingState, defaultSignalingStateTransitionHook, INFINITE_RETRY_COUNT_SENTINEL, STATUS_SIGNALING_READY_CALLBACK_FAILED}, + {SIGNALING_STATE_READY, SIGNALING_STATE_GET_ENDPOINT | SIGNALING_STATE_GET_ICE_CONFIG | SIGNALING_STATE_DISCONNECTED | SIGNALING_STATE_READY, + fromReadySignalingState, executeReadySignalingState, defaultSignalingStateTransitionHook, INFINITE_RETRY_COUNT_SENTINEL, + STATUS_SIGNALING_READY_CALLBACK_FAILED}, {SIGNALING_STATE_CONNECT, SIGNALING_STATE_READY | SIGNALING_STATE_DISCONNECTED | SIGNALING_STATE_CONNECTED | SIGNALING_STATE_JOIN_SESSION | SIGNALING_STATE_CONNECT, fromConnectSignalingState, executeConnectSignalingState, defaultSignalingStateTransitionHook, INFINITE_RETRY_COUNT_SENTINEL, @@ -346,7 +347,6 @@ STATUS executeGetTokenSignalingState(UINT64 customData, UINT64 time) STATUS retStatus = STATUS_SUCCESS; PSignalingClient pSignalingClient = SIGNALING_CLIENT_FROM_CUSTOM_DATA(customData); SERVICE_CALL_RESULT serviceCallResult; - UINT64 startTimeInMacro = 0; CHK(pSignalingClient != NULL, STATUS_NULL_ARG); @@ -362,9 +362,10 @@ STATUS executeGetTokenSignalingState(UINT64 customData, UINT64 time) THREAD_SLEEP_UNTIL(time); // Use the credential provider to get the token - PROFILE_CALL_WITH_T_OBJ(retStatus = pSignalingClient->pCredentialProvider->getCredentialsFn(pSignalingClient->pCredentialProvider, - &pSignalingClient->pAwsCredentials), - pSignalingClient->diagnostics.getTokenCallTime, "Get token call"); + PROFILE_CALL_WITH_START_END_T_OBJ(retStatus = pSignalingClient->pCredentialProvider->getCredentialsFn(pSignalingClient->pCredentialProvider, + &pSignalingClient->pAwsCredentials), + pSignalingClient->diagnostics.getTokenStartTime, pSignalingClient->diagnostics.getTokenEndTime, + pSignalingClient->diagnostics.getTokenCallTime, "Get token call"); // Check the expiration if (NULL == pSignalingClient->pAwsCredentials || SIGNALING_GET_CURRENT_TIME(pSignalingClient) >= pSignalingClient->pAwsCredentials->expiration) { @@ -433,7 +434,6 @@ STATUS executeDescribeSignalingState(UINT64 customData, UINT64 time) ENTERS(); STATUS retStatus = STATUS_SUCCESS; PSignalingClient pSignalingClient = SIGNALING_CLIENT_FROM_CUSTOM_DATA(customData); - UINT64 startTimeInMacro = 0; CHK(pSignalingClient != NULL, STATUS_NULL_ARG); ATOMIC_STORE(&pSignalingClient->result, (SIZE_T) SERVICE_CALL_RESULT_NOT_SET); @@ -446,8 +446,9 @@ STATUS executeDescribeSignalingState(UINT64 customData, UINT64 time) } // Call the aggregate function - PROFILE_CALL_WITH_T_OBJ(retStatus = describeChannel(pSignalingClient, time), pSignalingClient->diagnostics.describeCallTime, - "Describe signaling call"); + PROFILE_CALL_WITH_START_END_T_OBJ(retStatus = describeChannel(pSignalingClient, time), pSignalingClient->diagnostics.describeChannelStartTime, + pSignalingClient->diagnostics.describeChannelEndTime, pSignalingClient->diagnostics.describeCallTime, + "Describe signaling call"); CleanUp: @@ -561,7 +562,6 @@ STATUS executeCreateSignalingState(UINT64 customData, UINT64 time) ENTERS(); STATUS retStatus = STATUS_SUCCESS; PSignalingClient pSignalingClient = SIGNALING_CLIENT_FROM_CUSTOM_DATA(customData); - UINT64 startTimeInMacro = 0; CHK(pSignalingClient != NULL, STATUS_NULL_ARG); ATOMIC_STORE(&pSignalingClient->result, (SIZE_T) SERVICE_CALL_RESULT_NOT_SET); @@ -574,7 +574,9 @@ STATUS executeCreateSignalingState(UINT64 customData, UINT64 time) } // Call the aggregate function - PROFILE_CALL_WITH_T_OBJ(retStatus = createChannel(pSignalingClient, time), pSignalingClient->diagnostics.createCallTime, "Create signaling call"); + PROFILE_CALL_WITH_START_END_T_OBJ(retStatus = createChannel(pSignalingClient, time), pSignalingClient->diagnostics.createChannelStartTime, + pSignalingClient->diagnostics.createChannelEndTime, pSignalingClient->diagnostics.createCallTime, + "Create signaling call"); CleanUp: @@ -595,7 +597,7 @@ STATUS fromGetEndpointSignalingState(UINT64 customData, PUINT64 pState) result = ATOMIC_LOAD(&pSignalingClient->result); switch (result) { case SERVICE_CALL_RESULT_OK: - state = SIGNALING_STATE_GET_ICE_CONFIG; + state = SIGNALING_STATE_READY; break; case SERVICE_CALL_FORBIDDEN: @@ -619,7 +621,6 @@ STATUS executeGetEndpointSignalingState(UINT64 customData, UINT64 time) ENTERS(); STATUS retStatus = STATUS_SUCCESS; PSignalingClient pSignalingClient = SIGNALING_CLIENT_FROM_CUSTOM_DATA(customData); - UINT64 startTimeInMacro = 0; CHK(pSignalingClient != NULL, STATUS_NULL_ARG); ATOMIC_STORE(&pSignalingClient->result, (SIZE_T) SERVICE_CALL_RESULT_NOT_SET); @@ -632,8 +633,10 @@ STATUS executeGetEndpointSignalingState(UINT64 customData, UINT64 time) } // Call the aggregate function - PROFILE_CALL_WITH_T_OBJ(retStatus = getChannelEndpoint(pSignalingClient, time), pSignalingClient->diagnostics.getEndpointCallTime, - "Get endpoint signaling call"); + PROFILE_CALL_WITH_START_END_T_OBJ(retStatus = getChannelEndpoint(pSignalingClient, time), + pSignalingClient->diagnostics.getSignalingChannelEndpointStartTime, + pSignalingClient->diagnostics.getSignalingChannelEndpointEndTime, + pSignalingClient->diagnostics.getEndpointCallTime, "Get endpoint signaling call"); CleanUp: @@ -687,7 +690,6 @@ STATUS executeGetIceConfigSignalingState(UINT64 customData, UINT64 time) ENTERS(); STATUS retStatus = STATUS_SUCCESS; PSignalingClient pSignalingClient = SIGNALING_CLIENT_FROM_CUSTOM_DATA(customData); - UINT64 startTimeInMacro = 0; CHK(pSignalingClient != NULL, STATUS_NULL_ARG); ATOMIC_STORE(&pSignalingClient->result, (SIZE_T) SERVICE_CALL_RESULT_NOT_SET); @@ -700,8 +702,10 @@ STATUS executeGetIceConfigSignalingState(UINT64 customData, UINT64 time) } // Call the aggregate function - PROFILE_CALL_WITH_T_OBJ(retStatus = getIceConfig(pSignalingClient, time), pSignalingClient->diagnostics.getIceConfigCallTime, - "Get ICE config signaling call"); + PROFILE_CALL_WITH_START_END_T_OBJ(retStatus = getIceConfig(pSignalingClient, time), pSignalingClient->diagnostics.getIceServerConfigStartTime, + pSignalingClient->diagnostics.getIceServerConfigEndTime, pSignalingClient->diagnostics.getIceConfigCallTime, + "Get ICE config signaling call"); + CleanUp: LEAVES(); @@ -845,7 +849,6 @@ STATUS executeConnectSignalingState(UINT64 customData, UINT64 time) ENTERS(); STATUS retStatus = STATUS_SUCCESS; PSignalingClient pSignalingClient = SIGNALING_CLIENT_FROM_CUSTOM_DATA(customData); - UINT64 startTimeInMacro = 0; CHK(pSignalingClient != NULL, STATUS_NULL_ARG); @@ -855,8 +858,9 @@ STATUS executeConnectSignalingState(UINT64 customData, UINT64 time) SIGNALING_CLIENT_STATE_CONNECTING)); } - PROFILE_CALL_WITH_T_OBJ(retStatus = connectSignalingChannel(pSignalingClient, time), pSignalingClient->diagnostics.connectCallTime, - "Connect signaling call"); + PROFILE_CALL_WITH_START_END_T_OBJ(retStatus = connectSignalingChannel(pSignalingClient, time), pSignalingClient->diagnostics.connectStartTime, + pSignalingClient->diagnostics.connectEndTime, pSignalingClient->diagnostics.connectCallTime, + "Connect signaling call"); CleanUp: diff --git a/src/source/Stun/Stun.c b/src/source/Stun/Stun.c index d1a45ece34..a099e39a29 100644 --- a/src/source/Stun/Stun.c +++ b/src/source/Stun/Stun.c @@ -1215,6 +1215,8 @@ STATUS getStunAttribute(PStunPacket pStunPacket, STUN_ATTRIBUTE_TYPE attributeTy CleanUp: + CHK_LOG_ERR(retStatus); + if (ppStunAttribute != NULL) { *ppStunAttribute = pTargetAttribute; } diff --git a/src/source/Threadpool/ThreadPoolContext.c b/src/source/Threadpool/ThreadPoolContext.c new file mode 100644 index 0000000000..d4400f7b7b --- /dev/null +++ b/src/source/Threadpool/ThreadPoolContext.c @@ -0,0 +1,94 @@ +#define LOG_CLASS "ThreadPoolContext" +#include "../Include_i.h" + +// Function to get access to the Singleton instance +PThreadPoolContext getThreadContextInstance() +{ + static ThreadPoolContext t = {.pThreadpool = NULL, .isInitialized = FALSE, .threadpoolContextLock = INVALID_MUTEX_VALUE}; + return &t; +} + +STATUS createThreadPoolContext() +{ + STATUS retStatus = STATUS_SUCCESS; + BOOL locked = FALSE; + PCHAR pMinThreads, pMaxThreads; + UINT32 minThreads, maxThreads; + + PThreadPoolContext pThreadPoolContext = getThreadContextInstance(); + + if (NULL == (pMinThreads = GETENV(WEBRTC_THREADPOOL_MIN_THREADS_ENV_VAR)) || STATUS_SUCCESS != STRTOUI32(pMinThreads, NULL, 10, &minThreads)) { + minThreads = THREADPOOL_MIN_THREADS; + } + if (NULL == (pMaxThreads = GETENV(WEBRTC_THREADPOOL_MAX_THREADS_ENV_VAR)) || STATUS_SUCCESS != STRTOUI32(pMaxThreads, NULL, 10, &maxThreads)) { + maxThreads = THREADPOOL_MAX_THREADS; + } + + CHK_ERR(!IS_VALID_MUTEX_VALUE(pThreadPoolContext->threadpoolContextLock), STATUS_INVALID_OPERATION, "Mutex seems to have been created already"); + + pThreadPoolContext->threadpoolContextLock = MUTEX_CREATE(FALSE); + // Protecting this section to ensure we are not pushing threads / destroying the pool + // when it is being created. + MUTEX_LOCK(pThreadPoolContext->threadpoolContextLock); + locked = TRUE; + CHK_WARN(!pThreadPoolContext->isInitialized, retStatus, "Threadpool already set up. Nothing to do"); + CHK_WARN(pThreadPoolContext->pThreadpool == NULL, STATUS_INVALID_OPERATION, "Threadpool object already allocated"); + CHK_STATUS(threadpoolCreate(&pThreadPoolContext->pThreadpool, minThreads, maxThreads)); + pThreadPoolContext->isInitialized = TRUE; +CleanUp: + if (locked) { + MUTEX_UNLOCK(pThreadPoolContext->threadpoolContextLock); + } + return retStatus; +} + +STATUS threadpoolContextPush(startRoutine fn, PVOID customData) +{ + STATUS retStatus = STATUS_SUCCESS; + BOOL locked = FALSE; + PThreadPoolContext pThreadPoolContext = getThreadContextInstance(); + + // Protecting this section to ensure we are destroying the pool + // when it is being used. + MUTEX_LOCK(pThreadPoolContext->threadpoolContextLock); + locked = TRUE; + CHK_ERR(pThreadPoolContext->isInitialized, STATUS_INVALID_OPERATION, "Threadpool not initialized yet"); + CHK_ERR(pThreadPoolContext->pThreadpool != NULL, STATUS_NULL_ARG, "Threadpool object is NULL"); + CHK_STATUS(threadpoolPush(pThreadPoolContext->pThreadpool, fn, customData)); +CleanUp: + if (locked) { + MUTEX_UNLOCK(pThreadPoolContext->threadpoolContextLock); + } + return retStatus; +} + +STATUS destroyThreadPoolContext() +{ + STATUS retStatus = STATUS_SUCCESS; + BOOL locked = FALSE; + PThreadPoolContext pThreadPoolContext = getThreadContextInstance(); + + // Ensure we do not destroy the pool if threads are still being pushed + MUTEX_LOCK(pThreadPoolContext->threadpoolContextLock); + locked = TRUE; + CHK_WARN(pThreadPoolContext->isInitialized, STATUS_INVALID_OPERATION, "Threadpool not initialized yet, nothing to destroy"); + CHK_WARN(pThreadPoolContext->pThreadpool != NULL, STATUS_NULL_ARG, "Destroying threadpool without setting up"); + threadpoolFree(pThreadPoolContext->pThreadpool); + + // All members of the static instance **MUST** be reset after destruction to allow for + // the static object to be re-created after destruction (more relevant for unit tests) + pThreadPoolContext->pThreadpool = NULL; + pThreadPoolContext->isInitialized = FALSE; +CleanUp: + if (locked) { + MUTEX_UNLOCK(pThreadPoolContext->threadpoolContextLock); + } + if (IS_VALID_MUTEX_VALUE(pThreadPoolContext->threadpoolContextLock)) { + MUTEX_FREE(pThreadPoolContext->threadpoolContextLock); + + // Important to reset, specifically in case of unit tests where initKvsWebRtc() and + // deinitKvsWebRtc() is invoked before and after every test suite + pThreadPoolContext->threadpoolContextLock = INVALID_MUTEX_VALUE; + } + return retStatus; +}; \ No newline at end of file diff --git a/src/source/Threadpool/ThreadpoolContext.h b/src/source/Threadpool/ThreadpoolContext.h new file mode 100644 index 0000000000..adc45800c2 --- /dev/null +++ b/src/source/Threadpool/ThreadpoolContext.h @@ -0,0 +1,31 @@ +/******************************************* +Main internal include file +*******************************************/ +#ifndef __KINESIS_VIDEO_WEBRTC_CLIENT_THREADPOOLCONTEXT__ +#define __KINESIS_VIDEO_WEBRTC_CLIENT_THREADPOOLCONTEXT__ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////////////////// +// Project include files +//////////////////////////////////////////////////// + +typedef struct { + PThreadpool pThreadpool; + BOOL isInitialized; + MUTEX threadpoolContextLock; +} ThreadPoolContext, *PThreadPoolContext; + +PUBLIC_API STATUS createThreadPoolContext(); +PUBLIC_API STATUS getThreadPoolContext(PThreadPoolContext); +PUBLIC_API STATUS threadpoolContextPush(startRoutine, PVOID); +PUBLIC_API STATUS destroyThreadPoolContext(); + +#ifdef __cplusplus +} +#endif +#endif /* __KINESIS_VIDEO_WEBRTC_CLIENT_THREADPOOLCONTEXT__ */ diff --git a/tst/CMakeLists.txt b/tst/CMakeLists.txt index fd75b33534..864f4030bd 100644 --- a/tst/CMakeLists.txt +++ b/tst/CMakeLists.txt @@ -42,6 +42,8 @@ add_executable(webrtc_client_test ${WEBRTC_CLIENT_TEST_SOURCE_FILES} SignalingAp target_link_libraries(webrtc_client_test kvsWebrtcClient kvsWebrtcSignalingClient + ${EXTRA_DEPS} + kvsCommonLws ${OPENSSL_CRYPTO_LIBRARY} kvspicUtils ${GTEST_LIBNAME} diff --git a/tst/DataChannelApiTest.cpp b/tst/DataChannelApiTest.cpp index 05f0836275..0c0f9bc1c6 100644 --- a/tst/DataChannelApiTest.cpp +++ b/tst/DataChannelApiTest.cpp @@ -1,5 +1,6 @@ #include "WebRTCClientTestFixture.h" +#ifdef ENABLE_DATA_CHANNEL namespace com { namespace amazonaws { namespace kinesis { @@ -37,3 +38,5 @@ TEST_F(DataChannelApiTest, createDataChannel_Disconnected) } // namespace kinesis } // namespace amazonaws } // namespace com + +#endif diff --git a/tst/DataChannelFunctionalityTest.cpp b/tst/DataChannelFunctionalityTest.cpp index abf373d5f5..a2a355cd36 100644 --- a/tst/DataChannelFunctionalityTest.cpp +++ b/tst/DataChannelFunctionalityTest.cpp @@ -1,5 +1,6 @@ #include "WebRTCClientTestFixture.h" +#ifdef ENABLE_DATA_CHANNEL namespace com { namespace amazonaws { namespace kinesis { @@ -595,3 +596,5 @@ TEST_F(DataChannelFunctionalityTest, createDataChannel_DataChannelMetricsTest) } // namespace kinesis } // namespace amazonaws } // namespace com + +#endif diff --git a/tst/DtlsApiTest.cpp b/tst/DtlsApiTest.cpp index 4c985dd1ee..161e7608a0 100644 --- a/tst/DtlsApiTest.cpp +++ b/tst/DtlsApiTest.cpp @@ -24,6 +24,51 @@ TEST_F(DtlsApiTest, createCertificateAndKey_Returns_Success) EXPECT_EQ(pKey, nullptr); } +TEST_F(DtlsApiTest, dtlsSessionIsInitFinished_Null_Check) +{ + PDtlsSession pClient = NULL; + BOOL isDtlsConnected = FALSE; + DtlsSessionCallbacks callbacks; + TIMER_QUEUE_HANDLE timerQueueHandle = INVALID_TIMER_QUEUE_HANDLE_VALUE; + EXPECT_EQ(STATUS_SUCCESS, timerQueueCreate(&timerQueueHandle)); + EXPECT_EQ(STATUS_NULL_ARG, dtlsSessionIsInitFinished(pClient, &isDtlsConnected)); + EXPECT_EQ(FALSE, isDtlsConnected); + EXPECT_EQ(STATUS_SUCCESS, createDtlsSession(&callbacks, timerQueueHandle, 0, FALSE, NULL, &pClient)); + EXPECT_EQ(STATUS_NULL_ARG, dtlsSessionIsInitFinished(pClient, NULL)); + freeDtlsSession(&pClient); + EXPECT_EQ(NULL, pClient); + timerQueueFree(&timerQueueHandle); + +} + +TEST_F(DtlsApiTest, dtlsSessionCreated_RefCount) +{ + DtlsSessionCallbacks callbacks; + PDtlsSession pClient = NULL; + TIMER_QUEUE_HANDLE timerQueueHandle = INVALID_TIMER_QUEUE_HANDLE_VALUE; + EXPECT_EQ(STATUS_SUCCESS, timerQueueCreate(&timerQueueHandle)); + EXPECT_EQ(STATUS_SUCCESS, createDtlsSession(&callbacks, timerQueueHandle, 0, FALSE, NULL, &pClient)); + EXPECT_EQ(0, pClient->objRefCount); + freeDtlsSession(&pClient); + EXPECT_EQ(NULL, pClient); + timerQueueFree(&timerQueueHandle); +} + +TEST_F(DtlsApiTest, dtlsProcessPacket_Api_Check) +{ + DtlsSessionCallbacks callbacks; + PDtlsSession pClient = NULL; + INT32 length; + TIMER_QUEUE_HANDLE timerQueueHandle = INVALID_TIMER_QUEUE_HANDLE_VALUE; + EXPECT_EQ(STATUS_NULL_ARG, dtlsSessionProcessPacket(pClient, NULL, &length)); + EXPECT_EQ(STATUS_SUCCESS, timerQueueCreate(&timerQueueHandle)); + EXPECT_EQ(STATUS_SUCCESS, createDtlsSession(&callbacks, timerQueueHandle, 0, FALSE, NULL, &pClient)); + EXPECT_EQ(STATUS_NULL_ARG, dtlsSessionProcessPacket(pClient, NULL, NULL)); + EXPECT_EQ(STATUS_SSL_PACKET_BEFORE_DTLS_READY, dtlsSessionProcessPacket(pClient, NULL, &length)); + freeDtlsSession(&pClient); + timerQueueFree(&timerQueueHandle); +} + #elif KVS_USE_MBEDTLS TEST_F(DtlsApiTest, createCertificateAndKey_Returns_Success) { diff --git a/tst/DtlsFunctionalityTest.cpp b/tst/DtlsFunctionalityTest.cpp index 13d5fcb91d..32faca8ab7 100644 --- a/tst/DtlsFunctionalityTest.cpp +++ b/tst/DtlsFunctionalityTest.cpp @@ -8,7 +8,7 @@ namespace webrtcclient { class DtlsFunctionalityTest : public WebRtcClientTestBase { public: - STATUS createAndConnect(TIMER_QUEUE_HANDLE timerQueueHandle, PDtlsSession* ppClient, PDtlsSession* ppServer) + STATUS createAndConnect(TIMER_QUEUE_HANDLE timerQueueHandle, PDtlsSession* ppClient, PDtlsSession* ppServer, BOOL useThread) { struct Context { std::mutex mtx; @@ -20,6 +20,7 @@ class DtlsFunctionalityTest : public WebRtcClientTestBase { PDtlsSession pClient = NULL, pServer = NULL; UINT64 sleepDelay = 20 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND; Context clientCtx, serverCtx; + std::thread dtlsClientThread, dtlsServerThread; MEMSET(&callbacks, 0, SIZEOF(callbacks)); callbacks.stateChangeFn = [](UINT64 customData, RTC_DTLS_TRANSPORT_STATE state) { @@ -68,8 +69,19 @@ class DtlsFunctionalityTest : public WebRtcClientTestBase { CHK_STATUS(dtlsSessionOnOutBoundData(pServer, (UINT64) &clientCtx, outboundPacketFn)); CHK_STATUS(dtlsSessionOnOutBoundData(pClient, (UINT64) &serverCtx, outboundPacketFn)); - CHK_STATUS(dtlsSessionStart(pServer, FALSE)); + // In case of mbedtls it will be a black return of SUCCESS +#ifdef KVS_USE_OPENSSL + if(useThread) { + dtlsClientThread = std::thread(dtlsSessionHandshakeInThread, pClient, TRUE); + dtlsServerThread = std::thread(dtlsSessionHandshakeInThread, pServer, FALSE); + } else { + CHK_STATUS(dtlsSessionStart(pClient, TRUE)); + CHK_STATUS(dtlsSessionStart(pServer, FALSE)); + } +#else CHK_STATUS(dtlsSessionStart(pClient, TRUE)); + CHK_STATUS(dtlsSessionStart(pServer, FALSE)); +#endif for (UINT64 duration = 0; duration < MAX_TEST_AWAIT_DURATION && ATOMIC_LOAD(&connectedCount) != 2; duration += sleepDelay) { CHK_STATUS(consumeMessages(&serverCtx, pServer)); @@ -82,6 +94,13 @@ class DtlsFunctionalityTest : public WebRtcClientTestBase { *ppClient = pClient; *ppServer = pServer; +#ifdef KVS_USE_OPENSSL + if(useThread) { + dtlsClientThread.join(); + dtlsServerThread.join(); + } +#endif + CleanUp: if (STATUS_FAILED(retStatus)) { @@ -117,7 +136,7 @@ TEST_F(DtlsFunctionalityTest, putApplicationDataWithVariedSizes) }; EXPECT_EQ(STATUS_SUCCESS, timerQueueCreate(&timerQueueHandle)); - EXPECT_EQ(STATUS_SUCCESS, createAndConnect(timerQueueHandle, &pClient, &pServer)); + EXPECT_EQ(STATUS_SUCCESS, createAndConnect(timerQueueHandle, &pClient, &pServer, FALSE)); EXPECT_EQ(STATUS_SUCCESS, dtlsSessionOnOutBoundData(pClient, 0, outboundPacketFnNoop)); EXPECT_EQ(STATUS_SUCCESS, dtlsSessionOnOutBoundData(pServer, 0, outboundPacketFnNoop)); @@ -148,7 +167,7 @@ TEST_F(DtlsFunctionalityTest, processPacketWithVariedSizes) INT32 readDataSize; EXPECT_EQ(STATUS_SUCCESS, timerQueueCreate(&timerQueueHandle)); - EXPECT_EQ(STATUS_SUCCESS, createAndConnect(timerQueueHandle, &pClient, &pServer)); + EXPECT_EQ(STATUS_SUCCESS, createAndConnect(timerQueueHandle, &pClient, &pServer, FALSE)); EXPECT_EQ(STATUS_SUCCESS, dtlsSessionOnOutBoundData(pServer, 0, outboundPacketFnNoop)); EXPECT_EQ(STATUS_SUCCESS, dtlsSessionOnOutBoundData(pClient, 0, outboundPacketFnNoop)); @@ -167,6 +186,69 @@ TEST_F(DtlsFunctionalityTest, processPacketWithVariedSizes) MEMFREE(pData); } +TEST_F(DtlsFunctionalityTest, putApplicationDataWithVariedSizesInThread) +{ + PDtlsSession pClient = NULL, pServer = NULL; + TIMER_QUEUE_HANDLE timerQueueHandle = INVALID_TIMER_QUEUE_HANDLE_VALUE; + PBYTE pData = NULL; + INT32 dataSizes[] = { + 4, // very small packet + DEFAULT_MTU_SIZE - 200, // small packet but should be still under mtu + DEFAULT_MTU_SIZE + 200, // big packet and bigger than even a jumbo frame + }; + + EXPECT_EQ(STATUS_SUCCESS, timerQueueCreate(&timerQueueHandle)); + EXPECT_EQ(STATUS_SUCCESS, createAndConnect(timerQueueHandle, &pClient, &pServer, TRUE)); + + EXPECT_EQ(STATUS_SUCCESS, dtlsSessionOnOutBoundData(pClient, 0, outboundPacketFnNoop)); + EXPECT_EQ(STATUS_SUCCESS, dtlsSessionOnOutBoundData(pServer, 0, outboundPacketFnNoop)); + + for (int i = 0; i < (INT32) ARRAY_SIZE(dataSizes); i++) { + pData = (PBYTE) MEMREALLOC(pData, dataSizes[i]); + ASSERT_TRUE(pData != NULL); + MEMSET(pData, 0x11, dataSizes[i]); + EXPECT_EQ(STATUS_SUCCESS, dtlsSessionPutApplicationData(pClient, pData, dataSizes[i])); + } + + freeDtlsSession(&pClient); + freeDtlsSession(&pServer); + timerQueueFree(&timerQueueHandle); +MEMFREE(pData); +} + +TEST_F(DtlsFunctionalityTest, processPacketWithVariedSizesInThread) +{ + PDtlsSession pClient = NULL, pServer = NULL; + TIMER_QUEUE_HANDLE timerQueueHandle = INVALID_TIMER_QUEUE_HANDLE_VALUE; + PBYTE pData = NULL; + INT32 dataSizes[] = { + 4, // very small packet + DEFAULT_MTU_SIZE - 200, // small packet but should be still under mtu + DEFAULT_MTU_SIZE + 200, // big packet and bigger than even a jumbo frame + }; + INT32 readDataSize; + + EXPECT_EQ(STATUS_SUCCESS, timerQueueCreate(&timerQueueHandle)); + EXPECT_EQ(STATUS_SUCCESS, createAndConnect(timerQueueHandle, &pClient, &pServer, TRUE)); + EXPECT_EQ(STATUS_SUCCESS, dtlsSessionOnOutBoundData(pServer, 0, outboundPacketFnNoop)); + EXPECT_EQ(STATUS_SUCCESS, dtlsSessionOnOutBoundData(pClient, 0, outboundPacketFnNoop)); + + + for (int i = 0; i < (INT32) ARRAY_SIZE(dataSizes); i++) { + pData = (PBYTE) MEMREALLOC(pData, dataSizes[i]); + readDataSize = dataSizes[i]; + ASSERT_TRUE(pData != NULL); + MEMSET(pData, 0x11, dataSizes[i]); + EXPECT_EQ(STATUS_SUCCESS, dtlsSessionProcessPacket(pServer, pData, &readDataSize)); + } + + freeDtlsSession(&pClient); + freeDtlsSession(&pServer); + timerQueueFree(&timerQueueHandle); + MEMFREE(pData); +} + + } // namespace webrtcclient } // namespace video } // namespace kinesis diff --git a/tst/IceFunctionalityTest.cpp b/tst/IceFunctionalityTest.cpp index 6ab40aa894..8693f8711c 100644 --- a/tst/IceFunctionalityTest.cpp +++ b/tst/IceFunctionalityTest.cpp @@ -6,8 +6,7 @@ namespace kinesis { namespace video { namespace webrtcclient { -class IceFunctionalityTest : public WebRtcClientTestBase { -}; +class IceFunctionalityTest : public WebRtcClientTestBase {}; // check if iceCandidatePairs is in descending order BOOL candidatePairsInOrder(PDoubleList iceCandidatePairs) @@ -135,7 +134,7 @@ PVOID connectionListenAddConnectionRoutine(PVOID arg) } for (i = 0; i < pCustomData->connectionToAdd; ++i) { - randomDelay = (UINT64)(RAND() % 300) * HUNDREDS_OF_NANOS_IN_A_MILLISECOND; + randomDelay = (UINT64) (RAND() % 300) * HUNDREDS_OF_NANOS_IN_A_MILLISECOND; THREAD_SLEEP(randomDelay); CHECK(STATUS_SUCCEEDED(createSocketConnection((KVS_IP_FAMILY_TYPE) localhost.family, KVS_SOCKET_PROTOCOL_UDP, &localhost, NULL, 0, NULL, 0, &pSocketConnection))); @@ -151,7 +150,7 @@ TEST_F(IceFunctionalityTest, connectionListenerFunctionalityTest) PConnectionListener pConnectionListener; ConnectionListenerTestCustomData routine1CustomData, routine2CustomData; TID routine1, routine2; - UINT32 connectionCount , newConnectionCount, i; + UINT32 connectionCount, newConnectionCount, i; PSocketConnection pSocketConnection = NULL; KvsIpAddress localhost; TID threadId; @@ -203,7 +202,7 @@ TEST_F(IceFunctionalityTest, connectionListenerFunctionalityTest) MUTEX_LOCK(pConnectionListener->lock); threadId = pConnectionListener->receiveDataRoutine; MUTEX_UNLOCK(pConnectionListener->lock); - EXPECT_TRUE( IS_VALID_TID_VALUE(threadId)); + EXPECT_TRUE(IS_VALID_TID_VALUE(threadId)); ATOMIC_STORE_BOOL(&pConnectionListener->terminate, TRUE); THREAD_JOIN(threadId, NULL); @@ -359,7 +358,7 @@ TEST_F(IceFunctionalityTest, IceAgentAddRemoteCandidateUnitTest) EXPECT_EQ(STATUS_SUCCESS, doubleListGetHeadNode(iceAgent.remoteCandidates, &pCurNode)); // parsing candidate priority correctly - EXPECT_EQ(2122260223, ((PIceCandidate)pCurNode->data)->priority); + EXPECT_EQ(2122260223, ((PIceCandidate) pCurNode->data)->priority); // candidate pair formed EXPECT_EQ(STATUS_SUCCESS, doubleListGetNodeCount(iceAgent.iceCandidatePairs, &iceCandidateCount)); @@ -378,7 +377,7 @@ TEST_F(IceFunctionalityTest, IceAgentAddRemoteCandidateUnitTest) // parsing candidate priority correctly EXPECT_EQ(STATUS_SUCCESS, doubleListGetHeadNode(iceAgent.remoteCandidates, &pCurNode)); - EXPECT_EQ(2122262783, ((PIceCandidate)pCurNode->data)->priority); + EXPECT_EQ(2122262783, ((PIceCandidate) pCurNode->data)->priority); iceAgent.iceAgentState = ICE_AGENT_STATE_CHECK_CONNECTION; EXPECT_EQ(STATUS_SUCCESS, iceAgentAddRemoteCandidate(&iceAgent, relayCandidateStr)); @@ -390,7 +389,7 @@ TEST_F(IceFunctionalityTest, IceAgentAddRemoteCandidateUnitTest) EXPECT_EQ(STATUS_SUCCESS, doubleListGetHeadNode(iceAgent.remoteCandidates, &pCurNode)); // parsing candidate priority correctly - EXPECT_EQ(41885439, ((PIceCandidate)pCurNode->data)->priority); + EXPECT_EQ(41885439, ((PIceCandidate) pCurNode->data)->priority); MUTEX_FREE(iceAgent.lock); EXPECT_EQ(STATUS_SUCCESS, doubleListGetHeadNode(iceAgent.iceCandidatePairs, &pCurNode)); @@ -663,7 +662,9 @@ TEST_F(IceFunctionalityTest, IceAgentCandidateGatheringTest) MEMSET(&iceAgentCallbacks, 0x00, SIZEOF(IceAgentCallbacks)); initializeSignalingClient(); - getIceServers(&configuration); + + SNPRINTF(configuration.iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, + TEST_DEFAULT_STUN_URL_POSTFIX); auto onICECandidateHdlr = [](UINT64 customData, PCHAR candidateStr) -> void { CandidateList* candidateList1 = (CandidateList*) customData; @@ -679,6 +680,9 @@ TEST_F(IceFunctionalityTest, IceAgentCandidateGatheringTest) iceAgentCallbacks.customData = (UINT64) &candidateList; iceAgentCallbacks.newLocalCandidateFn = onICECandidateHdlr; + // Set the STUN server + SNPRINTF(configuration.iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, TEST_DEFAULT_STUN_URL_POSTFIX); + EXPECT_EQ(STATUS_SUCCESS, generateJSONSafeString(localIceUfrag, LOCAL_ICE_UFRAG_LEN)); EXPECT_EQ(STATUS_SUCCESS, generateJSONSafeString(localIcePwd, LOCAL_ICE_PWD_LEN)); EXPECT_EQ(STATUS_SUCCESS, createConnectionListener(&pConnectionListener)); @@ -687,12 +691,11 @@ TEST_F(IceFunctionalityTest, IceAgentCandidateGatheringTest) createIceAgent(localIceUfrag, localIcePwd, &iceAgentCallbacks, &configuration, timerQueueHandle, pConnectionListener, &pIceAgent)); EXPECT_EQ(STATUS_SUCCESS, iceAgentStartGathering(pIceAgent)); + getIceServers(&configuration, pIceAgent); THREAD_SLEEP(KVS_ICE_GATHER_REFLEXIVE_AND_RELAYED_CANDIDATE_TIMEOUT + 2 * HUNDREDS_OF_NANOS_IN_A_SECOND); - // newLocalCandidateFn should've returned null in its last invocation, which was converted to empty string candidateList.lock.lock(); - EXPECT_TRUE(candidateList.list[candidateList.list.size() - 1].empty()); for (std::vector::iterator it = candidateList.list.begin(); it != candidateList.list.end(); ++it) { std::string candidateStr = *it; diff --git a/tst/IngestionFunctionalityTests.cpp b/tst/IngestionFunctionalityTests.cpp index 5b62fc5fc9..4216189fe8 100644 --- a/tst/IngestionFunctionalityTests.cpp +++ b/tst/IngestionFunctionalityTests.cpp @@ -383,7 +383,7 @@ TEST_F(IngestionFunctionalityTest, basicCreateConnectJoinSession) EXPECT_EQ(1, describeCount); EXPECT_EQ(1, describeMediaCount); EXPECT_EQ(1, getEndpointCount); - EXPECT_EQ(1, getIceConfigCount); + EXPECT_EQ(0, getIceConfigCount); EXPECT_EQ(1, connectCount); @@ -466,7 +466,7 @@ TEST_F(IngestionFunctionalityTest, iceReconnectEmulationWithJoinSession) EXPECT_EQ(1, describeCount); EXPECT_EQ(1, describeMediaCount); EXPECT_EQ(1, getEndpointCount); - EXPECT_EQ(1, getIceConfigCount); + EXPECT_EQ(0, getIceConfigCount); EXPECT_EQ(1, connectCount); // This channel has ENABLED status so we should be calling join session @@ -508,7 +508,7 @@ TEST_F(IngestionFunctionalityTest, iceReconnectEmulationWithJoinSession) EXPECT_EQ(1, describeCount); EXPECT_EQ(1, describeMediaCount); EXPECT_EQ(1, getEndpointCount); - EXPECT_EQ(2, getIceConfigCount); + EXPECT_EQ(1, getIceConfigCount); EXPECT_EQ(2, connectCount); // This channel has ENABLED status so we should be calling join session @@ -570,6 +570,9 @@ TEST_F(IngestionFunctionalityTest, iceServerConfigRefreshNotConnectedJoinSession EXPECT_TRUE(IS_VALID_SIGNALING_CLIENT_HANDLE(signalingHandle)); EXPECT_EQ(STATUS_SUCCESS,signalingClientFetchSync(signalingHandle)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); + pActiveClient = pSignalingClient; // Check the states first, we did not connect yet @@ -580,7 +583,7 @@ TEST_F(IngestionFunctionalityTest, iceServerConfigRefreshNotConnectedJoinSession EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_JOIN_SESSION]); @@ -609,7 +612,7 @@ TEST_F(IngestionFunctionalityTest, iceServerConfigRefreshNotConnectedJoinSession EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_LT(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_JOIN_SESSION]); @@ -632,7 +635,7 @@ TEST_F(IngestionFunctionalityTest, iceServerConfigRefreshNotConnectedJoinSession EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_LT(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_JOIN_SESSION]); @@ -723,7 +726,7 @@ TEST_F(IngestionFunctionalityTest, iceServerConfigRefreshConnectedJoinSessionWit // We should not be calling create because it's pre-created at the start of the test EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -752,7 +755,7 @@ TEST_F(IngestionFunctionalityTest, iceServerConfigRefreshConnectedJoinSessionWit EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE_MEDIA]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_LT(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_LT(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -853,7 +856,7 @@ TEST_F(IngestionFunctionalityTest, fileCachingTestWithDescribeMedia) FREMOVE(DEFAULT_CACHE_FILE_PATH); for (i = 0; i < totalChannelCount; ++i) { - SPRINTF(signalingChannelName, "%s%u", TEST_SIGNALING_CHANNEL_NAME, i); + SNPRINTF(signalingChannelName, SIZEOF(signalingChannelName), "%s%u", TEST_SIGNALING_CHANNEL_NAME, i); channelInfo.pChannelName = signalingChannelName; EXPECT_EQ(STATUS_SUCCESS, createSignalingSync(&clientInfoInternal, &channelInfo, &signalingClientCallbacks, (PAwsCredentialProvider) mTestCredentialProvider, @@ -869,7 +872,7 @@ TEST_F(IngestionFunctionalityTest, fileCachingTestWithDescribeMedia) getEndpointCountNoCache = getEndpointCount; for (i = 0; i < totalChannelCount; ++i) { - SPRINTF(signalingChannelName, "%s%u", TEST_SIGNALING_CHANNEL_NAME, i); + SNPRINTF(signalingChannelName, SIZEOF(signalingChannelName), "%s%u", TEST_SIGNALING_CHANNEL_NAME, i); channelInfo.pChannelName = signalingChannelName; channelInfo.pChannelArn = NULL; EXPECT_EQ(STATUS_SUCCESS, diff --git a/tst/MetricsApiTest.cpp b/tst/MetricsApiTest.cpp index 0e014421e3..7484d2db94 100644 --- a/tst/MetricsApiTest.cpp +++ b/tst/MetricsApiTest.cpp @@ -22,7 +22,7 @@ TEST_F(MetricsApiTest, webRtcGetMetrics) MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); - EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + ASSERT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); EXPECT_EQ(STATUS_NULL_ARG, rtcPeerConnectionGetMetrics(pRtcPeerConnection, NULL, NULL)); @@ -57,7 +57,7 @@ TEST_F(MetricsApiTest, webRtcIceServerGetMetrics) STRNCPY(configuration.iceServers[1].credential, (PCHAR) "username", MAX_ICE_CONFIG_CREDENTIAL_LEN); STRNCPY(configuration.iceServers[1].username, (PCHAR) "password", MAX_ICE_CONFIG_USER_NAME_LEN); - EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + ASSERT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); EXPECT_EQ(STATUS_ICE_SERVER_INDEX_INVALID, rtcPeerConnectionGetMetrics(pRtcPeerConnection, NULL, &rtcIceMetrics)); @@ -90,10 +90,15 @@ TEST_F(MetricsApiTest, webRtcIceCandidateGetMetrics) STRNCPY(configuration.iceServers[0].credential, (PCHAR) "", MAX_ICE_CONFIG_CREDENTIAL_LEN); STRNCPY(configuration.iceServers[0].username, (PCHAR) "", MAX_ICE_CONFIG_USER_NAME_LEN); - EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + ASSERT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); pIceAgent = ((PKvsPeerConnection) pRtcPeerConnection)->pIceAgent; + if(pIceAgent == NULL) { + DLOGI("ICE Agent null"); + } else { + DLOGI("ICE agent not null"); + } IceCandidate localCandidate; IceCandidate remoteCandidate; IceCandidatePair iceCandidatePair; diff --git a/tst/PeerConnectionApiTest.cpp b/tst/PeerConnectionApiTest.cpp index 22c64cc412..d2cd4be672 100644 --- a/tst/PeerConnectionApiTest.cpp +++ b/tst/PeerConnectionApiTest.cpp @@ -6,8 +6,7 @@ namespace kinesis { namespace video { namespace webrtcclient { -class PeerConnectionApiTest : public WebRtcClientTestBase { -}; +class PeerConnectionApiTest : public WebRtcClientTestBase {}; TEST_F(PeerConnectionApiTest, deserializeRtcIceCandidateInit) { @@ -194,6 +193,21 @@ TEST_F(PeerConnectionApiTest, connectionState) EXPECT_EQ(RTC_PEER_CONNECTION_STATE_DISCONNECTED, fromIceAgentState(pc, ICE_AGENT_STATE_DISCONNECTED)); EXPECT_EQ(RTC_PEER_CONNECTION_STATE_FAILED, fromIceAgentState(pc, ICE_AGENT_STATE_FAILED)); + closePeerConnection(pc); + freePeerConnection(&pc); +} + +TEST_F(PeerConnectionApiTest, addConfigToServerListUnitTest) +{ + PRtcPeerConnection pc = nullptr; + PIceConfigInfo pIceConfigInfo = nullptr; + RtcConfiguration config{}; + EXPECT_NE(STATUS_SUCCESS, addConfigToServerList(&pc, pIceConfigInfo)); + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pc)); + + EXPECT_NE(STATUS_SUCCESS, addConfigToServerList(&pc, pIceConfigInfo)); + + closePeerConnection(pc); freePeerConnection(&pc); } diff --git a/tst/PeerConnectionFunctionalityTest.cpp b/tst/PeerConnectionFunctionalityTest.cpp index 35d4a3cda0..bc3901d153 100644 --- a/tst/PeerConnectionFunctionalityTest.cpp +++ b/tst/PeerConnectionFunctionalityTest.cpp @@ -6,8 +6,7 @@ namespace kinesis { namespace video { namespace webrtcclient { -class PeerConnectionFunctionalityTest : public WebRtcClientTestBase { -}; +class PeerConnectionFunctionalityTest : public WebRtcClientTestBase {}; // Assert that two PeerConnections can connect to each other and go to connected TEST_F(PeerConnectionFunctionalityTest, connectTwoPeers) @@ -29,12 +28,40 @@ TEST_F(PeerConnectionFunctionalityTest, connectTwoPeers) freePeerConnection(&answerPc); } +TEST_F(PeerConnectionFunctionalityTest, connectTwoPeersWithAsyncGetIceConfigForceTurn) +{ + RtcConfiguration configuration; + PRtcPeerConnection offerPc = NULL, answerPc = NULL; + + MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); + SNPRINTF(configuration.iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, + TEST_DEFAULT_STUN_URL_POSTFIX); + configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_RELAY; + + initializeSignalingClient(); + + EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); + EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + + EXPECT_EQ(connectTwoPeersAsyncIce(offerPc, answerPc), TRUE); + + closePeerConnection(offerPc); + closePeerConnection(answerPc); + + freePeerConnection(&offerPc); + freePeerConnection(&answerPc); + + deinitializeSignalingClient(); +} + TEST_F(PeerConnectionFunctionalityTest, connectTwoPeersWithDelay) { RtcConfiguration configuration; RtcSessionDescriptionInit sdp; SIZE_T connectedCount = 0; PRtcPeerConnection offerPc = NULL, answerPc = NULL; + PeerContainer offer; + PeerContainer answer; MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); @@ -42,20 +69,34 @@ TEST_F(PeerConnectionFunctionalityTest, connectTwoPeersWithDelay) EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); auto onICECandidateHdlr = [](UINT64 customData, PCHAR candidateStr) -> void { + PPeerContainer container = (PPeerContainer)customData; if (candidateStr != NULL) { - std::thread( - [customData](std::string candidate) { - RtcIceCandidateInit iceCandidate; - EXPECT_EQ(STATUS_SUCCESS, deserializeRtcIceCandidateInit((PCHAR) candidate.c_str(), STRLEN(candidate.c_str()), &iceCandidate)); - EXPECT_EQ(STATUS_SUCCESS, addIceCandidate((PRtcPeerConnection) customData, iceCandidate.candidate)); - }, - std::string(candidateStr)) - .detach(); + container->client->lock.lock(); + if(!container->client->noNewThreads) { + container->client->threads.push_back(std::thread( + [container](std::string candidate) { + RtcIceCandidateInit iceCandidate; + EXPECT_EQ(STATUS_SUCCESS, deserializeRtcIceCandidateInit((PCHAR) candidate.c_str(), STRLEN(candidate.c_str()), &iceCandidate)); + EXPECT_EQ(STATUS_SUCCESS, addIceCandidate((PRtcPeerConnection) container->pc, iceCandidate.candidate)); + }, + std::string(candidateStr))); + } + container->client->lock.unlock(); } }; - EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) answerPc, onICECandidateHdlr)); - EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) offerPc, onICECandidateHdlr)); + offer.pc = offerPc; + offer.client = this; + answer.pc = answerPc; + answer.client = this; + + auto onICECandidateHdlrDone = [](UINT64 customData, PCHAR candidateStr) -> void { + UNUSED_PARAM(customData); + UNUSED_PARAM(candidateStr); + }; + + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) &answer, onICECandidateHdlr)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) &offer, onICECandidateHdlr)); auto onICEConnectionStateChangeHdlr = [](UINT64 customData, RTC_PEER_CONNECTION_STATE newState) -> void { if (newState == RTC_PEER_CONNECTION_STATE_CONNECTED) { @@ -83,6 +124,17 @@ TEST_F(PeerConnectionFunctionalityTest, connectTwoPeersWithDelay) EXPECT_EQ(2, connectedCount); + this->lock.lock(); + //join all threads before leaving + for (auto& th : this->threads) th.join(); + + this->threads.clear(); + this->noNewThreads = TRUE; + this->lock.unlock(); + + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) 0, onICECandidateHdlrDone)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) 0, onICECandidateHdlrDone)); + closePeerConnection(offerPc); closePeerConnection(answerPc); @@ -198,11 +250,13 @@ TEST_F(PeerConnectionFunctionalityTest, connectTwoPeersForcedTURN) configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_RELAY; initializeSignalingClient(); - getIceServers(&configuration); EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + getIceServers(&configuration, offerPc); + getIceServers(&configuration, answerPc); + EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); closePeerConnection(offerPc); @@ -292,10 +346,11 @@ TEST_F(PeerConnectionFunctionalityTest, sendDataWithClosedSocketConnectionWithFo configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_RELAY; initializeSignalingClient(); - getIceServers(&configuration); EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + getIceServers(&configuration, offerPc); + getIceServers(&configuration, answerPc); // addTrackToPeerConnection is necessary because we need to add a transceiver which will trigger the RTCP callback. The RTCP callback // will send application data. The expected behavior for the PeerConnection is to bail out when the socket connection that's being used @@ -354,11 +409,13 @@ TEST_F(PeerConnectionFunctionalityTest, shutdownTurnDueToP2PFoundBeforeTurnEstab MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); initializeSignalingClient(); - getIceServers(&configuration); EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + getIceServers(&configuration, offerPc); + getIceServers(&configuration, answerPc); + EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); THREAD_SLEEP(5 * HUNDREDS_OF_NANOS_IN_A_SECOND); @@ -416,11 +473,13 @@ TEST_F(PeerConnectionFunctionalityTest, shutdownTurnDueToP2PFoundAfterTurnEstabl MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); initializeSignalingClient(); - getIceServers(&configuration); EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + getIceServers(&configuration, offerPc); + getIceServers(&configuration, answerPc); + auto onICECandidateHdlr = [](UINT64 customData, PCHAR candidateStr) -> void { PSIZE_T pDoneGatherCandidate = (PSIZE_T) customData; if (candidateStr == NULL) { @@ -603,6 +662,9 @@ TEST_F(PeerConnectionFunctionalityTest, noLostFramesAfterConnected) ATOMIC_BOOL seenFirstFrame = FALSE; Frame videoFrame; + PeerContainer offer; + PeerContainer answer; + MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); MEMSET(&videoFrame, 0x00, SIZEOF(Frame)); @@ -622,6 +684,33 @@ TEST_F(PeerConnectionFunctionalityTest, noLostFramesAfterConnected) addTrackToPeerConnection(offerPc, &offerVideoTrack, &offerVideoTransceiver, RTC_CODEC_VP8, MEDIA_STREAM_TRACK_KIND_VIDEO); addTrackToPeerConnection(answerPc, &answerVideoTrack, &answerVideoTransceiver, RTC_CODEC_VP8, MEDIA_STREAM_TRACK_KIND_VIDEO); + auto onICECandidateHdlr = [](UINT64 customData, PCHAR candidateStr) -> void { + PPeerContainer container = (PPeerContainer)customData; + if (candidateStr != NULL) { + container->client->lock.lock(); + if(!container->client->noNewThreads) { + container->client->threads.push_back(std::thread( + [container](std::string candidate) { + RtcIceCandidateInit iceCandidate; + EXPECT_EQ(STATUS_SUCCESS, deserializeRtcIceCandidateInit((PCHAR) candidate.c_str(), STRLEN(candidate.c_str()), &iceCandidate)); + EXPECT_EQ(STATUS_SUCCESS, addIceCandidate((PRtcPeerConnection) container->pc, iceCandidate.candidate)); + }, + std::string(candidateStr))); + } + container->client->lock.unlock(); + } + }; + + offer.pc = offerPc; + offer.client = this; + answer.pc = answerPc; + answer.client = this; + + auto onICECandidateHdlrDone = [](UINT64 customData, PCHAR candidateStr) -> void { + UNUSED_PARAM(customData); + UNUSED_PARAM(candidateStr); + }; + auto onFrameHandler = [](UINT64 customData, PFrame pFrame) -> void { UNUSED_PARAM(pFrame); if (pFrame->frameData[0] == 1) { @@ -630,21 +719,8 @@ TEST_F(PeerConnectionFunctionalityTest, noLostFramesAfterConnected) }; EXPECT_EQ(transceiverOnFrame(answerVideoTransceiver, (UINT64) &seenFirstFrame, onFrameHandler), STATUS_SUCCESS); - auto onICECandidateHdlr = [](UINT64 customData, PCHAR candidateStr) -> void { - if (candidateStr != NULL) { - std::thread( - [customData](std::string candidate) { - RtcIceCandidateInit iceCandidate; - EXPECT_EQ(STATUS_SUCCESS, deserializeRtcIceCandidateInit((PCHAR) candidate.c_str(), STRLEN(candidate.c_str()), &iceCandidate)); - EXPECT_EQ(STATUS_SUCCESS, addIceCandidate((PRtcPeerConnection) customData, iceCandidate.candidate)); - }, - std::string(candidateStr)) - .detach(); - } - }; - - EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) answerPc, onICECandidateHdlr)); - EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) offerPc, onICECandidateHdlr)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) &answer, onICECandidateHdlr)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) &offer, onICECandidateHdlr)); auto onICEConnectionStateChangeHdlr = [](UINT64 customData, RTC_PEER_CONNECTION_STATE newState) -> void { Context* pContext = (Context*) customData; @@ -682,6 +758,16 @@ TEST_F(PeerConnectionFunctionalityTest, noLostFramesAfterConnected) THREAD_SLEEP(HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } + this->lock.lock(); + for (auto& th : this->threads) th.join(); + + this->threads.clear(); + this->noNewThreads = TRUE; + this->lock.unlock(); + + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) 0, onICECandidateHdlrDone)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) 0, onICECandidateHdlrDone)); + MEMFREE(videoFrame.frameData); closePeerConnection(offerPc); closePeerConnection(answerPc); @@ -860,11 +946,13 @@ TEST_F(PeerConnectionFunctionalityTest, iceRestartTestForcedTurn) configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_RELAY; initializeSignalingClient(); - getIceServers(&configuration); EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + getIceServers(&configuration, offerPc); + getIceServers(&configuration, answerPc); + EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); EXPECT_EQ(restartIce(offerPc), STATUS_SUCCESS); @@ -893,11 +981,13 @@ TEST_F(PeerConnectionFunctionalityTest, peerConnectionOfferCloseConnection) MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); initializeSignalingClient(); - getIceServers(&configuration); EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + getIceServers(&configuration, offerPc); + getIceServers(&configuration, answerPc); + EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); closePeerConnection(offerPc); @@ -919,11 +1009,13 @@ TEST_F(PeerConnectionFunctionalityTest, peerConnectionAnswerCloseConnection) MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); initializeSignalingClient(); - getIceServers(&configuration); EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + getIceServers(&configuration, offerPc); + getIceServers(&configuration, answerPc); + EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); closePeerConnection(answerPc); @@ -960,11 +1052,13 @@ TEST_F(PeerConnectionFunctionalityTest, DISABLED_exchangeMediaThroughTurnRandomS for (int i = 0; i < iteration; ++i) { MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_RELAY; - getIceServers(&configuration); EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + getIceServers(&configuration, offerPc); + getIceServers(&configuration, answerPc); + addTrackToPeerConnection(offerPc, &offerVideoTrack, &offerVideoTransceiver, RTC_CODEC_VP8, MEDIA_STREAM_TRACK_KIND_VIDEO); addTrackToPeerConnection(offerPc, &offerAudioTrack, &offerAudioTransceiver, RTC_CODEC_OPUS, MEDIA_STREAM_TRACK_KIND_AUDIO); addTrackToPeerConnection(answerPc, &answerVideoTrack, &answerVideoTransceiver, RTC_CODEC_VP8, MEDIA_STREAM_TRACK_KIND_VIDEO); @@ -980,7 +1074,7 @@ TEST_F(PeerConnectionFunctionalityTest, DISABLED_exchangeMediaThroughTurnRandomS MEMSET(stateChangeCount, 0x00, SIZEOF(stateChangeCount)); EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); - streamingTimeMs = (UINT64)(RAND() % (maxStreamingDurationMs - minStreamingDurationMs)) + minStreamingDurationMs; + streamingTimeMs = (UINT64) (RAND() % (maxStreamingDurationMs - minStreamingDurationMs)) + minStreamingDurationMs; DLOGI("Stop streaming after %u milliseconds.", streamingTimeMs); auto sendVideoWorker = [](PRtcRtpTransceiver pRtcRtpTransceiver, Frame frame, PSIZE_T pTerminationFlag) -> void { @@ -1022,6 +1116,212 @@ TEST_F(PeerConnectionFunctionalityTest, DISABLED_exchangeMediaThroughTurnRandomS deinitializeSignalingClient(); } +// Check that even when multiple successful candidate pairs are found, only one dtls negotiation takes place +// and that it is on the same candidate throughout the connection. +TEST_F(PeerConnectionFunctionalityTest, multipleCandidateSuccessOneDTLSCheck) +{ + RtcConfiguration configuration; + PRtcPeerConnection offerPc = NULL, answerPc = NULL; + + // This test can succeed if the highest priority candidate pair happens to be the first one + // to be nominated, even if the DTLS is broken. To be sure that this issue is fixed we want to + // run the test 10 times and have it never break once in that cycle. + for (auto i = 0; i < 10; i++) { + offerPc = NULL; + answerPc = NULL; + MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); + + EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); + EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + + // create a callback that can check values at every state of the ice agent state machine + auto masterOnIceConnectionStateChangeTest = [](UINT64 customData, UINT64 connectionState) -> void { + static PIceCandidatePair pSendingPair; + PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) customData; + // still use normal callback + onIceConnectionStateChange(customData, connectionState); + switch (connectionState) { + case ICE_AGENT_STATE_CHECK_CONNECTION: + // sleep(1); + break; + case ICE_AGENT_STATE_CONNECTED: + if (pKvsPeerConnection->pIceAgent->pDataSendingIceCandidatePair != NULL) { + pSendingPair = pKvsPeerConnection->pIceAgent->pDataSendingIceCandidatePair; + } + break; + case ICE_AGENT_STATE_READY: + if (pSendingPair != NULL) { + EXPECT_EQ(pSendingPair, pKvsPeerConnection->pIceAgent->pDataSendingIceCandidatePair); + pSendingPair = NULL; + } + break; + default: + break; + } + }; + + auto viewerOnIceConnectionStateChangeTest = [](UINT64 customData, UINT64 connectionState) -> void { + PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) customData; + PIceAgent pIceAgent = pKvsPeerConnection->pIceAgent; + PDoubleListNode pCurNode = NULL; + PIceCandidatePair pIceCandidatePair; + BOOL locked = FALSE; + // still use normal callback + onIceConnectionStateChange(customData, connectionState); + switch (connectionState) { + case ICE_AGENT_STATE_CONNECTED: + // send 'USE_CANDIDATE' for every ice candidate pair + MUTEX_LOCK(pIceAgent->lock); + locked = TRUE; + doubleListGetHeadNode(pIceAgent->iceCandidatePairs, &pCurNode); + while (pCurNode != NULL) { + pIceCandidatePair = (PIceCandidatePair) pCurNode->data; + pCurNode = pCurNode->pNext; + + pIceCandidatePair->nominated = TRUE; + } + if (locked) { + MUTEX_UNLOCK(pIceAgent->lock); + } + + break; + default: + break; + } + }; + + // overwrite normal callback + ((PKvsPeerConnection) answerPc)->pIceAgent->iceAgentCallbacks.connectionStateChangedFn = masterOnIceConnectionStateChangeTest; + ((PKvsPeerConnection) offerPc)->pIceAgent->iceAgentCallbacks.connectionStateChangedFn = viewerOnIceConnectionStateChangeTest; + + EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); + + closePeerConnection(offerPc); + closePeerConnection(answerPc); + + freePeerConnection(&offerPc); + freePeerConnection(&answerPc); + MEMSET(this->stateChangeCount, 0, SIZEOF(SIZE_T) * RTC_PEER_CONNECTION_TOTAL_STATE_COUNT); + if (::testing::Test::HasFailure()) { + break; + } + } +} + +// Check that even when multiple successful candidate pairs are found, only one dtls negotiation takes place +// and that it is on the same candidate throughout the connection. This time setting the viewer to use +// aggressive nomination +TEST_F(PeerConnectionFunctionalityTest, aggressiveNominationDTLSRaceConditionCheck) +{ + RtcConfiguration configuration; + PRtcPeerConnection offerPc = NULL, answerPc = NULL; + + // This test can succeed if the highest priority candidate pair happens to be the first one + // to be nominated, even if the DTLS is broken. To be sure that this issue is fixed we want to + // run the test 10 times and have it never break once in that cycle. + for (auto i = 0; i < 10; i++) { + offerPc = NULL; + answerPc = NULL; + MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); + + EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); + EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + + // create a callback that can check values at every state of the ice agent state machine + auto masterOnIceConnectionStateChangeTest = [](UINT64 customData, UINT64 connectionState) -> void { + static PIceCandidatePair pSendingPair; + PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) customData; + // still use normal callback + onIceConnectionStateChange(customData, connectionState); + switch (connectionState) { + case ICE_AGENT_STATE_CHECK_CONNECTION: + // sleep(1); + break; + case ICE_AGENT_STATE_CONNECTED: + if (pKvsPeerConnection->pIceAgent->pDataSendingIceCandidatePair != NULL) { + pSendingPair = pKvsPeerConnection->pIceAgent->pDataSendingIceCandidatePair; + } + break; + case ICE_AGENT_STATE_READY: + if (pSendingPair != NULL) { + EXPECT_EQ(pSendingPair, pKvsPeerConnection->pIceAgent->pDataSendingIceCandidatePair); + pSendingPair = NULL; + } + break; + default: + break; + } + }; + + auto viewerOnIceConnectionStateChangeTest = [](UINT64 customData, UINT64 connectionState) -> void { + static BOOL setUseCandidate = FALSE; + PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) customData; + PIceAgent pIceAgent = pKvsPeerConnection->pIceAgent; + PDoubleListNode pCurNode = NULL; + PIceCandidatePair pIceCandidatePair; + BOOL locked = FALSE; + // still use normal callback + onIceConnectionStateChange(customData, connectionState); + switch (connectionState) { + case ICE_AGENT_STATE_CHECK_CONNECTION: + MUTEX_LOCK(pIceAgent->lock); + locked = TRUE; + if (!setUseCandidate) { + setUseCandidate = TRUE; + appendStunFlagAttribute(pIceAgent->pBindingRequest, STUN_ATTRIBUTE_TYPE_USE_CANDIDATE); + } + doubleListGetHeadNode(pIceAgent->iceCandidatePairs, &pCurNode); + while (pCurNode != NULL) { + pIceCandidatePair = (PIceCandidatePair) pCurNode->data; + pCurNode = pCurNode->pNext; + + pIceCandidatePair->nominated = TRUE; + iceCandidatePairCheckConnection(pIceAgent->pBindingRequest, pIceAgent, pIceCandidatePair); + } + if (locked) { + MUTEX_UNLOCK(pIceAgent->lock); + } + break; + case ICE_AGENT_STATE_CONNECTED: + // send 'USE_CANDIDATE' for every ice candidate pair + setUseCandidate = FALSE; + MUTEX_LOCK(pIceAgent->lock); + locked = TRUE; + doubleListGetHeadNode(pIceAgent->iceCandidatePairs, &pCurNode); + while (pCurNode != NULL) { + pIceCandidatePair = (PIceCandidatePair) pCurNode->data; + pCurNode = pCurNode->pNext; + + pIceCandidatePair->nominated = TRUE; + } + if (locked) { + MUTEX_UNLOCK(pIceAgent->lock); + } + + break; + default: + break; + } + }; + + // overwrite normal callback + ((PKvsPeerConnection) answerPc)->pIceAgent->iceAgentCallbacks.connectionStateChangedFn = masterOnIceConnectionStateChangeTest; + ((PKvsPeerConnection) offerPc)->pIceAgent->iceAgentCallbacks.connectionStateChangedFn = viewerOnIceConnectionStateChangeTest; + + EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); + + closePeerConnection(offerPc); + closePeerConnection(answerPc); + + freePeerConnection(&offerPc); + freePeerConnection(&answerPc); + MEMSET(this->stateChangeCount, 0, SIZEOF(SIZE_T) * RTC_PEER_CONNECTION_TOTAL_STATE_COUNT); + if (::testing::Test::HasFailure()) { + break; + } + } +} + } // namespace webrtcclient } // namespace video } // namespace kinesis diff --git a/tst/SdpApiTest.cpp b/tst/SdpApiTest.cpp index a4dccd6a2d..15046430d8 100644 --- a/tst/SdpApiTest.cpp +++ b/tst/SdpApiTest.cpp @@ -1133,6 +1133,66 @@ a=ice-options:trickle }); } +TEST_F(SdpApiTest, noMediaTrickleIce) { + PRtcPeerConnection offerPc = NULL; + PRtcPeerConnection answerPc = NULL; + RtcConfiguration configurationOffer; + RtcConfiguration configurationAnswer; + RtcSessionDescriptionInit sessionDescriptionInitViewer; + RtcSessionDescriptionInit sessionDescriptionInitMaster; + + MEMSET(&configurationOffer, 0x00, SIZEOF(RtcConfiguration)); + MEMSET(&configurationAnswer, 0x00, SIZEOF(RtcConfiguration)); + + // Create peer connection + EXPECT_EQ(createPeerConnection(&configurationOffer, &offerPc), STATUS_SUCCESS); + EXPECT_EQ(createPeerConnection(&configurationAnswer, &answerPc), STATUS_SUCCESS); + + sessionDescriptionInitViewer.useTrickleIce = TRUE; + + EXPECT_EQ(STATUS_SUCCESS, createOffer(offerPc, &sessionDescriptionInitViewer)); + STRCPY(sessionDescriptionInitMaster.sdp, sessionDescriptionInitViewer.sdp); + sessionDescriptionInitMaster.type = SDP_TYPE_OFFER; + EXPECT_EQ(setRemoteDescription(answerPc, &sessionDescriptionInitMaster), STATUS_SUCCESS); + EXPECT_EQ(TRUE, canTrickleIceCandidates(answerPc).value); + + closePeerConnection(offerPc); + freePeerConnection(&offerPc); + + closePeerConnection(answerPc); + freePeerConnection(&answerPc); +} + +TEST_F(SdpApiTest, noMediaTrickleIceNegativeCase) { + PRtcPeerConnection offerPc = NULL; + PRtcPeerConnection answerPc = NULL; + RtcConfiguration configurationOffer; + RtcConfiguration configurationAnswer; + RtcSessionDescriptionInit sessionDescriptionInitViewer; + RtcSessionDescriptionInit sessionDescriptionInitMaster; + + MEMSET(&configurationOffer, 0x00, SIZEOF(RtcConfiguration)); + MEMSET(&configurationAnswer, 0x00, SIZEOF(RtcConfiguration)); + + // Create peer connection + EXPECT_EQ(createPeerConnection(&configurationOffer, &offerPc), STATUS_SUCCESS); + EXPECT_EQ(createPeerConnection(&configurationAnswer, &answerPc), STATUS_SUCCESS); + + sessionDescriptionInitViewer.useTrickleIce = FALSE; + + EXPECT_EQ(STATUS_SUCCESS, createOffer(offerPc, &sessionDescriptionInitViewer)); + STRCPY(sessionDescriptionInitMaster.sdp, sessionDescriptionInitViewer.sdp); + sessionDescriptionInitMaster.type = SDP_TYPE_OFFER; + EXPECT_EQ(setRemoteDescription(answerPc, &sessionDescriptionInitMaster), STATUS_SUCCESS); + EXPECT_EQ(FALSE, canTrickleIceCandidates(answerPc).value); + + closePeerConnection(offerPc); + freePeerConnection(&offerPc); + + closePeerConnection(answerPc); + freePeerConnection(&answerPc); +} + TEST_F(SdpApiTest, answerMlinesOrderSameAsOfferMLinesOrder) { auto offer = std::string(R"(v=0 @@ -2340,4 +2400,4 @@ INSTANTIATE_TEST_SUITE_P(SdpApiTest_SdpMatch_Safari, SdpApiTest_SdpMatch, ::test } // namespace video } // namespace kinesis } // namespace amazonaws -} // namespace com \ No newline at end of file +} // namespace com diff --git a/tst/SignalingApiFunctionalityTest.cpp b/tst/SignalingApiFunctionalityTest.cpp index 377b9b3cf5..f63162f697 100644 --- a/tst/SignalingApiFunctionalityTest.cpp +++ b/tst/SignalingApiFunctionalityTest.cpp @@ -969,14 +969,14 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedVariatio EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); - // The ICE api should have been called - EXPECT_EQ(1, getIceConfigCount); + // The ICE api shouldn't have been called + EXPECT_EQ(0, getIceConfigCount); // Ensure we can get the ICE configurations EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); @@ -984,7 +984,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedVariatio for (i = 0; i < iceCount; i++) { EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, i, &pIceConfigInfo)); } - // Make sure no APIs have been called + // Make sure APIs have been called EXPECT_EQ(1, getIceConfigCount); // Other state transacted @@ -994,7 +994,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedVariatio EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1031,7 +1031,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedVariatio EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1067,7 +1067,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedVariatio EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1104,7 +1104,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedVariatio EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1139,7 +1139,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedVariatio EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(6, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1233,14 +1233,14 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedVariations) EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); - // The ICE api should have been called - EXPECT_EQ(1, getIceConfigCount); + // The ICE api shouldn't have been called + EXPECT_EQ(0, getIceConfigCount); // Ensure we can get the ICE configurations EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); @@ -1259,9 +1259,9 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedVariations) EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1296,9 +1296,9 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedVariations) EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1332,9 +1332,9 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedVariations) EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); - EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); - EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); + EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); + EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1369,9 +1369,9 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedVariations) EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); - EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); - EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); + EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); + EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); // @@ -1404,9 +1404,9 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedVariations) EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); - EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); - EXPECT_EQ(5, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); + EXPECT_EQ(6, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(6, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); + EXPECT_EQ(6, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); // @@ -1484,12 +1484,30 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedAuthExpi EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); + //get config + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); + EXPECT_NE(0, iceCount); + EXPECT_NE((UINT64) NULL, (UINT64) pIceConfigInfo); + + // Check the states first + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_NEW]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_CREDENTIALS]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); + // Make sure the credentials expire THREAD_SLEEP(7 * HUNDREDS_OF_NANOS_IN_A_SECOND); @@ -1516,7 +1534,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedAuthExpi EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1533,7 +1551,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedAuthExpi EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1608,12 +1626,31 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedAuthExpirat EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); + //get config before credentials expire + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); + EXPECT_NE(0, iceCount); + EXPECT_NE((UINT64) NULL, (UINT64) pIceConfigInfo); + + // Check the states first + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_NEW]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_CREDENTIALS]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); + + // Make sure the credentials expire THREAD_SLEEP(7 * HUNDREDS_OF_NANOS_IN_A_SECOND); @@ -1640,9 +1677,9 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedAuthExpirat EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); // Attempt to retrieve the ice configuration should succeed @@ -1657,9 +1694,9 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedAuthExpirat EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); // We should have already been connected. This should be a No-op @@ -1733,7 +1770,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithFaul EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -1752,7 +1789,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithFaul EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -1765,7 +1802,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithFaul EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -1848,7 +1885,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedWithFaultIn EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -1863,14 +1900,13 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedWithFaultIn EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); // Trigger the ICE refresh on the next call - pSignalingClient->iceConfigCount = 0; EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); EXPECT_NE(0, iceCount); @@ -1882,7 +1918,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedWithFaultIn EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -1957,6 +1993,8 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithFaul EXPECT_TRUE(IS_VALID_SIGNALING_CLIENT_HANDLE(signalingHandle)); EXPECT_EQ(STATUS_SUCCESS,signalingClientFetchSync(signalingHandle)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); pActiveClient = pSignalingClient; // Check the states first @@ -1966,7 +2004,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithFaul EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1983,7 +2021,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithFaul EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_LT(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -1996,7 +2034,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithFaul EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_LT(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -2071,6 +2109,8 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedWithFaultIn EXPECT_EQ(STATUS_SUCCESS,signalingClientFetchSync(signalingHandle)); pActiveClient = pSignalingClient; + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); // Connect first EXPECT_EQ(STATUS_SUCCESS, signalingClientConnectSync(signalingHandle)); @@ -2082,7 +2122,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedWithFaultIn EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -2099,7 +2139,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedWithFaultIn EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_LT(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -2112,7 +2152,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedWithFaultIn EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_LT(4, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -2179,6 +2219,10 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithBadA EXPECT_TRUE(IS_VALID_SIGNALING_CLIENT_HANDLE(signalingHandle)); EXPECT_EQ(STATUS_SUCCESS,signalingClientFetchSync(signalingHandle)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); + + pActiveClient = pSignalingClient; // Check the states first @@ -2188,7 +2232,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithBadA EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -2213,7 +2257,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithBadA EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_LT(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -2231,7 +2275,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshNotConnectedWithBadA EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); EXPECT_LT(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); + EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); @@ -2310,7 +2354,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedWithBadAuth EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -2335,7 +2379,7 @@ TEST_F(SignalingApiFunctionalityTest, iceServerConfigRefreshConnectedWithBadAuth EXPECT_LT(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_LT(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_LT(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -2606,7 +2650,7 @@ TEST_F(SignalingApiFunctionalityTest, connectTimeoutEmulation) EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -2628,7 +2672,7 @@ TEST_F(SignalingApiFunctionalityTest, connectTimeoutEmulation) EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_LE(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -2724,7 +2768,7 @@ TEST_F(SignalingApiFunctionalityTest, channelInfoArnSkipDescribe) EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -2777,7 +2821,7 @@ TEST_F(SignalingApiFunctionalityTest, channelInfoArnSkipDescribe) EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -2856,7 +2900,7 @@ TEST_F(SignalingApiFunctionalityTest, deleteChannelCreatedWithArn) EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -2909,7 +2953,7 @@ TEST_F(SignalingApiFunctionalityTest, deleteChannelCreatedWithArn) EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -2990,7 +3034,7 @@ TEST_F(SignalingApiFunctionalityTest, deleteChannelCreatedAuthExpiration) EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -3008,7 +3052,7 @@ TEST_F(SignalingApiFunctionalityTest, deleteChannelCreatedAuthExpiration) EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -3028,7 +3072,7 @@ TEST_F(SignalingApiFunctionalityTest, deleteChannelCreatedAuthExpiration) EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -3161,7 +3205,7 @@ TEST_F(SignalingApiFunctionalityTest, cachingWithFaultInjection) // Account for 1 time failure EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -3183,7 +3227,7 @@ TEST_F(SignalingApiFunctionalityTest, cachingWithFaultInjection) EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_LE(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -3205,7 +3249,7 @@ TEST_F(SignalingApiFunctionalityTest, cachingWithFaultInjection) EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_LE(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_LE(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); @@ -3273,7 +3317,7 @@ TEST_F(SignalingApiFunctionalityTest, fileCachingTest) FREMOVE(DEFAULT_CACHE_FILE_PATH); for (i = 0; i < totalChannelCount; ++i) { - SPRINTF(signalingChannelName, "%s%u", TEST_SIGNALING_CHANNEL_NAME, i); + SNPRINTF(signalingChannelName, SIZEOF(signalingChannelName), "%s%u", TEST_SIGNALING_CHANNEL_NAME, i); channelInfo.pChannelName = signalingChannelName; EXPECT_EQ(STATUS_SUCCESS, createSignalingSync(&clientInfoInternal, &channelInfo, &signalingClientCallbacks, (PAwsCredentialProvider) mTestCredentialProvider, @@ -3289,7 +3333,7 @@ TEST_F(SignalingApiFunctionalityTest, fileCachingTest) getEndpointCountNoCache = getEndpointCount; for (i = 0; i < totalChannelCount; ++i) { - SPRINTF(signalingChannelName, "%s%u", TEST_SIGNALING_CHANNEL_NAME, i); + SNPRINTF(signalingChannelName, SIZEOF(signalingChannelName), "%s%u", TEST_SIGNALING_CHANNEL_NAME, i); channelInfo.pChannelName = signalingChannelName; channelInfo.pChannelArn = NULL; EXPECT_EQ(STATUS_SUCCESS, @@ -3405,11 +3449,11 @@ TEST_F(SignalingApiFunctionalityTest, fileCachingUpdateMultiChannelCache) MEMSET(testChannel, 0, MAX_CHANNEL_NAME_LEN+1); append = rand()%TEST_CHANNEL_COUNT; - SPRINTF(testWssEndpoint, "%s%d", "testWssEndpoint", append); - SPRINTF(testHttpsEndpoint, "%s%d", "testHttpsEndpoint", append); - SPRINTF(testRegion, "%s%d", "testRegion", append); - SPRINTF(testChannelArn, "%s%d", "testChannelArn", append); - SPRINTF(testChannel, "%s%d", "testChannel", append); + SNPRINTF(testWssEndpoint, SIZEOF(testWssEndpoint), "%s%d", "testWssEndpoint", append); + SNPRINTF(testHttpsEndpoint, SIZEOF(testHttpsEndpoint),"%s%d", "testHttpsEndpoint", append); + SNPRINTF(testRegion, SIZEOF(testRegion),"%s%d", "testRegion", append); + SNPRINTF(testChannelArn, SIZEOF(testChannelArn),"%s%d", "testChannelArn", append); + SNPRINTF(testChannel, SIZEOF(testChannel),"%s%d", "testChannel", append); STRCPY(testEntry.wssEndpoint, testWssEndpoint); STRCPY(testEntry.httpsEndpoint, testHttpsEndpoint); @@ -3472,11 +3516,11 @@ TEST_F(SignalingApiFunctionalityTest, fileCachingUpdateFullMultiChannelCache) MEMSET(testChannel, 0, MAX_CHANNEL_NAME_LEN+1); append = i; - SPRINTF(testWssEndpoint, "%s%d", "testWssEndpoint", append); - SPRINTF(testHttpsEndpoint, "%s%d", "testHttpsEndpoint", append); - SPRINTF(testRegion, "%s%d", "testRegion", append); - SPRINTF(testChannelArn, "%s%d", "testChannelArn", append); - SPRINTF(testChannel, "%s%d", "testChannel", append); + SNPRINTF(testWssEndpoint, SIZEOF(testWssEndpoint), "%s%d", "testWssEndpoint", append); + SNPRINTF(testHttpsEndpoint, SIZEOF(testHttpsEndpoint), "%s%d", "testHttpsEndpoint", append); + SNPRINTF(testRegion, SIZEOF(testRegion), "%s%d", "testRegion", append); + SNPRINTF(testChannelArn, SIZEOF(testChannelArn), "%s%d", "testChannelArn", append); + SNPRINTF(testChannel, SIZEOF(testChannel), "%s%d", "testChannel", append); STRCPY(testEntry.wssEndpoint, testWssEndpoint); STRCPY(testEntry.httpsEndpoint, testHttpsEndpoint); @@ -3561,12 +3605,17 @@ TEST_F(SignalingApiFunctionalityTest, receivingIceConfigOffer) EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); + EXPECT_NE(0, iceCount); + EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + // Ensure the ICE is not refreshed as we already have a current non-expired set EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); @@ -3750,12 +3799,17 @@ TEST_F(SignalingApiFunctionalityTest, receivingIceConfigOffer_SlowClockSkew) EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); + EXPECT_NE(0, iceCount); + EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + // Ensure the ICE is not refreshed as we already have a current non-expired set EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); @@ -3940,13 +3994,12 @@ TEST_F(SignalingApiFunctionalityTest, receivingIceConfigOffer_FastClockSkew) EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_DISCONNECTED]); - // Ensure the ICE is not refreshed as we already have a current non-expired set EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(signalingHandle, &iceCount)); EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(signalingHandle, 0, &pIceConfigInfo)); EXPECT_NE(0, iceCount); @@ -4132,7 +4185,7 @@ TEST_F(SignalingApiFunctionalityTest, receivingIceConfigOffer_FastClockSkew_Veri EXPECT_EQ(3, signalingStatesCounts[SIGNALING_CLIENT_STATE_DESCRIBE]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_CREATE]); EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ENDPOINT]); - EXPECT_EQ(2, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); + EXPECT_EQ(0, signalingStatesCounts[SIGNALING_CLIENT_STATE_GET_ICE_CONFIG]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_READY]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTING]); EXPECT_EQ(1, signalingStatesCounts[SIGNALING_CLIENT_STATE_CONNECTED]); diff --git a/tst/SignalingApiTest.cpp b/tst/SignalingApiTest.cpp index ec937f999a..c0bbde5111 100644 --- a/tst/SignalingApiTest.cpp +++ b/tst/SignalingApiTest.cpp @@ -41,6 +41,8 @@ TEST_F(SignalingApiTest, createValidateChannelInfo) // Test default agent postfix EXPECT_PRED_FORMAT2(testing::IsSubstring, agentString, rChannelInfo->pUserAgent); freeChannelInfo(&rChannelInfo); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, testChannelArnsValid) @@ -92,7 +94,6 @@ TEST_F(SignalingApiTest, testChannelArnsValid) freeChannelInfo(&pChannelInfo); } - TEST_F(SignalingApiTest, testChannelArnsInValid) { PChannelInfo pChannelInfo; @@ -238,6 +239,8 @@ TEST_F(SignalingApiTest, signalingSendMessageSync) EXPECT_EQ(expectedStatus, signalingClientSendMessageSync(mSignalingClientHandle, &signalingMessage)); deinitializeSignalingClient(); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingSendMessageSyncFileCredsProvider) @@ -293,6 +296,8 @@ TEST_F(SignalingApiTest, signalingSendMessageSyncFileCredsProvider) deinitializeSignalingClient(); EXPECT_EQ(STATUS_SUCCESS, freeFileCredentialProvider(&pAwsCredentialProvider)); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingClientConnectSync) @@ -309,6 +314,8 @@ TEST_F(SignalingApiTest, signalingClientConnectSync) EXPECT_EQ(expectedStatus, signalingClientConnectSync(mSignalingClientHandle)); deinitializeSignalingClient(); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingClientDeleteSync) @@ -339,6 +346,8 @@ TEST_F(SignalingApiTest, signalingClientDeleteSync) EXPECT_EQ(expectedStatus, signalingClientSendMessageSync(mSignalingClientHandle, &signalingMessage)); deinitializeSignalingClient(); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingClientGetIceConfigInfoCount) @@ -359,6 +368,8 @@ TEST_F(SignalingApiTest, signalingClientGetIceConfigInfoCount) } deinitializeSignalingClient(); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingClientGetIceConfigInfo) @@ -395,6 +406,8 @@ TEST_F(SignalingApiTest, signalingClientGetIceConfigInfo) } deinitializeSignalingClient(); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingClientGetCurrentState) @@ -414,6 +427,8 @@ TEST_F(SignalingApiTest, signalingClientGetCurrentState) EXPECT_EQ(expectedState, state); deinitializeSignalingClient(); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingClientGetStateString) @@ -425,11 +440,15 @@ TEST_F(SignalingApiTest, signalingClientGetStateString) EXPECT_EQ(STATUS_SUCCESS, signalingClientGetStateString((SIGNALING_CLIENT_STATE) i, &pStateStr)); DLOGV("Iterating states \"%s\"", pStateStr); } + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingClientDisconnectSync) { EXPECT_NE(STATUS_SUCCESS, signalingClientDisconnectSync(INVALID_SIGNALING_CLIENT_HANDLE_VALUE)); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingClientGetMetrics) @@ -454,11 +473,11 @@ TEST_F(SignalingApiTest, signalingClientGetMetrics) EXPECT_EQ(0, metrics.signalingClientStats.numberOfMessagesReceived); EXPECT_EQ(0, metrics.signalingClientStats.numberOfErrors); EXPECT_EQ(0, metrics.signalingClientStats.numberOfRuntimeErrors); - EXPECT_EQ(1, metrics.signalingClientStats.iceRefreshCount); + EXPECT_EQ(0, metrics.signalingClientStats.iceRefreshCount); EXPECT_NE(0, metrics.signalingClientStats.signalingClientUptime); EXPECT_EQ(0, metrics.signalingClientStats.connectionDuration); EXPECT_NE(0, metrics.signalingClientStats.cpApiCallLatency); - EXPECT_NE(0, metrics.signalingClientStats.dpApiCallLatency); + EXPECT_EQ(0, metrics.signalingClientStats.dpApiCallLatency); // Connect and get metrics EXPECT_EQ(STATUS_SUCCESS, signalingClientConnectSync(mSignalingClientHandle)); @@ -472,11 +491,11 @@ TEST_F(SignalingApiTest, signalingClientGetMetrics) EXPECT_EQ(0, metrics.signalingClientStats.numberOfMessagesReceived); EXPECT_EQ(0, metrics.signalingClientStats.numberOfErrors); EXPECT_EQ(0, metrics.signalingClientStats.numberOfRuntimeErrors); - EXPECT_EQ(1, metrics.signalingClientStats.iceRefreshCount); + EXPECT_EQ(0, metrics.signalingClientStats.iceRefreshCount); EXPECT_NE(0, metrics.signalingClientStats.signalingClientUptime); EXPECT_NE(0, metrics.signalingClientStats.connectionDuration); EXPECT_NE(0, metrics.signalingClientStats.cpApiCallLatency); - EXPECT_NE(0, metrics.signalingClientStats.dpApiCallLatency); + EXPECT_EQ(0, metrics.signalingClientStats.dpApiCallLatency); // Send a message and get metrics signalingMessage.version = SIGNALING_MESSAGE_CURRENT_VERSION; @@ -494,11 +513,11 @@ TEST_F(SignalingApiTest, signalingClientGetMetrics) EXPECT_EQ(0, metrics.signalingClientStats.numberOfMessagesReceived); EXPECT_EQ(0, metrics.signalingClientStats.numberOfErrors); EXPECT_EQ(0, metrics.signalingClientStats.numberOfRuntimeErrors); - EXPECT_EQ(1, metrics.signalingClientStats.iceRefreshCount); + EXPECT_EQ(0, metrics.signalingClientStats.iceRefreshCount); EXPECT_NE(0, metrics.signalingClientStats.signalingClientUptime); EXPECT_NE(0, metrics.signalingClientStats.connectionDuration); EXPECT_NE(0, metrics.signalingClientStats.cpApiCallLatency); - EXPECT_NE(0, metrics.signalingClientStats.dpApiCallLatency); + EXPECT_EQ(0, metrics.signalingClientStats.dpApiCallLatency); // Make a couple of bad API invocations EXPECT_NE(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(mSignalingClientHandle, NULL)); @@ -507,6 +526,22 @@ TEST_F(SignalingApiTest, signalingClientGetMetrics) EXPECT_NE(STATUS_SUCCESS, signalingClientGetMetrics(mSignalingClientHandle, NULL)); EXPECT_NE(STATUS_SUCCESS, signalingClientSendMessageSync(mSignalingClientHandle, NULL)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetMetrics(mSignalingClientHandle, &metrics)); + EXPECT_EQ(0, metrics.signalingClientStats.numberOfReconnects); + EXPECT_EQ(1, metrics.signalingClientStats.numberOfMessagesSent); + EXPECT_EQ(0, metrics.signalingClientStats.numberOfMessagesReceived); + EXPECT_EQ(5, metrics.signalingClientStats.numberOfErrors); + EXPECT_EQ(0, metrics.signalingClientStats.numberOfRuntimeErrors); + EXPECT_EQ(0, metrics.signalingClientStats.iceRefreshCount); + EXPECT_NE(0, metrics.signalingClientStats.signalingClientUptime); + EXPECT_NE(0, metrics.signalingClientStats.connectionDuration); + EXPECT_NE(0, metrics.signalingClientStats.cpApiCallLatency); + EXPECT_EQ(0, metrics.signalingClientStats.dpApiCallLatency); + + UINT32 iceCount = 0; + //Get ice config + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(mSignalingClientHandle, &iceCount)); + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetMetrics(mSignalingClientHandle, &metrics)); EXPECT_EQ(0, metrics.signalingClientStats.numberOfReconnects); EXPECT_EQ(1, metrics.signalingClientStats.numberOfMessagesSent); @@ -520,6 +555,8 @@ TEST_F(SignalingApiTest, signalingClientGetMetrics) EXPECT_NE(0, metrics.signalingClientStats.dpApiCallLatency); deinitializeSignalingClient(); + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } TEST_F(SignalingApiTest, signalingClientCreateWithClientInfoVariations) @@ -653,6 +690,8 @@ TEST_F(SignalingApiTest, signalingClientCreateWithClientInfoVariations) deinitializeSignalingClient(); mClientInfo.cacheFilePath = NULL; + //wait for threads of threadpool to close + THREAD_SLEEP(100 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); } } // namespace webrtcclient diff --git a/tst/TurnConnectionFunctionalityTest.cpp b/tst/TurnConnectionFunctionalityTest.cpp index c2de24c876..da2914cd63 100644 --- a/tst/TurnConnectionFunctionalityTest.cpp +++ b/tst/TurnConnectionFunctionalityTest.cpp @@ -34,6 +34,7 @@ class TurnConnectionFunctionalityTest : public WebRtcClientTestBase { for (uriCount = 0, i = 0; i < iceConfigCount; i++) { EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(mSignalingClientHandle, i, &pIceConfigInfo)); for (j = 0; j < pIceConfigInfo->uriCount; j++) { + iceServers[uriCount].setIpFn = NULL; EXPECT_EQ(STATUS_SUCCESS, parseIceServer(&iceServers[uriCount++], pIceConfigInfo->uris[j], pIceConfigInfo->userName, pIceConfigInfo->password)); } diff --git a/tst/WebRTCClientTestFixture.cpp b/tst/WebRTCClientTestFixture.cpp index d041db728e..7cfdc91eca 100644 --- a/tst/WebRTCClientTestFixture.cpp +++ b/tst/WebRTCClientTestFixture.cpp @@ -16,31 +16,62 @@ UINT64 gTotalWebRtcClientMemoryUsage = 0; // MUTEX gTotalWebRtcClientMemoryMutex; -STATUS createRtpPacketWithSeqNum(UINT16 seqNum, PRtpPacket *ppRtpPacket) { +STATUS createRtpPacketWithSeqNum(UINT16 seqNum, PRtpPacket* ppRtpPacket) +{ STATUS retStatus = STATUS_SUCCESS; BYTE payload[10]; PRtpPacket pRtpPacket = NULL; - CHK_STATUS(createRtpPacket(2, FALSE, FALSE, 0, FALSE, - 96, seqNum, 100, 0x1234ABCD, NULL, 0, 0, NULL, payload, 10, &pRtpPacket)); + CHK_STATUS(createRtpPacket(2, FALSE, FALSE, 0, FALSE, 96, seqNum, 100, 0x1234ABCD, NULL, 0, 0, NULL, payload, 10, &pRtpPacket)); *ppRtpPacket = pRtpPacket; CHK_STATUS(createBytesFromRtpPacket(pRtpPacket, NULL, &pRtpPacket->rawPacketLength)); CHK(NULL != (pRtpPacket->pRawPacket = (PBYTE) MEMALLOC(pRtpPacket->rawPacketLength)), STATUS_NOT_ENOUGH_MEMORY); CHK_STATUS(createBytesFromRtpPacket(pRtpPacket, pRtpPacket->pRawPacket, &pRtpPacket->rawPacketLength)); - CleanUp: +CleanUp: return retStatus; } -WebRtcClientTestBase::WebRtcClientTestBase() : - mSignalingClientHandle(INVALID_SIGNALING_CLIENT_HANDLE_VALUE), - mAccessKey(NULL), - mSecretKey(NULL), - mSessionToken(NULL), - mRegion(NULL), - mCaCertPath(NULL), - mAccessKeyIdSet(FALSE) +PVOID asyncGetIceConfigInfo(PVOID args) +{ + STATUS retStatus = STATUS_SUCCESS; + AsyncGetIceStruct* data = (AsyncGetIceStruct*) args; + PIceConfigInfo pIceConfigInfo = NULL; + UINT32 uriCount = 0; + UINT32 i = 0, maxTurnServer = 1; + + if (data != NULL) { + /* signalingClientGetIceConfigInfoCount can return more than one turn server. Use only one to optimize + * candidate gathering latency. But user can also choose to use more than 1 turn server. */ + for (uriCount = 0, i = 0; i < maxTurnServer; i++) { + /* + * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port?transport=udp" then ICE will try TURN over UDP + * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port?transport=tcp" then ICE will try TURN over TCP/TLS + * if configuration.iceServers[uriCount + 1].urls is "turns:ip:port?transport=udp", it's currently ignored because sdk dont do TURN + * over DTLS yet. if configuration.iceServers[uriCount + 1].urls is "turns:ip:port?transport=tcp" then ICE will try TURN over TCP/TLS + * if configuration.iceServers[uriCount + 1].urls is "turn:ip:port" then ICE will try both TURN over UDP and TCP/TLS + * + * It's recommended to not pass too many TURN iceServers to configuration because it will slow down ice gathering in non-trickle mode. + */ + CHK_STATUS(signalingClientGetIceConfigInfo(data->signalingClientHandle, i, &pIceConfigInfo)); + CHECK(uriCount < MAX_ICE_SERVERS_COUNT); + uriCount += pIceConfigInfo->uriCount; + CHK_STATUS(addConfigToServerList(&(data->pAnswer), pIceConfigInfo)); + CHK_STATUS(addConfigToServerList(&(data->pOffer), pIceConfigInfo)); + } + } + *(data->pUriCount) += uriCount; + +CleanUp: + SAFE_MEMFREE(data); + CHK_LOG_ERR(retStatus); + return NULL; +} + +WebRtcClientTestBase::WebRtcClientTestBase() + : mSignalingClientHandle(INVALID_SIGNALING_CLIENT_HANDLE_VALUE), mAccessKey(NULL), mSecretKey(NULL), mSessionToken(NULL), mRegion(NULL), + mCaCertPath(NULL), mAccessKeyIdSet(FALSE) { // Initialize the endianness of the library initializeEndianness(); @@ -57,6 +88,7 @@ void WebRtcClientTestBase::SetUp() mDroppedFrameIndex = 0; mExpectedFrameCount = 0; mExpectedDroppedFrameCount = 0; + noNewThreads = FALSE; SET_INSTRUMENTED_ALLOCATORS(); @@ -69,7 +101,9 @@ void WebRtcClientTestBase::SetUp() SET_LOGGER_LOG_LEVEL(mLogLevel); - initKvsWebRtc(); + if (STATUS_SUCCESS != initKvsWebRtc()) { + DLOGE("Test initKvsWebRtc FAILED!!!!"); + } if (NULL != (mAccessKey = getenv(ACCESS_KEY_ENV_VAR))) { mAccessKeyIdSet = TRUE; @@ -113,6 +147,8 @@ void WebRtcClientTestBase::TearDown() DLOGI("\nTearing down test: %s\n", GetTestName()); deinitKvsWebRtc(); + // Need this sleep for threads in threadpool to close + THREAD_SLEEP(400 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND); freeStaticCredentialProvider(&mTestCredentialProvider); @@ -183,22 +219,121 @@ bool WebRtcClientTestBase::connectTwoPeers(PRtcPeerConnection offerPc, PRtcPeerC PCHAR pAnswerCertFingerprint) { RtcSessionDescriptionInit sdp; + PeerContainer offer; + PeerContainer answer; + this->noNewThreads = FALSE; + + auto onICECandidateHdlr = [](UINT64 customData, PCHAR candidateStr) -> void { + PPeerContainer container = (PPeerContainer)customData; + if (candidateStr != NULL) { + container->client->lock.lock(); + if(!container->client->noNewThreads) { + container->client->threads.push_back(std::thread( + [container](std::string candidate) { + RtcIceCandidateInit iceCandidate; + EXPECT_EQ(STATUS_SUCCESS, deserializeRtcIceCandidateInit((PCHAR) candidate.c_str(), STRLEN(candidate.c_str()), &iceCandidate)); + EXPECT_EQ(STATUS_SUCCESS, addIceCandidate((PRtcPeerConnection) container->pc, iceCandidate.candidate)); + }, + std::string(candidateStr))); + } + container->client->lock.unlock(); + } + + }; + + auto onICECandidateHdlrDone = [](UINT64 customData, PCHAR candidateStr) -> void { + UNUSED_PARAM(customData); + UNUSED_PARAM(candidateStr); + }; + + offer.pc = offerPc; + offer.client = this; + answer.pc = answerPc; + answer.client = this; + + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) &answer, onICECandidateHdlr)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) &offer, onICECandidateHdlr)); + + auto onICEConnectionStateChangeHdlr = [](UINT64 customData, RTC_PEER_CONNECTION_STATE newState) -> void { + ATOMIC_INCREMENT((PSIZE_T) customData + newState); + }; + + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnConnectionStateChange(offerPc, (UINT64) this->stateChangeCount, onICEConnectionStateChangeHdlr)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnConnectionStateChange(answerPc, (UINT64) this->stateChangeCount, onICEConnectionStateChangeHdlr)); + + EXPECT_EQ(STATUS_SUCCESS, createOffer(offerPc, &sdp)); + EXPECT_EQ(STATUS_SUCCESS, setLocalDescription(offerPc, &sdp)); + EXPECT_EQ(STATUS_SUCCESS, setRemoteDescription(answerPc, &sdp)); + + // Validate the cert fingerprint if we are asked to do so + if (pOfferCertFingerprint != NULL) { + EXPECT_NE((PCHAR) NULL, STRSTR(sdp.sdp, pOfferCertFingerprint)); + } + + EXPECT_EQ(STATUS_SUCCESS, createAnswer(answerPc, &sdp)); + EXPECT_EQ(STATUS_SUCCESS, setLocalDescription(answerPc, &sdp)); + EXPECT_EQ(STATUS_SUCCESS, setRemoteDescription(offerPc, &sdp)); + + if (pAnswerCertFingerprint != NULL) { + EXPECT_NE((PCHAR) NULL, STRSTR(sdp.sdp, pAnswerCertFingerprint)); + } + + for (auto i = 0; i <= 100 && ATOMIC_LOAD(&this->stateChangeCount[RTC_PEER_CONNECTION_STATE_CONNECTED]) != 2; i++) { + THREAD_SLEEP(HUNDREDS_OF_NANOS_IN_A_SECOND); + } + + this->lock.lock(); + //join all threads before leaving + for (auto& th : this->threads) th.join(); + + this->threads.clear(); + this->noNewThreads = TRUE; + this->lock.unlock(); + + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) 0, onICECandidateHdlrDone)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) 0, onICECandidateHdlrDone)); + + + return ATOMIC_LOAD(&this->stateChangeCount[RTC_PEER_CONNECTION_STATE_CONNECTED]) == 2; +} + +bool WebRtcClientTestBase::connectTwoPeersAsyncIce(PRtcPeerConnection offerPc, PRtcPeerConnection answerPc, PCHAR pOfferCertFingerprint, + PCHAR pAnswerCertFingerprint) +{ + RtcSessionDescriptionInit sdp; + PeerContainer offer; + PeerContainer answer; + this->noNewThreads = FALSE; auto onICECandidateHdlr = [](UINT64 customData, PCHAR candidateStr) -> void { + PPeerContainer container = (PPeerContainer)customData; if (candidateStr != NULL) { - std::thread( - [customData](std::string candidate) { - RtcIceCandidateInit iceCandidate; - EXPECT_EQ(STATUS_SUCCESS, deserializeRtcIceCandidateInit((PCHAR) candidate.c_str(), STRLEN(candidate.c_str()), &iceCandidate)); - EXPECT_EQ(STATUS_SUCCESS, addIceCandidate((PRtcPeerConnection) customData, iceCandidate.candidate)); - }, - std::string(candidateStr)) - .detach(); + container->client->lock.lock(); + if(!container->client->noNewThreads) { + container->client->threads.push_back(std::thread( + [container](std::string candidate) { + RtcIceCandidateInit iceCandidate; + EXPECT_EQ(STATUS_SUCCESS, deserializeRtcIceCandidateInit((PCHAR) candidate.c_str(), STRLEN(candidate.c_str()), &iceCandidate)); + EXPECT_EQ(STATUS_SUCCESS, addIceCandidate((PRtcPeerConnection) container->pc, iceCandidate.candidate)); + }, + std::string(candidateStr))); + } + container->client->lock.unlock(); } }; - EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) answerPc, onICECandidateHdlr)); - EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) offerPc, onICECandidateHdlr)); + auto onICECandidateHdlrDone = [](UINT64 customData, PCHAR candidateStr) -> void { + UNUSED_PARAM(customData); + UNUSED_PARAM(candidateStr); + }; + + offer.pc = offerPc; + offer.client = this; + answer.pc = answerPc; + answer.client = this; + + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) &answer, onICECandidateHdlr)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) &offer, onICECandidateHdlr)); auto onICEConnectionStateChangeHdlr = [](UINT64 customData, RTC_PEER_CONNECTION_STATE newState) -> void { ATOMIC_INCREMENT((PSIZE_T) customData + newState); @@ -220,6 +355,8 @@ bool WebRtcClientTestBase::connectTwoPeers(PRtcPeerConnection offerPc, PRtcPeerC EXPECT_EQ(STATUS_SUCCESS, setLocalDescription(answerPc, &sdp)); EXPECT_EQ(STATUS_SUCCESS, setRemoteDescription(offerPc, &sdp)); + asyncGetIceConfig(offerPc, answerPc); + if (pAnswerCertFingerprint != NULL) { EXPECT_NE((PCHAR) NULL, STRSTR(sdp.sdp, pAnswerCertFingerprint)); } @@ -228,6 +365,17 @@ bool WebRtcClientTestBase::connectTwoPeers(PRtcPeerConnection offerPc, PRtcPeerC THREAD_SLEEP(HUNDREDS_OF_NANOS_IN_A_SECOND); } + this->lock.lock(); + //join all threads before leaving + for (auto& th : this->threads) th.join(); + + this->threads.clear(); + this->noNewThreads = TRUE; + this->lock.unlock(); + + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(offerPc, (UINT64) 0, onICECandidateHdlrDone)); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnIceCandidate(answerPc, (UINT64) 0, onICECandidateHdlrDone)); + return ATOMIC_LOAD(&this->stateChangeCount[RTC_PEER_CONNECTION_STATE_CONNECTED]) == 2; } @@ -247,16 +395,42 @@ void WebRtcClientTestBase::addTrackToPeerConnection(PRtcPeerConnection pRtcPeerC EXPECT_EQ(STATUS_SUCCESS, addTransceiver(pRtcPeerConnection, track, NULL, transceiver)); } -void WebRtcClientTestBase::getIceServers(PRtcConfiguration pRtcConfiguration) +void WebRtcClientTestBase::getIceServers(PRtcConfiguration pRtcConfiguration, PIceAgent pIceAgent) +{ + UINT32 i, j, iceConfigCount = 0, uriCount; + PIceConfigInfo pIceConfigInfo; + + // Assume signaling client is already created + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(mSignalingClientHandle, &iceConfigCount)); + + // Set the STUN server + SNPRINTF(pRtcConfiguration->iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, + TEST_DEFAULT_STUN_URL_POSTFIX); + + for (uriCount = 0, i = 0; i < iceConfigCount; i++) { + EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(mSignalingClientHandle, i, &pIceConfigInfo)); + for (j = 0; j < pIceConfigInfo->uriCount; j++) { + STRNCPY(pRtcConfiguration->iceServers[uriCount + 1].urls, pIceConfigInfo->uris[j], MAX_ICE_CONFIG_URI_LEN); + STRNCPY(pRtcConfiguration->iceServers[uriCount + 1].credential, pIceConfigInfo->password, MAX_ICE_CONFIG_CREDENTIAL_LEN); + STRNCPY(pRtcConfiguration->iceServers[uriCount + 1].username, pIceConfigInfo->userName, MAX_ICE_CONFIG_USER_NAME_LEN); + + uriCount++; + } + EXPECT_EQ(STATUS_SUCCESS, iceAgentAddConfig(pIceAgent, pIceConfigInfo)); + } +} + +void WebRtcClientTestBase::getIceServers(PRtcConfiguration pRtcConfiguration, PRtcPeerConnection pRtcPeerConnection) { - UINT32 i, j, iceConfigCount, uriCount; + UINT32 i, j, iceConfigCount = 0, uriCount; PIceConfigInfo pIceConfigInfo; // Assume signaling client is already created EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfoCount(mSignalingClientHandle, &iceConfigCount)); // Set the STUN server - SNPRINTF(pRtcConfiguration->iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, TEST_DEFAULT_STUN_URL_POSTFIX); + SNPRINTF(pRtcConfiguration->iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, + TEST_DEFAULT_STUN_URL_POSTFIX); for (uriCount = 0, i = 0; i < iceConfigCount; i++) { EXPECT_EQ(STATUS_SUCCESS, signalingClientGetIceConfigInfo(mSignalingClientHandle, i, &pIceConfigInfo)); @@ -267,6 +441,7 @@ void WebRtcClientTestBase::getIceServers(PRtcConfiguration pRtcConfiguration) uriCount++; } + EXPECT_EQ(STATUS_SUCCESS, addConfigToServerList(&pRtcPeerConnection, pIceConfigInfo)); } } diff --git a/tst/WebRTCClientTestFixture.h b/tst/WebRTCClientTestFixture.h index cd1a9d93f0..5e372b501a 100644 --- a/tst/WebRTCClientTestFixture.h +++ b/tst/WebRTCClientTestFixture.h @@ -24,6 +24,13 @@ #define MAX_TEST_AWAIT_DURATION (2 * HUNDREDS_OF_NANOS_IN_A_SECOND) #define TEST_CACHE_FILE_PATH (PCHAR) "./.TestSignalingCache_v0" +typedef struct { + SIGNALING_CLIENT_HANDLE signalingClientHandle; + PRtcPeerConnection pOffer; + PRtcPeerConnection pAnswer; + PUINT32 pUriCount; +} AsyncGetIceStruct; + namespace com { namespace amazonaws { namespace kinesis { @@ -39,6 +46,8 @@ typedef struct { STATUS createRtpPacketWithSeqNum(UINT16 seqNum, PRtpPacket* ppRtpPacket); +PVOID asyncGetIceConfigInfo(PVOID args); + class WebRtcClientTestBase : public ::testing::Test { public: PUINT32 mExpectedFrameSizeArr; @@ -48,7 +57,11 @@ class WebRtcClientTestBase : public ::testing::Test { UINT32 mExpectedDroppedFrameCount; PRtpPacket* mPRtpPackets; UINT32 mRtpPacketCount; + UINT32 mUriCount = 0; SIGNALING_CLIENT_HANDLE mSignalingClientHandle; + std::vector threads; + std::mutex lock; + BOOL noNewThreads = FALSE; WebRtcClientTestBase(); @@ -111,6 +124,7 @@ class WebRtcClientTestBase : public ::testing::Test { mChannelInfo.reconnect = TRUE; mChannelInfo.pCertPath = mCaCertPath; mChannelInfo.messageTtl = TEST_SIGNALING_MESSAGE_TTL; + if ((mChannelInfo.pRegion = getenv(DEFAULT_REGION_ENV_VAR)) == NULL) { mChannelInfo.pRegion = (PCHAR) TEST_DEFAULT_REGION; } @@ -149,6 +163,18 @@ class WebRtcClientTestBase : public ::testing::Test { return STATUS_SUCCESS; } + STATUS asyncGetIceConfig(PRtcPeerConnection pOffer, PRtcPeerConnection pAnswer) + { + AsyncGetIceStruct* pAsyncData = NULL; + pAsyncData = (AsyncGetIceStruct*) MEMCALLOC(1, SIZEOF(AsyncGetIceStruct)); + pAsyncData->signalingClientHandle = mSignalingClientHandle; + pAsyncData->pAnswer = pAnswer; + pAsyncData->pOffer = pOffer; + pAsyncData->pUriCount = &(this->mUriCount); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionAsync(asyncGetIceConfigInfo, (PVOID) pAsyncData)); + return STATUS_SUCCESS; + } + static STATUS testFrameReadyFunc(UINT64 customData, UINT16 startIndex, UINT16 endIndex, UINT32 frameSize) { WebRtcClientTestBase* base = (WebRtcClientTestBase*) customData; @@ -270,9 +296,13 @@ class WebRtcClientTestBase : public ::testing::Test { bool connectTwoPeers(PRtcPeerConnection offerPc, PRtcPeerConnection answerPc, PCHAR pOfferCertFingerprint = NULL, PCHAR pAnswerCertFingerprint = NULL); + bool connectTwoPeersAsyncIce(PRtcPeerConnection offerPc, PRtcPeerConnection answerPc, PCHAR pOfferCertFingerprint = NULL, + PCHAR pAnswerCertFingerprint = NULL); void addTrackToPeerConnection(PRtcPeerConnection pRtcPeerConnection, PRtcMediaStreamTrack track, PRtcRtpTransceiver* transceiver, RTC_CODEC codec, MEDIA_STREAM_TRACK_KIND kind); - void getIceServers(PRtcConfiguration pRtcConfiguration); + void getIceServers(PRtcConfiguration pRtcConfiguration, PRtcPeerConnection pRtcPeerConnection); + + void getIceServers(PRtcConfiguration pRtcConfiguration, PIceAgent pIceAgent); protected: virtual void SetUp(); @@ -312,6 +342,11 @@ class WebRtcClientTestBase : public ::testing::Test { Tag mTags[3]; }; +typedef struct { + PRtcPeerConnection pc; + WebRtcClientTestBase* client; +} PeerContainer, *PPeerContainer; + } // namespace webrtcclient } // namespace video } // namespace kinesis diff --git a/tst/suppressions/TSAN.supp b/tst/suppressions/TSAN.supp index 0c1179bfbd..636d016122 100644 --- a/tst/suppressions/TSAN.supp +++ b/tst/suppressions/TSAN.supp @@ -176,4 +176,4 @@ deadlock:com::amazonaws::kinesis::video::webrtcclient::DtlsFunctionalityTest::cr # instead of flagging each individual instance of the test. deadlock:lwsListenerHandler deadlock:connectSignalingChannelLws -race:lwsListenerHandler \ No newline at end of file +race:lwsListenerHandler