mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
166 Commits
Author | SHA1 | Date | |
---|---|---|---|
c93f44f132 | |||
517b69043f | |||
b04db3a744 | |||
36090a24e4 | |||
6b75b3e227 | |||
afed83f5f0 | |||
75c42592bf | |||
b35bfbeb0a | |||
e481e896cb | |||
202467928a | |||
71650ce163 | |||
706a8b7160 | |||
6f419a32ea | |||
f5ff042d62 | |||
822b2e6558 | |||
5b251af1d7 | |||
5b56291b67 | |||
777f5a8dfe | |||
971e6e8b91 | |||
a3fb52c173 | |||
db00253c18 | |||
dd2967b0e1 | |||
cc4e215067 | |||
bbeed01eb0 | |||
aecc2b8fda | |||
d60e18d963 | |||
e41019a1f0 | |||
5825e44fc8 | |||
dc9a8114bc | |||
3e827f9798 | |||
0a6b263bc3 | |||
e02c30027b | |||
c4380ebcc4 | |||
add0649335 | |||
cd28340de3 | |||
c675aedb83 | |||
e32d139056 | |||
a790161168 | |||
2697ef0d76 | |||
5044aedbec | |||
44c90c1cb4 | |||
be79c68540 | |||
226a927df1 | |||
4e1b9bb3c2 | |||
8bc016cc08 | |||
5afbe10d01 | |||
8df07ca68d | |||
884bd2316e | |||
dadecce709 | |||
103935bdd5 | |||
62e6954949 | |||
3ac2d155cc | |||
6d8788c2a1 | |||
603dd01b87 | |||
8091508428 | |||
b38f63f077 | |||
d87539937e | |||
eb09cadded | |||
79e0c62321 | |||
ef38777129 | |||
313f081061 | |||
6108b05e0d | |||
f68601b45f | |||
b94f2ab339 | |||
9c79c8516b | |||
ad03549f8c | |||
5cc8eb1ce6 | |||
d19ff754b2 | |||
fb0f903e2e | |||
6628297580 | |||
ccc05b9999 | |||
fb2f480f92 | |||
cca0ac859a | |||
9fbc7c6ea8 | |||
83d65c805a | |||
0a5cef331d | |||
7c1714f83c | |||
f3024d0552 | |||
bcd1972270 | |||
57501b1739 | |||
cfe8a0e9c6 | |||
10061b3d4b | |||
52959ee700 | |||
0e86d6e3f1 | |||
62675e4c21 | |||
eafe86e4c0 | |||
a2b2126930 | |||
b9a663de75 | |||
f5faeba7b0 | |||
98f02c5195 | |||
d8ab5b4820 | |||
b569e78c02 | |||
4eac16e053 | |||
bb13b4bd37 | |||
aabb435fc7 | |||
3d7764c1e9 | |||
37687c3cd6 | |||
63f10303e0 | |||
d656d739f3 | |||
dbc706b69d | |||
1985088f4f | |||
ccbaa8beda | |||
642d304af9 | |||
df2102497d | |||
ad676815bd | |||
4bd40799fd | |||
4d1d1fa6fe | |||
50f61b19aa | |||
b7dbe7cdd9 | |||
7bfd731ed3 | |||
4da8b8c9a3 | |||
0382067d92 | |||
325230404a | |||
2a721015f8 | |||
5752b17a6f | |||
878d15b746 | |||
26d240e3ba | |||
ca7fc8b26f | |||
35cb9ea4be | |||
ec42736a2f | |||
b4f7c506da | |||
a577bb9004 | |||
bfd858ab13 | |||
18fe326090 | |||
384454b293 | |||
34db6ae673 | |||
31c88b0783 | |||
c502b1f207 | |||
858e181be1 | |||
d00c73e993 | |||
9403818a12 | |||
b233e655cc | |||
b625519c4a | |||
82e604b869 | |||
60169cc676 | |||
ee0139402a | |||
661d6827c6 | |||
5a331f1087 | |||
1aedbddc55 | |||
35d58bb4e5 | |||
0da5985ef6 | |||
8440c085ca | |||
b68ccb4d71 | |||
8e3ec73ca6 | |||
c29de9dd1e | |||
2ca3b07938 | |||
679263c9f7 | |||
9d635feb30 | |||
fc29073577 | |||
83bb6878f7 | |||
672124aa29 | |||
1e734906d3 | |||
d0695aa9cb | |||
3a941367b8 | |||
52dcae6453 | |||
22a1c56863 | |||
74c5cbcf9f | |||
5b2c0cbc08 | |||
d853bb59c3 | |||
c30927b6fa | |||
3a72adf8c8 | |||
767d719563 | |||
af87e5a1b8 | |||
c83196ee40 | |||
dfcae8f4fd | |||
64be7a62f4 |
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "examples/web"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
7
.github/workflows/build-gnutls.yml
vendored
7
.github/workflows/build-gnutls.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Build and test with GnuTLS
|
name: Build with GnuTLS
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@ -7,7 +7,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
build-ubuntu:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@ -31,9 +31,6 @@ jobs:
|
|||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: cmake
|
- name: cmake
|
||||||
run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=1
|
run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=1
|
||||||
env:
|
|
||||||
# hack to bypass EPERM issue on sendto()
|
|
||||||
CFLAGS: -DJUICE_ENABLE_ADDRS_LOCALHOST
|
|
||||||
- name: make
|
- name: make
|
||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
|
4
.github/workflows/build-nice.yml
vendored
4
.github/workflows/build-nice.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Build and test with libnice
|
name: Build with libnice
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@ -7,7 +7,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
build-ubuntu:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
24
.github/workflows/build-openssl.yml
vendored
24
.github/workflows/build-openssl.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Build and test with OpenSSL
|
name: Build with OpenSSL
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@ -7,7 +7,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
build-ubuntu:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@ -34,9 +34,25 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
||||||
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
||||||
# hack to bypass EPERM issue on sendto()
|
|
||||||
CFLAGS: -DJUICE_ENABLE_ADDRS_LOCALHOST
|
|
||||||
- name: make
|
- name: make
|
||||||
run: (cd build; make -j2)
|
run: (cd build; make -j2)
|
||||||
- name: test
|
- name: test
|
||||||
run: ./build/tests
|
run: ./build/tests
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: ilammy/msvc-dev-cmd@v1
|
||||||
|
- name: install packages
|
||||||
|
run: choco install openssl
|
||||||
|
- name: submodules
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
- name: cmake
|
||||||
|
run: cmake -B build -G "NMake Makefiles" -DUSE_JUICE=1 -DUSE_GNUTLS=0
|
||||||
|
- name: nmake
|
||||||
|
run: |
|
||||||
|
cd build
|
||||||
|
nmake
|
||||||
|
- name: test
|
||||||
|
run: build/tests.exe
|
||||||
|
|
||||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,6 +1,6 @@
|
|||||||
[submodule "deps/plog"]
|
[submodule "deps/plog"]
|
||||||
path = deps/plog
|
path = deps/plog
|
||||||
url = https://github.com/SergiusTheBest/plog
|
url = https://github.com/paullouisageneau/plog
|
||||||
[submodule "usrsctp"]
|
[submodule "usrsctp"]
|
||||||
path = deps/usrsctp
|
path = deps/usrsctp
|
||||||
url = https://github.com/sctplab/usrsctp.git
|
url = https://github.com/sctplab/usrsctp.git
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.7)
|
||||||
project(libdatachannel
|
project(libdatachannel
|
||||||
DESCRIPTION "WebRTC DataChannels Library"
|
DESCRIPTION "WebRTC Data Channels Library"
|
||||||
VERSION 0.6.0
|
VERSION 0.6.5
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
|
||||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||||
option(USE_JUICE "Use libjuice instead of libnice" OFF)
|
option(USE_JUICE "Use libjuice instead of libnice" OFF)
|
||||||
option(RTC_ENABLE_WEBSOCKET "Build WebSocket support" ON)
|
option(USE_SRTP "Enable SRTP for media support" OFF)
|
||||||
|
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
||||||
|
option(NO_EXAMPLES "Disable examples" OFF)
|
||||||
|
|
||||||
if(USE_GNUTLS)
|
if(USE_GNUTLS)
|
||||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
||||||
@ -21,6 +23,8 @@ if(WIN32)
|
|||||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
add_definitions(-DNOMINMAX)
|
add_definitions(-DNOMINMAX)
|
||||||
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||||
|
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -40,6 +44,8 @@ set(LIBDATACHANNEL_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
|
||||||
@ -74,12 +80,12 @@ set(TESTS_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/websocket.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/websocket.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/benchmark.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
find_package(SRTP)
|
|
||||||
|
|
||||||
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
||||||
add_subdirectory(deps/plog)
|
add_subdirectory(deps/plog)
|
||||||
@ -96,7 +102,14 @@ endif()
|
|||||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
||||||
|
|
||||||
if (RTC_ENABLE_WEBSOCKET)
|
if (NO_WEBSOCKET)
|
||||||
|
add_library(datachannel SHARED
|
||||||
|
${LIBDATACHANNEL_SOURCES})
|
||||||
|
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
||||||
|
${LIBDATACHANNEL_SOURCES})
|
||||||
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
||||||
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
||||||
|
else()
|
||||||
add_library(datachannel SHARED
|
add_library(datachannel SHARED
|
||||||
${LIBDATACHANNEL_SOURCES}
|
${LIBDATACHANNEL_SOURCES}
|
||||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
||||||
@ -105,13 +118,6 @@ if (RTC_ENABLE_WEBSOCKET)
|
|||||||
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
|
||||||
else()
|
|
||||||
add_library(datachannel SHARED
|
|
||||||
${LIBDATACHANNEL_SOURCES})
|
|
||||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
|
|
||||||
${LIBDATACHANNEL_SOURCES})
|
|
||||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
|
||||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_target_properties(datachannel PROPERTIES
|
set_target_properties(datachannel PROPERTIES
|
||||||
@ -138,7 +144,8 @@ if(WIN32)
|
|||||||
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(SRTP_FOUND)
|
if(USE_SRTP)
|
||||||
|
find_package(SRTP REQUIRED)
|
||||||
if(NOT TARGET SRTP::SRTP)
|
if(NOT TARGET SRTP::SRTP)
|
||||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||||
set_target_properties(SRTP::SRTP PROPERTIES
|
set_target_properties(SRTP::SRTP PROPERTIES
|
||||||
@ -160,10 +167,10 @@ if (USE_GNUTLS)
|
|||||||
if(NOT TARGET GnuTLS::GnuTLS)
|
if(NOT TARGET GnuTLS::GnuTLS)
|
||||||
add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED)
|
add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED)
|
||||||
set_target_properties(GnuTLS::GnuTLS PROPERTIES
|
set_target_properties(GnuTLS::GnuTLS PROPERTIES
|
||||||
INTERFACE_INCLUDE_DIRECTORIES ${GNUTLS_INCLUDE_DIRS}
|
INTERFACE_INCLUDE_DIRECTORIES "${GNUTLS_INCLUDE_DIRS}"
|
||||||
INTERFACE_COMPILE_DEFINITIONS ${GNUTLS_DEFINITIONS}
|
INTERFACE_COMPILE_DEFINITIONS "${GNUTLS_DEFINITIONS}"
|
||||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||||
IMPORTED_LOCATION ${GNUTLS_LIBRARIES})
|
IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
|
||||||
endif()
|
endif()
|
||||||
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
|
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
|
||||||
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
|
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
|
||||||
@ -204,12 +211,32 @@ set_target_properties(datachannel-tests PROPERTIES
|
|||||||
CXX_STANDARD 17)
|
CXX_STANDARD 17)
|
||||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||||
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
target_link_libraries(datachannel-tests datachannel nlohmann_json::nlohmann_json)
|
if(WIN32)
|
||||||
|
target_link_libraries(datachannel-tests datachannel-static) # DLL exports only the C API
|
||||||
|
else()
|
||||||
|
target_link_libraries(datachannel-tests datachannel)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Benchmark
|
||||||
|
add_executable(datachannel-benchmark test/benchmark.cpp)
|
||||||
|
set_target_properties(datachannel-benchmark PROPERTIES
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
CXX_STANDARD 17)
|
||||||
|
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
|
||||||
|
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
|
||||||
|
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(datachannel-benchmark datachannel-static) # DLL exports only the C API
|
||||||
|
else()
|
||||||
|
target_link_libraries(datachannel-benchmark datachannel)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
if(NOT NO_EXAMPLES)
|
||||||
add_subdirectory(deps/json)
|
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||||
add_subdirectory(examples/client)
|
add_subdirectory(deps/json)
|
||||||
add_subdirectory(examples/copy-paste)
|
add_subdirectory(examples/client)
|
||||||
add_subdirectory(examples/copy-paste-capi)
|
add_subdirectory(examples/copy-paste)
|
||||||
|
add_subdirectory(examples/copy-paste-capi)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
214
Jamfile
214
Jamfile
@ -3,6 +3,12 @@ import feature : feature ;
|
|||||||
project libdatachannel ;
|
project libdatachannel ;
|
||||||
path-constant CWD : . ;
|
path-constant CWD : . ;
|
||||||
|
|
||||||
|
feature gnutls : off on : composite propagated ;
|
||||||
|
feature.compose <gnutls>off
|
||||||
|
: <define>USE_GNUTLS=0 ;
|
||||||
|
feature.compose <gnutls>on
|
||||||
|
: <define>USE_GNUTLS=1 ;
|
||||||
|
|
||||||
lib libdatachannel
|
lib libdatachannel
|
||||||
: # sources
|
: # sources
|
||||||
[ glob ./src/*.cpp ]
|
[ glob ./src/*.cpp ]
|
||||||
@ -12,25 +18,25 @@ lib libdatachannel
|
|||||||
<define>USE_JUICE=1
|
<define>USE_JUICE=1
|
||||||
<define>RTC_ENABLE_MEDIA=0
|
<define>RTC_ENABLE_MEDIA=0
|
||||||
<define>RTC_ENABLE_WEBSOCKET=0
|
<define>RTC_ENABLE_WEBSOCKET=0
|
||||||
|
<toolset>msvc:<define>WIN32_LEAN_AND_MEAN
|
||||||
|
<toolset>msvc:<define>NOMINMAX
|
||||||
|
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS
|
||||||
|
<toolset>msvc:<define>_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||||
<library>/libdatachannel//usrsctp
|
<library>/libdatachannel//usrsctp
|
||||||
<library>/libdatachannel//juice
|
<library>/libdatachannel//juice
|
||||||
<library>/libdatachannel//plog
|
<library>/libdatachannel//plog
|
||||||
|
<gnutls>on:<library>gnutls/<link>shared
|
||||||
|
<gnutls>off:<library>ssl
|
||||||
|
<gnutls>off:<library>crypto
|
||||||
: # default build
|
: # default build
|
||||||
<link>static
|
<link>static
|
||||||
: # usage requirements
|
: # usage requirements
|
||||||
<include>./include
|
<include>./include
|
||||||
<library>/libdatachannel//plog
|
<library>/libdatachannel//plog
|
||||||
<cxxflags>-pthread
|
<toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||||
<toolset>gcc:<cxxflags>"-Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
<toolset>clang:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||||
<toolset>clang:<cxxflags>"-Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
|
||||||
;
|
;
|
||||||
|
|
||||||
feature gnutls : off on : composite propagated ;
|
|
||||||
feature.compose <gnutls>off
|
|
||||||
: <define>USE_GNUTLS=0 ;
|
|
||||||
feature.compose <gnutls>on
|
|
||||||
: <define>USE_GNUTLS=1 ;
|
|
||||||
|
|
||||||
alias plog
|
alias plog
|
||||||
: # no sources
|
: # no sources
|
||||||
: # no build requirements
|
: # no build requirements
|
||||||
@ -48,41 +54,207 @@ alias usrsctp
|
|||||||
<library>libusrsctp.a
|
<library>libusrsctp.a
|
||||||
;
|
;
|
||||||
|
|
||||||
|
alias usrsctp
|
||||||
|
: # no sources
|
||||||
|
: <toolset>msvc
|
||||||
|
: # no default build
|
||||||
|
: # usage requirements
|
||||||
|
<include>./deps/usrsctp/usrsctplib
|
||||||
|
<library>usrsctp.lib
|
||||||
|
;
|
||||||
|
|
||||||
alias juice
|
alias juice
|
||||||
: # no sources
|
: # no sources
|
||||||
: # no build requirements
|
: # no build requirements
|
||||||
: # no default build
|
: # no default build
|
||||||
: # usage requirements
|
: # usage requirements
|
||||||
<include>./deps/libjuice/include
|
<include>./deps/libjuice/include
|
||||||
<library>libjuice.a
|
<library>libjuice-static.a
|
||||||
|
<gnutls>on:<library>nettle
|
||||||
|
;
|
||||||
|
|
||||||
|
alias juice
|
||||||
|
: # no sources
|
||||||
|
: <toolset>msvc
|
||||||
|
: # no default build
|
||||||
|
: # usage requirements
|
||||||
|
<include>./deps/libjuice/include
|
||||||
|
<library>juice-static.lib
|
||||||
;
|
;
|
||||||
|
|
||||||
make libusrsctp.a : : @make_libusrsctp ;
|
make libusrsctp.a : : @make_libusrsctp ;
|
||||||
|
make usrsctp.lib : : @make_libusrsctp_msvc ;
|
||||||
|
|
||||||
|
rule make_libusrsctp ( targets * : sources * : properties * )
|
||||||
|
{
|
||||||
|
local VARIANT = [ feature.get-values <variant> : $(properties) ] ;
|
||||||
|
VARIANT on $(targets) = $(VARIANT) ;
|
||||||
|
if <gnutls>on in $(properties)
|
||||||
|
{ BUILD_DIR on $(targets) = "build-gnutls-$(VARIANT)" ; }
|
||||||
|
else
|
||||||
|
{ BUILD_DIR on $(targets) = "build-openssl-$(VARIANT)" ; }
|
||||||
|
}
|
||||||
actions make_libusrsctp
|
actions make_libusrsctp
|
||||||
{
|
{
|
||||||
(cd $(CWD)/deps/usrsctp && \
|
(cd $(CWD)/deps/usrsctp && mkdir $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC" .. && make -j2 usrsctp-static)
|
||||||
./bootstrap && \
|
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
|
||||||
./configure --enable-static --disable-debug CFLAGS="-fPIC -Wno-address-of-packed-member" && \
|
}
|
||||||
make)
|
rule make_libusrsctp_msvc ( targets * : sources * : properties * )
|
||||||
cp $(CWD)/deps/usrsctp/usrsctplib/.libs/libusrsctp.a $(<)
|
{
|
||||||
|
local VARIANT = [ feature.get-values <variant> : $(properties) ] ;
|
||||||
|
VARIANT on $(targets) = $(VARIANT) ;
|
||||||
|
if <gnutls>on in $(properties)
|
||||||
|
{ BUILD_DIR on $(targets) = "build-gnutls-$(VARIANT)" ; }
|
||||||
|
else
|
||||||
|
{ BUILD_DIR on $(targets) = "build-openssl-$(VARIANT)" ; }
|
||||||
|
}
|
||||||
|
actions make_libusrsctp_msvc
|
||||||
|
{
|
||||||
|
SET OLDD=%CD%
|
||||||
|
cd $(CWD)/deps/usrsctp
|
||||||
|
mkdir $(BUILD_SIR)
|
||||||
|
cd $(BUILD_DIR)
|
||||||
|
cmake -G "Visual Studio 16 2019" ..
|
||||||
|
msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
|
||||||
|
cd %OLDD%
|
||||||
|
cp $(CWD)/deps/usrsctp/build/usrsctplib/Release/usrsctp.lib $(<)
|
||||||
}
|
}
|
||||||
|
|
||||||
make libjuice.a : : @make_libjuice ;
|
make libjuice-static.a : : @make_libjuice ;
|
||||||
|
make juice-static.lib : : @make_libjuice_msvc ;
|
||||||
|
|
||||||
rule make_libjuice ( targets * : sources * : properties * )
|
rule make_libjuice ( targets * : sources * : properties * )
|
||||||
{
|
{
|
||||||
if <crypto>gnutls in $(properties)
|
local VARIANT = [ feature.get-values <variant> : $(properties) ] ;
|
||||||
|
VARIANT on $(targets) = $(VARIANT) ;
|
||||||
|
if <gnutls>on in $(properties)
|
||||||
{
|
{
|
||||||
MAKEOPTS on $(targets) = "USE_NETTLE=1" ;
|
BUILD_DIR on $(targets) = "build-gnutls-$(VARIANT)" ;
|
||||||
|
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=1" ;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MAKEOPTS on $(targets) = "USE_NETTLE=0" ;
|
local OPENSSL_INCLUDE = [ feature.get-values <openssl-include> : $(properties) ] ;
|
||||||
|
|
||||||
|
if <target-os>darwin in $(properties) && $(OPENSSL_INCLUDE) = ""
|
||||||
|
{
|
||||||
|
# on macOS, default to pick up openssl from the homebrew installation
|
||||||
|
# brew install openssl
|
||||||
|
OPENSSL_INCLUDE = /usr/local/opt/openssl/include ;
|
||||||
|
}
|
||||||
|
|
||||||
|
BUILD_DIR on $(targets) = "build-openssl-$(VARIANT)" ;
|
||||||
|
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=0" ;
|
||||||
|
if $(OPENSSL_INCLUDE) != ""
|
||||||
|
{ CMAKEOPTS on $(targets) += " -DOPENSSL_ROOT_DIR=$(OPENSSL_INCLUDE)/.." ; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actions make_libjuice
|
actions make_libjuice
|
||||||
{
|
{
|
||||||
(cd $(CWD)/deps/libjuice && make $(MAKEOPTS))
|
(cd $(CWD)/deps/libjuice && mkdir $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_BUILD_TYPE=$(VARIANT) $(CMAKEOPTS) .. && make -j2 juice-static)
|
||||||
cp $(CWD)/deps/libjuice/libjuice.a $(<)
|
cp $(CWD)/deps/libjuice/$(BUILD_DIR)/libjuice-static.a $(<)
|
||||||
|
}
|
||||||
|
rule make_libjuice_msvc ( targets * : sources * : properties * )
|
||||||
|
{
|
||||||
|
local VARIANT = [ feature.get-values <variant> : $(properties) ] ;
|
||||||
|
VARIANT on $(targets) = $(VARIANT) ;
|
||||||
|
if <gnutls>on in $(properties)
|
||||||
|
{
|
||||||
|
BUILD_DIR on $(targets) += "build-gnutls-$(VARIANT)" ;
|
||||||
|
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=1" ;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BUILD_DIR on $(targets) += "build-openssl-$(VARIANT)" ;
|
||||||
|
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=0" ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions make_libjuice_msvc
|
||||||
|
{
|
||||||
|
SET OLDD=%CD%
|
||||||
|
cd $(CWD)/deps/libjuice
|
||||||
|
mkdir $(BUILD_DIR)
|
||||||
|
cd $(BUILD_DIR)
|
||||||
|
cmake -G "Visual Studio 16 2019" $(CMAKEOPTS) ..
|
||||||
|
msbuild libjuice.sln /property:Configuration=$(VARIANT)
|
||||||
|
cd %OLDD%
|
||||||
|
cp $(CWD)/deps/libjuice/$(BUILD_DIR)/Release/juice-static.lib $(<)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# the search path to pick up the openssl libraries from. This is the <search>
|
||||||
|
# property of those libraries
|
||||||
|
rule openssl-lib-path ( properties * )
|
||||||
|
{
|
||||||
|
local OPENSSL_LIB = [ feature.get-values <openssl-lib> : $(properties) ] ;
|
||||||
|
|
||||||
|
if <target-os>darwin in $(properties) && $(OPENSSL_LIB) = ""
|
||||||
|
{
|
||||||
|
# on macOS, default to pick up openssl from the homebrew installation
|
||||||
|
# brew install openssl
|
||||||
|
OPENSSL_LIB = /usr/local/opt/openssl/lib ;
|
||||||
|
}
|
||||||
|
else if <target-os>windows in $(properties) && $(OPENSSL_LIB) = ""
|
||||||
|
{
|
||||||
|
# on windows, assume openssl is installed to c:\OpenSSL-Win32
|
||||||
|
if <address-model>64 in $(properties)
|
||||||
|
{ OPENSSL_LIB = c:\\OpenSSL-Win64\\lib ; }
|
||||||
|
else
|
||||||
|
{ OPENSSL_LIB = c:\\OpenSSL-Win32\\lib ; }
|
||||||
|
}
|
||||||
|
|
||||||
|
local result ;
|
||||||
|
result += <search>$(OPENSSL_LIB) ;
|
||||||
|
return $(result) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
# the include path to pick up openssl headers from. This is the
|
||||||
|
# usage-requirement for the openssl-related libraries
|
||||||
|
rule openssl-include-path ( properties * )
|
||||||
|
{
|
||||||
|
local OPENSSL_INCLUDE = [ feature.get-values <openssl-include> : $(properties) ] ;
|
||||||
|
|
||||||
|
if <target-os>darwin in $(properties) && $(OPENSSL_INCLUDE) = ""
|
||||||
|
{
|
||||||
|
# on macOS, default to pick up openssl from the homebrew installation
|
||||||
|
# brew install openssl
|
||||||
|
OPENSSL_INCLUDE = /usr/local/opt/openssl/include ;
|
||||||
|
}
|
||||||
|
else if <target-os>windows in $(properties) && $(OPENSSL_INCLUDE) = ""
|
||||||
|
{
|
||||||
|
# on windows, assume openssl is installed to c:\OpenSSL-Win32
|
||||||
|
if <address-model>64 in $(properties)
|
||||||
|
{ OPENSSL_INCLUDE = c:\\OpenSSL-Win64\\include ; }
|
||||||
|
else
|
||||||
|
{ OPENSSL_INCLUDE = c:\\OpenSSL-Win32\\include ; }
|
||||||
|
}
|
||||||
|
|
||||||
|
local result ;
|
||||||
|
result += <include>$(OPENSSL_INCLUDE) ;
|
||||||
|
return $(result) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
# libraries for OpenSSL on Windows
|
||||||
|
lib advapi32 : : <name>advapi32 ;
|
||||||
|
lib user32 : : <name>user32 ;
|
||||||
|
lib shell32 : : <name>shell32 ;
|
||||||
|
lib gdi32 : : <name>gdi32 ;
|
||||||
|
lib bcrypt : : <name>bcrypt ;
|
||||||
|
lib z : : <link>shared <name>z ;
|
||||||
|
alias ssl-deps : advapi32 user32 shell32 gdi32 ;
|
||||||
|
|
||||||
|
# OpenSSL on Windows
|
||||||
|
lib crypto : ssl-deps : <toolset>msvc <openssl-version>1.1 <name>libcrypto
|
||||||
|
<conditional>@openssl-lib-path : : <conditional>@openssl-include-path ;
|
||||||
|
lib ssl : ssl-deps : <toolset>msvc <openssl-version>1.1 <name>libssl <use>crypto
|
||||||
|
<conditional>@openssl-lib-path : : <conditional>@openssl-include-path ;
|
||||||
|
|
||||||
|
# OpenSSL on other platforms
|
||||||
|
lib crypto : : <name>crypto <use>z <conditional>@openssl-lib-path : :
|
||||||
|
<conditional>@openssl-include-path ;
|
||||||
|
lib ssl : : <name>ssl <use>crypto <conditional>@openssl-lib-path : :
|
||||||
|
<conditional>@openssl-include-path ;
|
||||||
|
|
||||||
|
# GnuTLS
|
||||||
|
lib gnutls : : <link>shared <name>gnutls ;
|
||||||
|
lib nettle : : <link>shared <name>nettle ;
|
||||||
|
|
||||||
|
10
Makefile
10
Makefile
@ -38,22 +38,22 @@ else
|
|||||||
LIBS+=glib-2.0 gobject-2.0 nice
|
LIBS+=glib-2.0 gobject-2.0 nice
|
||||||
endif
|
endif
|
||||||
|
|
||||||
RTC_ENABLE_MEDIA ?= 0
|
USE_SRTP ?= 0
|
||||||
ifneq ($(RTC_ENABLE_MEDIA), 0)
|
ifneq ($(USE_SRTP), 0)
|
||||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=1
|
CPPFLAGS+=-DRTC_ENABLE_MEDIA=1
|
||||||
LIBS+=srtp
|
LIBS+=srtp
|
||||||
else
|
else
|
||||||
CPPFLAGS+=-DRTC_ENABLE_MEDIA=0
|
CPPFLAGS+=-DRTC_ENABLE_MEDIA=0
|
||||||
endif
|
endif
|
||||||
|
|
||||||
RTC_ENABLE_WEBSOCKET ?= 1
|
|
||||||
ifneq ($(RTC_ENABLE_WEBSOCKET), 0)
|
NO_WEBSOCKET ?= 0
|
||||||
|
ifeq ($(NO_WEBSOCKET), 0)
|
||||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=1
|
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=1
|
||||||
else
|
else
|
||||||
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=0
|
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=0
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
|
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
|
||||||
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
||||||
|
|
||||||
|
46
README.md
46
README.md
@ -1,7 +1,6 @@
|
|||||||
# libdatachannel - C/C++ WebRTC Data Channels
|
# libdatachannel - C/C++ WebRTC Data Channels
|
||||||
|
|
||||||
libdatachannel is a standalone implementation of WebRTC Data Channels and WebSockets in C++17 with C bindings for POSIX platforms (including Linux and Apple macOS) and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC and WebSocket browser API, in order to ease the design of cross-environment applications.
|
libdatachannel is a standalone implementation of WebRTC Data Channels and WebSockets in C++17 with C bindings for POSIX platforms (including Linux and Apple macOS) and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. The interface consists of simplified versions of the JavaScript WebRTC and WebSocket APIs present in browsers, in order to ease the design of cross-environment applications.
|
||||||
|
|
||||||
It can be compiled with multiple backends:
|
It can be compiled with multiple backends:
|
||||||
- The security layer can be provided through [GnuTLS](https://www.gnutls.org/) or [OpenSSL](https://www.openssl.org/).
|
- The security layer can be provided through [GnuTLS](https://www.gnutls.org/) or [OpenSSL](https://www.openssl.org/).
|
||||||
- The connectivity for WebRTC can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice).
|
- The connectivity for WebRTC can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice).
|
||||||
@ -26,6 +25,7 @@ Protocol stack:
|
|||||||
Features:
|
Features:
|
||||||
- Full IPv6 support
|
- Full IPv6 support
|
||||||
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
|
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
|
||||||
|
- JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26))
|
||||||
- Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
|
- Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
|
||||||
- TURN relaying ([RFC5766](https://tools.ietf.org/html/rfc5766)) with [libnice](https://github.com/libnice/libnice) as ICE backend
|
- TURN relaying ([RFC5766](https://tools.ietf.org/html/rfc5766)) with [libnice](https://github.com/libnice/libnice) as ICE backend
|
||||||
- SRTP media transport ([RFC3711](https://tools.ietf.org/html/rfc3711)) with [libSRTP](https://github.com/cisco/libsrtp)
|
- SRTP media transport ([RFC3711](https://tools.ietf.org/html/rfc3711)) with [libSRTP](https://github.com/cisco/libsrtp)
|
||||||
@ -44,31 +44,55 @@ Features:
|
|||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
- GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/
|
- GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/
|
||||||
|
|
||||||
Optional:
|
Optional dependencies:
|
||||||
- libnice: https://nice.freedesktop.org/ (substituable with libjuice)
|
- libnice: https://nice.freedesktop.org/ (substituable with libjuice)
|
||||||
- libSRTP: https://github.com/cisco/libsrtp
|
- libSRTP: https://github.com/cisco/libsrtp (only necessary for media transport)
|
||||||
|
|
||||||
Submodules:
|
Submodules:
|
||||||
- libjuice: https://github.com/paullouisageneau/libjuice
|
- libjuice: https://github.com/paullouisageneau/libjuice
|
||||||
- usrsctp: https://github.com/sctplab/usrsctp
|
- usrsctp: https://github.com/sctplab/usrsctp
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
### Building with CMake (preferred)
|
|
||||||
|
### Clone repository and submodules
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
$ git clone https://github.com/paullouisageneau/libdatachannel.git
|
||||||
|
$ cd libdatachannel
|
||||||
$ git submodule update --init --recursive
|
$ git submodule update --init --recursive
|
||||||
$ mkdir build
|
|
||||||
$ cd build
|
|
||||||
$ cmake -DUSE_JUICE=1 -DUSE_GNUTLS=1 ..
|
|
||||||
$ make
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building directly with Make
|
### Building with CMake
|
||||||
|
|
||||||
|
The CMake library targets `libdatachannel` and `libdatachannel-static` respectively correspond to the shared and static libraries. On Windows, the DLL resulting from the shared library build only exposes the C API, use the static library for the C++ API. The default target will build tests and examples.
|
||||||
|
|
||||||
|
#### POSIX-compliant operating systems (including Linux and Apple macOS)
|
||||||
|
```bash
|
||||||
|
$ cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=1
|
||||||
|
$ cd build
|
||||||
|
$ make -j2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Microsoft Windows with MinGW cross-compilation
|
||||||
|
```bash
|
||||||
|
$ cmake -B build -DUSE_JUICE=1 -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
|
||||||
|
$ cd build
|
||||||
|
$ make -j2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Microsoft Windows with Microsoft Visual C++
|
||||||
|
```bash
|
||||||
|
$ cmake -B build -G "NMake Makefiles" -DUSE_JUICE=1
|
||||||
|
$ cd build
|
||||||
|
$ nmake
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building directly with Make (Linux only)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git submodule update --init --recursive
|
|
||||||
$ make USE_JUICE=1 USE_GNUTLS=1
|
$ make USE_JUICE=1 USE_GNUTLS=1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: 41e4b66ef6...92a2ed7d44
2
deps/plog
vendored
2
deps/plog
vendored
Submodule deps/plog updated: 47883f0609...afb6f6f0e8
2
deps/usrsctp
vendored
2
deps/usrsctp
vendored
Submodule deps/usrsctp updated: aa10d60bc2...ffed0925f2
@ -7,5 +7,11 @@ add_executable(datachannel-client main.cpp)
|
|||||||
set_target_properties(datachannel-client PROPERTIES
|
set_target_properties(datachannel-client PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME client)
|
OUTPUT_NAME client)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(datachannel-client datachannel-static) # DLL exports only the C API
|
||||||
|
else()
|
||||||
|
target_link_libraries(datachannel-client datachannel)
|
||||||
|
endif()
|
||||||
target_link_libraries(datachannel-client datachannel nlohmann_json)
|
target_link_libraries(datachannel-client datachannel nlohmann_json)
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ string randomId(size_t length) {
|
|||||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
||||||
string id(length, '0');
|
string id(length, '0');
|
||||||
default_random_engine rng(random_device{}());
|
default_random_engine rng(random_device{}());
|
||||||
uniform_int_distribution<int> dist(0, characters.size() - 1);
|
uniform_int_distribution<int> dist(0, int(characters.size() - 1));
|
||||||
generate(id.begin(), id.end(), [&]() { return characters.at(dist(rng)); });
|
generate(id.begin(), id.end(), [&]() { return characters.at(dist(rng)); });
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.5.1)
|
|||||||
project(offerer C)
|
project(offerer C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
set(CMAKE_C_FLAGS "-Wall -g -O2")
|
|
||||||
|
|
||||||
add_executable(datachannel-copy-paste-capi-offerer offerer.c)
|
add_executable(datachannel-copy-paste-capi-offerer offerer.c)
|
||||||
set_target_properties(datachannel-copy-paste-capi-offerer PROPERTIES
|
set_target_properties(datachannel-copy-paste-capi-offerer PROPERTIES
|
||||||
@ -13,3 +12,4 @@ add_executable(datachannel-copy-paste-capi-answerer answerer.c)
|
|||||||
set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
|
set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
|
||||||
OUTPUT_NAME answerer)
|
OUTPUT_NAME answerer)
|
||||||
target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)
|
target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)
|
||||||
|
|
||||||
|
@ -19,12 +19,19 @@
|
|||||||
|
|
||||||
#include <rtc/rtc.h>
|
#include <rtc/rtc.h>
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "getline.h"
|
||||||
|
#include <windows.h>
|
||||||
|
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||||
|
#else
|
||||||
#include <unistd.h> // for sleep
|
#include <unistd.h> // for sleep
|
||||||
#include <ctype.h>
|
#endif
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
rtcState state;
|
rtcState state;
|
||||||
@ -34,30 +41,22 @@ typedef struct {
|
|||||||
bool connected;
|
bool connected;
|
||||||
} Peer;
|
} Peer;
|
||||||
|
|
||||||
Peer *peer = NULL;
|
|
||||||
|
|
||||||
static void dataChannelCallback(int dc, void *ptr);
|
static void dataChannelCallback(int dc, void *ptr);
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||||
|
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||||
|
|
||||||
static void stateChangeCallback(rtcState state, void *ptr);
|
static void stateChangeCallback(rtcState state, void *ptr);
|
||||||
|
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||||
|
|
||||||
static void closedCallback(void *ptr);
|
static void closedCallback(void *ptr);
|
||||||
|
|
||||||
static void messageCallback(const char *message, int size, void *ptr);
|
static void messageCallback(const char *message, int size, void *ptr);
|
||||||
|
|
||||||
static void deletePeer(Peer *peer);
|
static void deletePeer(Peer *peer);
|
||||||
|
|
||||||
int all_space(const char *str);
|
|
||||||
char* state_print(rtcState state);
|
char* state_print(rtcState state);
|
||||||
char* rtcGatheringState_print(rtcState state);
|
char *rtcGatheringState_print(rtcGatheringState state);
|
||||||
|
|
||||||
|
int all_space(const char *str);
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
rtcInitLogger(RTC_LOG_DEBUG);
|
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
||||||
|
|
||||||
// Create peer
|
// Create peer
|
||||||
rtcConfiguration config;
|
rtcConfiguration config;
|
||||||
@ -65,10 +64,8 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
||||||
if (!peer) {
|
if (!peer) {
|
||||||
|
fprintf(stderr, "Error allocating memory for peer\n");
|
||||||
printf("Error allocating memory for peer\n");
|
return -1;
|
||||||
deletePeer(peer);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
memset(peer, 0, sizeof(Peer));
|
memset(peer, 0, sizeof(Peer));
|
||||||
|
|
||||||
@ -86,17 +83,12 @@ int main(int argc, char **argv) {
|
|||||||
rtcSetUserPointer(peer->dc, NULL);
|
rtcSetUserPointer(peer->dc, NULL);
|
||||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
|
||||||
bool exit = false;
|
bool exit = false;
|
||||||
|
|
||||||
while (!exit) {
|
while (!exit) {
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("***************************************************************************************\n");
|
printf("***************************************************************************************\n");
|
||||||
|
|
||||||
// << endl
|
|
||||||
printf("* 0: Exit /"
|
printf("* 0: Exit /"
|
||||||
" 1: Enter remote description /"
|
" 1: Enter remote description /"
|
||||||
" 2: Enter remote candidate /"
|
" 2: Enter remote candidate /"
|
||||||
@ -106,15 +98,15 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
int command = -1;
|
int command = -1;
|
||||||
int c;
|
int c;
|
||||||
// int check_scan
|
|
||||||
if (scanf("%d", &command)) {
|
|
||||||
|
|
||||||
}else {
|
if (!scanf("%d", &command)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
while ((c = getchar()) != '\n' && c != EOF) { }
|
|
||||||
|
|
||||||
|
while ((c = getchar()) != '\n' && c != EOF) {
|
||||||
|
}
|
||||||
fflush(stdin);
|
fflush(stdin);
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 0: {
|
case 0: {
|
||||||
exit = true;
|
exit = true;
|
||||||
@ -123,8 +115,6 @@ int main(int argc, char **argv) {
|
|||||||
case 1: {
|
case 1: {
|
||||||
// Parse Description
|
// Parse Description
|
||||||
printf("[Description]: ");
|
printf("[Description]: ");
|
||||||
|
|
||||||
|
|
||||||
char *line = NULL;
|
char *line = NULL;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
size_t read = 0;
|
size_t read = 0;
|
||||||
@ -151,7 +141,7 @@ int main(int argc, char **argv) {
|
|||||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||||
free(candidate);
|
free(candidate);
|
||||||
|
|
||||||
}else {
|
} else {
|
||||||
printf("Error reading line\n");
|
printf("Error reading line\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -171,7 +161,7 @@ int main(int argc, char **argv) {
|
|||||||
if(getline(&message, &message_size, stdin)) {
|
if(getline(&message, &message_size, stdin)) {
|
||||||
rtcSendMessage(peer->dc, message, -1);
|
rtcSendMessage(peer->dc, message, -1);
|
||||||
free(message);
|
free(message);
|
||||||
}else {
|
} else {
|
||||||
printf("Error reading line\n");
|
printf("Error reading line\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -200,12 +190,10 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
deletePeer(peer);
|
deletePeer(peer);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
// Peer *peer = (Peer *)ptr;
|
||||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||||
@ -229,22 +217,19 @@ static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
|||||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void closedCallback(void *ptr) {
|
static void closedCallback(void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->connected = false;
|
peer->connected = false;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void messageCallback(const char *message, int size, void *ptr) {
|
static void messageCallback(const char *message, int size, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
|
||||||
if (size < 0) { // negative size indicates a null-terminated string
|
if (size < 0) { // negative size indicates a null-terminated string
|
||||||
printf("Message %s: %s\n", "answerer", message);
|
printf("Message %s: %s\n", "answerer", message);
|
||||||
} else {
|
} else {
|
||||||
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void deletePeer(Peer *peer) {
|
static void deletePeer(Peer *peer) {
|
||||||
if (peer) {
|
if (peer) {
|
||||||
if (peer->dc)
|
if (peer->dc)
|
||||||
@ -264,19 +249,9 @@ static void dataChannelCallback(int dc, void *ptr) {
|
|||||||
char buffer[256];
|
char buffer[256];
|
||||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
int all_space(const char *str) {
|
|
||||||
while (*str) {
|
|
||||||
if (!isspace(*str++)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char* state_print(rtcState state) {
|
char *state_print(rtcState state) {
|
||||||
char *str = NULL;
|
char *str = NULL;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case RTC_NEW:
|
case RTC_NEW:
|
||||||
@ -302,11 +277,10 @@ char* state_print(rtcState state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char* rtcGatheringState_print(rtcState state) {
|
char *rtcGatheringState_print(rtcGatheringState state) {
|
||||||
char* str = NULL;
|
char *str = NULL;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case RTC_GATHERING_NEW:
|
case RTC_GATHERING_NEW:
|
||||||
str = "RTC_GATHERING_NEW";
|
str = "RTC_GATHERING_NEW";
|
||||||
@ -322,5 +296,14 @@ char* rtcGatheringState_print(rtcState state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
int all_space(const char *str) {
|
||||||
|
while (*str) {
|
||||||
|
if (!isspace(*str++)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
48
examples/copy-paste-capi/getline.h
Normal file
48
examples/copy-paste-capi/getline.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Simple POSIX getline() implementation
|
||||||
|
// This code is public domain
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int getline(char **lineptr, size_t *n, FILE *stream) {
|
||||||
|
if (!lineptr || !stream || !n)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int c = getc(stream);
|
||||||
|
if (c == EOF)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!*lineptr) {
|
||||||
|
*lineptr = malloc(128);
|
||||||
|
if (!*lineptr)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
*n = 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
while(c != EOF) {
|
||||||
|
if (pos + 1 >= *n) {
|
||||||
|
size_t new_size = *n + (*n >> 2);
|
||||||
|
if (new_size < 128)
|
||||||
|
new_size = 128;
|
||||||
|
|
||||||
|
char *new_ptr = realloc(*lineptr, new_size);
|
||||||
|
if (!new_ptr)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
*n = new_size;
|
||||||
|
*lineptr = new_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
((unsigned char *)(*lineptr))[pos ++] = c;
|
||||||
|
if (c == '\n')
|
||||||
|
break;
|
||||||
|
|
||||||
|
c = getc(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
(*lineptr)[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
@ -19,15 +19,19 @@
|
|||||||
|
|
||||||
#include <rtc/rtc.h>
|
#include <rtc/rtc.h>
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h> // for sleep
|
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
char* state_print(rtcState state);
|
#ifdef _WIN32
|
||||||
char* rtcGatheringState_print(rtcState state);
|
#include "getline.h"
|
||||||
|
#include <windows.h>
|
||||||
|
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||||
|
#else
|
||||||
|
#include <unistd.h> // for sleep
|
||||||
|
#endif
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
rtcState state;
|
rtcState state;
|
||||||
@ -37,28 +41,22 @@ typedef struct {
|
|||||||
bool connected;
|
bool connected;
|
||||||
} Peer;
|
} Peer;
|
||||||
|
|
||||||
Peer *peer = NULL;
|
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||||
|
|
||||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||||
|
|
||||||
static void stateChangeCallback(rtcState state, void *ptr);
|
static void stateChangeCallback(rtcState state, void *ptr);
|
||||||
|
|
||||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||||
|
|
||||||
static void openCallback(void *ptr);
|
static void openCallback(void *ptr);
|
||||||
|
|
||||||
static void closedCallback(void *ptr);
|
static void closedCallback(void *ptr);
|
||||||
|
|
||||||
static void messageCallback(const char *message, int size, void *ptr);
|
static void messageCallback(const char *message, int size, void *ptr);
|
||||||
|
|
||||||
static void deletePeer(Peer *peer);
|
static void deletePeer(Peer *peer);
|
||||||
|
|
||||||
|
char *state_print(rtcState state);
|
||||||
|
char *rtcGatheringState_print(rtcGatheringState state);
|
||||||
|
|
||||||
int all_space(const char *str);
|
int all_space(const char *str);
|
||||||
|
|
||||||
int main(int argc, char **argv){
|
int main(int argc, char **argv){
|
||||||
rtcInitLogger(RTC_LOG_DEBUG);
|
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
||||||
|
|
||||||
// Create peer
|
// Create peer
|
||||||
rtcConfiguration config;
|
rtcConfiguration config;
|
||||||
@ -66,10 +64,8 @@ int main(int argc, char **argv){
|
|||||||
|
|
||||||
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
||||||
if (!peer) {
|
if (!peer) {
|
||||||
|
fprintf(stderr, "Error allocating memory for peer\n");
|
||||||
printf("Error allocating memory for peer\n");
|
return -1;
|
||||||
deletePeer(peer);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
memset(peer, 0, sizeof(Peer));
|
memset(peer, 0, sizeof(Peer));
|
||||||
|
|
||||||
@ -85,25 +81,17 @@ int main(int argc, char **argv){
|
|||||||
|
|
||||||
// Since this is the offere, we will create a datachannel
|
// Since this is the offere, we will create a datachannel
|
||||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||||
|
|
||||||
rtcSetOpenCallback(peer->dc, openCallback);
|
rtcSetOpenCallback(peer->dc, openCallback);
|
||||||
|
|
||||||
|
|
||||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||||
|
|
||||||
rtcSetMessageCallback(peer->dc, messageCallback);
|
rtcSetMessageCallback(peer->dc, messageCallback);
|
||||||
|
|
||||||
|
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
|
||||||
bool exit = false;
|
bool exit = false;
|
||||||
|
|
||||||
while (!exit) {
|
while (!exit) {
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("***************************************************************************************\n");
|
printf("***************************************************************************************\n");
|
||||||
|
|
||||||
// << endl
|
|
||||||
printf("* 0: Exit /"
|
printf("* 0: Exit /"
|
||||||
" 1: Enter remote description /"
|
" 1: Enter remote description /"
|
||||||
" 2: Enter remote candidate /"
|
" 2: Enter remote candidate /"
|
||||||
@ -113,12 +101,13 @@ int main(int argc, char **argv){
|
|||||||
|
|
||||||
int command = -1;
|
int command = -1;
|
||||||
int c;
|
int c;
|
||||||
if (scanf("%d", &command)) {
|
|
||||||
|
|
||||||
}else {
|
if (!scanf("%d", &command)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
while ((c = getchar()) != '\n' && c != EOF) { }
|
|
||||||
|
while ((c = getchar()) != '\n' && c != EOF) {
|
||||||
|
}
|
||||||
fflush(stdin);
|
fflush(stdin);
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
@ -210,11 +199,6 @@ int main(int argc, char **argv){
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||||
// Peer *peer = (Peer *)ptr;
|
// Peer *peer = (Peer *)ptr;
|
||||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||||
@ -238,22 +222,17 @@ static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
|||||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void openCallback(void *ptr) {
|
static void openCallback(void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->connected = true;
|
peer->connected = true;
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||||
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void closedCallback(void *ptr) {
|
static void closedCallback(void *ptr) {
|
||||||
Peer *peer = (Peer *)ptr;
|
Peer *peer = (Peer *)ptr;
|
||||||
peer->connected = false;
|
peer->connected = false;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void messageCallback(const char *message, int size, void *ptr) {
|
static void messageCallback(const char *message, int size, void *ptr) {
|
||||||
@ -264,6 +243,7 @@ static void messageCallback(const char *message, int size, void *ptr) {
|
|||||||
printf("Message %s: [binary of size %d]\n", "offerer", size);
|
printf("Message %s: [binary of size %d]\n", "offerer", size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void deletePeer(Peer *peer) {
|
static void deletePeer(Peer *peer) {
|
||||||
if (peer) {
|
if (peer) {
|
||||||
if (peer->dc)
|
if (peer->dc)
|
||||||
@ -274,17 +254,7 @@ static void deletePeer(Peer *peer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *state_print(rtcState state) {
|
||||||
int all_space(const char *str) {
|
|
||||||
while (*str) {
|
|
||||||
if (!isspace(*str++)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* state_print(rtcState state) {
|
|
||||||
char *str = NULL;
|
char *str = NULL;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case RTC_NEW:
|
case RTC_NEW:
|
||||||
@ -310,11 +280,10 @@ char* state_print(rtcState state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char* rtcGatheringState_print(rtcState state) {
|
char *rtcGatheringState_print(rtcGatheringState state) {
|
||||||
char* str = NULL;
|
char *str = NULL;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case RTC_GATHERING_NEW:
|
case RTC_GATHERING_NEW:
|
||||||
str = "RTC_GATHERING_NEW";
|
str = "RTC_GATHERING_NEW";
|
||||||
@ -330,5 +299,14 @@ char* rtcGatheringState_print(rtcState state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
int all_space(const char *str) {
|
||||||
|
while (*str) {
|
||||||
|
if (!isspace(*str++)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,19 @@ add_executable(datachannel-copy-paste-offerer offerer.cpp)
|
|||||||
set_target_properties(datachannel-copy-paste-offerer PROPERTIES
|
set_target_properties(datachannel-copy-paste-offerer PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME offerer)
|
OUTPUT_NAME offerer)
|
||||||
target_link_libraries(datachannel-copy-paste-offerer datachannel)
|
if(WIN32)
|
||||||
|
target_link_libraries(datachannel-copy-paste-offerer datachannel-static) # DLL exports only the C API
|
||||||
|
else()
|
||||||
|
target_link_libraries(datachannel-copy-paste-offerer datachannel)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_executable(datachannel-copy-paste-answerer answerer.cpp)
|
add_executable(datachannel-copy-paste-answerer answerer.cpp)
|
||||||
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
|
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME answerer)
|
OUTPUT_NAME answerer)
|
||||||
target_link_libraries(datachannel-copy-paste-answerer datachannel)
|
if(WIN32)
|
||||||
|
target_link_libraries(datachannel-copy-paste-answerer datachannel-static) # DLL exports only the C API
|
||||||
|
else()
|
||||||
|
target_link_libraries(datachannel-copy-paste-answerer datachannel)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
@ -108,8 +108,8 @@ template <typename Iterator> bool DataChannel::sendBuffer(Iterator first, Iterat
|
|||||||
auto message = std::make_shared<Message>(size);
|
auto message = std::make_shared<Message>(size);
|
||||||
auto pos = message->begin();
|
auto pos = message->begin();
|
||||||
for (Iterator it = first; it != last; ++it) {
|
for (Iterator it = first; it != last; ++it) {
|
||||||
auto [bytes, size] = to_bytes(*it);
|
auto [bytes, len] = to_bytes(*it);
|
||||||
pos = std::copy(bytes, bytes + size, pos);
|
pos = std::copy(bytes, bytes + len, pos);
|
||||||
}
|
}
|
||||||
return outgoing(message);
|
return outgoing(message);
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ public:
|
|||||||
bool ended() const;
|
bool ended() const;
|
||||||
|
|
||||||
void hintType(Type type);
|
void hintType(Type type);
|
||||||
|
void setDataMid(string mid);
|
||||||
void setFingerprint(string fingerprint);
|
void setFingerprint(string fingerprint);
|
||||||
void setSctpPort(uint16_t port);
|
void setSctpPort(uint16_t port);
|
||||||
void setMaxMessageSize(size_t size);
|
void setMaxMessageSize(size_t size);
|
||||||
@ -86,7 +87,7 @@ private:
|
|||||||
string mid;
|
string mid;
|
||||||
std::vector<string> attributes;
|
std::vector<string> attributes;
|
||||||
};
|
};
|
||||||
std::map<string, Media> mMedia; // by mid
|
std::map<int, Media> mMedia; // by m-line index
|
||||||
|
|
||||||
// Candidates
|
// Candidates
|
||||||
std::vector<Candidate> mCandidates;
|
std::vector<Candidate> mCandidates;
|
||||||
|
@ -64,6 +64,8 @@ const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
|
|||||||
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
|
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
|
||||||
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
|
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
|
||||||
|
|
||||||
|
const int THREADPOOL_SIZE = 4; // Number of threads in the global thread pool
|
||||||
|
|
||||||
// overloaded helper
|
// overloaded helper
|
||||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||||
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
||||||
|
@ -25,12 +25,12 @@
|
|||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
class Init;
|
using init_token = std::shared_ptr<void>;
|
||||||
using init_token = std::shared_ptr<Init>;
|
|
||||||
|
|
||||||
class Init {
|
class Init {
|
||||||
public:
|
public:
|
||||||
static init_token Token();
|
static init_token Token();
|
||||||
|
static void Preload();
|
||||||
static void Cleanup();
|
static void Cleanup();
|
||||||
|
|
||||||
~Init();
|
~Init();
|
||||||
@ -38,11 +38,13 @@ public:
|
|||||||
private:
|
private:
|
||||||
Init();
|
Init();
|
||||||
|
|
||||||
static std::weak_ptr<Init> Weak;
|
static std::weak_ptr<void> Weak;
|
||||||
static init_token Global;
|
static std::shared_ptr<void> *Global;
|
||||||
static std::mutex Mutex;
|
static bool Initialized;
|
||||||
|
static std::recursive_mutex Mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline void Preload() { Init::Preload(); }
|
||||||
inline void Cleanup() { Init::Cleanup(); }
|
inline void Cleanup() { Init::Cleanup(); }
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
class Certificate;
|
class Certificate;
|
||||||
|
class Processor;
|
||||||
class IceTransport;
|
class IceTransport;
|
||||||
class DtlsTransport;
|
class DtlsTransport;
|
||||||
class SctpTransport;
|
class SctpTransport;
|
||||||
@ -101,7 +102,7 @@ public:
|
|||||||
// Media
|
// Media
|
||||||
bool hasMedia() const;
|
bool hasMedia() const;
|
||||||
void sendMedia(const binary &packet);
|
void sendMedia(const binary &packet);
|
||||||
void send(const byte *packet, size_t size);
|
void sendMedia(const byte *packet, size_t size);
|
||||||
|
|
||||||
void onMedia(std::function<void(const binary &packet)> callback);
|
void onMedia(std::function<void(const binary &packet)> callback);
|
||||||
|
|
||||||
@ -139,13 +140,13 @@ private:
|
|||||||
|
|
||||||
void outgoingMedia(message_ptr message);
|
void outgoingMedia(message_ptr message);
|
||||||
|
|
||||||
|
const init_token mInitToken = Init::Token();
|
||||||
const Configuration mConfig;
|
const Configuration mConfig;
|
||||||
const future_certificate_ptr mCertificate;
|
const future_certificate_ptr mCertificate;
|
||||||
|
const std::unique_ptr<Processor> mProcessor;
|
||||||
init_token mInitToken = Init::Token();
|
|
||||||
|
|
||||||
std::optional<Description> mLocalDescription, mRemoteDescription;
|
std::optional<Description> mLocalDescription, mRemoteDescription;
|
||||||
mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
mutable std::mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
||||||
|
|
||||||
std::shared_ptr<IceTransport> mIceTransport;
|
std::shared_ptr<IceTransport> mIceTransport;
|
||||||
std::shared_ptr<DtlsTransport> mDtlsTransport;
|
std::shared_ptr<DtlsTransport> mDtlsTransport;
|
||||||
|
@ -23,9 +23,11 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdint.h>
|
#ifdef _WIN32
|
||||||
|
#define RTC_EXPORT __declspec(dllexport)
|
||||||
// libdatachannel C API
|
#else
|
||||||
|
#define RTC_EXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef RTC_ENABLE_MEDIA
|
#ifndef RTC_ENABLE_MEDIA
|
||||||
#define RTC_ENABLE_MEDIA 1
|
#define RTC_ENABLE_MEDIA 1
|
||||||
@ -35,6 +37,10 @@ extern "C" {
|
|||||||
#define RTC_ENABLE_WEBSOCKET 1
|
#define RTC_ENABLE_WEBSOCKET 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// libdatachannel C API
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
RTC_NEW = 0,
|
RTC_NEW = 0,
|
||||||
RTC_CONNECTING = 1,
|
RTC_CONNECTING = 1,
|
||||||
@ -60,6 +66,10 @@ typedef enum { // Don't change, it must match plog severity
|
|||||||
RTC_LOG_VERBOSE = 6
|
RTC_LOG_VERBOSE = 6
|
||||||
} rtcLogLevel;
|
} rtcLogLevel;
|
||||||
|
|
||||||
|
#define RTC_ERR_SUCCESS 0
|
||||||
|
#define RTC_ERR_INVALID -1 // invalid argument
|
||||||
|
#define RTC_ERR_FAILURE -2 // runtime error
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char **iceServers;
|
const char **iceServers;
|
||||||
int iceServersCount;
|
int iceServersCount;
|
||||||
@ -67,70 +77,72 @@ typedef struct {
|
|||||||
uint16_t portRangeEnd;
|
uint16_t portRangeEnd;
|
||||||
} rtcConfiguration;
|
} rtcConfiguration;
|
||||||
|
|
||||||
typedef void (*dataChannelCallbackFunc)(int dc, void *ptr);
|
typedef void (*rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
|
||||||
typedef void (*descriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
typedef void (*rtcDataChannelCallbackFunc)(int dc, void *ptr);
|
||||||
typedef void (*candidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
typedef void (*rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
||||||
typedef void (*stateChangeCallbackFunc)(rtcState state, void *ptr);
|
typedef void (*rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
||||||
typedef void (*gatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
typedef void (*rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
|
||||||
typedef void (*openCallbackFunc)(void *ptr);
|
typedef void (*rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
||||||
typedef void (*closedCallbackFunc)(void *ptr);
|
typedef void (*rtcOpenCallbackFunc)(void *ptr);
|
||||||
typedef void (*errorCallbackFunc)(const char *error, void *ptr);
|
typedef void (*rtcClosedCallbackFunc)(void *ptr);
|
||||||
typedef void (*messageCallbackFunc)(const char *message, int size, void *ptr);
|
typedef void (*rtcErrorCallbackFunc)(const char *error, void *ptr);
|
||||||
typedef void (*bufferedAmountLowCallbackFunc)(void *ptr);
|
typedef void (*rtcMessageCallbackFunc)(const char *message, int size, void *ptr);
|
||||||
typedef void (*availableCallbackFunc)(void *ptr);
|
typedef void (*rtcBufferedAmountLowCallbackFunc)(void *ptr);
|
||||||
|
typedef void (*rtcAvailableCallbackFunc)(void *ptr);
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
void rtcInitLogger(rtcLogLevel level);
|
RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb); // NULL cb to log to stdout
|
||||||
|
|
||||||
// User pointer
|
// User pointer
|
||||||
void rtcSetUserPointer(int id, void *ptr);
|
RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
|
||||||
|
|
||||||
// PeerConnection
|
// PeerConnection
|
||||||
int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id
|
RTC_EXPORT int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id
|
||||||
int rtcDeletePeerConnection(int pc);
|
RTC_EXPORT int rtcDeletePeerConnection(int pc);
|
||||||
|
|
||||||
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb);
|
RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
|
||||||
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb);
|
RTC_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb);
|
||||||
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb);
|
RTC_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
|
||||||
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb);
|
RTC_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
|
||||||
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb);
|
RTC_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
|
||||||
|
|
||||||
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
||||||
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
|
RTC_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
|
||||||
|
|
||||||
int rtcGetLocalAddress(int pc, char *buffer, int size);
|
RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
|
||||||
int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
||||||
|
|
||||||
// DataChannel
|
// DataChannel
|
||||||
int rtcCreateDataChannel(int pc, const char *label); // returns dc id
|
RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
|
||||||
int rtcDeleteDataChannel(int dc);
|
RTC_EXPORT int rtcDeleteDataChannel(int dc);
|
||||||
|
|
||||||
int rtcGetDataChannelLabel(int dc, char *buffer, int size);
|
RTC_EXPORT int rtcGetDataChannelLabel(int dc, char *buffer, int size);
|
||||||
|
|
||||||
// WebSocket
|
// WebSocket
|
||||||
#if RTC_ENABLE_WEBSOCKET
|
#if RTC_ENABLE_WEBSOCKET
|
||||||
int rtcCreateWebSocket(const char *url); // returns ws id
|
RTC_EXPORT int rtcCreateWebSocket(const char *url); // returns ws id
|
||||||
int rtcDeleteWebsocket(int ws);
|
RTC_EXPORT int rtcDeleteWebsocket(int ws);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// DataChannel and WebSocket common API
|
// DataChannel and WebSocket common API
|
||||||
int rtcSetOpenCallback(int id, openCallbackFunc cb);
|
RTC_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb);
|
||||||
int rtcSetClosedCallback(int id, closedCallbackFunc cb);
|
RTC_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb);
|
||||||
int rtcSetErrorCallback(int id, errorCallbackFunc cb);
|
RTC_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb);
|
||||||
int rtcSetMessageCallback(int id, messageCallbackFunc cb);
|
RTC_EXPORT int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb);
|
||||||
int rtcSendMessage(int id, const char *data, int size);
|
RTC_EXPORT int rtcSendMessage(int id, const char *data, int size);
|
||||||
|
|
||||||
int rtcGetBufferedAmount(int id); // total size buffered to send
|
RTC_EXPORT int rtcGetBufferedAmount(int id); // total size buffered to send
|
||||||
int rtcSetBufferedAmountLowThreshold(int id, int amount);
|
RTC_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount);
|
||||||
int rtcSetBufferedAmountLowCallback(int id, bufferedAmountLowCallbackFunc cb);
|
RTC_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb);
|
||||||
|
|
||||||
// DataChannel and WebSocket common extended API
|
// DataChannel and WebSocket common extended API
|
||||||
int rtcGetAvailableAmount(int id); // total size available to receive
|
RTC_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive
|
||||||
int rtcSetAvailableCallback(int id, availableCallbackFunc cb);
|
RTC_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb);
|
||||||
int rtcReceiveMessage(int id, char *buffer, int *size);
|
RTC_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size);
|
||||||
|
|
||||||
// Cleanup
|
// Optional preload and cleanup
|
||||||
void rtcCleanup();
|
RTC_EXPORT void rtcPreload(void);
|
||||||
|
RTC_EXPORT void rtcCleanup(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
@ -42,7 +42,7 @@ string to_base64(const binary &data) {
|
|||||||
i += 3;
|
i += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
int left = data.size() - i;
|
int left = int(data.size() - i);
|
||||||
if (left) {
|
if (left) {
|
||||||
auto d0 = to_integer<uint8_t>(data[i]);
|
auto d0 = to_integer<uint8_t>(data[i]);
|
||||||
out += tab[d0 >> 2];
|
out += tab[d0 >> 2];
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#else
|
#else
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@ -98,8 +99,8 @@ bool Candidate::resolve(ResolveMode mode) {
|
|||||||
// Rewrite the candidate
|
// Rewrite the candidate
|
||||||
char nodebuffer[MAX_NUMERICNODE_LEN];
|
char nodebuffer[MAX_NUMERICNODE_LEN];
|
||||||
char servbuffer[MAX_NUMERICSERV_LEN];
|
char servbuffer[MAX_NUMERICSERV_LEN];
|
||||||
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
|
if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
|
||||||
servbuffer, MAX_NUMERICSERV_LEN,
|
MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
|
||||||
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
|
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
|
||||||
const char sp{' '};
|
const char sp{' '};
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
@ -107,8 +108,9 @@ bool Candidate::resolve(ResolveMode mode) {
|
|||||||
oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
|
oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
|
||||||
oss << left;
|
oss << left;
|
||||||
mCandidate = oss.str();
|
mCandidate = oss.str();
|
||||||
|
mIsResolved = true;
|
||||||
PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
|
PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
|
||||||
return mIsResolved = true;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +118,7 @@ bool Candidate::resolve(ResolveMode mode) {
|
|||||||
freeaddrinfo(result);
|
freeaddrinfo(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return mIsResolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Candidate::isResolved() const { return mIsResolved; }
|
bool Candidate::isResolved() const { return mIsResolved; }
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "certificate.hpp"
|
#include "certificate.hpp"
|
||||||
|
#include "threadpool.hpp"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@ -132,14 +133,14 @@ namespace rtc {
|
|||||||
|
|
||||||
Certificate::Certificate(string crt_pem, string key_pem) {
|
Certificate::Certificate(string crt_pem, string key_pem) {
|
||||||
BIO *bio = BIO_new(BIO_s_mem());
|
BIO *bio = BIO_new(BIO_s_mem());
|
||||||
BIO_write(bio, crt_pem.data(), crt_pem.size());
|
BIO_write(bio, crt_pem.data(), int(crt_pem.size()));
|
||||||
mX509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, 0, 0), X509_free);
|
mX509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, 0, 0), X509_free);
|
||||||
BIO_free(bio);
|
BIO_free(bio);
|
||||||
if (!mX509)
|
if (!mX509)
|
||||||
throw std::invalid_argument("Unable to import certificate PEM");
|
throw std::invalid_argument("Unable to import certificate PEM");
|
||||||
|
|
||||||
bio = BIO_new(BIO_s_mem());
|
bio = BIO_new(BIO_s_mem());
|
||||||
BIO_write(bio, key_pem.data(), key_pem.size());
|
BIO_write(bio, key_pem.data(), int(key_pem.size()));
|
||||||
mPKey = shared_ptr<EVP_PKEY>(PEM_read_bio_PrivateKey(bio, nullptr, 0, 0), EVP_PKEY_free);
|
mPKey = shared_ptr<EVP_PKEY>(PEM_read_bio_PrivateKey(bio, nullptr, 0, 0), EVP_PKEY_free);
|
||||||
BIO_free(bio);
|
BIO_free(bio);
|
||||||
if (!mPKey)
|
if (!mPKey)
|
||||||
@ -201,8 +202,8 @@ certificate_ptr make_certificate_impl(string commonName) {
|
|||||||
auto *commonNameBytes =
|
auto *commonNameBytes =
|
||||||
reinterpret_cast<unsigned char *>(const_cast<char *>(commonName.c_str()));
|
reinterpret_cast<unsigned char *>(const_cast<char *>(commonName.c_str()));
|
||||||
|
|
||||||
if (!X509_gmtime_adj(X509_get_notBefore(x509.get()), 3600 * -1) ||
|
if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 3600 * -1) ||
|
||||||
!X509_gmtime_adj(X509_get_notAfter(x509.get()), 3600 * 24 * 365) ||
|
!X509_gmtime_adj(X509_getm_notAfter(x509.get()), 3600 * 24 * 365) ||
|
||||||
!X509_set_version(x509.get(), 1) || !X509_set_pubkey(x509.get(), pkey.get()) ||
|
!X509_set_version(x509.get(), 1) || !X509_set_pubkey(x509.get(), pkey.get()) ||
|
||||||
!BN_pseudo_rand(serial_number.get(), serialSize, 0, 0) ||
|
!BN_pseudo_rand(serial_number.get(), serialSize, 0, 0) ||
|
||||||
!BN_to_ASN1_INTEGER(serial_number.get(), X509_get_serialNumber(x509.get())) ||
|
!BN_to_ASN1_INTEGER(serial_number.get(), X509_get_serialNumber(x509.get())) ||
|
||||||
@ -230,19 +231,6 @@ namespace rtc {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Helper function roughly equivalent to std::async with policy std::launch::async
|
|
||||||
// since std::async might be unreliable on some platforms (e.g. Mingw32 on Windows)
|
|
||||||
template <class F, class... Args>
|
|
||||||
std::future<std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>> thread_call(F &&f,
|
|
||||||
Args &&... args) {
|
|
||||||
using R = std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>;
|
|
||||||
std::packaged_task<R()> task(std::bind(f, std::forward<Args>(args)...));
|
|
||||||
std::future<R> future = task.get_future();
|
|
||||||
std::thread t(std::move(task));
|
|
||||||
t.detach();
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::unordered_map<string, future_certificate_ptr> CertificateCache;
|
static std::unordered_map<string, future_certificate_ptr> CertificateCache;
|
||||||
static std::mutex CertificateCacheMutex;
|
static std::mutex CertificateCacheMutex;
|
||||||
|
|
||||||
@ -254,7 +242,7 @@ future_certificate_ptr make_certificate(string commonName) {
|
|||||||
if (auto it = CertificateCache.find(commonName); it != CertificateCache.end())
|
if (auto it = CertificateCache.find(commonName); it != CertificateCache.end())
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
auto future = thread_call(make_certificate_impl, commonName);
|
auto future = ThreadPool::Instance().enqueue(make_certificate_impl, commonName);
|
||||||
auto shared = future.share();
|
auto shared = future.share();
|
||||||
CertificateCache.emplace(std::move(commonName), shared);
|
CertificateCache.emplace(std::move(commonName), shared);
|
||||||
return shared;
|
return shared;
|
||||||
|
@ -61,7 +61,7 @@ string make_fingerprint(X509 *x509);
|
|||||||
using certificate_ptr = std::shared_ptr<Certificate>;
|
using certificate_ptr = std::shared_ptr<Certificate>;
|
||||||
using future_certificate_ptr = std::shared_future<certificate_ptr>;
|
using future_certificate_ptr = std::shared_future<certificate_ptr>;
|
||||||
|
|
||||||
future_certificate_ptr make_certificate(string commonName); // cached
|
future_certificate_ptr make_certificate(string commonName = "libdatachannel"); // cached
|
||||||
|
|
||||||
void CleanupCertificateCache();
|
void CleanupCertificateCache();
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ void DataChannel::close() {
|
|||||||
mIsClosed = true;
|
mIsClosed = true;
|
||||||
if (mIsOpen.exchange(false))
|
if (mIsOpen.exchange(false))
|
||||||
if (auto transport = mSctpTransport.lock())
|
if (auto transport = mSctpTransport.lock())
|
||||||
transport->close(mStream);
|
transport->closeStream(mStream);
|
||||||
|
|
||||||
mSctpTransport.reset();
|
mSctpTransport.reset();
|
||||||
resetCallbacks();
|
resetCallbacks();
|
||||||
@ -186,8 +186,8 @@ void DataChannel::open(shared_ptr<SctpTransport> transport) {
|
|||||||
open.channelType = mReliability->type;
|
open.channelType = mReliability->type;
|
||||||
open.priority = htons(0);
|
open.priority = htons(0);
|
||||||
open.reliabilityParameter = htonl(reliabilityParameter);
|
open.reliabilityParameter = htonl(reliabilityParameter);
|
||||||
open.labelLength = htons(mLabel.size());
|
open.labelLength = htons(uint16_t(mLabel.size()));
|
||||||
open.protocolLength = htons(mProtocol.size());
|
open.protocolLength = htons(uint16_t(mProtocol.size()));
|
||||||
|
|
||||||
auto end = reinterpret_cast<char *>(buffer.data() + sizeof(OpenMessage));
|
auto end = reinterpret_cast<char *>(buffer.data() + sizeof(OpenMessage));
|
||||||
std::copy(mLabel.begin(), mLabel.end(), end);
|
std::copy(mLabel.begin(), mLabel.end(), end);
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
using std::size_t;
|
using std::size_t;
|
||||||
using std::string;
|
using std::string;
|
||||||
|
using std::chrono::system_clock;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ Description::Description(const string &sdp, Type type, Role role)
|
|||||||
mData.mid = "data";
|
mData.mid = "data";
|
||||||
hintType(type);
|
hintType(type);
|
||||||
|
|
||||||
auto seed = std::chrono::system_clock::now().time_since_epoch().count();
|
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||||
std::default_random_engine generator(seed);
|
std::default_random_engine generator(seed);
|
||||||
std::uniform_int_distribution<uint32_t> uniform;
|
std::uniform_int_distribution<uint32_t> uniform;
|
||||||
mSessionId = std::to_string(uniform(generator));
|
mSessionId = std::to_string(uniform(generator));
|
||||||
@ -62,6 +63,7 @@ Description::Description(const string &sdp, Type type, Role role)
|
|||||||
std::istringstream ss(sdp);
|
std::istringstream ss(sdp);
|
||||||
std::optional<Media> currentMedia;
|
std::optional<Media> currentMedia;
|
||||||
|
|
||||||
|
int mlineIndex = 0;
|
||||||
bool finished;
|
bool finished;
|
||||||
do {
|
do {
|
||||||
string line;
|
string line;
|
||||||
@ -75,7 +77,9 @@ Description::Description(const string &sdp, Type type, Role role)
|
|||||||
if (currentMedia->type == "application")
|
if (currentMedia->type == "application")
|
||||||
mData.mid = currentMedia->mid;
|
mData.mid = currentMedia->mid;
|
||||||
else
|
else
|
||||||
mMedia.emplace(currentMedia->mid, std::move(*currentMedia));
|
mMedia.emplace(mlineIndex, std::move(*currentMedia));
|
||||||
|
|
||||||
|
++mlineIndex;
|
||||||
|
|
||||||
} else if (line.find(" ICE/SDP") != string::npos) {
|
} else if (line.find(" ICE/SDP") != string::npos) {
|
||||||
PLOG_WARNING << "SDP \"m=\" line has no corresponding mid, ignoring";
|
PLOG_WARNING << "SDP \"m=\" line has no corresponding mid, ignoring";
|
||||||
@ -112,7 +116,8 @@ Description::Description(const string &sdp, Type type, Role role)
|
|||||||
if (match_prefix(value, "sha-256 ")) {
|
if (match_prefix(value, "sha-256 ")) {
|
||||||
mFingerprint = value.substr(8);
|
mFingerprint = value.substr(8);
|
||||||
std::transform(mFingerprint->begin(), mFingerprint->end(),
|
std::transform(mFingerprint->begin(), mFingerprint->end(),
|
||||||
mFingerprint->begin(), [](char c) { return std::toupper(c); });
|
mFingerprint->begin(),
|
||||||
|
[](char c) { return char(std::toupper(c)); });
|
||||||
} else {
|
} else {
|
||||||
PLOG_WARNING << "Unknown SDP fingerprint type: " << value;
|
PLOG_WARNING << "Unknown SDP fingerprint type: " << value;
|
||||||
}
|
}
|
||||||
@ -161,6 +166,8 @@ void Description::hintType(Type type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Description::setDataMid(string mid) { mData.mid = mid; }
|
||||||
|
|
||||||
void Description::setFingerprint(string fingerprint) {
|
void Description::setFingerprint(string fingerprint) {
|
||||||
mFingerprint.emplace(std::move(fingerprint));
|
mFingerprint.emplace(std::move(fingerprint));
|
||||||
}
|
}
|
||||||
@ -185,11 +192,8 @@ std::vector<Candidate> Description::extractCandidates() {
|
|||||||
bool Description::hasMedia() const { return !mMedia.empty(); }
|
bool Description::hasMedia() const { return !mMedia.empty(); }
|
||||||
|
|
||||||
void Description::addMedia(const Description &source) {
|
void Description::addMedia(const Description &source) {
|
||||||
for (auto [mid, media] : source.mMedia)
|
for (auto p : source.mMedia)
|
||||||
if (mid != mData.mid)
|
mMedia.emplace(p);
|
||||||
mMedia.emplace(mid, media);
|
|
||||||
else
|
|
||||||
PLOG_WARNING << "Media mid \"" << mid << "\" is the same as data mid, ignoring";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Description::operator string() const { return generateSdp("\r\n"); }
|
Description::operator string() const { return generateSdp("\r\n"); }
|
||||||
@ -210,13 +214,40 @@ string Description::generateSdp(const string &eol) const {
|
|||||||
// see Negotiating Media Multiplexing Using the Session Description Protocol
|
// see Negotiating Media Multiplexing Using the Session Description Protocol
|
||||||
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54
|
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54
|
||||||
sdp << "a=group:BUNDLE";
|
sdp << "a=group:BUNDLE";
|
||||||
for (const auto &[mid, _] : mMedia)
|
for (int i = 0; i < int(mMedia.size() + 1); ++i)
|
||||||
sdp << " " << mid;
|
if (auto it = mMedia.find(i); it != mMedia.end())
|
||||||
sdp << " " << mData.mid << eol;
|
sdp << ' ' << it->second.mid;
|
||||||
|
else
|
||||||
|
sdp << ' ' << mData.mid;
|
||||||
|
sdp << eol;
|
||||||
|
|
||||||
|
sdp << "a=msid-semantic: WMS" << eol;
|
||||||
|
|
||||||
|
// Non-data media
|
||||||
|
if (!mMedia.empty()) {
|
||||||
|
// Lip-sync
|
||||||
|
sdp << "a=group:LS";
|
||||||
|
for (const auto &p : mMedia)
|
||||||
|
sdp << " " << p.second.mid;
|
||||||
|
sdp << eol;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptions and attributes
|
||||||
|
for (int i = 0; i < int(mMedia.size() + 1); ++i) {
|
||||||
|
if (auto it = mMedia.find(i); it != mMedia.end()) {
|
||||||
|
// Non-data media
|
||||||
|
const auto &media = it->second;
|
||||||
|
sdp << "m=" << media.type << ' ' << 0 << ' ' << media.description << eol;
|
||||||
|
sdp << "c=IN IP4 0.0.0.0" << eol;
|
||||||
|
sdp << "a=bundle-only" << eol;
|
||||||
|
sdp << "a=mid:" << media.mid << eol;
|
||||||
|
for (const auto &attr : media.attributes)
|
||||||
|
sdp << "a=" << attr << eol;
|
||||||
|
|
||||||
|
} else {
|
||||||
// Data
|
// Data
|
||||||
const string dataDescription = "UDP/DTLS/SCTP webrtc-datachannel";
|
const string description = "UDP/DTLS/SCTP webrtc-datachannel";
|
||||||
sdp << "m=application" << ' ' << (!mMedia.empty() ? 0 : 9) << ' ' << dataDescription << eol;
|
sdp << "m=application" << ' ' << (!mMedia.empty() ? 0 : 9) << ' ' << description << eol;
|
||||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
sdp << "c=IN IP4 0.0.0.0" << eol;
|
||||||
if (!mMedia.empty())
|
if (!mMedia.empty())
|
||||||
sdp << "a=bundle-only" << eol;
|
sdp << "a=bundle-only" << eol;
|
||||||
@ -225,32 +256,18 @@ string Description::generateSdp(const string &eol) const {
|
|||||||
sdp << "a=sctp-port:" << *mData.sctpPort << eol;
|
sdp << "a=sctp-port:" << *mData.sctpPort << eol;
|
||||||
if (mData.maxMessageSize)
|
if (mData.maxMessageSize)
|
||||||
sdp << "a=max-message-size:" << *mData.maxMessageSize << eol;
|
sdp << "a=max-message-size:" << *mData.maxMessageSize << eol;
|
||||||
|
|
||||||
// Non-data media
|
|
||||||
if (!mMedia.empty()) {
|
|
||||||
// Lip-sync
|
|
||||||
sdp << "a=group:LS";
|
|
||||||
for (const auto &[mid, _] : mMedia)
|
|
||||||
sdp << " " << mid;
|
|
||||||
sdp << eol;
|
|
||||||
|
|
||||||
// Descriptions and attributes
|
|
||||||
for (const auto &[_, media] : mMedia) {
|
|
||||||
sdp << "m=" << media.type << ' ' << 0 << ' ' << media.description << eol;
|
|
||||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
|
||||||
sdp << "a=bundle-only" << eol;
|
|
||||||
sdp << "a=mid:" << media.mid << eol;
|
|
||||||
for (const auto &attr : media.attributes)
|
|
||||||
sdp << "a=" << attr << eol;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common
|
// Common
|
||||||
|
if (!mEnded)
|
||||||
sdp << "a=ice-options:trickle" << eol;
|
sdp << "a=ice-options:trickle" << eol;
|
||||||
|
|
||||||
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
||||||
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
||||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||||
sdp << "a=dtls-id:1" << eol;
|
sdp << "a=tls-id:1" << eol;
|
||||||
|
|
||||||
if (mFingerprint)
|
if (mFingerprint)
|
||||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||||
|
|
||||||
|
@ -43,37 +43,65 @@ DtlsSrtpTransport::DtlsSrtpTransport(std::shared_ptr<IceTransport> lower,
|
|||||||
std::move(stateChangeCallback)),
|
std::move(stateChangeCallback)),
|
||||||
mSrtpRecvCallback(std::move(srtpRecvCallback)) { // distinct from Transport recv callback
|
mSrtpRecvCallback(std::move(srtpRecvCallback)) { // distinct from Transport recv callback
|
||||||
|
|
||||||
PLOG_DEBUG << "Initializing SRTP transport";
|
PLOG_DEBUG << "Initializing DTLS-SRTP transport";
|
||||||
|
|
||||||
#if USE_GNUTLS
|
if (srtp_err_status_t err = srtp_create(&mSrtpIn, nullptr)) {
|
||||||
PLOG_DEBUG << "Initializing DTLS-SRTP transport (GnuTLS)";
|
throw std::runtime_error("SRTP create failed, status=" + to_string(static_cast<int>(err)));
|
||||||
gnutls::check(gnutls_srtp_set_profile(mSession, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80),
|
}
|
||||||
"Failed to set SRTP profile");
|
if (srtp_err_status_t err = srtp_create(&mSrtpOut, nullptr)) {
|
||||||
#else
|
srtp_dealloc(mSrtpIn);
|
||||||
PLOG_DEBUG << "Initializing DTLS-SRTP transport (OpenSSL)";
|
throw std::runtime_error("SRTP create failed, status=" + to_string(static_cast<int>(err)));
|
||||||
openssl::check(SSL_set_tlsext_use_srtp(mSsl, "SRTP_AES128_CM_SHA1_80"),
|
}
|
||||||
"Failed to set SRTP profile");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DtlsSrtpTransport::~DtlsSrtpTransport() {
|
DtlsSrtpTransport::~DtlsSrtpTransport() {
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
if (mCreated)
|
srtp_dealloc(mSrtpIn);
|
||||||
srtp_dealloc(mSrtp);
|
srtp_dealloc(mSrtpOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DtlsSrtpTransport::send(message_ptr message) {
|
bool DtlsSrtpTransport::sendMedia(message_ptr message) {
|
||||||
if (!message)
|
if (!message)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!mInitDone) {
|
||||||
|
PLOG_WARNING << "SRTP media sent before keys are derived";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int size = message->size();
|
int size = message->size();
|
||||||
PLOG_VERBOSE << "Send size=" << size;
|
PLOG_VERBOSE << "Send size=" << size;
|
||||||
|
|
||||||
// srtp_protect() assumes that it can write SRTP_MAX_TRAILER_LEN (for the authentication tag)
|
// The RTP header has a minimum size of 12 bytes
|
||||||
// into the location in memory immediately following the RTP packet.
|
if (size < 12)
|
||||||
|
throw std::runtime_error("RTP/RTCP packet too short");
|
||||||
|
|
||||||
|
// srtp_protect() and srtp_protect_rtcp() assume that they can write SRTP_MAX_TRAILER_LEN (for
|
||||||
|
// the authentication tag) into the location in memory immediately following the RTP packet.
|
||||||
message->resize(size + SRTP_MAX_TRAILER_LEN);
|
message->resize(size + SRTP_MAX_TRAILER_LEN);
|
||||||
if (srtp_err_status_t err = srtp_protect(mSrtp, message->data(), &size)) {
|
|
||||||
|
uint8_t value2 = to_integer<uint8_t>(*(message->begin() + 1)) & 0x7F;
|
||||||
|
PLOG_VERBOSE << "Demultiplexing SRTCP and SRTP with RTP payload type, value="
|
||||||
|
<< unsigned(value2);
|
||||||
|
|
||||||
|
// RFC 5761 Multiplexing RTP and RTCP 4. Distinguishable RTP and RTCP Packets
|
||||||
|
// It is RECOMMENDED to follow the guidelines in the RTP/AVP profile for the choice of RTP
|
||||||
|
// payload type values, with the additional restriction that payload type values in the
|
||||||
|
// range 64-95 MUST NOT be used. Specifically, dynamic RTP payload types SHOULD be chosen in
|
||||||
|
// the range 96-127 where possible. Values below 64 MAY be used if that is insufficient
|
||||||
|
// [...]
|
||||||
|
if (value2 >= 64 && value2 <= 95) { // Range 64-95 (inclusive) MUST be RTCP
|
||||||
|
if (srtp_err_status_t err = srtp_protect_rtcp(mSrtpOut, message->data(), &size)) {
|
||||||
|
if (err == srtp_err_status_replay_fail)
|
||||||
|
throw std::runtime_error("SRTCP packet is a replay");
|
||||||
|
else
|
||||||
|
throw std::runtime_error("SRTCP protect error, status=" +
|
||||||
|
to_string(static_cast<int>(err)));
|
||||||
|
}
|
||||||
|
PLOG_VERBOSE << "Protected SRTCP packet, size=" << size;
|
||||||
|
} else {
|
||||||
|
if (srtp_err_status_t err = srtp_protect(mSrtpOut, message->data(), &size)) {
|
||||||
if (err == srtp_err_status_replay_fail)
|
if (err == srtp_err_status_replay_fail)
|
||||||
throw std::runtime_error("SRTP packet is a replay");
|
throw std::runtime_error("SRTP packet is a replay");
|
||||||
else
|
else
|
||||||
@ -81,12 +109,20 @@ bool DtlsSrtpTransport::send(message_ptr message) {
|
|||||||
to_string(static_cast<int>(err)));
|
to_string(static_cast<int>(err)));
|
||||||
}
|
}
|
||||||
PLOG_VERBOSE << "Protected SRTP packet, size=" << size;
|
PLOG_VERBOSE << "Protected SRTP packet, size=" << size;
|
||||||
|
}
|
||||||
|
|
||||||
message->resize(size);
|
message->resize(size);
|
||||||
outgoing(message);
|
outgoing(message);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtlsSrtpTransport::incoming(message_ptr message) {
|
void DtlsSrtpTransport::incoming(message_ptr message) {
|
||||||
|
if (!mInitDone) {
|
||||||
|
// Bypas
|
||||||
|
DtlsTransport::incoming(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int size = message->size();
|
int size = message->size();
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
return;
|
return;
|
||||||
@ -95,15 +131,39 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
|
|||||||
// The process for demultiplexing a packet is as follows. The receiver looks at the first byte
|
// The process for demultiplexing a packet is as follows. The receiver looks at the first byte
|
||||||
// of the packet. [...] If the value is in between 128 and 191 (inclusive), then the packet is
|
// of the packet. [...] If the value is in between 128 and 191 (inclusive), then the packet is
|
||||||
// RTP (or RTCP [...]). If the value is between 20 and 63 (inclusive), the packet is DTLS.
|
// RTP (or RTCP [...]). If the value is between 20 and 63 (inclusive), the packet is DTLS.
|
||||||
uint8_t value = to_integer<uint8_t>(*message->begin());
|
uint8_t value1 = to_integer<uint8_t>(*message->begin());
|
||||||
|
PLOG_VERBOSE << "Demultiplexing DTLS and SRTP/SRTCP with first byte, value="
|
||||||
|
<< unsigned(value1);
|
||||||
|
|
||||||
if (value >= 128 && value <= 192) {
|
if (value1 >= 20 && value1 <= 63) {
|
||||||
PLOG_VERBOSE << "Incoming DTLS packet, size=" << size;
|
PLOG_VERBOSE << "Incoming DTLS packet, size=" << size;
|
||||||
DtlsTransport::incoming(message);
|
DtlsTransport::incoming(message);
|
||||||
} else if (value >= 20 && value <= 64) {
|
|
||||||
PLOG_VERBOSE << "Incoming SRTP packet, size=" << size;
|
|
||||||
|
|
||||||
if (srtp_err_status_t err = srtp_unprotect(mSrtp, message->data(), &size)) {
|
} else if (value1 >= 128 && value1 <= 191) {
|
||||||
|
// The RTP header has a minimum size of 12 bytes
|
||||||
|
if (size < 12) {
|
||||||
|
PLOG_WARNING << "Incoming SRTP/SRTCP packet too short, size=" << size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t value2 = to_integer<uint8_t>(*(message->begin() + 1)) & 0x7F;
|
||||||
|
PLOG_VERBOSE << "Demultiplexing SRTCP and SRTP with RTP payload type, value="
|
||||||
|
<< unsigned(value2);
|
||||||
|
|
||||||
|
// See RFC 5761 reference above
|
||||||
|
if (value2 >= 64 && value2 <= 95) { // Range 64-95 (inclusive) MUST be RTCP
|
||||||
|
PLOG_VERBOSE << "Incoming SRTCP packet, size=" << size;
|
||||||
|
if (srtp_err_status_t err = srtp_unprotect_rtcp(mSrtpIn, message->data(), &size)) {
|
||||||
|
if (err == srtp_err_status_replay_fail)
|
||||||
|
PLOG_WARNING << "Incoming SRTCP packet is a replay";
|
||||||
|
else
|
||||||
|
PLOG_WARNING << "SRTCP unprotect error, status=" << err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PLOG_VERBOSE << "Unprotected SRTCP packet, size=" << size;
|
||||||
|
} else {
|
||||||
|
PLOG_VERBOSE << "Incoming SRTP packet, size=" << size;
|
||||||
|
if (srtp_err_status_t err = srtp_unprotect(mSrtpIn, message->data(), &size)) {
|
||||||
if (err == srtp_err_status_replay_fail)
|
if (err == srtp_err_status_replay_fail)
|
||||||
PLOG_WARNING << "Incoming SRTP packet is a replay";
|
PLOG_WARNING << "Incoming SRTP packet is a replay";
|
||||||
else
|
else
|
||||||
@ -111,33 +171,40 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PLOG_VERBOSE << "Unprotected SRTP packet, size=" << size;
|
PLOG_VERBOSE << "Unprotected SRTP packet, size=" << size;
|
||||||
|
}
|
||||||
|
|
||||||
message->resize(size);
|
message->resize(size);
|
||||||
mSrtpRecvCallback(message);
|
mSrtpRecvCallback(message);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
PLOG_WARNING << "Unknown packet type, value=" << value << ", size=" << size;
|
PLOG_WARNING << "Unknown packet type, value=" << unsigned(value1) << ", size=" << size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DtlsSrtpTransport::postCreation() {
|
||||||
|
#if USE_GNUTLS
|
||||||
|
PLOG_DEBUG << "Setting SRTP profile (GnuTLS)";
|
||||||
|
gnutls::check(gnutls_srtp_set_profile(mSession, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80),
|
||||||
|
"Failed to set SRTP profile");
|
||||||
|
#else
|
||||||
|
PLOG_DEBUG << "Setting SRTP profile (OpenSSL)";
|
||||||
|
// returns 0 on success, 1 on error
|
||||||
|
if (SSL_set_tlsext_use_srtp(mSsl, "SRTP_AES128_CM_SHA1_80"), "Failed to set SRTP profile")
|
||||||
|
throw std::runtime_error("Failed to set SRTP profile: " + openssl::error_string(ERR_get_error()));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void DtlsSrtpTransport::postHandshake() {
|
void DtlsSrtpTransport::postHandshake() {
|
||||||
if (mCreated)
|
if (mInitDone)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
srtp_policy_t inbound = {};
|
|
||||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
|
|
||||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
|
|
||||||
inbound.ssrc.type = ssrc_any_inbound;
|
|
||||||
|
|
||||||
srtp_policy_t outbound = {};
|
|
||||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp);
|
|
||||||
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp);
|
|
||||||
outbound.ssrc.type = ssrc_any_outbound;
|
|
||||||
|
|
||||||
const size_t materialLen = SRTP_AES_ICM_128_KEY_LEN_WSALT * 2;
|
const size_t materialLen = SRTP_AES_ICM_128_KEY_LEN_WSALT * 2;
|
||||||
unsigned char material[materialLen];
|
unsigned char material[materialLen];
|
||||||
const unsigned char *clientKey, *clientSalt, *serverKey, *serverSalt;
|
const unsigned char *clientKey, *clientSalt, *serverKey, *serverSalt;
|
||||||
|
|
||||||
#if USE_GNUTLS
|
#if USE_GNUTLS
|
||||||
|
PLOG_INFO << "Deriving SRTP keying material (GnuTLS)";
|
||||||
|
|
||||||
gnutls_datum_t clientKeyDatum, clientSaltDatum, serverKeyDatum, serverSaltDatum;
|
gnutls_datum_t clientKeyDatum, clientSaltDatum, serverKeyDatum, serverSaltDatum;
|
||||||
gnutls::check(gnutls_srtp_get_keys(mSession, material, materialLen, &clientKeyDatum,
|
gnutls::check(gnutls_srtp_get_keys(mSession, material, materialLen, &clientKeyDatum,
|
||||||
&clientSaltDatum, &serverKeyDatum, &serverSaltDatum),
|
&clientSaltDatum, &serverKeyDatum, &serverSaltDatum),
|
||||||
@ -160,18 +227,23 @@ void DtlsSrtpTransport::postHandshake() {
|
|||||||
serverKey = reinterpret_cast<const unsigned char *>(serverKeyDatum.data);
|
serverKey = reinterpret_cast<const unsigned char *>(serverKeyDatum.data);
|
||||||
serverSalt = reinterpret_cast<const unsigned char *>(serverSaltDatum.data);
|
serverSalt = reinterpret_cast<const unsigned char *>(serverSaltDatum.data);
|
||||||
#else
|
#else
|
||||||
// This provides the client write master key, the server write master key, the client write
|
PLOG_INFO << "Deriving SRTP keying material (OpenSSL)";
|
||||||
// master salt and the server write master salt in that order.
|
|
||||||
|
// The extractor provides the client write master key, the server write master key, the client
|
||||||
|
// write master salt and the server write master salt in that order.
|
||||||
const string label = "EXTRACTOR-dtls_srtp";
|
const string label = "EXTRACTOR-dtls_srtp";
|
||||||
openssl::check(SSL_export_keying_material(mSsl, material, materialLen, label.c_str(),
|
|
||||||
label.size(), nullptr, 0, 0),
|
// returns 1 on success, 0 or -1 on failure (OpenSSL API is a complete mess...)
|
||||||
"Failed to derive SRTP keys");
|
if (SSL_export_keying_material(mSsl, material, materialLen, label.c_str(), label.size(),
|
||||||
|
nullptr, 0, 0) <= 0)
|
||||||
|
throw std::runtime_error("Failed to derive SRTP keys: " +
|
||||||
|
openssl::error_string(ERR_get_error()));
|
||||||
|
|
||||||
clientKey = material;
|
clientKey = material;
|
||||||
clientSalt = clientKey + SRTP_AES_128_KEY_LEN;
|
clientSalt = clientKey + SRTP_AES_128_KEY_LEN;
|
||||||
|
|
||||||
serverKey = material + SRTP_AES_ICM_128_KEY_LEN_WSALT;
|
serverKey = material + SRTP_AES_ICM_128_KEY_LEN_WSALT;
|
||||||
serverSalt = serverSalt + SRTP_AES_128_KEY_LEN;
|
serverSalt = serverKey + SRTP_AES_128_KEY_LEN;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
unsigned char clientSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
|
unsigned char clientSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
|
||||||
@ -182,22 +254,31 @@ void DtlsSrtpTransport::postHandshake() {
|
|||||||
std::memcpy(serverSessionKey, serverKey, SRTP_AES_128_KEY_LEN);
|
std::memcpy(serverSessionKey, serverKey, SRTP_AES_128_KEY_LEN);
|
||||||
std::memcpy(serverSessionKey + SRTP_AES_128_KEY_LEN, serverSalt, SRTP_SALT_LEN);
|
std::memcpy(serverSessionKey + SRTP_AES_128_KEY_LEN, serverSalt, SRTP_SALT_LEN);
|
||||||
|
|
||||||
if (mIsClient) {
|
srtp_policy_t inbound = {};
|
||||||
inbound.key = serverSessionKey;
|
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
|
||||||
outbound.key = clientSessionKey;
|
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
|
||||||
} else {
|
inbound.ssrc.type = ssrc_any_inbound;
|
||||||
inbound.key = clientSessionKey;
|
inbound.ssrc.value = 0;
|
||||||
outbound.key = serverSessionKey;
|
inbound.key = mIsClient ? serverSessionKey : clientSessionKey;
|
||||||
}
|
inbound.next = nullptr;
|
||||||
|
|
||||||
srtp_policy_t *policies = &inbound;
|
if (srtp_err_status_t err = srtp_add_stream(mSrtpIn, &inbound))
|
||||||
inbound.next = &outbound;
|
throw std::runtime_error("SRTP add inbound stream failed, status=" +
|
||||||
|
to_string(static_cast<int>(err)));
|
||||||
|
|
||||||
|
srtp_policy_t outbound = {};
|
||||||
|
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp);
|
||||||
|
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp);
|
||||||
|
outbound.ssrc.type = ssrc_any_outbound;
|
||||||
|
outbound.ssrc.value = 0;
|
||||||
|
outbound.key = mIsClient ? clientSessionKey : serverSessionKey;
|
||||||
outbound.next = nullptr;
|
outbound.next = nullptr;
|
||||||
|
|
||||||
if (srtp_err_status_t err = srtp_create(&mSrtp, policies))
|
if (srtp_err_status_t err = srtp_add_stream(mSrtpOut, &outbound))
|
||||||
throw std::runtime_error("SRTP create failed, status=" + to_string(static_cast<int>(err)));
|
throw std::runtime_error("SRTP add outbound stream failed, status=" +
|
||||||
|
to_string(static_cast<int>(err)));
|
||||||
|
|
||||||
mCreated = true;
|
mInitDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
@ -38,16 +38,17 @@ public:
|
|||||||
state_callback stateChangeCallback);
|
state_callback stateChangeCallback);
|
||||||
~DtlsSrtpTransport();
|
~DtlsSrtpTransport();
|
||||||
|
|
||||||
bool send(message_ptr message) override;
|
bool sendMedia(message_ptr message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void incoming(message_ptr message) override;
|
void incoming(message_ptr message) override;
|
||||||
|
void postCreation() override;
|
||||||
void postHandshake() override;
|
void postHandshake() override;
|
||||||
|
|
||||||
message_callback mSrtpRecvCallback;
|
message_callback mSrtpRecvCallback;
|
||||||
|
|
||||||
srtp_t mSrtp;
|
srtp_t mSrtpIn, mSrtpOut;
|
||||||
bool mCreated = false;
|
bool mInitDone = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
@ -24,6 +24,14 @@
|
|||||||
#include <exception>
|
#include <exception>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#if !USE_GNUTLS
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h> // for timeval
|
||||||
|
#else
|
||||||
|
#include <sys/time.h> // for timeval
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
@ -36,12 +44,10 @@ namespace rtc {
|
|||||||
#if USE_GNUTLS
|
#if USE_GNUTLS
|
||||||
|
|
||||||
void DtlsTransport::Init() {
|
void DtlsTransport::Init() {
|
||||||
// Nothing to do
|
gnutls_global_init(); // optional
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtlsTransport::Cleanup() {
|
void DtlsTransport::Cleanup() { gnutls_global_deinit(); }
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
|
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
|
||||||
verifier_callback verifierCallback, state_callback stateChangeCallback)
|
verifier_callback verifierCallback, state_callback stateChangeCallback)
|
||||||
@ -79,6 +85,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr cer
|
|||||||
gnutls_transport_set_pull_function(mSession, ReadCallback);
|
gnutls_transport_set_pull_function(mSession, ReadCallback);
|
||||||
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
|
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
|
||||||
|
|
||||||
|
postCreation();
|
||||||
|
|
||||||
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
||||||
registerIncoming();
|
registerIncoming();
|
||||||
|
|
||||||
@ -131,6 +139,10 @@ void DtlsTransport::incoming(message_ptr message) {
|
|||||||
mIncomingQueue.push(message);
|
mIncomingQueue.push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DtlsTransport::postCreation() {
|
||||||
|
// Dummy
|
||||||
|
}
|
||||||
|
|
||||||
void DtlsTransport::postHandshake() {
|
void DtlsTransport::postHandshake() {
|
||||||
// Dummy
|
// Dummy
|
||||||
}
|
}
|
||||||
@ -302,7 +314,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
|||||||
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
|
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!(mCtx = SSL_CTX_new(DTLS_method())))
|
mCtx = SSL_CTX_new(DTLS_method());
|
||||||
|
if (!mCtx)
|
||||||
throw std::runtime_error("Failed to create SSL context");
|
throw std::runtime_error("Failed to create SSL context");
|
||||||
|
|
||||||
openssl::check(SSL_CTX_set_cipher_list(mCtx, "ALL:!LOW:!EXP:!RC4:!MD5:@STRENGTH"),
|
openssl::check(SSL_CTX_set_cipher_list(mCtx, "ALL:!LOW:!EXP:!RC4:!MD5:@STRENGTH"),
|
||||||
@ -326,7 +339,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
|||||||
|
|
||||||
openssl::check(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed");
|
openssl::check(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed");
|
||||||
|
|
||||||
if (!(mSsl = SSL_new(mCtx)))
|
mSsl = SSL_new(mCtx);
|
||||||
|
if (!mSsl)
|
||||||
throw std::runtime_error("Failed to create SSL instance");
|
throw std::runtime_error("Failed to create SSL instance");
|
||||||
|
|
||||||
SSL_set_ex_data(mSsl, TransportExIndex, this);
|
SSL_set_ex_data(mSsl, TransportExIndex, this);
|
||||||
@ -336,7 +350,9 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
|||||||
else
|
else
|
||||||
SSL_set_accept_state(mSsl);
|
SSL_set_accept_state(mSsl);
|
||||||
|
|
||||||
if (!(mInBio = BIO_new(BIO_s_mem())) || !(mOutBio = BIO_new(BioMethods)))
|
mInBio = BIO_new(BIO_s_mem());
|
||||||
|
mOutBio = BIO_new(BioMethods);
|
||||||
|
if (!mInBio || !mOutBio)
|
||||||
throw std::runtime_error("Failed to create BIO");
|
throw std::runtime_error("Failed to create BIO");
|
||||||
|
|
||||||
BIO_set_mem_eof_return(mInBio, BIO_EOF);
|
BIO_set_mem_eof_return(mInBio, BIO_EOF);
|
||||||
@ -384,7 +400,7 @@ bool DtlsTransport::send(message_ptr message) {
|
|||||||
|
|
||||||
PLOG_VERBOSE << "Send size=" << message->size();
|
PLOG_VERBOSE << "Send size=" << message->size();
|
||||||
|
|
||||||
int ret = SSL_write(mSsl, message->data(), message->size());
|
int ret = SSL_write(mSsl, message->data(), int(message->size()));
|
||||||
return openssl::check(mSsl, ret);
|
return openssl::check(mSsl, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,6 +414,10 @@ void DtlsTransport::incoming(message_ptr message) {
|
|||||||
mIncomingQueue.push(message);
|
mIncomingQueue.push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DtlsTransport::postCreation() {
|
||||||
|
// Dummy
|
||||||
|
}
|
||||||
|
|
||||||
void DtlsTransport::postHandshake() {
|
void DtlsTransport::postHandshake() {
|
||||||
// Dummy
|
// Dummy
|
||||||
}
|
}
|
||||||
@ -418,11 +438,11 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
// Process pending messages
|
// Process pending messages
|
||||||
while (!mIncomingQueue.empty()) {
|
while (!mIncomingQueue.empty()) {
|
||||||
auto message = *mIncomingQueue.pop();
|
auto message = *mIncomingQueue.pop();
|
||||||
BIO_write(mInBio, message->data(), message->size());
|
BIO_write(mInBio, message->data(), int(message->size()));
|
||||||
|
|
||||||
if (state() == State::Connecting) {
|
if (state() == State::Connecting) {
|
||||||
// Continue the handshake
|
// Continue the handshake
|
||||||
int ret = SSL_do_handshake(mSsl);
|
ret = SSL_do_handshake(mSsl);
|
||||||
if (!openssl::check(mSsl, ret, "Handshake failed"))
|
if (!openssl::check(mSsl, ret, "Handshake failed"))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -436,7 +456,7 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
postHandshake();
|
postHandshake();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int ret = SSL_read(mSsl, buffer, bufferSize);
|
ret = SSL_read(mSsl, buffer, bufferSize);
|
||||||
if (!openssl::check(mSsl, ret))
|
if (!openssl::check(mSsl, ret))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -449,7 +469,7 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
std::optional<milliseconds> duration;
|
std::optional<milliseconds> duration;
|
||||||
if (state() == State::Connecting) {
|
if (state() == State::Connecting) {
|
||||||
// Warning: This function breaks the usual return value convention
|
// Warning: This function breaks the usual return value convention
|
||||||
int ret = DTLSv1_handle_timeout(mSsl);
|
ret = DTLSv1_handle_timeout(mSsl);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
throw std::runtime_error("Handshake timeout"); // write BIO can't fail
|
throw std::runtime_error("Handshake timeout"); // write BIO can't fail
|
||||||
} else if (ret > 0) {
|
} else if (ret > 0) {
|
||||||
@ -488,7 +508,7 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int DtlsTransport::CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx) {
|
int DtlsTransport::CertificateCallback(int /*preverify_ok*/, X509_STORE_CTX *ctx) {
|
||||||
SSL *ssl =
|
SSL *ssl =
|
||||||
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
||||||
DtlsTransport *t =
|
DtlsTransport *t =
|
||||||
@ -537,7 +557,7 @@ int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
|
|||||||
return inl; // can't fail
|
return inl; // can't fail
|
||||||
}
|
}
|
||||||
|
|
||||||
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) {
|
long DtlsTransport::BioMethodCtrl(BIO * /*bio*/, int cmd, long /*num*/, void * /*ptr*/) {
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case BIO_CTRL_FLUSH:
|
case BIO_CTRL_FLUSH:
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -52,6 +52,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void incoming(message_ptr message) override;
|
virtual void incoming(message_ptr message) override;
|
||||||
|
virtual void postCreation();
|
||||||
virtual void postHandshake();
|
virtual void postHandshake();
|
||||||
void runRecvLoop();
|
void runRecvLoop();
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ using namespace std::chrono_literals;
|
|||||||
|
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
using std::weak_ptr;
|
using std::weak_ptr;
|
||||||
|
using std::chrono::system_clock;
|
||||||
|
|
||||||
#if USE_JUICE
|
#if USE_JUICE
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
|
|
||||||
// Randomize servers order
|
// Randomize servers order
|
||||||
std::vector<IceServer> servers = config.iceServers;
|
std::vector<IceServer> servers = config.iceServers;
|
||||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||||
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
||||||
|
|
||||||
// Pick a STUN server (TURN support is not implemented in libjuice yet)
|
// Pick a STUN server (TURN support is not implemented in libjuice yet)
|
||||||
@ -82,7 +83,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
mStunHostname = server.hostname;
|
mStunHostname = server.hostname;
|
||||||
mStunService = server.service;
|
mStunService = server.service;
|
||||||
jconfig.stun_server_host = mStunHostname.c_str();
|
jconfig.stun_server_host = mStunHostname.c_str();
|
||||||
jconfig.stun_server_port = std::stoul(mStunService);
|
jconfig.stun_server_port = uint16_t(std::stoul(mStunService));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,7 +207,7 @@ void IceTransport::processCandidate(const string &candidate) {
|
|||||||
|
|
||||||
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
|
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
|
||||||
|
|
||||||
void IceTransport::StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
|
void IceTransport::StateChangeCallback(juice_agent_t *, juice_state_t state, void *user_ptr) {
|
||||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||||
try {
|
try {
|
||||||
iceTransport->processStateChange(static_cast<unsigned int>(state));
|
iceTransport->processStateChange(static_cast<unsigned int>(state));
|
||||||
@ -215,7 +216,7 @@ void IceTransport::StateChangeCallback(juice_agent_t *agent, juice_state_t state
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IceTransport::CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr) {
|
void IceTransport::CandidateCallback(juice_agent_t *, const char *sdp, void *user_ptr) {
|
||||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||||
try {
|
try {
|
||||||
iceTransport->processCandidate(sdp);
|
iceTransport->processCandidate(sdp);
|
||||||
@ -224,7 +225,7 @@ void IceTransport::CandidateCallback(juice_agent_t *agent, const char *sdp, void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IceTransport::GatheringDoneCallback(juice_agent_t *agent, void *user_ptr) {
|
void IceTransport::GatheringDoneCallback(juice_agent_t *, void *user_ptr) {
|
||||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||||
try {
|
try {
|
||||||
iceTransport->processGatheringDone();
|
iceTransport->processGatheringDone();
|
||||||
@ -233,8 +234,7 @@ void IceTransport::GatheringDoneCallback(juice_agent_t *agent, void *user_ptr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IceTransport::RecvCallback(juice_agent_t *agent, const char *data, size_t size,
|
void IceTransport::RecvCallback(juice_agent_t *, const char *data, size_t size, void *user_ptr) {
|
||||||
void *user_ptr) {
|
|
||||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||||
try {
|
try {
|
||||||
PLOG_VERBOSE << "Incoming size=" << size;
|
PLOG_VERBOSE << "Incoming size=" << size;
|
||||||
@ -337,7 +337,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
|
|
||||||
// Randomize order
|
// Randomize order
|
||||||
std::vector<IceServer> servers = config.iceServers;
|
std::vector<IceServer> servers = config.iceServers;
|
||||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||||
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
||||||
|
|
||||||
// Add one STUN server
|
// Add one STUN server
|
||||||
|
81
src/init.cpp
81
src/init.cpp
@ -21,6 +21,7 @@
|
|||||||
#include "certificate.hpp"
|
#include "certificate.hpp"
|
||||||
#include "dtlstransport.hpp"
|
#include "dtlstransport.hpp"
|
||||||
#include "sctptransport.hpp"
|
#include "sctptransport.hpp"
|
||||||
|
#include "threadpool.hpp"
|
||||||
#include "tls.hpp"
|
#include "tls.hpp"
|
||||||
|
|
||||||
#if RTC_ENABLE_WEBSOCKET
|
#if RTC_ENABLE_WEBSOCKET
|
||||||
@ -39,31 +40,19 @@ using std::shared_ptr;
|
|||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
std::weak_ptr<Init> Init::Weak;
|
namespace {
|
||||||
init_token Init::Global;
|
|
||||||
std::mutex Init::Mutex;
|
|
||||||
|
|
||||||
init_token Init::Token() {
|
void doInit() {
|
||||||
std::lock_guard lock(Mutex);
|
PLOG_DEBUG << "Global initialization";
|
||||||
|
|
||||||
if (!Global) {
|
|
||||||
if (auto token = Weak.lock())
|
|
||||||
Global = token;
|
|
||||||
else
|
|
||||||
Global = shared_ptr<Init>(new Init());
|
|
||||||
}
|
|
||||||
return Global;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Init::Cleanup() { Global.reset(); }
|
|
||||||
|
|
||||||
Init::Init() {
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
WSADATA wsaData;
|
WSADATA wsaData;
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
|
||||||
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
|
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
ThreadPool::Instance().spawn(THREADPOOL_SIZE);
|
||||||
|
|
||||||
#if USE_GNUTLS
|
#if USE_GNUTLS
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
#else
|
#else
|
||||||
@ -80,8 +69,12 @@ Init::Init() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Init::~Init() {
|
void doCleanup() {
|
||||||
|
PLOG_DEBUG << "Global cleanup";
|
||||||
|
|
||||||
|
ThreadPool::Instance().join();
|
||||||
CleanupCertificateCache();
|
CleanupCertificateCache();
|
||||||
|
|
||||||
SctpTransport::Cleanup();
|
SctpTransport::Cleanup();
|
||||||
DtlsTransport::Cleanup();
|
DtlsTransport::Cleanup();
|
||||||
#if RTC_ENABLE_WEBSOCKET
|
#if RTC_ENABLE_WEBSOCKET
|
||||||
@ -96,5 +89,57 @@ Init::~Init() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::weak_ptr<void> Init::Weak;
|
||||||
|
std::shared_ptr<void> *Init::Global = nullptr;
|
||||||
|
bool Init::Initialized = false;
|
||||||
|
std::recursive_mutex Init::Mutex;
|
||||||
|
|
||||||
|
init_token Init::Token() {
|
||||||
|
std::unique_lock lock(Mutex);
|
||||||
|
if (auto token = Weak.lock())
|
||||||
|
return token;
|
||||||
|
|
||||||
|
delete Global;
|
||||||
|
Global = new shared_ptr<void>(new Init());
|
||||||
|
Weak = *Global;
|
||||||
|
return *Global;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init::Preload() {
|
||||||
|
std::unique_lock lock(Mutex);
|
||||||
|
auto token = Token();
|
||||||
|
if (!Global)
|
||||||
|
Global = new shared_ptr<void>(token);
|
||||||
|
|
||||||
|
PLOG_DEBUG << "Preloading certificate";
|
||||||
|
make_certificate().wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init::Cleanup() {
|
||||||
|
std::unique_lock lock(Mutex);
|
||||||
|
delete Global;
|
||||||
|
Global = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Init::Init() {
|
||||||
|
// Mutex is locked by Token() here
|
||||||
|
if (!std::exchange(Initialized, true))
|
||||||
|
doInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Init::~Init() {
|
||||||
|
std::thread t([]() {
|
||||||
|
// We need to lock Mutex ourselves
|
||||||
|
std::unique_lock lock(Mutex);
|
||||||
|
if (Global)
|
||||||
|
return;
|
||||||
|
if (std::exchange(Initialized, false))
|
||||||
|
doCleanup();
|
||||||
|
});
|
||||||
|
t.detach();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
|
|
||||||
#include "plog/Appenders/ColorConsoleAppender.h"
|
#include "plog/Appenders/ColorConsoleAppender.h"
|
||||||
|
#include "plog/Formatters/TxtFormatter.h"
|
||||||
|
#include "plog/Init.h"
|
||||||
#include "plog/Log.h"
|
#include "plog/Log.h"
|
||||||
#include "plog/Logger.h"
|
#include "plog/Logger.h"
|
||||||
|
|
||||||
|
@ -18,9 +18,12 @@
|
|||||||
|
|
||||||
#include "peerconnection.hpp"
|
#include "peerconnection.hpp"
|
||||||
#include "certificate.hpp"
|
#include "certificate.hpp"
|
||||||
|
#include "include.hpp"
|
||||||
|
#include "processor.hpp"
|
||||||
|
#include "threadpool.hpp"
|
||||||
|
|
||||||
#include "dtlstransport.hpp"
|
#include "dtlstransport.hpp"
|
||||||
#include "icetransport.hpp"
|
#include "icetransport.hpp"
|
||||||
#include "include.hpp"
|
|
||||||
#include "sctptransport.hpp"
|
#include "sctptransport.hpp"
|
||||||
|
|
||||||
#if RTC_ENABLE_MEDIA
|
#if RTC_ENABLE_MEDIA
|
||||||
@ -39,9 +42,12 @@ using std::weak_ptr;
|
|||||||
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
|
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
|
||||||
|
|
||||||
PeerConnection::PeerConnection(const Configuration &config)
|
PeerConnection::PeerConnection(const Configuration &config)
|
||||||
: mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New),
|
: mConfig(config), mCertificate(make_certificate()), mProcessor(std::make_unique<Processor>()),
|
||||||
mGatheringState(GatheringState::New) {
|
mState(State::New), mGatheringState(GatheringState::New) {
|
||||||
PLOG_VERBOSE << "Creating PeerConnection";
|
PLOG_VERBOSE << "Creating PeerConnection";
|
||||||
|
|
||||||
|
if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
|
||||||
|
throw std::invalid_argument("Invalid port range");
|
||||||
}
|
}
|
||||||
|
|
||||||
PeerConnection::~PeerConnection() {
|
PeerConnection::~PeerConnection() {
|
||||||
@ -89,18 +95,20 @@ void PeerConnection::setLocalDescription(std::optional<Description> description)
|
|||||||
|
|
||||||
void PeerConnection::setRemoteDescription(Description description) {
|
void PeerConnection::setRemoteDescription(Description description) {
|
||||||
description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
|
description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
|
||||||
auto remoteCandidates = description.extractCandidates();
|
auto type = description.type();
|
||||||
|
auto remoteCandidates = description.extractCandidates(); // Candidates will be added at the end
|
||||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
|
||||||
mRemoteDescription.emplace(std::move(description));
|
|
||||||
|
|
||||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
if (!iceTransport)
|
if (!iceTransport)
|
||||||
iceTransport = initIceTransport(Description::Role::ActPass);
|
iceTransport = initIceTransport(Description::Role::ActPass);
|
||||||
|
iceTransport->setRemoteDescription(description);
|
||||||
|
|
||||||
iceTransport->setRemoteDescription(*mRemoteDescription);
|
{
|
||||||
|
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||||
|
mRemoteDescription.emplace(std::move(description));
|
||||||
|
}
|
||||||
|
|
||||||
if (mRemoteDescription->type() == Description::Type::Offer) {
|
if (type == Description::Type::Offer) {
|
||||||
// This is an offer and we are the answerer.
|
// This is an offer and we are the answerer.
|
||||||
Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer);
|
Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer);
|
||||||
localDescription.addMedia(description); // blindly accept media
|
localDescription.addMedia(description); // blindly accept media
|
||||||
@ -131,18 +139,16 @@ void PeerConnection::setRemoteDescription(Description description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::addRemoteCandidate(Candidate candidate) {
|
void PeerConnection::addRemoteCandidate(Candidate candidate) {
|
||||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
|
||||||
|
|
||||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
if (!mRemoteDescription || !iceTransport)
|
if (!mRemoteDescription || !iceTransport)
|
||||||
throw std::logic_error("Remote candidate set without remote description");
|
throw std::logic_error("Remote candidate set without remote description");
|
||||||
|
|
||||||
mRemoteDescription->addCandidate(candidate);
|
|
||||||
|
|
||||||
if (candidate.resolve(Candidate::ResolveMode::Simple)) {
|
if (candidate.resolve(Candidate::ResolveMode::Simple)) {
|
||||||
iceTransport->addRemoteCandidate(candidate);
|
iceTransport->addRemoteCandidate(candidate);
|
||||||
} else {
|
} else {
|
||||||
// OK, we might need a lookup, do it asynchronously
|
// OK, we might need a lookup, do it asynchronously
|
||||||
|
// We don't use the thread pool because we have no control on the timeout
|
||||||
weak_ptr<IceTransport> weakIceTransport{iceTransport};
|
weak_ptr<IceTransport> weakIceTransport{iceTransport};
|
||||||
std::thread t([weakIceTransport, candidate]() mutable {
|
std::thread t([weakIceTransport, candidate]() mutable {
|
||||||
if (candidate.resolve(Candidate::ResolveMode::Lookup))
|
if (candidate.resolve(Candidate::ResolveMode::Lookup))
|
||||||
@ -151,6 +157,9 @@ void PeerConnection::addRemoteCandidate(Candidate candidate) {
|
|||||||
});
|
});
|
||||||
t.detach();
|
t.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||||
|
mRemoteDescription->addCandidate(candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<string> PeerConnection::localAddress() const {
|
std::optional<string> PeerConnection::localAddress() const {
|
||||||
@ -222,7 +231,7 @@ void PeerConnection::sendMedia(const binary &packet) {
|
|||||||
outgoingMedia(make_message(packet.begin(), packet.end(), Message::Binary));
|
outgoingMedia(make_message(packet.begin(), packet.end(), Message::Binary));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::send(const byte *packet, size_t size) {
|
void PeerConnection::sendMedia(const byte *packet, size_t size) {
|
||||||
outgoingMedia(make_message(packet, packet + size, Message::Binary));
|
outgoingMedia(make_message(packet, packet + size, Message::Binary));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +248,7 @@ void PeerConnection::outgoingMedia(message_ptr message) {
|
|||||||
if (!transport)
|
if (!transport)
|
||||||
throw std::runtime_error("PeerConnection is not open");
|
throw std::runtime_error("PeerConnection is not open");
|
||||||
|
|
||||||
std::dynamic_pointer_cast<DtlsSrtpTransport>(transport)->send(message);
|
std::dynamic_pointer_cast<DtlsSrtpTransport>(transport)->sendMedia(message);
|
||||||
#else
|
#else
|
||||||
PLOG_WARNING << "Ignoring sent media (not compiled with SRTP support)";
|
PLOG_WARNING << "Ignoring sent media (not compiled with SRTP support)";
|
||||||
#endif
|
#endif
|
||||||
@ -440,8 +449,7 @@ void PeerConnection::closeTransports() {
|
|||||||
auto sctp = std::atomic_exchange(&mSctpTransport, decltype(mSctpTransport)(nullptr));
|
auto sctp = std::atomic_exchange(&mSctpTransport, decltype(mSctpTransport)(nullptr));
|
||||||
auto dtls = std::atomic_exchange(&mDtlsTransport, decltype(mDtlsTransport)(nullptr));
|
auto dtls = std::atomic_exchange(&mDtlsTransport, decltype(mDtlsTransport)(nullptr));
|
||||||
auto ice = std::atomic_exchange(&mIceTransport, decltype(mIceTransport)(nullptr));
|
auto ice = std::atomic_exchange(&mIceTransport, decltype(mIceTransport)(nullptr));
|
||||||
if (sctp || dtls || ice) {
|
ThreadPool::Instance().enqueue([sctp, dtls, ice]() mutable {
|
||||||
std::thread t([sctp, dtls, ice, token = mInitToken]() mutable {
|
|
||||||
if (sctp)
|
if (sctp)
|
||||||
sctp->stop();
|
sctp->stop();
|
||||||
if (dtls)
|
if (dtls)
|
||||||
@ -453,8 +461,6 @@ void PeerConnection::closeTransports() {
|
|||||||
dtls.reset();
|
dtls.reset();
|
||||||
ice.reset();
|
ice.reset();
|
||||||
});
|
});
|
||||||
t.detach();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::endLocalCandidates() {
|
void PeerConnection::endLocalCandidates() {
|
||||||
@ -478,7 +484,7 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto channel = findDataChannel(message->stream);
|
auto channel = findDataChannel(uint16_t(message->stream));
|
||||||
|
|
||||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||||
@ -497,7 +503,7 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
|||||||
mDataChannels.insert(std::make_pair(message->stream, channel));
|
mDataChannels.insert(std::make_pair(message->stream, channel));
|
||||||
} else {
|
} else {
|
||||||
// Invalid, close the DataChannel
|
// Invalid, close the DataChannel
|
||||||
sctpTransport->close(message->stream);
|
sctpTransport->closeStream(message->stream);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -589,18 +595,26 @@ void PeerConnection::remoteCloseDataChannels() {
|
|||||||
|
|
||||||
void PeerConnection::processLocalDescription(Description description) {
|
void PeerConnection::processLocalDescription(Description description) {
|
||||||
std::optional<uint16_t> remoteSctpPort;
|
std::optional<uint16_t> remoteSctpPort;
|
||||||
if (auto remote = remoteDescription())
|
std::optional<string> remoteDataMid;
|
||||||
|
if (auto remote = remoteDescription()) {
|
||||||
|
remoteDataMid = remote->dataMid();
|
||||||
remoteSctpPort = remote->sctpPort();
|
remoteSctpPort = remote->sctpPort();
|
||||||
|
}
|
||||||
|
|
||||||
auto certificate = mCertificate.get(); // wait for certificate if not ready
|
auto certificate = mCertificate.get(); // wait for certificate if not ready
|
||||||
|
|
||||||
|
{
|
||||||
std::lock_guard lock(mLocalDescriptionMutex);
|
std::lock_guard lock(mLocalDescriptionMutex);
|
||||||
mLocalDescription.emplace(std::move(description));
|
mLocalDescription.emplace(std::move(description));
|
||||||
|
if (remoteDataMid)
|
||||||
|
mLocalDescription->setDataMid(*remoteDataMid);
|
||||||
|
|
||||||
mLocalDescription->setFingerprint(certificate->fingerprint());
|
mLocalDescription->setFingerprint(certificate->fingerprint());
|
||||||
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
||||||
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
mLocalDescriptionCallback(*mLocalDescription);
|
mProcessor->enqueue([this]() { mLocalDescriptionCallback(*mLocalDescription); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::processLocalCandidate(Candidate candidate) {
|
void PeerConnection::processLocalCandidate(Candidate candidate) {
|
||||||
@ -610,7 +624,8 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
|
|||||||
|
|
||||||
mLocalDescription->addCandidate(candidate);
|
mLocalDescription->addCandidate(candidate);
|
||||||
|
|
||||||
mLocalCandidateCallback(candidate);
|
mProcessor->enqueue(
|
||||||
|
[this, candidate = std::move(candidate)]() { mLocalCandidateCallback(candidate); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
|
void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
|
||||||
@ -618,7 +633,8 @@ void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
|
|||||||
if (!dataChannel)
|
if (!dataChannel)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mDataChannelCallback(dataChannel);
|
mProcessor->enqueue(
|
||||||
|
[this, dataChannel = std::move(dataChannel)]() { mDataChannelCallback(dataChannel); });
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PeerConnection::changeState(State state) {
|
bool PeerConnection::changeState(State state) {
|
||||||
@ -632,13 +648,13 @@ bool PeerConnection::changeState(State state) {
|
|||||||
|
|
||||||
} while (!mState.compare_exchange_weak(current, state));
|
} while (!mState.compare_exchange_weak(current, state));
|
||||||
|
|
||||||
mStateChangeCallback(state);
|
mProcessor->enqueue([this, state]() { mStateChangeCallback(state); });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PeerConnection::changeGatheringState(GatheringState state) {
|
bool PeerConnection::changeGatheringState(GatheringState state) {
|
||||||
if (mGatheringState.exchange(state) != state)
|
if (mGatheringState.exchange(state) != state)
|
||||||
mGatheringStateChangeCallback(state);
|
mProcessor->enqueue([this, state] { mGatheringStateChangeCallback(state); });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,13 +667,14 @@ void PeerConnection::resetCallbacks() {
|
|||||||
mGatheringStateChangeCallback = nullptr;
|
mGatheringStateChangeCallback = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PeerConnection::getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote) {
|
bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] CandidateInfo *local,
|
||||||
#if not USE_JUICE
|
[[maybe_unused]] CandidateInfo *remote) {
|
||||||
|
#if USE_JUICE
|
||||||
|
PLOG_WARNING << "getSelectedCandidatePair() is not implemented for libjuice";
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
return iceTransport->getSelectedCandidatePair(local, remote);
|
return iceTransport->getSelectedCandidatePair(local, remote);
|
||||||
#else
|
|
||||||
PLOG_WARNING << "getSelectedCandidatePair is not implemented for libjuice";
|
|
||||||
return false;
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
44
src/processor.cpp
Normal file
44
src/processor.cpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "processor.hpp"
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
Processor::~Processor() { join(); }
|
||||||
|
|
||||||
|
void Processor::join() {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
mCondition.wait(lock, [this]() { return !mPending && mTasks.empty(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Processor::schedule() {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
if (mTasks.empty()) {
|
||||||
|
// No more tasks
|
||||||
|
mPending = false;
|
||||||
|
mCondition.notify_all();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadPool::Instance().enqueue(std::move(mTasks.front()));
|
||||||
|
mTasks.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
92
src/processor.hpp
Normal file
92
src/processor.hpp
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RTC_PROCESSOR_H
|
||||||
|
#define RTC_PROCESSOR_H
|
||||||
|
|
||||||
|
#include "include.hpp"
|
||||||
|
#include "init.hpp"
|
||||||
|
#include "threadpool.hpp"
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
// Processed tasks in order by delegating them to the thread pool
|
||||||
|
class Processor final {
|
||||||
|
public:
|
||||||
|
Processor() = default;
|
||||||
|
~Processor();
|
||||||
|
|
||||||
|
Processor(const Processor &) = delete;
|
||||||
|
Processor &operator=(const Processor &) = delete;
|
||||||
|
Processor(Processor &&) = delete;
|
||||||
|
Processor &operator=(Processor &&) = delete;
|
||||||
|
|
||||||
|
void join();
|
||||||
|
|
||||||
|
template <class F, class... Args>
|
||||||
|
auto enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...>;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void schedule();
|
||||||
|
|
||||||
|
// Keep an init token
|
||||||
|
const init_token mInitToken = Init::Token();
|
||||||
|
|
||||||
|
std::queue<std::function<void()>> mTasks;
|
||||||
|
bool mPending = false; // true iff a task is pending in the thread pool
|
||||||
|
|
||||||
|
mutable std::mutex mMutex;
|
||||||
|
std::condition_variable mCondition;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class F, class... Args>
|
||||||
|
auto Processor::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
|
||||||
|
auto task = std::make_shared<std::packaged_task<R()>>(
|
||||||
|
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
|
||||||
|
std::future<R> result = task->get_future();
|
||||||
|
|
||||||
|
auto bundle = [this, task = std::move(task)]() {
|
||||||
|
try {
|
||||||
|
(*task)();
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
PLOG_WARNING << "Unhandled exception in task: " << e.what();
|
||||||
|
}
|
||||||
|
schedule(); // chain the next task
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!mPending) {
|
||||||
|
ThreadPool::Instance().enqueue(std::move(bundle));
|
||||||
|
mPending = true;
|
||||||
|
} else {
|
||||||
|
mTasks.emplace(std::move(bundle));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
428
src/rtc.cpp
428
src/rtc.cpp
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||||
*
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
@ -18,32 +18,32 @@
|
|||||||
|
|
||||||
#include "include.hpp"
|
#include "include.hpp"
|
||||||
|
|
||||||
#include "datachannel.hpp"
|
#include "rtc.h"
|
||||||
#include "peerconnection.hpp"
|
|
||||||
|
|
||||||
|
#include "datachannel.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "peerconnection.hpp"
|
||||||
#if RTC_ENABLE_WEBSOCKET
|
#if RTC_ENABLE_WEBSOCKET
|
||||||
#include "websocket.hpp"
|
#include "websocket.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <rtc.h>
|
#include "plog/Formatters/FuncMessageFormatter.h"
|
||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <codecvt>
|
||||||
|
#include <locale>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
using std::string;
|
using std::string;
|
||||||
|
|
||||||
#define CATCH(statement) \
|
|
||||||
try { \
|
|
||||||
statement; \
|
|
||||||
} catch (const std::exception &e) { \
|
|
||||||
PLOG_ERROR << e.what(); \
|
|
||||||
return -1; \
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
|
std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
|
||||||
@ -55,36 +55,38 @@ std::unordered_map<int, void *> userPointerMap;
|
|||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
int lastId = 0;
|
int lastId = 0;
|
||||||
|
|
||||||
void *getUserPointer(int id) {
|
std::optional<void *> getUserPointer(int id) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
auto it = userPointerMap.find(id);
|
auto it = userPointerMap.find(id);
|
||||||
return it != userPointerMap.end() ? it->second : nullptr;
|
return it != userPointerMap.end() ? std::make_optional(it->second) : nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUserPointer(int i, void *ptr) {
|
void setUserPointer(int i, void *ptr) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
if (ptr)
|
userPointerMap[i] = ptr;
|
||||||
userPointerMap.insert(std::make_pair(i, ptr));
|
|
||||||
else
|
|
||||||
userPointerMap.erase(i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PeerConnection> getPeerConnection(int id) {
|
shared_ptr<PeerConnection> getPeerConnection(int id) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
auto it = peerConnectionMap.find(id);
|
if (auto it = peerConnectionMap.find(id); it != peerConnectionMap.end())
|
||||||
return it != peerConnectionMap.end() ? it->second : nullptr;
|
return it->second;
|
||||||
|
else
|
||||||
|
throw std::invalid_argument("PeerConnection ID does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<DataChannel> getDataChannel(int id) {
|
shared_ptr<DataChannel> getDataChannel(int id) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
auto it = dataChannelMap.find(id);
|
if (auto it = dataChannelMap.find(id); it != dataChannelMap.end())
|
||||||
return it != dataChannelMap.end() ? it->second : nullptr;
|
return it->second;
|
||||||
|
else
|
||||||
|
throw std::invalid_argument("DataChannel ID does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
|
int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
int pc = ++lastId;
|
int pc = ++lastId;
|
||||||
peerConnectionMap.emplace(std::make_pair(pc, ptr));
|
peerConnectionMap.emplace(std::make_pair(pc, ptr));
|
||||||
|
userPointerMap.emplace(std::make_pair(pc, nullptr));
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,45 +94,46 @@ int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
|
|||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
int dc = ++lastId;
|
int dc = ++lastId;
|
||||||
dataChannelMap.emplace(std::make_pair(dc, ptr));
|
dataChannelMap.emplace(std::make_pair(dc, ptr));
|
||||||
|
userPointerMap.emplace(std::make_pair(dc, nullptr));
|
||||||
return dc;
|
return dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool erasePeerConnection(int pc) {
|
void erasePeerConnection(int pc) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
if (peerConnectionMap.erase(pc) == 0)
|
if (peerConnectionMap.erase(pc) == 0)
|
||||||
return false;
|
throw std::invalid_argument("PeerConnection ID does not exist");
|
||||||
userPointerMap.erase(pc);
|
userPointerMap.erase(pc);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eraseDataChannel(int dc) {
|
void eraseDataChannel(int dc) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
if (dataChannelMap.erase(dc) == 0)
|
if (dataChannelMap.erase(dc) == 0)
|
||||||
return false;
|
throw std::invalid_argument("DataChannel ID does not exist");
|
||||||
userPointerMap.erase(dc);
|
userPointerMap.erase(dc);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if RTC_ENABLE_WEBSOCKET
|
#if RTC_ENABLE_WEBSOCKET
|
||||||
shared_ptr<WebSocket> getWebSocket(int id) {
|
shared_ptr<WebSocket> getWebSocket(int id) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
auto it = webSocketMap.find(id);
|
if (auto it = webSocketMap.find(id); it != webSocketMap.end())
|
||||||
return it != webSocketMap.end() ? it->second : nullptr;
|
return it->second;
|
||||||
|
else
|
||||||
|
throw std::invalid_argument("WebSocket ID does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
int emplaceWebSocket(shared_ptr<WebSocket> ptr) {
|
int emplaceWebSocket(shared_ptr<WebSocket> ptr) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
int ws = ++lastId;
|
int ws = ++lastId;
|
||||||
webSocketMap.emplace(std::make_pair(ws, ptr));
|
webSocketMap.emplace(std::make_pair(ws, ptr));
|
||||||
|
userPointerMap.emplace(std::make_pair(ws, nullptr));
|
||||||
return ws;
|
return ws;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eraseWebSocket(int ws) {
|
void eraseWebSocket(int ws) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
if (webSocketMap.erase(ws) == 0)
|
if (webSocketMap.erase(ws) == 0)
|
||||||
return false;
|
throw std::invalid_argument("WebSocket ID does not exist");
|
||||||
userPointerMap.erase(ws);
|
userPointerMap.erase(ws);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -142,16 +145,75 @@ shared_ptr<Channel> getChannel(int id) {
|
|||||||
if (auto it = webSocketMap.find(id); it != webSocketMap.end())
|
if (auto it = webSocketMap.find(id); it != webSocketMap.end())
|
||||||
return it->second;
|
return it->second;
|
||||||
#endif
|
#endif
|
||||||
return nullptr;
|
throw std::invalid_argument("DataChannel or WebSocket ID does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename F> int wrap(F func) {
|
||||||
|
try {
|
||||||
|
return int(func());
|
||||||
|
|
||||||
|
} catch (const std::invalid_argument &e) {
|
||||||
|
PLOG_ERROR << e.what();
|
||||||
|
return RTC_ERR_INVALID;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
PLOG_ERROR << e.what();
|
||||||
|
return RTC_ERR_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WRAP(statement) \
|
||||||
|
wrap([&]() { \
|
||||||
|
statement; \
|
||||||
|
return RTC_ERR_SUCCESS; \
|
||||||
|
})
|
||||||
|
|
||||||
|
class plog_appender : public plog::IAppender {
|
||||||
|
public:
|
||||||
|
plog_appender(rtcLogCallbackFunc cb = nullptr) { set_callback(cb); }
|
||||||
|
|
||||||
|
void set_callback(rtcLogCallbackFunc cb) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
callback = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const plog::Record &record) override {
|
||||||
|
plog::Severity severity = record.getSeverity();
|
||||||
|
auto formatted = plog::FuncMessageFormatter::format(record);
|
||||||
|
formatted.pop_back(); // remove newline
|
||||||
|
#ifdef _WIN32
|
||||||
|
using convert_type = std::codecvt_utf8<wchar_t>;
|
||||||
|
std::wstring_convert<convert_type, wchar_t> converter;
|
||||||
|
std::string str = converter.to_bytes(formatted);
|
||||||
|
#else
|
||||||
|
std::string str = formatted;
|
||||||
|
#endif
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
if (callback)
|
||||||
|
callback(static_cast<rtcLogLevel>(record.getSeverity()), str.c_str());
|
||||||
|
else
|
||||||
|
std::cout << plog::severityToString(severity) << " " << str << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
rtcLogCallbackFunc callback;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void rtcInitLogger(rtcLogLevel level) { InitLogger(static_cast<LogLevel>(level)); }
|
void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
|
||||||
|
static std::optional<plog_appender> appender;
|
||||||
|
if (appender)
|
||||||
|
appender->set_callback(cb);
|
||||||
|
else if (cb)
|
||||||
|
appender.emplace(plog_appender(cb));
|
||||||
|
|
||||||
|
InitLogger(static_cast<plog::Severity>(level), appender ? &appender.value() : nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
|
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
|
||||||
|
|
||||||
int rtcCreatePeerConnection(const rtcConfiguration *config) {
|
int rtcCreatePeerConnection(const rtcConfiguration *config) {
|
||||||
|
return WRAP({
|
||||||
Configuration c;
|
Configuration c;
|
||||||
for (int i = 0; i < config->iceServersCount; ++i)
|
for (int i = 0; i < config->iceServersCount; ++i)
|
||||||
c.iceServers.emplace_back(string(config->iceServers[i]));
|
c.iceServers.emplace_back(string(config->iceServers[i]));
|
||||||
@ -162,13 +224,12 @@ int rtcCreatePeerConnection(const rtcConfiguration *config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
|
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcDeletePeerConnection(int pc) {
|
int rtcDeletePeerConnection(int pc) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
peerConnection->onDataChannel(nullptr);
|
peerConnection->onDataChannel(nullptr);
|
||||||
peerConnection->onLocalDescription(nullptr);
|
peerConnection->onLocalDescription(nullptr);
|
||||||
peerConnection->onLocalCandidate(nullptr);
|
peerConnection->onLocalCandidate(nullptr);
|
||||||
@ -176,25 +237,22 @@ int rtcDeletePeerConnection(int pc) {
|
|||||||
peerConnection->onGatheringStateChange(nullptr);
|
peerConnection->onGatheringStateChange(nullptr);
|
||||||
|
|
||||||
erasePeerConnection(pc);
|
erasePeerConnection(pc);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcCreateDataChannel(int pc, const char *label) {
|
int rtcCreateDataChannel(int pc, const char *label) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
|
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
|
||||||
void *ptr = getUserPointer(pc);
|
if (auto ptr = getUserPointer(pc))
|
||||||
rtcSetUserPointer(dc, ptr);
|
rtcSetUserPointer(dc, *ptr);
|
||||||
return dc;
|
return dc;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcDeleteDataChannel(int dc) {
|
int rtcDeleteDataChannel(int dc) {
|
||||||
|
return WRAP({
|
||||||
auto dataChannel = getDataChannel(dc);
|
auto dataChannel = getDataChannel(dc);
|
||||||
if (!dataChannel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
dataChannel->onOpen(nullptr);
|
dataChannel->onOpen(nullptr);
|
||||||
dataChannel->onClosed(nullptr);
|
dataChannel->onClosed(nullptr);
|
||||||
dataChannel->onError(nullptr);
|
dataChannel->onError(nullptr);
|
||||||
@ -203,21 +261,21 @@ int rtcDeleteDataChannel(int dc) {
|
|||||||
dataChannel->onAvailable(nullptr);
|
dataChannel->onAvailable(nullptr);
|
||||||
|
|
||||||
eraseDataChannel(dc);
|
eraseDataChannel(dc);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#if RTC_ENABLE_WEBSOCKET
|
#if RTC_ENABLE_WEBSOCKET
|
||||||
int rtcCreateWebSocket(const char *url) {
|
int rtcCreateWebSocket(const char *url) {
|
||||||
|
return WRAP({
|
||||||
auto ws = std::make_shared<WebSocket>();
|
auto ws = std::make_shared<WebSocket>();
|
||||||
ws->open(url);
|
ws->open(url);
|
||||||
return emplaceWebSocket(ws);
|
return emplaceWebSocket(ws);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcDeleteWebsocket(int ws) {
|
int rtcDeleteWebsocket(int ws) {
|
||||||
|
return WRAP({
|
||||||
auto webSocket = getWebSocket(ws);
|
auto webSocket = getWebSocket(ws);
|
||||||
if (!webSocket)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
webSocket->onOpen(nullptr);
|
webSocket->onOpen(nullptr);
|
||||||
webSocket->onClosed(nullptr);
|
webSocket->onClosed(nullptr);
|
||||||
webSocket->onError(nullptr);
|
webSocket->onError(nullptr);
|
||||||
@ -226,284 +284,295 @@ int rtcDeleteWebsocket(int ws) {
|
|||||||
webSocket->onAvailable(nullptr);
|
webSocket->onAvailable(nullptr);
|
||||||
|
|
||||||
eraseWebSocket(ws);
|
eraseWebSocket(ws);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb) {
|
int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
|
peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
|
||||||
int dc = emplaceDataChannel(dataChannel);
|
int dc = emplaceDataChannel(dataChannel);
|
||||||
void *ptr = getUserPointer(pc);
|
if (auto ptr = getUserPointer(pc)) {
|
||||||
rtcSetUserPointer(dc, ptr);
|
rtcSetUserPointer(dc, *ptr);
|
||||||
cb(dc, ptr);
|
cb(dc, *ptr);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
peerConnection->onDataChannel(nullptr);
|
peerConnection->onDataChannel(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
|
int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
|
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
|
||||||
cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
|
if (auto ptr = getUserPointer(pc))
|
||||||
|
cb(string(desc).c_str(), desc.typeString().c_str(), *ptr);
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
peerConnection->onLocalDescription(nullptr);
|
peerConnection->onLocalDescription(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) {
|
int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
|
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
|
||||||
cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
|
if (auto ptr = getUserPointer(pc))
|
||||||
|
cb(cand.candidate().c_str(), cand.mid().c_str(), *ptr);
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
peerConnection->onLocalCandidate(nullptr);
|
peerConnection->onLocalCandidate(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) {
|
int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
|
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
|
||||||
cb(static_cast<rtcState>(state), getUserPointer(pc));
|
if (auto ptr = getUserPointer(pc))
|
||||||
|
cb(static_cast<rtcState>(state), *ptr);
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
peerConnection->onStateChange(nullptr);
|
peerConnection->onStateChange(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) {
|
int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
|
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
|
||||||
cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
|
if (auto ptr = getUserPointer(pc))
|
||||||
|
cb(static_cast<rtcGatheringState>(state), *ptr);
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
peerConnection->onGatheringStateChange(nullptr);
|
peerConnection->onGatheringStateChange(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
|
if (!sdp)
|
||||||
return 0;
|
throw std::invalid_argument("Unexpected null pointer");
|
||||||
|
|
||||||
|
peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
|
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
CATCH(peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}))
|
if (!cand)
|
||||||
return 0;
|
throw std::invalid_argument("Unexpected null pointer");
|
||||||
|
|
||||||
|
peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
if (!buffer)
|
||||||
|
throw std::invalid_argument("Unexpected null pointer");
|
||||||
|
|
||||||
|
if (size <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (auto addr = peerConnection->localAddress()) {
|
if (auto addr = peerConnection->localAddress()) {
|
||||||
size = std::min(size_t(size - 1), addr->size());
|
const char *data = addr->data();
|
||||||
std::copy(addr->data(), addr->data() + size, buffer);
|
size = std::min(size - 1, int(addr->size()));
|
||||||
|
std::copy(data, data + size, buffer);
|
||||||
buffer[size] = '\0';
|
buffer[size] = '\0';
|
||||||
return size + 1;
|
return size + 1;
|
||||||
}
|
}
|
||||||
return -1;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
||||||
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (!peerConnection)
|
|
||||||
return -1;
|
if (!buffer)
|
||||||
|
throw std::invalid_argument("Unexpected null pointer");
|
||||||
|
|
||||||
|
if (size <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (auto addr = peerConnection->remoteAddress()) {
|
if (auto addr = peerConnection->remoteAddress()) {
|
||||||
size = std::min(size_t(size - 1), addr->size());
|
const char *data = addr->data();
|
||||||
std::copy(addr->data(), addr->data() + size, buffer);
|
size = std::min(size - 1, int(addr->size()));
|
||||||
|
std::copy(data, data + size, buffer);
|
||||||
buffer[size] = '\0';
|
buffer[size] = '\0';
|
||||||
return size + 1;
|
return int(size + 1);
|
||||||
}
|
}
|
||||||
return -1;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
||||||
|
return WRAP({
|
||||||
auto dataChannel = getDataChannel(dc);
|
auto dataChannel = getDataChannel(dc);
|
||||||
if (!dataChannel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (!size)
|
if (!buffer)
|
||||||
|
throw std::invalid_argument("Unexpected null pointer");
|
||||||
|
|
||||||
|
if (size <= 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
string label = dataChannel->label();
|
string label = dataChannel->label();
|
||||||
size = std::min(size_t(size - 1), label.size());
|
const char *data = label.data();
|
||||||
std::copy(label.data(), label.data() + size, buffer);
|
size = std::min(size - 1, int(label.size()));
|
||||||
|
std::copy(data, data + size, buffer);
|
||||||
buffer[size] = '\0';
|
buffer[size] = '\0';
|
||||||
return size + 1;
|
return int(size + 1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetOpenCallback(int id, openCallbackFunc cb) {
|
int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
channel->onOpen([id, cb]() { cb(getUserPointer(id)); });
|
channel->onOpen([id, cb]() {
|
||||||
|
if (auto ptr = getUserPointer(id))
|
||||||
|
cb(*ptr);
|
||||||
|
});
|
||||||
else
|
else
|
||||||
channel->onOpen(nullptr);
|
channel->onOpen(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetClosedCallback(int id, closedCallbackFunc cb) {
|
int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
channel->onClosed([id, cb]() { cb(getUserPointer(id)); });
|
channel->onClosed([id, cb]() {
|
||||||
|
if (auto ptr = getUserPointer(id))
|
||||||
|
cb(*ptr);
|
||||||
|
});
|
||||||
else
|
else
|
||||||
channel->onClosed(nullptr);
|
channel->onClosed(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetErrorCallback(int id, errorCallbackFunc cb) {
|
int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
channel->onError([id, cb](const string &error) { cb(error.c_str(), getUserPointer(id)); });
|
channel->onError([id, cb](const string &error) {
|
||||||
|
if (auto ptr = getUserPointer(id))
|
||||||
|
cb(error.c_str(), *ptr);
|
||||||
|
});
|
||||||
else
|
else
|
||||||
channel->onError(nullptr);
|
channel->onError(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetMessageCallback(int id, messageCallbackFunc cb) {
|
int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
channel->onMessage(
|
channel->onMessage(
|
||||||
[id, cb](const binary &b) {
|
[id, cb](const binary &b) {
|
||||||
cb(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(id));
|
if (auto ptr = getUserPointer(id))
|
||||||
|
cb(reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
|
||||||
},
|
},
|
||||||
[id, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(id)); });
|
[id, cb](const string &s) {
|
||||||
|
if (auto ptr = getUserPointer(id))
|
||||||
|
cb(s.c_str(), -int(s.size() + 1), *ptr);
|
||||||
|
});
|
||||||
else
|
else
|
||||||
channel->onMessage(nullptr);
|
channel->onMessage(nullptr);
|
||||||
|
});
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSendMessage(int id, const char *data, int size) {
|
int rtcSendMessage(int id, const char *data, int size) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
|
||||||
return -1;
|
if (!data)
|
||||||
|
throw std::invalid_argument("Unexpected null pointer");
|
||||||
|
|
||||||
if (size >= 0) {
|
if (size >= 0) {
|
||||||
auto b = reinterpret_cast<const byte *>(data);
|
auto b = reinterpret_cast<const byte *>(data);
|
||||||
CATCH(channel->send(binary(b, b + size)));
|
channel->send(binary(b, b + size));
|
||||||
return size;
|
return size;
|
||||||
} else {
|
} else {
|
||||||
string str(data);
|
string str(data);
|
||||||
int len = str.size();
|
int len = int(str.size());
|
||||||
CATCH(channel->send(std::move(str)));
|
channel->send(std::move(str));
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcGetBufferedAmount(int id) {
|
int rtcGetBufferedAmount(int id) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
return int(channel->bufferedAmount());
|
||||||
return -1;
|
});
|
||||||
|
|
||||||
CATCH(return int(channel->bufferedAmount()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetBufferedAmountLowThreshold(int id, int amount) {
|
int rtcSetBufferedAmountLowThreshold(int id, int amount) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
channel->setBufferedAmountLowThreshold(size_t(amount));
|
||||||
return -1;
|
});
|
||||||
|
|
||||||
CATCH(channel->setBufferedAmountLowThreshold(size_t(amount)));
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetBufferedAmountLowCallback(int id, bufferedAmountLowCallbackFunc cb) {
|
int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
channel->onBufferedAmountLow([id, cb]() { cb(getUserPointer(id)); });
|
channel->onBufferedAmountLow([id, cb]() {
|
||||||
|
if (auto ptr = getUserPointer(id))
|
||||||
|
cb(*ptr);
|
||||||
|
});
|
||||||
else
|
else
|
||||||
channel->onBufferedAmountLow(nullptr);
|
channel->onBufferedAmountLow(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcGetAvailableAmount(int id) {
|
int rtcGetAvailableAmount(int id) {
|
||||||
auto channel = getChannel(id);
|
return WRAP({ return int(getChannel(id)->availableAmount()); });
|
||||||
if (!channel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
CATCH(return int(channel->availableAmount()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSetAvailableCallback(int id, availableCallbackFunc cb) {
|
int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (cb)
|
if (cb)
|
||||||
channel->onOpen([id, cb]() { cb(getUserPointer(id)); });
|
channel->onOpen([id, cb]() {
|
||||||
|
if (auto ptr = getUserPointer(id))
|
||||||
|
cb(*ptr);
|
||||||
|
});
|
||||||
else
|
else
|
||||||
channel->onOpen(nullptr);
|
channel->onOpen(nullptr);
|
||||||
return 0;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcReceiveMessage(int id, char *buffer, int *size) {
|
int rtcReceiveMessage(int id, char *buffer, int *size) {
|
||||||
|
return WRAP({
|
||||||
auto channel = getChannel(id);
|
auto channel = getChannel(id);
|
||||||
if (!channel)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (!size)
|
if (!buffer || !size)
|
||||||
return -1;
|
throw std::invalid_argument("Unexpected null pointer");
|
||||||
|
|
||||||
CATCH({
|
|
||||||
auto message = channel->receive();
|
|
||||||
if (!message)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
|
if (auto message = channel->receive())
|
||||||
return std::visit( //
|
return std::visit( //
|
||||||
overloaded{ //
|
overloaded{ //
|
||||||
[&](const binary &b) {
|
[&](const binary &b) {
|
||||||
*size = std::min(*size, int(b.size()));
|
*size = std::min(*size, int(b.size()));
|
||||||
auto data = reinterpret_cast<const char *>(b.data());
|
auto data = reinterpret_cast<const char *>(b.data());
|
||||||
std::copy(data, data + *size, buffer);
|
std::copy(data, data + *size, buffer);
|
||||||
return *size;
|
return 1;
|
||||||
},
|
},
|
||||||
[&](const string &s) {
|
[&](const string &s) {
|
||||||
int len = std::min(*size - 1, int(s.size()));
|
int len = std::min(*size - 1, int(s.size()));
|
||||||
@ -512,10 +581,13 @@ int rtcReceiveMessage(int id, char *buffer, int *size) {
|
|||||||
buffer[len] = '\0';
|
buffer[len] = '\0';
|
||||||
}
|
}
|
||||||
*size = -(len + 1);
|
*size = -(len + 1);
|
||||||
return len + 1;
|
return 1;
|
||||||
}},
|
}},
|
||||||
*message);
|
*message);
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void rtcPreload() { rtc::Preload(); }
|
||||||
void rtcCleanup() { rtc::Cleanup(); }
|
void rtcCleanup() { rtc::Cleanup(); }
|
||||||
|
@ -50,6 +50,9 @@ using std::shared_ptr;
|
|||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
|
std::unordered_set<SctpTransport *> SctpTransport::Instances;
|
||||||
|
std::shared_mutex SctpTransport::InstancesMutex;
|
||||||
|
|
||||||
void SctpTransport::Init() {
|
void SctpTransport::Init() {
|
||||||
usrsctp_init(0, &SctpTransport::WriteCallback, nullptr);
|
usrsctp_init(0, &SctpTransport::WriteCallback, nullptr);
|
||||||
usrsctp_sysctl_set_sctp_ecn_enable(0);
|
usrsctp_sysctl_set_sctp_ecn_enable(0);
|
||||||
@ -61,6 +64,20 @@ void SctpTransport::Init() {
|
|||||||
usrsctp_sysctl_set_sctp_rto_initial_default(1 * 1000); // ms
|
usrsctp_sysctl_set_sctp_rto_initial_default(1 * 1000); // ms
|
||||||
usrsctp_sysctl_set_sctp_init_rto_max_default(10 * 1000); // ms
|
usrsctp_sysctl_set_sctp_init_rto_max_default(10 * 1000); // ms
|
||||||
usrsctp_sysctl_set_sctp_heartbeat_interval_default(10 * 1000); // ms
|
usrsctp_sysctl_set_sctp_heartbeat_interval_default(10 * 1000); // ms
|
||||||
|
|
||||||
|
usrsctp_sysctl_set_sctp_max_chunks_on_queue(10 * 1024);
|
||||||
|
|
||||||
|
// Change congestion control from the default TCP Reno (RFC 2581) to H-TCP
|
||||||
|
usrsctp_sysctl_set_sctp_default_cc_module(SCTP_CC_HTCP);
|
||||||
|
|
||||||
|
// Enable Non-Renegable Selective Acknowledgments (NR-SACKs)
|
||||||
|
usrsctp_sysctl_set_sctp_nrsack_enable(1);
|
||||||
|
|
||||||
|
// Increase the initial window size to 10 MTUs (RFC 6928)
|
||||||
|
usrsctp_sysctl_set_sctp_initial_cwnd(10);
|
||||||
|
|
||||||
|
// Reduce SACK delay from the default 200ms to 20ms
|
||||||
|
usrsctp_sysctl_set_sctp_delayed_sack_time_default(20); // ms
|
||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::Cleanup() {
|
void SctpTransport::Cleanup() {
|
||||||
@ -78,6 +95,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
|||||||
PLOG_DEBUG << "Initializing SCTP transport";
|
PLOG_DEBUG << "Initializing SCTP transport";
|
||||||
|
|
||||||
usrsctp_register_address(this);
|
usrsctp_register_address(this);
|
||||||
|
{
|
||||||
|
std::unique_lock lock(InstancesMutex);
|
||||||
|
Instances.insert(this);
|
||||||
|
}
|
||||||
|
|
||||||
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
|
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
|
||||||
&SctpTransport::SendCallback, 0, this);
|
&SctpTransport::SendCallback, 0, this);
|
||||||
if (!mSock)
|
if (!mSock)
|
||||||
@ -158,11 +180,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
|||||||
// The default send and receive window size of usrsctp is 256KiB, which is too small for
|
// The default send and receive window size of usrsctp is 256KiB, which is too small for
|
||||||
// realistic RTTs, therefore we increase it to 1MiB for better performance.
|
// realistic RTTs, therefore we increase it to 1MiB for better performance.
|
||||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685
|
||||||
int bufSize = 1024 * 1024;
|
int bufferSize = 1024 * 1024;
|
||||||
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &bufSize, sizeof(bufSize)))
|
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)))
|
||||||
throw std::runtime_error("Could not set SCTP recv buffer size, errno=" +
|
throw std::runtime_error("Could not set SCTP recv buffer size, errno=" +
|
||||||
std::to_string(errno));
|
std::to_string(errno));
|
||||||
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_SNDBUF, &bufSize, sizeof(bufSize)))
|
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)))
|
||||||
throw std::runtime_error("Could not set SCTP send buffer size, errno=" +
|
throw std::runtime_error("Could not set SCTP send buffer size, errno=" +
|
||||||
std::to_string(errno));
|
std::to_string(errno));
|
||||||
|
|
||||||
@ -172,14 +194,21 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
|||||||
|
|
||||||
SctpTransport::~SctpTransport() {
|
SctpTransport::~SctpTransport() {
|
||||||
stop();
|
stop();
|
||||||
|
close();
|
||||||
if (mSock)
|
|
||||||
usrsctp_close(mSock);
|
|
||||||
|
|
||||||
usrsctp_deregister_address(this);
|
usrsctp_deregister_address(this);
|
||||||
|
{
|
||||||
|
std::unique_lock lock(InstancesMutex);
|
||||||
|
Instances.erase(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SctpTransport::stop() {
|
bool SctpTransport::stop() {
|
||||||
|
// Transport::stop() will unregister incoming() from the lower layer, therefore we need to make
|
||||||
|
// sure the thread from lower layers is not blocked in incoming() by the WrittenOnce condition.
|
||||||
|
mWrittenOnce = true;
|
||||||
|
mWrittenCondition.notify_all();
|
||||||
|
|
||||||
if (!Transport::stop())
|
if (!Transport::stop())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -190,6 +219,13 @@ bool SctpTransport::stop() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SctpTransport::close() {
|
||||||
|
if (mSock) {
|
||||||
|
usrsctp_close(mSock);
|
||||||
|
mSock = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SctpTransport::connect() {
|
void SctpTransport::connect() {
|
||||||
if (!mSock)
|
if (!mSock)
|
||||||
return;
|
return;
|
||||||
@ -226,9 +262,7 @@ void SctpTransport::shutdown() {
|
|||||||
PLOG_WARNING << "SCTP shutdown failed, errno=" << errno;
|
PLOG_WARNING << "SCTP shutdown failed, errno=" << errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
// close() abort the connection when linger is disabled, call it now
|
close();
|
||||||
usrsctp_close(mSock);
|
|
||||||
mSock = nullptr;
|
|
||||||
|
|
||||||
PLOG_INFO << "SCTP disconnected";
|
PLOG_INFO << "SCTP disconnected";
|
||||||
changeState(State::Disconnected);
|
changeState(State::Disconnected);
|
||||||
@ -248,11 +282,11 @@ bool SctpTransport::send(message_ptr message) {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
mSendQueue.push(message);
|
mSendQueue.push(message);
|
||||||
updateBufferedAmount(message->stream, message_size_func(message));
|
updateBufferedAmount(uint16_t(message->stream), long(message_size_func(message)));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::close(unsigned int stream) {
|
void SctpTransport::closeStream(unsigned int stream) {
|
||||||
send(make_message(0, Message::Reset, uint16_t(stream)));
|
send(make_message(0, Message::Reset, uint16_t(stream)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,7 +299,7 @@ void SctpTransport::incoming(message_ptr message) {
|
|||||||
// There could be a race condition here where we receive the remote INIT before the local one is
|
// There could be a race condition here where we receive the remote INIT before the local one is
|
||||||
// sent, which would result in the connection being aborted. Therefore, we need to wait for data
|
// sent, which would result in the connection being aborted. Therefore, we need to wait for data
|
||||||
// to be sent on our side (i.e. the local INIT) before proceeding.
|
// to be sent on our side (i.e. the local INIT) before proceeding.
|
||||||
{
|
if (!mWrittenOnce) { // test the atomic boolean is not set first to prevent a lock contention
|
||||||
std::unique_lock lock(mWriteMutex);
|
std::unique_lock lock(mWriteMutex);
|
||||||
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || state() != State::Connected; });
|
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || state() != State::Connected; });
|
||||||
}
|
}
|
||||||
@ -288,7 +322,7 @@ bool SctpTransport::trySendQueue() {
|
|||||||
if (!trySendMessage(message))
|
if (!trySendMessage(message))
|
||||||
return false;
|
return false;
|
||||||
mSendQueue.pop();
|
mSendQueue.pop();
|
||||||
updateBufferedAmount(message->stream, -message_size_func(message));
|
updateBufferedAmount(uint16_t(message->stream), -long(message_size_func(message)));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -310,7 +344,7 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
|||||||
ppid = PPID_CONTROL;
|
ppid = PPID_CONTROL;
|
||||||
break;
|
break;
|
||||||
case Message::Reset:
|
case Message::Reset:
|
||||||
sendReset(message->stream);
|
sendReset(uint16_t(message->stream));
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
// Ignore
|
// Ignore
|
||||||
@ -362,18 +396,20 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
|||||||
ret = usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0);
|
ret = usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret >= 0) {
|
if (ret < 0) {
|
||||||
|
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||||
|
PLOG_VERBOSE << "SCTP sending not possible";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PLOG_ERROR << "SCTP sending failed, errno=" << errno;
|
||||||
|
throw std::runtime_error("Sending failed, errno=" + std::to_string(errno));
|
||||||
|
}
|
||||||
|
|
||||||
PLOG_VERBOSE << "SCTP sent size=" << message->size();
|
PLOG_VERBOSE << "SCTP sent size=" << message->size();
|
||||||
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
|
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
|
||||||
mBytesSent += message->size();
|
mBytesSent += message->size();
|
||||||
return true;
|
return true;
|
||||||
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
|
||||||
PLOG_VERBOSE << "SCTP sending not possible";
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
PLOG_ERROR << "SCTP sending failed, errno=" << errno;
|
|
||||||
throw std::runtime_error("Sending failed, errno=" + std::to_string(errno));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
|
void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
|
||||||
@ -385,7 +421,13 @@ void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
|
|||||||
else
|
else
|
||||||
it->second = amount;
|
it->second = amount;
|
||||||
|
|
||||||
|
mSendMutex.unlock();
|
||||||
|
try {
|
||||||
mBufferedAmountCallback(streamId, amount);
|
mBufferedAmountCallback(streamId, amount);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
PLOG_DEBUG << "SCTP buffered amount callback: " << e.what();
|
||||||
|
}
|
||||||
|
mSendMutex.lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::sendReset(uint16_t streamId) {
|
void SctpTransport::sendReset(uint16_t streamId) {
|
||||||
@ -421,37 +463,51 @@ bool SctpTransport::safeFlush() {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_ERROR << "SCTP flush: " << e.what();
|
PLOG_WARNING << "SCTP flush: " << e.what();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data,
|
int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*addr*/,
|
||||||
size_t len, struct sctp_rcvinfo info, int flags) {
|
const byte *data, size_t len, struct sctp_rcvinfo info, int flags) {
|
||||||
try {
|
try {
|
||||||
PLOG_VERBOSE << "Handle recv, len=" << len;
|
PLOG_VERBOSE << "Handle recv, len=" << len;
|
||||||
if (!len)
|
if (!len)
|
||||||
return -1;
|
return 0; // Ignore
|
||||||
|
|
||||||
// This is valid because SCTP_FRAGMENT_INTERLEAVE is set to level 0
|
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
|
||||||
// so partial messages and notifications may not be interleaved.
|
// therefore partial notifications and messages need to be handled separately.
|
||||||
|
if (flags & MSG_NOTIFICATION) {
|
||||||
|
// SCTP event notification
|
||||||
if (flags & MSG_EOR) {
|
if (flags & MSG_EOR) {
|
||||||
if (!mPartialRecv.empty()) {
|
if (!mPartialNotification.empty()) {
|
||||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
|
||||||
data = mPartialRecv.data();
|
data = mPartialNotification.data();
|
||||||
len = mPartialRecv.size();
|
len = mPartialNotification.size();
|
||||||
}
|
}
|
||||||
// Message/Notification is complete, process it
|
// Notification is complete, process it
|
||||||
if (flags & MSG_NOTIFICATION)
|
|
||||||
processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
|
processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
|
||||||
else
|
mPartialNotification.clear();
|
||||||
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
|
|
||||||
|
|
||||||
mPartialRecv.clear();
|
|
||||||
} else {
|
} else {
|
||||||
// Message/Notification is not complete
|
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
|
||||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// SCTP message
|
||||||
|
if (flags & MSG_EOR) {
|
||||||
|
if (!mPartialMessage.empty()) {
|
||||||
|
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
|
||||||
|
data = mPartialMessage.data();
|
||||||
|
len = mPartialMessage.size();
|
||||||
|
}
|
||||||
|
// Message is complete, process it
|
||||||
|
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
|
||||||
|
mPartialMessage.clear();
|
||||||
|
} else {
|
||||||
|
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_ERROR << "SCTP recv: " << e.what();
|
PLOG_ERROR << "SCTP recv: " << e.what();
|
||||||
return -1;
|
return -1;
|
||||||
@ -464,13 +520,14 @@ int SctpTransport::handleSend(size_t free) {
|
|||||||
return safeFlush() ? 0 : -1;
|
return safeFlush() ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df) {
|
int SctpTransport::handleWrite(byte *data, size_t len, uint8_t /*tos*/, uint8_t /*set_df*/) {
|
||||||
try {
|
try {
|
||||||
|
std::unique_lock lock(mWriteMutex);
|
||||||
PLOG_VERBOSE << "Handle write, len=" << len;
|
PLOG_VERBOSE << "Handle write, len=" << len;
|
||||||
|
|
||||||
std::unique_lock lock(mWriteMutex);
|
|
||||||
if (!outgoing(make_message(data, data + len)))
|
if (!outgoing(make_message(data, data + len)))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
mWritten = true;
|
mWritten = true;
|
||||||
mWrittenOnce = true;
|
mWrittenOnce = true;
|
||||||
mWrittenCondition.notify_all();
|
mWrittenCondition.notify_all();
|
||||||
@ -591,7 +648,7 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
|
|||||||
if (flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
|
if (flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
uint16_t streamId = reset_event.strreset_stream_list[i];
|
uint16_t streamId = reset_event.strreset_stream_list[i];
|
||||||
close(streamId);
|
closeStream(streamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (flags & SCTP_STREAM_RESET_INCOMING_SSN) {
|
if (flags & SCTP_STREAM_RESET_INCOMING_SSN) {
|
||||||
@ -649,7 +706,15 @@ int SctpTransport::SendCallback(struct socket *sock, uint32_t sb_free) {
|
|||||||
|
|
||||||
auto sconn = reinterpret_cast<struct sockaddr_conn *>(&paddrinfo.spinfo_address);
|
auto sconn = reinterpret_cast<struct sockaddr_conn *>(&paddrinfo.spinfo_address);
|
||||||
void *ptr = sconn->sconn_addr;
|
void *ptr = sconn->sconn_addr;
|
||||||
return static_cast<SctpTransport *>(ptr)->handleSend(size_t(sb_free));
|
auto *transport = static_cast<SctpTransport *>(ptr);
|
||||||
|
|
||||||
|
// Workaround for sctplab/usrsctp#405: Send callback is invoked on already closed socket
|
||||||
|
// https://github.com/sctplab/usrsctp/issues/405
|
||||||
|
std::shared_lock lock(InstancesMutex);
|
||||||
|
if (Instances.find(transport) == Instances.end())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return transport->handleSend(size_t(sb_free));
|
||||||
}
|
}
|
||||||
|
|
||||||
int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) {
|
int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) {
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "usrsctp.h"
|
#include "usrsctp.h"
|
||||||
|
|
||||||
@ -46,7 +48,7 @@ public:
|
|||||||
|
|
||||||
bool stop() override;
|
bool stop() override;
|
||||||
bool send(message_ptr message) override; // false if buffered
|
bool send(message_ptr message) override; // false if buffered
|
||||||
void close(unsigned int stream);
|
void closeStream(unsigned int stream);
|
||||||
void flush();
|
void flush();
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
@ -70,6 +72,7 @@ private:
|
|||||||
|
|
||||||
void connect();
|
void connect();
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
void close();
|
||||||
void incoming(message_ptr message) override;
|
void incoming(message_ptr message) override;
|
||||||
|
|
||||||
bool trySendQueue();
|
bool trySendQueue();
|
||||||
@ -97,9 +100,10 @@ private:
|
|||||||
std::mutex mWriteMutex;
|
std::mutex mWriteMutex;
|
||||||
std::condition_variable mWrittenCondition;
|
std::condition_variable mWrittenCondition;
|
||||||
std::atomic<bool> mWritten = false; // written outside lock
|
std::atomic<bool> mWritten = false; // written outside lock
|
||||||
bool mWrittenOnce = false;
|
std::atomic<bool> mWrittenOnce = false; // same
|
||||||
|
|
||||||
binary mPartialRecv, mPartialStringData, mPartialBinaryData;
|
binary mPartialMessage, mPartialNotification;
|
||||||
|
binary mPartialStringData, mPartialBinaryData;
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
|
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
|
||||||
@ -108,6 +112,9 @@ private:
|
|||||||
struct sctp_rcvinfo recv_info, int flags, void *user_data);
|
struct sctp_rcvinfo recv_info, int flags, void *user_data);
|
||||||
static int SendCallback(struct socket *sock, uint32_t sb_free);
|
static int SendCallback(struct socket *sock, uint32_t sb_free);
|
||||||
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
|
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||||
|
|
||||||
|
static std::unordered_set<SctpTransport *> Instances;
|
||||||
|
static std::shared_mutex InstancesMutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#if RTC_ENABLE_WEBSOCKET
|
#if RTC_ENABLE_WEBSOCKET
|
||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -37,8 +38,8 @@ SelectInterrupter::SelectInterrupter() {
|
|||||||
throw std::runtime_error("Failed to create pipe");
|
throw std::runtime_error("Failed to create pipe");
|
||||||
::fcntl(pipefd[0], F_SETFL, O_NONBLOCK);
|
::fcntl(pipefd[0], F_SETFL, O_NONBLOCK);
|
||||||
::fcntl(pipefd[1], F_SETFL, O_NONBLOCK);
|
::fcntl(pipefd[1], F_SETFL, O_NONBLOCK);
|
||||||
mPipeOut = pipefd[0]; // read
|
mPipeOut = pipefd[1]; // read
|
||||||
mPipeIn = pipefd[1]; // write
|
mPipeIn = pipefd[0]; // write
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,13 +60,10 @@ int SelectInterrupter::prepare(fd_set &readfds, fd_set &writefds) {
|
|||||||
if (mDummySock == INVALID_SOCKET)
|
if (mDummySock == INVALID_SOCKET)
|
||||||
mDummySock = ::socket(AF_INET, SOCK_DGRAM, 0);
|
mDummySock = ::socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
FD_SET(mDummySock, &readfds);
|
FD_SET(mDummySock, &readfds);
|
||||||
return SOCK_TO_INT(mDummySock) + 1;
|
return SOCKET_TO_INT(mDummySock) + 1;
|
||||||
#else
|
#else
|
||||||
int ret;
|
|
||||||
do {
|
|
||||||
char dummy;
|
char dummy;
|
||||||
ret = ::read(mPipeIn, &dummy, 1);
|
::read(mPipeIn, &dummy, 1);
|
||||||
} while (ret > 0);
|
|
||||||
FD_SET(mPipeIn, &readfds);
|
FD_SET(mPipeIn, &readfds);
|
||||||
return mPipeIn + 1;
|
return mPipeIn + 1;
|
||||||
#endif
|
#endif
|
||||||
@ -106,6 +104,7 @@ bool TcpTransport::stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TcpTransport::send(message_ptr message) {
|
bool TcpTransport::send(message_ptr message) {
|
||||||
|
std::unique_lock lock(mSockMutex);
|
||||||
if (state() != State::Connected)
|
if (state() != State::Connected)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -125,6 +124,7 @@ void TcpTransport::incoming(message_ptr message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TcpTransport::outgoing(message_ptr message) {
|
bool TcpTransport::outgoing(message_ptr message) {
|
||||||
|
// mSockMutex must be locked
|
||||||
// If nothing is pending, try to send directly
|
// If nothing is pending, try to send directly
|
||||||
// It's safe because if the queue is empty, the thread is not sending
|
// It's safe because if the queue is empty, the thread is not sending
|
||||||
if (mSendQueue.empty() && trySendMessage(message))
|
if (mSendQueue.empty() && trySendMessage(message))
|
||||||
@ -150,7 +150,7 @@ void TcpTransport::connect(const string &hostname, const string &service) {
|
|||||||
|
|
||||||
for (auto p = result; p; p = p->ai_next) {
|
for (auto p = result; p; p = p->ai_next) {
|
||||||
try {
|
try {
|
||||||
connect(p->ai_addr, p->ai_addrlen);
|
connect(p->ai_addr, socklen_t(p->ai_addrlen));
|
||||||
|
|
||||||
PLOG_INFO << "Connected to " << hostname << ":" << service;
|
PLOG_INFO << "Connected to " << hostname << ":" << service;
|
||||||
freeaddrinfo(result);
|
freeaddrinfo(result);
|
||||||
@ -173,6 +173,7 @@ void TcpTransport::connect(const string &hostname, const string &service) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
|
void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
|
||||||
|
std::unique_lock lock(mSockMutex);
|
||||||
try {
|
try {
|
||||||
char node[MAX_NUMERICNODE_LEN];
|
char node[MAX_NUMERICNODE_LEN];
|
||||||
char serv[MAX_NUMERICSERV_LEN];
|
char serv[MAX_NUMERICSERV_LEN];
|
||||||
@ -201,7 +202,7 @@ void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
|
|||||||
|
|
||||||
// Initiate connection
|
// Initiate connection
|
||||||
int ret = ::connect(mSock, addr, addrlen);
|
int ret = ::connect(mSock, addr, addrlen);
|
||||||
if (ret < 0 && errno != EINPROGRESS) {
|
if (ret < 0 && sockerrno != SEINPROGRESS && sockerrno != SEWOULDBLOCK) {
|
||||||
std::ostringstream msg;
|
std::ostringstream msg;
|
||||||
msg << "TCP connection to " << node << ":" << serv << " failed, errno=" << sockerrno;
|
msg << "TCP connection to " << node << ":" << serv << " failed, errno=" << sockerrno;
|
||||||
throw std::runtime_error(msg.str());
|
throw std::runtime_error(msg.str());
|
||||||
@ -226,7 +227,7 @@ void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
|
|||||||
|
|
||||||
int error = 0;
|
int error = 0;
|
||||||
socklen_t errorlen = sizeof(error);
|
socklen_t errorlen = sizeof(error);
|
||||||
if (::getsockopt(mSock, SOL_SOCKET, SO_ERROR, &error, &errorlen) != 0)
|
if (::getsockopt(mSock, SOL_SOCKET, SO_ERROR, (char *)&error, &errorlen) != 0)
|
||||||
throw std::runtime_error("Failed to get socket error code");
|
throw std::runtime_error("Failed to get socket error code");
|
||||||
|
|
||||||
if (error != 0) {
|
if (error != 0) {
|
||||||
@ -247,15 +248,18 @@ void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TcpTransport::close() {
|
void TcpTransport::close() {
|
||||||
|
std::unique_lock lock(mSockMutex);
|
||||||
if (mSock != INVALID_SOCKET) {
|
if (mSock != INVALID_SOCKET) {
|
||||||
PLOG_DEBUG << "Closing TCP socket";
|
PLOG_DEBUG << "Closing TCP socket";
|
||||||
::closesocket(mSock);
|
::closesocket(mSock);
|
||||||
mSock = INVALID_SOCKET;
|
mSock = INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
changeState(State::Disconnected);
|
changeState(State::Disconnected);
|
||||||
|
interruptSelect();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TcpTransport::trySendQueue() {
|
bool TcpTransport::trySendQueue() {
|
||||||
|
// mSockMutex must be locked
|
||||||
while (auto next = mSendQueue.peek()) {
|
while (auto next = mSendQueue.peek()) {
|
||||||
auto message = *next;
|
auto message = *next;
|
||||||
if (!trySendMessage(message)) {
|
if (!trySendMessage(message)) {
|
||||||
@ -268,17 +272,18 @@ bool TcpTransport::trySendQueue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TcpTransport::trySendMessage(message_ptr &message) {
|
bool TcpTransport::trySendMessage(message_ptr &message) {
|
||||||
|
// mSockMutex must be locked
|
||||||
auto data = reinterpret_cast<const char *>(message->data());
|
auto data = reinterpret_cast<const char *>(message->data());
|
||||||
auto size = message->size();
|
auto size = message->size();
|
||||||
while (size) {
|
while (size) {
|
||||||
#ifdef __APPLE__
|
#if defined(__APPLE__) || defined(_WIN32)
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
#else
|
#else
|
||||||
int flags = MSG_NOSIGNAL;
|
int flags = MSG_NOSIGNAL;
|
||||||
#endif
|
#endif
|
||||||
int len = ::send(mSock, data, size, flags);
|
int len = ::send(mSock, data, int(size), flags);
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
|
||||||
message = make_message(message->end() - size, message->end());
|
message = make_message(message->end() - size, message->end());
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@ -313,13 +318,22 @@ void TcpTransport::runLoop() {
|
|||||||
changeState(State::Connected);
|
changeState(State::Connected);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
std::unique_lock lock(mSockMutex);
|
||||||
|
if (mSock == INVALID_SOCKET)
|
||||||
|
break;
|
||||||
|
|
||||||
fd_set readfds, writefds;
|
fd_set readfds, writefds;
|
||||||
int n = prepareSelect(readfds, writefds);
|
int n = prepareSelect(readfds, writefds);
|
||||||
|
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
tv.tv_sec = 10;
|
tv.tv_sec = 10;
|
||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
|
lock.unlock();
|
||||||
int ret = ::select(n, &readfds, &writefds, NULL, &tv);
|
int ret = ::select(n, &readfds, &writefds, NULL, &tv);
|
||||||
|
lock.lock();
|
||||||
|
if (mSock == INVALID_SOCKET)
|
||||||
|
break;
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
throw std::runtime_error("Failed to wait on socket");
|
throw std::runtime_error("Failed to wait on socket");
|
||||||
} else if (ret == 0) {
|
} else if (ret == 0) {
|
||||||
@ -335,7 +349,7 @@ void TcpTransport::runLoop() {
|
|||||||
char buffer[bufferSize];
|
char buffer[bufferSize];
|
||||||
int len = ::recv(mSock, buffer, bufferSize, 0);
|
int len = ::recv(mSock, buffer, bufferSize, 0);
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
throw std::runtime_error("Connection lost");
|
throw std::runtime_error("Connection lost");
|
||||||
|
@ -78,6 +78,7 @@ private:
|
|||||||
string mHostname, mService;
|
string mHostname, mService;
|
||||||
|
|
||||||
socket_t mSock = INVALID_SOCKET;
|
socket_t mSock = INVALID_SOCKET;
|
||||||
|
std::mutex mSockMutex;
|
||||||
std::thread mThread;
|
std::thread mThread;
|
||||||
SelectInterrupter mInterrupter;
|
SelectInterrupter mInterrupter;
|
||||||
Queue<message_ptr> mSendQueue;
|
Queue<message_ptr> mSendQueue;
|
||||||
|
81
src/threadpool.cpp
Normal file
81
src/threadpool.cpp
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "threadpool.hpp"
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
ThreadPool &ThreadPool::Instance() {
|
||||||
|
static ThreadPool instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadPool::~ThreadPool() { join(); }
|
||||||
|
|
||||||
|
int ThreadPool::count() const {
|
||||||
|
std::unique_lock lock(mWorkersMutex);
|
||||||
|
return mWorkers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadPool::spawn(int count) {
|
||||||
|
std::unique_lock lock(mWorkersMutex);
|
||||||
|
mJoining = false;
|
||||||
|
while (count-- > 0)
|
||||||
|
mWorkers.emplace_back(std::bind(&ThreadPool::run, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadPool::join() {
|
||||||
|
std::unique_lock lock(mWorkersMutex);
|
||||||
|
mJoining = true;
|
||||||
|
mCondition.notify_all();
|
||||||
|
|
||||||
|
for (auto &w : mWorkers)
|
||||||
|
w.join();
|
||||||
|
|
||||||
|
mWorkers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadPool::run() {
|
||||||
|
while (runOne()) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ThreadPool::runOne() {
|
||||||
|
if (auto task = dequeue()) {
|
||||||
|
try {
|
||||||
|
task();
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
PLOG_WARNING << "Unhandled exception in task: " << e.what();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::function<void()> ThreadPool::dequeue() {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
mCondition.wait(lock, [this]() { return !mTasks.empty() || mJoining; });
|
||||||
|
if (mTasks.empty())
|
||||||
|
return nullptr;
|
||||||
|
auto task = std::move(mTasks.front());
|
||||||
|
mTasks.pop();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
87
src/threadpool.hpp
Normal file
87
src/threadpool.hpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RTC_THREADPOOL_H
|
||||||
|
#define RTC_THREADPOOL_H
|
||||||
|
|
||||||
|
#include "include.hpp"
|
||||||
|
#include "init.hpp"
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
template <class F, class... Args>
|
||||||
|
using invoke_future_t = std::future<std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>>;
|
||||||
|
|
||||||
|
class ThreadPool final {
|
||||||
|
public:
|
||||||
|
static ThreadPool &Instance();
|
||||||
|
|
||||||
|
ThreadPool(const ThreadPool &) = delete;
|
||||||
|
ThreadPool &operator=(const ThreadPool &) = delete;
|
||||||
|
ThreadPool(ThreadPool &&) = delete;
|
||||||
|
ThreadPool &operator=(ThreadPool &&) = delete;
|
||||||
|
|
||||||
|
int count() const;
|
||||||
|
void spawn(int count = 1);
|
||||||
|
void join();
|
||||||
|
void run();
|
||||||
|
bool runOne();
|
||||||
|
|
||||||
|
template <class F, class... Args>
|
||||||
|
auto enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...>;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ThreadPool() = default;
|
||||||
|
~ThreadPool();
|
||||||
|
|
||||||
|
std::function<void()> dequeue(); // returns null function if joining
|
||||||
|
|
||||||
|
std::vector<std::thread> mWorkers;
|
||||||
|
std::queue<std::function<void()>> mTasks;
|
||||||
|
std::atomic<bool> mJoining = false;
|
||||||
|
|
||||||
|
mutable std::mutex mMutex, mWorkersMutex;
|
||||||
|
std::condition_variable mCondition;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class F, class... Args>
|
||||||
|
auto ThreadPool::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
|
||||||
|
auto task = std::make_shared<std::packaged_task<R()>>(
|
||||||
|
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
|
||||||
|
std::future<R> result = task->get_future();
|
||||||
|
|
||||||
|
mTasks.emplace([task = std::move(task), token = Init::Token()]() { return (*task)(); });
|
||||||
|
mCondition.notify_one();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
|
||||||
|
#endif
|
@ -86,9 +86,8 @@ void init() {
|
|||||||
|
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
if (!done) {
|
if (!done) {
|
||||||
OPENSSL_init_ssl(0, NULL);
|
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
|
||||||
SSL_load_error_strings();
|
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
|
||||||
ERR_load_crypto_strings();
|
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,11 @@ gnutls_datum_t make_datum(char *data, size_t size);
|
|||||||
|
|
||||||
#else // USE_GNUTLS==0
|
#else // USE_GNUTLS==0
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Include winsock2.h header first since OpenSSL may include winsock.h
|
||||||
|
#include <winsock2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
#include <openssl/bio.h>
|
||||||
@ -55,6 +60,8 @@ gnutls_datum_t make_datum(char *data, size_t size);
|
|||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/pem.h>
|
#include <openssl/pem.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/bn.h>
|
||||||
|
|
||||||
#ifndef BIO_EOF
|
#ifndef BIO_EOF
|
||||||
#define BIO_EOF -1
|
#define BIO_EOF -1
|
||||||
|
@ -269,9 +269,19 @@ TlsTransport::TlsTransport(shared_ptr<TcpTransport> lower, string host, state_ca
|
|||||||
SSL_CTX_set_quiet_shutdown(mCtx, 1);
|
SSL_CTX_set_quiet_shutdown(mCtx, 1);
|
||||||
SSL_CTX_set_info_callback(mCtx, InfoCallback);
|
SSL_CTX_set_info_callback(mCtx, InfoCallback);
|
||||||
|
|
||||||
SSL_CTX_set_default_verify_paths(mCtx);
|
// SSL_CTX_set_default_verify_paths() does nothing on Windows
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (SSL_CTX_set_default_verify_paths(mCtx)) {
|
||||||
|
#else
|
||||||
|
if (false) {
|
||||||
|
#endif
|
||||||
|
PLOG_INFO << "SSL root CA certificates available, server verification enabled";
|
||||||
SSL_CTX_set_verify(mCtx, SSL_VERIFY_PEER, NULL);
|
SSL_CTX_set_verify(mCtx, SSL_VERIFY_PEER, NULL);
|
||||||
SSL_CTX_set_verify_depth(mCtx, 4);
|
SSL_CTX_set_verify_depth(mCtx, 4);
|
||||||
|
} else {
|
||||||
|
PLOG_WARNING << "SSL root CA certificates unavailable, server verification disabled";
|
||||||
|
SSL_CTX_set_verify(mCtx, SSL_VERIFY_NONE, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
if (!(mSsl = SSL_new(mCtx)))
|
if (!(mSsl = SSL_new(mCtx)))
|
||||||
throw std::runtime_error("Failed to create SSL instance");
|
throw std::runtime_error("Failed to create SSL instance");
|
||||||
@ -337,7 +347,7 @@ bool TlsTransport::send(message_ptr message) {
|
|||||||
if (message->size() == 0)
|
if (message->size() == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
int ret = SSL_write(mSsl, message->data(), message->size());
|
int ret = SSL_write(mSsl, message->data(), int(message->size()));
|
||||||
if (!openssl::check(mSsl, ret))
|
if (!openssl::check(mSsl, ret))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -393,7 +403,7 @@ void TlsTransport::runRecvLoop() {
|
|||||||
|
|
||||||
message_ptr message = *next;
|
message_ptr message = *next;
|
||||||
if (message->size() > 0)
|
if (message->size() > 0)
|
||||||
BIO_write(mInBio, message->data(), message->size()); // Input
|
BIO_write(mInBio, message->data(), int(message->size())); // Input
|
||||||
else
|
else
|
||||||
recv(message); // Pass zero-sized messages through
|
recv(message); // Pass zero-sized messages through
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
|
|
||||||
#if RTC_ENABLE_WEBSOCKET
|
#if RTC_ENABLE_WEBSOCKET
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
@ -41,12 +41,17 @@ public:
|
|||||||
|
|
||||||
virtual ~Transport() {
|
virtual ~Transport() {
|
||||||
stop();
|
stop();
|
||||||
if (mLower)
|
|
||||||
mLower->onRecv(nullptr); // doing it on stop could cause a deadlock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool stop() {
|
virtual bool stop() {
|
||||||
return !mShutdown.exchange(true);
|
if (mShutdown.exchange(true))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We don't want incoming() to be called by the lower layer anymore
|
||||||
|
if (mLower)
|
||||||
|
mLower->onRecv(nullptr);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerIncoming() {
|
void registerIncoming() {
|
||||||
|
@ -18,8 +18,9 @@
|
|||||||
|
|
||||||
#if RTC_ENABLE_WEBSOCKET
|
#if RTC_ENABLE_WEBSOCKET
|
||||||
|
|
||||||
#include "include.hpp"
|
|
||||||
#include "websocket.hpp"
|
#include "websocket.hpp"
|
||||||
|
#include "include.hpp"
|
||||||
|
#include "threadpool.hpp"
|
||||||
|
|
||||||
#include "tcptransport.hpp"
|
#include "tcptransport.hpp"
|
||||||
#include "tlstransport.hpp"
|
#include "tlstransport.hpp"
|
||||||
@ -301,8 +302,7 @@ void WebSocket::closeTransports() {
|
|||||||
auto ws = std::atomic_exchange(&mWsTransport, decltype(mWsTransport)(nullptr));
|
auto ws = std::atomic_exchange(&mWsTransport, decltype(mWsTransport)(nullptr));
|
||||||
auto tls = std::atomic_exchange(&mTlsTransport, decltype(mTlsTransport)(nullptr));
|
auto tls = std::atomic_exchange(&mTlsTransport, decltype(mTlsTransport)(nullptr));
|
||||||
auto tcp = std::atomic_exchange(&mTcpTransport, decltype(mTcpTransport)(nullptr));
|
auto tcp = std::atomic_exchange(&mTcpTransport, decltype(mTcpTransport)(nullptr));
|
||||||
if (ws || tls || tcp) {
|
ThreadPool::Instance().enqueue([ws, tls, tcp]() mutable {
|
||||||
std::thread t([ws, tls, tcp, token = mInitToken]() mutable {
|
|
||||||
if (ws)
|
if (ws)
|
||||||
ws->stop();
|
ws->stop();
|
||||||
if (tls)
|
if (tls)
|
||||||
@ -314,8 +314,6 @@ void WebSocket::closeTransports() {
|
|||||||
tls.reset();
|
tls.reset();
|
||||||
tcp.reset();
|
tcp.reset();
|
||||||
});
|
});
|
||||||
t.detach();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
@ -50,7 +50,7 @@ using std::to_integer;
|
|||||||
using std::to_string;
|
using std::to_string;
|
||||||
|
|
||||||
using random_bytes_engine =
|
using random_bytes_engine =
|
||||||
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char>;
|
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned short>;
|
||||||
|
|
||||||
WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string path,
|
WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string path,
|
||||||
message_callback recvCallback, state_callback stateCallback)
|
message_callback recvCallback, state_callback stateCallback)
|
||||||
@ -145,12 +145,12 @@ void WsTransport::close() {
|
|||||||
bool WsTransport::sendHttpRequest() {
|
bool WsTransport::sendHttpRequest() {
|
||||||
changeState(State::Connecting);
|
changeState(State::Connecting);
|
||||||
|
|
||||||
auto seed = system_clock::now().time_since_epoch().count();
|
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||||
random_bytes_engine generator(seed);
|
random_bytes_engine generator(seed);
|
||||||
|
|
||||||
binary key(16);
|
binary key(16);
|
||||||
auto k = reinterpret_cast<uint8_t *>(key.data());
|
auto k = reinterpret_cast<uint8_t *>(key.data());
|
||||||
std::generate(k, k + key.size(), generator);
|
std::generate(k, k + key.size(), [&]() { return uint8_t(generator()); });
|
||||||
|
|
||||||
const string request = "GET " + mPath +
|
const string request = "GET " + mPath +
|
||||||
" HTTP/1.1\r\n"
|
" HTTP/1.1\r\n"
|
||||||
@ -283,7 +283,7 @@ size_t WsTransport::readFrame(byte *buffer, size_t size, Frame &frame) {
|
|||||||
cur += 4;
|
cur += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end - cur < frame.length)
|
if (size_t(end - cur) < frame.length)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
frame.payload = cur;
|
frame.payload = cur;
|
||||||
@ -292,7 +292,7 @@ size_t WsTransport::readFrame(byte *buffer, size_t size, Frame &frame) {
|
|||||||
frame.payload[i] ^= maskingKey[i % 4];
|
frame.payload[i] ^= maskingKey[i % 4];
|
||||||
cur += frame.length;
|
cur += frame.length;
|
||||||
|
|
||||||
return cur - buffer;
|
return size_t(cur - buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WsTransport::recvFrame(const Frame &frame) {
|
void WsTransport::recvFrame(const Frame &frame) {
|
||||||
@ -378,13 +378,13 @@ bool WsTransport::sendFrame(const Frame &frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frame.mask) {
|
if (frame.mask) {
|
||||||
auto seed = system_clock::now().time_since_epoch().count();
|
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||||
random_bytes_engine generator(seed);
|
random_bytes_engine generator(seed);
|
||||||
|
|
||||||
byte *maskingKey = reinterpret_cast<byte *>(cur);
|
byte *maskingKey = reinterpret_cast<byte *>(cur);
|
||||||
|
|
||||||
auto u = reinterpret_cast<uint8_t *>(maskingKey);
|
auto u = reinterpret_cast<uint8_t *>(maskingKey);
|
||||||
std::generate(u, u + 4, generator);
|
std::generate(u, u + 4, [&]() { return uint8_t(generator()); });
|
||||||
cur += 4;
|
cur += 4;
|
||||||
|
|
||||||
for (size_t i = 0; i < frame.length; ++i)
|
for (size_t i = 0; i < frame.length; ++i)
|
||||||
|
192
test/benchmark.cpp
Normal file
192
test/benchmark.cpp
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2019 Paul-Louis Ageneau
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
using namespace rtc;
|
||||||
|
using namespace std;
|
||||||
|
using namespace chrono_literals;
|
||||||
|
|
||||||
|
using chrono::duration_cast;
|
||||||
|
using chrono::milliseconds;
|
||||||
|
using chrono::steady_clock;
|
||||||
|
|
||||||
|
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||||
|
|
||||||
|
size_t benchmark(milliseconds duration) {
|
||||||
|
rtc::InitLogger(LogLevel::Warning);
|
||||||
|
rtc::Preload();
|
||||||
|
|
||||||
|
Configuration config1;
|
||||||
|
// config1.iceServers.emplace_back("stun:stun.l.google.com:19302");
|
||||||
|
|
||||||
|
auto pc1 = std::make_shared<PeerConnection>(config1);
|
||||||
|
|
||||||
|
Configuration config2;
|
||||||
|
// config2.iceServers.emplace_back("stun:stun.l.google.com:19302");
|
||||||
|
|
||||||
|
auto pc2 = std::make_shared<PeerConnection>(config2);
|
||||||
|
|
||||||
|
pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](const Description &sdp) {
|
||||||
|
auto pc2 = wpc2.lock();
|
||||||
|
if (!pc2)
|
||||||
|
return;
|
||||||
|
cout << "Description 1: " << sdp << endl;
|
||||||
|
pc2->setRemoteDescription(sdp);
|
||||||
|
});
|
||||||
|
|
||||||
|
pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](const Candidate &candidate) {
|
||||||
|
auto pc2 = wpc2.lock();
|
||||||
|
if (!pc2)
|
||||||
|
return;
|
||||||
|
cout << "Candidate 1: " << candidate << endl;
|
||||||
|
pc2->addRemoteCandidate(candidate);
|
||||||
|
});
|
||||||
|
|
||||||
|
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
||||||
|
pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||||
|
cout << "Gathering state 1: " << state << endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](const Description &sdp) {
|
||||||
|
auto pc1 = wpc1.lock();
|
||||||
|
if (!pc1)
|
||||||
|
return;
|
||||||
|
cout << "Description 2: " << sdp << endl;
|
||||||
|
pc1->setRemoteDescription(sdp);
|
||||||
|
});
|
||||||
|
|
||||||
|
pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](const Candidate &candidate) {
|
||||||
|
auto pc1 = wpc1.lock();
|
||||||
|
if (!pc1)
|
||||||
|
return;
|
||||||
|
cout << "Candidate 2: " << candidate << endl;
|
||||||
|
pc1->addRemoteCandidate(candidate);
|
||||||
|
});
|
||||||
|
|
||||||
|
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
||||||
|
pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||||
|
cout << "Gathering state 2: " << state << endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
const size_t messageSize = 65535;
|
||||||
|
binary messageData(messageSize);
|
||||||
|
fill(messageData.begin(), messageData.end(), byte(0xFF));
|
||||||
|
|
||||||
|
atomic<size_t> receivedSize = 0;
|
||||||
|
|
||||||
|
steady_clock::time_point startTime, openTime, receivedTime, endTime;
|
||||||
|
|
||||||
|
shared_ptr<DataChannel> dc2;
|
||||||
|
pc2->onDataChannel(
|
||||||
|
[&dc2, &receivedSize, &receivedTime](shared_ptr<DataChannel> dc) {
|
||||||
|
dc->onMessage([&receivedTime, &receivedSize](const variant<binary, string> &message) {
|
||||||
|
if (holds_alternative<binary>(message)) {
|
||||||
|
const auto &bin = get<binary>(message);
|
||||||
|
if (receivedSize == 0)
|
||||||
|
receivedTime = steady_clock::now();
|
||||||
|
receivedSize += bin.size();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dc->onClosed([]() { cout << "DataChannel closed." << endl; });
|
||||||
|
|
||||||
|
std::atomic_store(&dc2, dc);
|
||||||
|
});
|
||||||
|
|
||||||
|
startTime = steady_clock::now();
|
||||||
|
auto dc1 = pc1->createDataChannel("benchmark");
|
||||||
|
|
||||||
|
dc1->onOpen([wdc1 = make_weak_ptr(dc1), &messageData, &openTime]() {
|
||||||
|
auto dc1 = wdc1.lock();
|
||||||
|
if (!dc1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
openTime = steady_clock::now();
|
||||||
|
|
||||||
|
cout << "DataChannel open, sending data..." << endl;
|
||||||
|
while (dc1->bufferedAmount() == 0) {
|
||||||
|
dc1->send(messageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When sent data is buffered in the DataChannel,
|
||||||
|
// wait for onBufferedAmountLow callback to continue
|
||||||
|
});
|
||||||
|
|
||||||
|
dc1->onBufferedAmountLow([wdc1 = make_weak_ptr(dc1), &messageData]() {
|
||||||
|
auto dc1 = wdc1.lock();
|
||||||
|
if (!dc1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Continue sending
|
||||||
|
while (dc1->bufferedAmount() == 0) {
|
||||||
|
dc1->send(messageData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const int steps = 10;
|
||||||
|
const auto stepDuration = duration / 10;
|
||||||
|
for (int i = 0; i < steps; ++i) {
|
||||||
|
this_thread::sleep_for(stepDuration);
|
||||||
|
cout << "Received: " << receivedSize.load() / 1000 << " KB" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
dc1->close();
|
||||||
|
|
||||||
|
endTime = steady_clock::now();
|
||||||
|
|
||||||
|
auto connectDuration = duration_cast<milliseconds>(openTime - startTime);
|
||||||
|
auto transferDuration = duration_cast<milliseconds>(endTime - receivedTime);
|
||||||
|
|
||||||
|
cout << "Test duration: " << duration.count() << " ms" << endl;
|
||||||
|
cout << "Connect duration: " << connectDuration.count() << " ms" << endl;
|
||||||
|
|
||||||
|
size_t received = receivedSize.load();
|
||||||
|
size_t goodput = transferDuration.count() > 0 ? received / transferDuration.count() : 0;
|
||||||
|
cout << "Goodput: " << goodput * 0.001 << " MB/s"
|
||||||
|
<< " (" << goodput * 0.001 * 8 << " Mbit/s)" << endl;
|
||||||
|
|
||||||
|
pc1->close();
|
||||||
|
pc2->close();
|
||||||
|
this_thread::sleep_for(1s);
|
||||||
|
|
||||||
|
rtc::Cleanup();
|
||||||
|
return goodput;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef BENCHMARK_MAIN
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
try {
|
||||||
|
size_t goodput = benchmark(30s);
|
||||||
|
if (goodput == 0)
|
||||||
|
throw runtime_error("No data received");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
cerr << "Benchmark failed: " << e.what() << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include <rtc/rtc.h>
|
#include <rtc/rtc.h>
|
||||||
|
|
||||||
#include <cstdbool>
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -136,7 +135,7 @@ static void deletePeer(Peer *peer) {
|
|||||||
int test_capi_main() {
|
int test_capi_main() {
|
||||||
int attempts;
|
int attempts;
|
||||||
|
|
||||||
rtcInitLogger(RTC_LOG_DEBUG);
|
rtcInitLogger(RTC_LOG_DEBUG, nullptr);
|
||||||
|
|
||||||
// Create peer 1
|
// Create peer 1
|
||||||
rtcConfiguration config1;
|
rtcConfiguration config1;
|
||||||
@ -201,7 +200,7 @@ int test_capi_main() {
|
|||||||
|
|
||||||
// You may call rtcCleanup() when finished to free static resources
|
// You may call rtcCleanup() when finished to free static resources
|
||||||
rtcCleanup();
|
rtcCleanup();
|
||||||
sleep(1);
|
sleep(2);
|
||||||
|
|
||||||
printf("Success\n");
|
printf("Success\n");
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "rtc/rtc.hpp"
|
#include "rtc/rtc.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -63,6 +64,7 @@ void test_connectivity() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
||||||
|
|
||||||
pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||||
cout << "Gathering state 1: " << state << endl;
|
cout << "Gathering state 1: " << state << endl;
|
||||||
});
|
});
|
||||||
@ -84,6 +86,7 @@ void test_connectivity() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
||||||
|
|
||||||
pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||||
cout << "Gathering state 2: " << state << endl;
|
cout << "Gathering state 2: " << state << endl;
|
||||||
});
|
});
|
||||||
@ -91,13 +94,16 @@ void test_connectivity() {
|
|||||||
shared_ptr<DataChannel> dc2;
|
shared_ptr<DataChannel> dc2;
|
||||||
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
||||||
cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
|
cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
|
||||||
dc2 = dc;
|
|
||||||
dc2->onMessage([](const variant<binary, string> &message) {
|
dc->onMessage([](const variant<binary, string> &message) {
|
||||||
if (holds_alternative<string>(message)) {
|
if (holds_alternative<string>(message)) {
|
||||||
cout << "Message 2: " << get<string>(message) << endl;
|
cout << "Message 2: " << get<string>(message) << endl;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dc2->send("Hello from 2");
|
|
||||||
|
dc->send("Hello from 2");
|
||||||
|
|
||||||
|
std::atomic_store(&dc2, dc);
|
||||||
});
|
});
|
||||||
|
|
||||||
auto dc1 = pc1->createDataChannel("test");
|
auto dc1 = pc1->createDataChannel("test");
|
||||||
@ -105,6 +111,7 @@ void test_connectivity() {
|
|||||||
auto dc1 = wdc1.lock();
|
auto dc1 = wdc1.lock();
|
||||||
if (!dc1)
|
if (!dc1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
cout << "DataChannel 1: Open" << endl;
|
cout << "DataChannel 1: Open" << endl;
|
||||||
dc1->send("Hello from 1");
|
dc1->send("Hello from 1");
|
||||||
});
|
});
|
||||||
@ -115,14 +122,15 @@ void test_connectivity() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
int attempts = 10;
|
int attempts = 10;
|
||||||
while ((!dc2 || !dc2->isOpen() || !dc1->isOpen()) && attempts--)
|
shared_ptr<DataChannel> adc2;
|
||||||
|
while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
|
||||||
this_thread::sleep_for(1s);
|
this_thread::sleep_for(1s);
|
||||||
|
|
||||||
if (pc1->state() != PeerConnection::State::Connected &&
|
if (pc1->state() != PeerConnection::State::Connected &&
|
||||||
pc2->state() != PeerConnection::State::Connected)
|
pc2->state() != PeerConnection::State::Connected)
|
||||||
throw runtime_error("PeerConnection is not connected");
|
throw runtime_error("PeerConnection is not connected");
|
||||||
|
|
||||||
if (!dc1->isOpen() || !dc2->isOpen())
|
if (!adc2 || !adc2->isOpen() || !dc1->isOpen())
|
||||||
throw runtime_error("DataChannel is not open");
|
throw runtime_error("DataChannel is not open");
|
||||||
|
|
||||||
if (auto addr = pc1->localAddress())
|
if (auto addr = pc1->localAddress())
|
||||||
@ -142,7 +150,7 @@ void test_connectivity() {
|
|||||||
|
|
||||||
// You may call rtc::Cleanup() when finished to free static resources
|
// You may call rtc::Cleanup() when finished to free static resources
|
||||||
rtc::Cleanup();
|
rtc::Cleanup();
|
||||||
this_thread::sleep_for(1s);
|
this_thread::sleep_for(2s);
|
||||||
|
|
||||||
cout << "Success" << endl;
|
cout << "Success" << endl;
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,28 @@
|
|||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace chrono_literals;
|
||||||
|
|
||||||
void test_connectivity();
|
void test_connectivity();
|
||||||
void test_capi();
|
void test_capi();
|
||||||
void test_websocket();
|
void test_websocket();
|
||||||
|
size_t benchmark(chrono::milliseconds duration);
|
||||||
|
|
||||||
|
void test_benchmark() {
|
||||||
|
size_t goodput = benchmark(10s);
|
||||||
|
|
||||||
|
if (goodput == 0)
|
||||||
|
throw runtime_error("No data received");
|
||||||
|
|
||||||
|
const size_t threshold = 1000; // 1 MB/s;
|
||||||
|
if (goodput < threshold)
|
||||||
|
throw runtime_error("Goodput is too low");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
try {
|
try {
|
||||||
@ -51,5 +66,16 @@ int main(int argc, char **argv) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
try {
|
||||||
|
cout << endl << "*** Running WebRTC benchmark..." << endl;
|
||||||
|
test_benchmark();
|
||||||
|
cout << "*** Finished WebRTC benchmark" << endl;
|
||||||
|
} catch (const exception &e) {
|
||||||
|
cerr << "WebRTC benchmark failed: " << e.what() << endl;
|
||||||
|
std::this_thread::sleep_for(2s);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(2s);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ void test_websocket() {
|
|||||||
throw runtime_error("Expected message not received");
|
throw runtime_error("Expected message not received");
|
||||||
|
|
||||||
ws->close();
|
ws->close();
|
||||||
this_thread::sleep_for(1s);
|
this_thread::sleep_for(2s);
|
||||||
|
|
||||||
// You may call rtc::Cleanup() when finished to free static resources
|
// You may call rtc::Cleanup() when finished to free static resources
|
||||||
rtc::Cleanup();
|
rtc::Cleanup();
|
||||||
|
Reference in New Issue
Block a user