mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
103 Commits
Author | SHA1 | Date | |
---|---|---|---|
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:
|
||||
push:
|
||||
branches:
|
||||
@ -7,7 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -31,9 +31,6 @@ jobs:
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
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
|
||||
run: (cd build; make -j2)
|
||||
- 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:
|
||||
push:
|
||||
branches:
|
||||
@ -7,7 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
22
.github/workflows/build-openssl.yml
vendored
22
.github/workflows/build-openssl.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Build and test with OpenSSL
|
||||
name: Build with OpenSSL
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@ -7,7 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -34,9 +34,23 @@ jobs:
|
||||
env:
|
||||
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
||||
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
||||
# hack to bypass EPERM issue on sendto()
|
||||
CFLAGS: -DJUICE_ENABLE_ADDRS_LOCALHOST
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
run: ./build/tests
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- 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"]
|
||||
path = deps/plog
|
||||
url = https://github.com/SergiusTheBest/plog
|
||||
url = https://github.com/paullouisageneau/plog
|
||||
[submodule "usrsctp"]
|
||||
path = deps/usrsctp
|
||||
url = https://github.com/sctplab/usrsctp.git
|
||||
|
@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
project(libdatachannel
|
||||
DESCRIPTION "WebRTC DataChannels Library"
|
||||
VERSION 0.6.0
|
||||
VERSION 0.6.3
|
||||
LANGUAGES CXX)
|
||||
|
||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||
@ -21,6 +21,8 @@ if(WIN32)
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||
if (MSVC)
|
||||
add_definitions(-DNOMINMAX)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -74,6 +76,7 @@ set(TESTS_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/websocket.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/benchmark.cpp
|
||||
)
|
||||
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
@ -160,10 +163,10 @@ if (USE_GNUTLS)
|
||||
if(NOT TARGET GnuTLS::GnuTLS)
|
||||
add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED)
|
||||
set_target_properties(GnuTLS::GnuTLS PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${GNUTLS_INCLUDE_DIRS}
|
||||
INTERFACE_COMPILE_DEFINITIONS ${GNUTLS_DEFINITIONS}
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${GNUTLS_INCLUDE_DIRS}"
|
||||
INTERFACE_COMPILE_DEFINITIONS "${GNUTLS_DEFINITIONS}"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
IMPORTED_LOCATION ${GNUTLS_LIBRARIES})
|
||||
IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
|
||||
endif()
|
||||
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
|
||||
@ -204,7 +207,25 @@ set_target_properties(datachannel-tests PROPERTIES
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||
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
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
|
196
Jamfile
196
Jamfile
@ -3,6 +3,12 @@ import feature : feature ;
|
||||
project libdatachannel ;
|
||||
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
|
||||
: # sources
|
||||
[ glob ./src/*.cpp ]
|
||||
@ -12,25 +18,25 @@ lib libdatachannel
|
||||
<define>USE_JUICE=1
|
||||
<define>RTC_ENABLE_MEDIA=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//juice
|
||||
<library>/libdatachannel//plog
|
||||
<gnutls>on:<library>gnutls
|
||||
<gnutls>off:<library>ssl
|
||||
<gnutls>off:<library>crypto
|
||||
: # default build
|
||||
<link>static
|
||||
: # usage requirements
|
||||
<include>./include
|
||||
<library>/libdatachannel//plog
|
||||
<cxxflags>-pthread
|
||||
<toolset>gcc:<cxxflags>"-Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||
<toolset>clang:<cxxflags>"-Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||
<toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
|
||||
<toolset>clang:<cxxflags>"-pthread -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
|
||||
: # no sources
|
||||
: # no build requirements
|
||||
@ -45,7 +51,16 @@ alias usrsctp
|
||||
: # no default build
|
||||
: # usage requirements
|
||||
<include>./deps/usrsctp/usrsctplib
|
||||
<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
|
||||
@ -54,35 +69,168 @@ alias juice
|
||||
: # no default build
|
||||
: # usage requirements
|
||||
<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 usrsctp.lib : : @make_libusrsctp_msvc ;
|
||||
|
||||
actions make_libusrsctp
|
||||
{
|
||||
(cd $(CWD)/deps/usrsctp && \
|
||||
./bootstrap && \
|
||||
./configure --enable-static --disable-debug CFLAGS="-fPIC -Wno-address-of-packed-member" && \
|
||||
make)
|
||||
cp $(CWD)/deps/usrsctp/usrsctplib/.libs/libusrsctp.a $(<)
|
||||
(cd $(CWD)/deps/usrsctp && mkdir build && cd build && cmake -DCMAKE_C_FLAGS="-fPIC" .. && make -j2 usrsctp-static)
|
||||
cp $(CWD)/deps/usrsctp/build/usrsctplib/libusrsctp.a $(<)
|
||||
}
|
||||
actions make_libusrsctp_msvc
|
||||
{
|
||||
SET OLDD=%CD%
|
||||
cd $(CWD)/deps/usrsctp
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G "Visual Studio 16 2019" ..
|
||||
cd build
|
||||
msbuild usrsctplib.sln /property:Configuration=Release
|
||||
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 * )
|
||||
{
|
||||
if <crypto>gnutls in $(properties)
|
||||
if <gnutls>on in $(properties)
|
||||
{
|
||||
MAKEOPTS on $(targets) = "USE_NETTLE=1" ;
|
||||
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=1" ;
|
||||
}
|
||||
else
|
||||
{
|
||||
MAKEOPTS on $(targets) = "USE_NETTLE=0" ;
|
||||
else {
|
||||
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 ;
|
||||
}
|
||||
|
||||
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=0" ;
|
||||
if $(OPENSSL_INCLUDE) != ""
|
||||
{
|
||||
CMAKEOPTS on $(targets) += " -DOPENSSL_ROOT_DIR=$(OPENSSL_INCLUDE)/.." ;
|
||||
}
|
||||
}
|
||||
}
|
||||
actions make_libjuice
|
||||
{
|
||||
(cd $(CWD)/deps/libjuice && make $(MAKEOPTS))
|
||||
cp $(CWD)/deps/libjuice/libjuice.a $(<)
|
||||
(cd $(CWD)/deps/libjuice && mkdir build && cd build && cmake -DCMAKE_C_FLAGS="-fPIC" $(CMAKEOPTS) .. && make -j2 juice-static)
|
||||
cp $(CWD)/deps/libjuice/build/libjuice-static.a $(<)
|
||||
}
|
||||
rule make_libjuice_msvc ( targets * : sources * : properties * )
|
||||
{
|
||||
if <gnutls>on in $(properties)
|
||||
{
|
||||
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=1" ;
|
||||
}
|
||||
else
|
||||
{
|
||||
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=0" ;
|
||||
}
|
||||
}
|
||||
actions make_libjuice_msvc
|
||||
{
|
||||
SET OLDD=%CD%
|
||||
cd $(CWD)/deps/libjuice
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G "Visual Studio 16 2019" $(CMAKEOPTS) ..
|
||||
msbuild libjuice.sln /property:Configuration=Release
|
||||
cd %OLDD%
|
||||
cp $(CWD)/deps/libjuice/build/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 ;
|
||||
|
||||
|
46
README.md
46
README.md
@ -1,7 +1,6 @@
|
||||
# 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:
|
||||
- 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).
|
||||
@ -26,6 +25,7 @@ Protocol stack:
|
||||
Features:
|
||||
- Full IPv6 support
|
||||
- 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))
|
||||
- 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)
|
||||
@ -44,31 +44,55 @@ Features:
|
||||
|
||||
## Dependencies
|
||||
|
||||
Dependencies:
|
||||
- GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/
|
||||
|
||||
Optional:
|
||||
Optional dependencies:
|
||||
- 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:
|
||||
- libjuice: https://github.com/paullouisageneau/libjuice
|
||||
- usrsctp: https://github.com/sctplab/usrsctp
|
||||
|
||||
## Building
|
||||
### Building with CMake (preferred)
|
||||
|
||||
### Clone repository and submodules
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/paullouisageneau/libdatachannel.git
|
||||
$ cd libdatachannel
|
||||
$ 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
|
||||
$ git submodule update --init --recursive
|
||||
$ make USE_JUICE=1 USE_GNUTLS=1
|
||||
```
|
||||
|
||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: 41e4b66ef6...c6566d3c6f
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...fdc4d2b99b
@ -7,5 +7,11 @@ add_executable(datachannel-client main.cpp)
|
||||
set_target_properties(datachannel-client PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
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)
|
||||
|
||||
|
@ -212,7 +212,7 @@ string randomId(size_t length) {
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
||||
string id(length, '0');
|
||||
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)); });
|
||||
return id;
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.5.1)
|
||||
project(offerer C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_FLAGS "-Wall -g -O2")
|
||||
|
||||
add_executable(datachannel-copy-paste-capi-offerer offerer.c)
|
||||
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
|
||||
OUTPUT_NAME answerer)
|
||||
target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)
|
||||
|
||||
|
@ -19,12 +19,19 @@
|
||||
|
||||
#include <rtc/rtc.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.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 <ctype.h>
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
@ -34,42 +41,32 @@ typedef struct {
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
Peer *peer = NULL;
|
||||
|
||||
static void dataChannelCallback(int dc, 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 stateChangeCallback(rtcState state, void *ptr);
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
|
||||
static void closedCallback(void *ptr);
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
int all_space(const char *str);
|
||||
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) {
|
||||
rtcInitLogger(RTC_LOG_DEBUG);
|
||||
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
||||
|
||||
// Create peer
|
||||
rtcConfiguration config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
// Create peer
|
||||
rtcConfiguration config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
||||
if (!peer) {
|
||||
|
||||
printf("Error allocating memory for peer\n");
|
||||
deletePeer(peer);
|
||||
|
||||
}
|
||||
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
||||
if (!peer) {
|
||||
fprintf(stderr, "Error allocating memory for peer\n");
|
||||
return -1;
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
@ -86,17 +83,12 @@ int main(int argc, char **argv) {
|
||||
rtcSetUserPointer(peer->dc, NULL);
|
||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||
|
||||
sleep(1);
|
||||
|
||||
|
||||
|
||||
bool exit = false;
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
|
||||
// << endl
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
@ -106,16 +98,16 @@ int main(int argc, char **argv) {
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
// int check_scan
|
||||
if (scanf("%d", &command)) {
|
||||
|
||||
}else {
|
||||
break;
|
||||
}
|
||||
while ((c = getchar()) != '\n' && c != EOF) { }
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
fflush(stdin);
|
||||
switch (command) {
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
@ -123,8 +115,6 @@ int main(int argc, char **argv) {
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
|
||||
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
@ -151,12 +141,12 @@ int main(int argc, char **argv) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
@ -171,11 +161,11 @@ int main(int argc, char **argv) {
|
||||
if(getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
@ -200,12 +190,10 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "answerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
||||
}
|
||||
}
|
||||
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
@ -264,63 +249,61 @@ static void dataChannelCallback(int dc, void *ptr) {
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||
|
||||
|
||||
}
|
||||
|
||||
char *state_print(rtcState state) {
|
||||
char *str = NULL;
|
||||
switch (state) {
|
||||
case RTC_NEW:
|
||||
str = "RTC_NEW";
|
||||
break;
|
||||
case RTC_CONNECTING:
|
||||
str = "RTC_CONNECTING";
|
||||
break;
|
||||
case RTC_CONNECTED:
|
||||
str = "RTC_CONNECTED";
|
||||
break;
|
||||
case RTC_DISCONNECTED:
|
||||
str = "RTC_DISCONNECTED";
|
||||
break;
|
||||
case RTC_FAILED:
|
||||
str = "RTC_FAILED";
|
||||
break;
|
||||
case RTC_CLOSED:
|
||||
str = "RTC_CLOSED";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
char *rtcGatheringState_print(rtcGatheringState state) {
|
||||
char *str = NULL;
|
||||
switch (state) {
|
||||
case RTC_GATHERING_NEW:
|
||||
str = "RTC_GATHERING_NEW";
|
||||
break;
|
||||
case RTC_GATHERING_INPROGRESS:
|
||||
str = "RTC_GATHERING_INPROGRESS";
|
||||
break;
|
||||
case RTC_GATHERING_COMPLETE:
|
||||
str = "RTC_GATHERING_COMPLETE";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
int all_space(const char *str) {
|
||||
while (*str) {
|
||||
if (!isspace(*str++)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* state_print(rtcState state) {
|
||||
char *str = NULL;
|
||||
switch (state) {
|
||||
case RTC_NEW:
|
||||
str = "RTC_NEW";
|
||||
break;
|
||||
case RTC_CONNECTING:
|
||||
str = "RTC_CONNECTING";
|
||||
break;
|
||||
case RTC_CONNECTED:
|
||||
str = "RTC_CONNECTED";
|
||||
break;
|
||||
case RTC_DISCONNECTED:
|
||||
str = "RTC_DISCONNECTED";
|
||||
break;
|
||||
case RTC_FAILED:
|
||||
str = "RTC_FAILED";
|
||||
break;
|
||||
case RTC_CLOSED:
|
||||
str = "RTC_CLOSED";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
|
||||
}
|
||||
|
||||
char* rtcGatheringState_print(rtcState state) {
|
||||
char* str = NULL;
|
||||
switch (state) {
|
||||
case RTC_GATHERING_NEW:
|
||||
str = "RTC_GATHERING_NEW";
|
||||
break;
|
||||
case RTC_GATHERING_INPROGRESS:
|
||||
str = "RTC_GATHERING_INPROGRESS";
|
||||
break;
|
||||
case RTC_GATHERING_COMPLETE:
|
||||
str = "RTC_GATHERING_COMPLETE";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 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 <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h> // for sleep
|
||||
#include <ctype.h>
|
||||
|
||||
char* state_print(rtcState state);
|
||||
char* rtcGatheringState_print(rtcState state);
|
||||
#ifdef _WIN32
|
||||
#include "getline.h"
|
||||
#include <windows.h>
|
||||
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#else
|
||||
#include <unistd.h> // for sleep
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
@ -37,41 +41,33 @@ typedef struct {
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
Peer *peer = NULL;
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
|
||||
static void openCallback(void *ptr);
|
||||
|
||||
static void closedCallback(void *ptr);
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
char *state_print(rtcState state);
|
||||
char *rtcGatheringState_print(rtcGatheringState state);
|
||||
|
||||
int all_space(const char *str);
|
||||
|
||||
int main(int argc, char **argv){
|
||||
rtcInitLogger(RTC_LOG_DEBUG);
|
||||
rtcInitLogger(RTC_LOG_DEBUG, NULL);
|
||||
|
||||
// Create peer
|
||||
rtcConfiguration config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
// Create peer
|
||||
rtcConfiguration config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
||||
if (!peer) {
|
||||
|
||||
printf("Error allocating memory for peer\n");
|
||||
deletePeer(peer);
|
||||
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
||||
if (!peer) {
|
||||
fprintf(stderr, "Error allocating memory for peer\n");
|
||||
return -1;
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
|
||||
@ -85,25 +81,17 @@ int main(int argc, char **argv){
|
||||
|
||||
// Since this is the offere, we will create a datachannel
|
||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||
|
||||
rtcSetOpenCallback(peer->dc, openCallback);
|
||||
|
||||
|
||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||
|
||||
rtcSetMessageCallback(peer->dc, messageCallback);
|
||||
|
||||
sleep(1);
|
||||
|
||||
sleep(1);
|
||||
|
||||
bool exit = false;
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
|
||||
// << endl
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
@ -113,13 +101,14 @@ int main(int argc, char **argv){
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
if (scanf("%d", &command)) {
|
||||
|
||||
}else {
|
||||
break;
|
||||
}
|
||||
while ((c = getchar()) != '\n' && c != EOF) { }
|
||||
fflush(stdin);
|
||||
if (!scanf("%d", &command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ((c = getchar()) != '\n' && c != EOF) {
|
||||
}
|
||||
fflush(stdin);
|
||||
|
||||
switch (command) {
|
||||
case 0: {
|
||||
@ -210,11 +199,6 @@ int main(int argc, char **argv){
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
@ -274,61 +254,59 @@ static void deletePeer(Peer *peer) {
|
||||
}
|
||||
}
|
||||
|
||||
char *state_print(rtcState state) {
|
||||
char *str = NULL;
|
||||
switch (state) {
|
||||
case RTC_NEW:
|
||||
str = "RTC_NEW";
|
||||
break;
|
||||
case RTC_CONNECTING:
|
||||
str = "RTC_CONNECTING";
|
||||
break;
|
||||
case RTC_CONNECTED:
|
||||
str = "RTC_CONNECTED";
|
||||
break;
|
||||
case RTC_DISCONNECTED:
|
||||
str = "RTC_DISCONNECTED";
|
||||
break;
|
||||
case RTC_FAILED:
|
||||
str = "RTC_FAILED";
|
||||
break;
|
||||
case RTC_CLOSED:
|
||||
str = "RTC_CLOSED";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
char *rtcGatheringState_print(rtcGatheringState state) {
|
||||
char *str = NULL;
|
||||
switch (state) {
|
||||
case RTC_GATHERING_NEW:
|
||||
str = "RTC_GATHERING_NEW";
|
||||
break;
|
||||
case RTC_GATHERING_INPROGRESS:
|
||||
str = "RTC_GATHERING_INPROGRESS";
|
||||
break;
|
||||
case RTC_GATHERING_COMPLETE:
|
||||
str = "RTC_GATHERING_COMPLETE";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
int all_space(const char *str) {
|
||||
while (*str) {
|
||||
if (!isspace(*str++)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* state_print(rtcState state) {
|
||||
char *str = NULL;
|
||||
switch (state) {
|
||||
case RTC_NEW:
|
||||
str = "RTC_NEW";
|
||||
break;
|
||||
case RTC_CONNECTING:
|
||||
str = "RTC_CONNECTING";
|
||||
break;
|
||||
case RTC_CONNECTED:
|
||||
str = "RTC_CONNECTED";
|
||||
break;
|
||||
case RTC_DISCONNECTED:
|
||||
str = "RTC_DISCONNECTED";
|
||||
break;
|
||||
case RTC_FAILED:
|
||||
str = "RTC_FAILED";
|
||||
break;
|
||||
case RTC_CLOSED:
|
||||
str = "RTC_CLOSED";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
|
||||
}
|
||||
|
||||
char* rtcGatheringState_print(rtcState state) {
|
||||
char* str = NULL;
|
||||
switch (state) {
|
||||
case RTC_GATHERING_NEW:
|
||||
str = "RTC_GATHERING_NEW";
|
||||
break;
|
||||
case RTC_GATHERING_INPROGRESS:
|
||||
str = "RTC_GATHERING_INPROGRESS";
|
||||
break;
|
||||
case RTC_GATHERING_COMPLETE:
|
||||
str = "RTC_GATHERING_COMPLETE";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 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
|
||||
CXX_STANDARD 17
|
||||
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)
|
||||
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
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 pos = message->begin();
|
||||
for (Iterator it = first; it != last; ++it) {
|
||||
auto [bytes, size] = to_bytes(*it);
|
||||
pos = std::copy(bytes, bytes + size, pos);
|
||||
auto [bytes, len] = to_bytes(*it);
|
||||
pos = std::copy(bytes, bytes + len, pos);
|
||||
}
|
||||
return outgoing(message);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ using init_token = std::shared_ptr<Init>;
|
||||
class Init {
|
||||
public:
|
||||
static init_token Token();
|
||||
static void Preload();
|
||||
static void Cleanup();
|
||||
|
||||
~Init();
|
||||
@ -43,6 +44,7 @@ private:
|
||||
static std::mutex Mutex;
|
||||
};
|
||||
|
||||
inline void Preload() { Init::Preload(); }
|
||||
inline void Cleanup() { Init::Cleanup(); }
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -145,7 +145,7 @@ private:
|
||||
init_token mInitToken = Init::Token();
|
||||
|
||||
std::optional<Description> mLocalDescription, mRemoteDescription;
|
||||
mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
||||
mutable std::mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
||||
|
||||
std::shared_ptr<IceTransport> mIceTransport;
|
||||
std::shared_ptr<DtlsTransport> mDtlsTransport;
|
||||
|
@ -23,9 +23,11 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// libdatachannel C API
|
||||
#ifdef _WIN32
|
||||
#define RTC_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define RTC_EXPORT
|
||||
#endif
|
||||
|
||||
#ifndef RTC_ENABLE_MEDIA
|
||||
#define RTC_ENABLE_MEDIA 1
|
||||
@ -35,6 +37,10 @@ extern "C" {
|
||||
#define RTC_ENABLE_WEBSOCKET 1
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// libdatachannel C API
|
||||
|
||||
typedef enum {
|
||||
RTC_NEW = 0,
|
||||
RTC_CONNECTING = 1,
|
||||
@ -60,6 +66,10 @@ typedef enum { // Don't change, it must match plog severity
|
||||
RTC_LOG_VERBOSE = 6
|
||||
} rtcLogLevel;
|
||||
|
||||
#define RTC_ERR_SUCCESS 0
|
||||
#define RTC_ERR_INVALID -1 // invalid argument
|
||||
#define RTC_ERR_FAILURE -2 // runtime error
|
||||
|
||||
typedef struct {
|
||||
const char **iceServers;
|
||||
int iceServersCount;
|
||||
@ -67,70 +77,72 @@ typedef struct {
|
||||
uint16_t portRangeEnd;
|
||||
} rtcConfiguration;
|
||||
|
||||
typedef void (*dataChannelCallbackFunc)(int dc, void *ptr);
|
||||
typedef void (*descriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
||||
typedef void (*candidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
||||
typedef void (*stateChangeCallbackFunc)(rtcState state, void *ptr);
|
||||
typedef void (*gatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
||||
typedef void (*openCallbackFunc)(void *ptr);
|
||||
typedef void (*closedCallbackFunc)(void *ptr);
|
||||
typedef void (*errorCallbackFunc)(const char *error, void *ptr);
|
||||
typedef void (*messageCallbackFunc)(const char *message, int size, void *ptr);
|
||||
typedef void (*bufferedAmountLowCallbackFunc)(void *ptr);
|
||||
typedef void (*availableCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
|
||||
typedef void (*rtcDataChannelCallbackFunc)(int dc, void *ptr);
|
||||
typedef void (*rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
||||
typedef void (*rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
||||
typedef void (*rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
|
||||
typedef void (*rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
||||
typedef void (*rtcOpenCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcClosedCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcErrorCallbackFunc)(const char *error, void *ptr);
|
||||
typedef void (*rtcMessageCallbackFunc)(const char *message, int size, void *ptr);
|
||||
typedef void (*rtcBufferedAmountLowCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcAvailableCallbackFunc)(void *ptr);
|
||||
|
||||
// Log
|
||||
void rtcInitLogger(rtcLogLevel level);
|
||||
RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb); // NULL cb to log to stdout
|
||||
|
||||
// User pointer
|
||||
void rtcSetUserPointer(int id, void *ptr);
|
||||
RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
|
||||
|
||||
// PeerConnection
|
||||
int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id
|
||||
int rtcDeletePeerConnection(int pc);
|
||||
RTC_EXPORT int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id
|
||||
RTC_EXPORT int rtcDeletePeerConnection(int pc);
|
||||
|
||||
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb);
|
||||
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb);
|
||||
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb);
|
||||
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb);
|
||||
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
|
||||
|
||||
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
||||
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
|
||||
RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
||||
RTC_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
|
||||
|
||||
int rtcGetLocalAddress(int pc, char *buffer, int size);
|
||||
int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
||||
RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
|
||||
RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
||||
|
||||
// DataChannel
|
||||
int rtcCreateDataChannel(int pc, const char *label); // returns dc id
|
||||
int rtcDeleteDataChannel(int dc);
|
||||
RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
|
||||
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
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
int rtcCreateWebSocket(const char *url); // returns ws id
|
||||
int rtcDeleteWebsocket(int ws);
|
||||
RTC_EXPORT int rtcCreateWebSocket(const char *url); // returns ws id
|
||||
RTC_EXPORT int rtcDeleteWebsocket(int ws);
|
||||
#endif
|
||||
|
||||
// DataChannel and WebSocket common API
|
||||
int rtcSetOpenCallback(int id, openCallbackFunc cb);
|
||||
int rtcSetClosedCallback(int id, closedCallbackFunc cb);
|
||||
int rtcSetErrorCallback(int id, errorCallbackFunc cb);
|
||||
int rtcSetMessageCallback(int id, messageCallbackFunc cb);
|
||||
int rtcSendMessage(int id, const char *data, int size);
|
||||
RTC_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSendMessage(int id, const char *data, int size);
|
||||
|
||||
int rtcGetBufferedAmount(int id); // total size buffered to send
|
||||
int rtcSetBufferedAmountLowThreshold(int id, int amount);
|
||||
int rtcSetBufferedAmountLowCallback(int id, bufferedAmountLowCallbackFunc cb);
|
||||
RTC_EXPORT int rtcGetBufferedAmount(int id); // total size buffered to send
|
||||
RTC_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount);
|
||||
RTC_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb);
|
||||
|
||||
// DataChannel and WebSocket common extended API
|
||||
int rtcGetAvailableAmount(int id); // total size available to receive
|
||||
int rtcSetAvailableCallback(int id, availableCallbackFunc cb);
|
||||
int rtcReceiveMessage(int id, char *buffer, int *size);
|
||||
RTC_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive
|
||||
RTC_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb);
|
||||
RTC_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size);
|
||||
|
||||
// Cleanup
|
||||
void rtcCleanup();
|
||||
// Optional preload and cleanup
|
||||
RTC_EXPORT void rtcPreload(void);
|
||||
RTC_EXPORT void rtcCleanup(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
|
@ -42,7 +42,7 @@ string to_base64(const binary &data) {
|
||||
i += 3;
|
||||
}
|
||||
|
||||
int left = data.size() - i;
|
||||
int left = int(data.size() - i);
|
||||
if (left) {
|
||||
auto d0 = to_integer<uint8_t>(data[i]);
|
||||
out += tab[d0 >> 2];
|
||||
|
@ -98,8 +98,8 @@ bool Candidate::resolve(ResolveMode mode) {
|
||||
// Rewrite the candidate
|
||||
char nodebuffer[MAX_NUMERICNODE_LEN];
|
||||
char servbuffer[MAX_NUMERICSERV_LEN];
|
||||
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
|
||||
servbuffer, MAX_NUMERICSERV_LEN,
|
||||
if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
|
||||
MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
|
||||
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
|
||||
const char sp{' '};
|
||||
std::ostringstream oss;
|
||||
|
@ -132,14 +132,14 @@ namespace rtc {
|
||||
|
||||
Certificate::Certificate(string crt_pem, string key_pem) {
|
||||
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);
|
||||
BIO_free(bio);
|
||||
if (!mX509)
|
||||
throw std::invalid_argument("Unable to import certificate PEM");
|
||||
|
||||
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);
|
||||
BIO_free(bio);
|
||||
if (!mPKey)
|
||||
@ -233,9 +233,9 @@ 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::future<std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>>
|
||||
thread_call(F &&f, Args &&... args) {
|
||||
using R = std::invoke_result_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));
|
||||
|
@ -61,7 +61,7 @@ string make_fingerprint(X509 *x509);
|
||||
using certificate_ptr = std::shared_ptr<Certificate>;
|
||||
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();
|
||||
|
||||
|
@ -186,8 +186,8 @@ void DataChannel::open(shared_ptr<SctpTransport> transport) {
|
||||
open.channelType = mReliability->type;
|
||||
open.priority = htons(0);
|
||||
open.reliabilityParameter = htonl(reliabilityParameter);
|
||||
open.labelLength = htons(mLabel.size());
|
||||
open.protocolLength = htons(mProtocol.size());
|
||||
open.labelLength = htons(uint16_t(mLabel.size()));
|
||||
open.protocolLength = htons(uint16_t(mProtocol.size()));
|
||||
|
||||
auto end = reinterpret_cast<char *>(buffer.data() + sizeof(OpenMessage));
|
||||
std::copy(mLabel.begin(), mLabel.end(), end);
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
using std::size_t;
|
||||
using std::string;
|
||||
using std::chrono::system_clock;
|
||||
|
||||
namespace {
|
||||
|
||||
@ -54,7 +55,7 @@ Description::Description(const string &sdp, Type type, Role role)
|
||||
mData.mid = "data";
|
||||
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::uniform_int_distribution<uint32_t> uniform;
|
||||
mSessionId = std::to_string(uniform(generator));
|
||||
@ -112,7 +113,8 @@ Description::Description(const string &sdp, Type type, Role role)
|
||||
if (match_prefix(value, "sha-256 ")) {
|
||||
mFingerprint = value.substr(8);
|
||||
std::transform(mFingerprint->begin(), mFingerprint->end(),
|
||||
mFingerprint->begin(), [](char c) { return std::toupper(c); });
|
||||
mFingerprint->begin(),
|
||||
[](char c) { return char(std::toupper(c)); });
|
||||
} else {
|
||||
PLOG_WARNING << "Unknown SDP fingerprint type: " << value;
|
||||
}
|
||||
@ -185,11 +187,11 @@ std::vector<Candidate> Description::extractCandidates() {
|
||||
bool Description::hasMedia() const { return !mMedia.empty(); }
|
||||
|
||||
void Description::addMedia(const Description &source) {
|
||||
for (auto [mid, media] : source.mMedia)
|
||||
if (mid != mData.mid)
|
||||
mMedia.emplace(mid, media);
|
||||
for (auto p : source.mMedia)
|
||||
if (p.first != mData.mid)
|
||||
mMedia.emplace(std::move(p));
|
||||
else
|
||||
PLOG_WARNING << "Media mid \"" << mid << "\" is the same as data mid, ignoring";
|
||||
PLOG_WARNING << "Media mid \"" << p.first << "\" is the same as data mid, ignoring";
|
||||
}
|
||||
|
||||
Description::operator string() const { return generateSdp("\r\n"); }
|
||||
@ -210,8 +212,8 @@ string Description::generateSdp(const string &eol) const {
|
||||
// see Negotiating Media Multiplexing Using the Session Description Protocol
|
||||
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54
|
||||
sdp << "a=group:BUNDLE";
|
||||
for (const auto &[mid, _] : mMedia)
|
||||
sdp << " " << mid;
|
||||
for (const auto &m : mMedia)
|
||||
sdp << " " << m.first; // mid
|
||||
sdp << " " << mData.mid << eol;
|
||||
|
||||
// Data
|
||||
@ -230,12 +232,13 @@ string Description::generateSdp(const string &eol) const {
|
||||
if (!mMedia.empty()) {
|
||||
// Lip-sync
|
||||
sdp << "a=group:LS";
|
||||
for (const auto &[mid, _] : mMedia)
|
||||
sdp << " " << mid;
|
||||
for (const auto &m : mMedia)
|
||||
sdp << " " << m.first; // mid
|
||||
sdp << eol;
|
||||
|
||||
// Descriptions and attributes
|
||||
for (const auto &[_, media] : mMedia) {
|
||||
for (const auto &m : mMedia) {
|
||||
const auto &media = m.second;
|
||||
sdp << "m=" << media.type << ' ' << 0 << ' ' << media.description << eol;
|
||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
||||
sdp << "a=bundle-only" << eol;
|
||||
@ -250,7 +253,7 @@ string Description::generateSdp(const string &eol) const {
|
||||
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
||||
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||
sdp << "a=dtls-id:1" << eol;
|
||||
sdp << "a=tls-id:1" << eol;
|
||||
if (mFingerprint)
|
||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||
|
||||
|
@ -24,6 +24,14 @@
|
||||
#include <exception>
|
||||
#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 std::shared_ptr;
|
||||
@ -36,12 +44,10 @@ namespace rtc {
|
||||
#if USE_GNUTLS
|
||||
|
||||
void DtlsTransport::Init() {
|
||||
// Nothing to do
|
||||
gnutls_global_init(); // optional
|
||||
}
|
||||
|
||||
void DtlsTransport::Cleanup() {
|
||||
// Nothing to do
|
||||
}
|
||||
void DtlsTransport::Cleanup() { gnutls_global_deinit(); }
|
||||
|
||||
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
|
||||
verifier_callback verifierCallback, state_callback stateChangeCallback)
|
||||
@ -302,7 +308,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
|
||||
|
||||
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");
|
||||
|
||||
openssl::check(SSL_CTX_set_cipher_list(mCtx, "ALL:!LOW:!EXP:!RC4:!MD5:@STRENGTH"),
|
||||
@ -326,7 +333,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
|
||||
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");
|
||||
|
||||
SSL_set_ex_data(mSsl, TransportExIndex, this);
|
||||
@ -336,7 +344,9 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
else
|
||||
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");
|
||||
|
||||
BIO_set_mem_eof_return(mInBio, BIO_EOF);
|
||||
@ -384,7 +394,7 @@ bool DtlsTransport::send(message_ptr message) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -418,11 +428,11 @@ void DtlsTransport::runRecvLoop() {
|
||||
// Process pending messages
|
||||
while (!mIncomingQueue.empty()) {
|
||||
auto message = *mIncomingQueue.pop();
|
||||
BIO_write(mInBio, message->data(), message->size());
|
||||
BIO_write(mInBio, message->data(), int(message->size()));
|
||||
|
||||
if (state() == State::Connecting) {
|
||||
// Continue the handshake
|
||||
int ret = SSL_do_handshake(mSsl);
|
||||
ret = SSL_do_handshake(mSsl);
|
||||
if (!openssl::check(mSsl, ret, "Handshake failed"))
|
||||
break;
|
||||
|
||||
@ -436,7 +446,7 @@ void DtlsTransport::runRecvLoop() {
|
||||
postHandshake();
|
||||
}
|
||||
} else {
|
||||
int ret = SSL_read(mSsl, buffer, bufferSize);
|
||||
ret = SSL_read(mSsl, buffer, bufferSize);
|
||||
if (!openssl::check(mSsl, ret))
|
||||
break;
|
||||
|
||||
@ -449,7 +459,7 @@ void DtlsTransport::runRecvLoop() {
|
||||
std::optional<milliseconds> duration;
|
||||
if (state() == State::Connecting) {
|
||||
// Warning: This function breaks the usual return value convention
|
||||
int ret = DTLSv1_handle_timeout(mSsl);
|
||||
ret = DTLSv1_handle_timeout(mSsl);
|
||||
if (ret < 0) {
|
||||
throw std::runtime_error("Handshake timeout"); // write BIO can't fail
|
||||
} else if (ret > 0) {
|
||||
@ -488,7 +498,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 =
|
||||
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
||||
DtlsTransport *t =
|
||||
@ -537,7 +547,7 @@ int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
|
||||
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) {
|
||||
case BIO_CTRL_FLUSH:
|
||||
return 1;
|
||||
|
@ -40,6 +40,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::weak_ptr;
|
||||
using std::chrono::system_clock;
|
||||
|
||||
#if USE_JUICE
|
||||
|
||||
@ -69,7 +70,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
|
||||
// Randomize servers order
|
||||
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));
|
||||
|
||||
// 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;
|
||||
mStunService = server.service;
|
||||
jconfig.stun_server_host = mStunHostname.c_str();
|
||||
jconfig.stun_server_port = std::stoul(mStunService);
|
||||
jconfig.stun_server_port = uint16_t(std::stoul(mStunService));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -206,7 +207,7 @@ void IceTransport::processCandidate(const string &candidate) {
|
||||
|
||||
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);
|
||||
try {
|
||||
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);
|
||||
try {
|
||||
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);
|
||||
try {
|
||||
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 *user_ptr) {
|
||||
void IceTransport::RecvCallback(juice_agent_t *, const char *data, size_t size, void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
PLOG_VERBOSE << "Incoming size=" << size;
|
||||
@ -337,7 +337,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
|
||||
// Randomize order
|
||||
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));
|
||||
|
||||
// Add one STUN server
|
||||
|
11
src/init.cpp
11
src/init.cpp
@ -55,7 +55,15 @@ init_token Init::Token() {
|
||||
return Global;
|
||||
}
|
||||
|
||||
void Init::Cleanup() { Global.reset(); }
|
||||
void Init::Preload() {
|
||||
Token(); // pre-init
|
||||
make_certificate().wait(); // preload certificate
|
||||
}
|
||||
|
||||
void Init::Cleanup() {
|
||||
Global.reset();
|
||||
CleanupCertificateCache();
|
||||
}
|
||||
|
||||
Init::Init() {
|
||||
#ifdef _WIN32
|
||||
@ -81,7 +89,6 @@ Init::Init() {
|
||||
}
|
||||
|
||||
Init::~Init() {
|
||||
CleanupCertificateCache();
|
||||
SctpTransport::Cleanup();
|
||||
DtlsTransport::Cleanup();
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include "log.hpp"
|
||||
|
||||
#include "plog/Appenders/ColorConsoleAppender.h"
|
||||
#include "plog/Formatters/TxtFormatter.h"
|
||||
#include "plog/Init.h"
|
||||
#include "plog/Log.h"
|
||||
#include "plog/Logger.h"
|
||||
|
||||
|
@ -39,9 +39,12 @@ using std::weak_ptr;
|
||||
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
|
||||
|
||||
PeerConnection::PeerConnection(const Configuration &config)
|
||||
: mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New),
|
||||
: mConfig(config), mCertificate(make_certificate()), mState(State::New),
|
||||
mGatheringState(GatheringState::New) {
|
||||
PLOG_VERBOSE << "Creating PeerConnection";
|
||||
|
||||
if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
|
||||
throw std::invalid_argument("Invalid port range");
|
||||
}
|
||||
|
||||
PeerConnection::~PeerConnection() {
|
||||
@ -89,18 +92,20 @@ void PeerConnection::setLocalDescription(std::optional<Description> description)
|
||||
|
||||
void PeerConnection::setRemoteDescription(Description description) {
|
||||
description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
|
||||
auto remoteCandidates = description.extractCandidates();
|
||||
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
mRemoteDescription.emplace(std::move(description));
|
||||
auto type = description.type();
|
||||
auto remoteCandidates = description.extractCandidates(); // Candidates will be added at the end
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
if (!iceTransport)
|
||||
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.
|
||||
Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer);
|
||||
localDescription.addMedia(description); // blindly accept media
|
||||
@ -131,14 +136,11 @@ void PeerConnection::setRemoteDescription(Description description) {
|
||||
}
|
||||
|
||||
void PeerConnection::addRemoteCandidate(Candidate candidate) {
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
if (!mRemoteDescription || !iceTransport)
|
||||
throw std::logic_error("Remote candidate set without remote description");
|
||||
|
||||
mRemoteDescription->addCandidate(candidate);
|
||||
|
||||
if (candidate.resolve(Candidate::ResolveMode::Simple)) {
|
||||
iceTransport->addRemoteCandidate(candidate);
|
||||
} else {
|
||||
@ -151,6 +153,9 @@ void PeerConnection::addRemoteCandidate(Candidate candidate) {
|
||||
});
|
||||
t.detach();
|
||||
}
|
||||
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
mRemoteDescription->addCandidate(candidate);
|
||||
}
|
||||
|
||||
std::optional<string> PeerConnection::localAddress() const {
|
||||
@ -478,7 +483,7 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto channel = findDataChannel(message->stream);
|
||||
auto channel = findDataChannel(uint16_t(message->stream));
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
@ -594,11 +599,13 @@ void PeerConnection::processLocalDescription(Description description) {
|
||||
|
||||
auto certificate = mCertificate.get(); // wait for certificate if not ready
|
||||
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
mLocalDescription.emplace(std::move(description));
|
||||
mLocalDescription->setFingerprint(certificate->fingerprint());
|
||||
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
||||
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||
{
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
mLocalDescription.emplace(std::move(description));
|
||||
mLocalDescription->setFingerprint(certificate->fingerprint());
|
||||
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
||||
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||
}
|
||||
|
||||
mLocalDescriptionCallback(*mLocalDescription);
|
||||
}
|
||||
@ -651,13 +658,14 @@ void PeerConnection::resetCallbacks() {
|
||||
mGatheringStateChangeCallback = nullptr;
|
||||
}
|
||||
|
||||
bool PeerConnection::getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote) {
|
||||
#if not USE_JUICE
|
||||
bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] CandidateInfo *local,
|
||||
[[maybe_unused]] CandidateInfo *remote) {
|
||||
#if USE_JUICE
|
||||
PLOG_WARNING << "getSelectedCandidatePair() is not implemented for libjuice";
|
||||
return false;
|
||||
#else
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
return iceTransport->getSelectedCandidatePair(local, remote);
|
||||
#else
|
||||
PLOG_WARNING << "getSelectedCandidatePair is not implemented for libjuice";
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
800
src/rtc.cpp
800
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
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -18,32 +18,32 @@
|
||||
|
||||
#include "include.hpp"
|
||||
|
||||
#include "datachannel.hpp"
|
||||
#include "peerconnection.hpp"
|
||||
#include "rtc.h"
|
||||
|
||||
#include "datachannel.hpp"
|
||||
#include "log.hpp"
|
||||
#include "peerconnection.hpp"
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
#include "websocket.hpp"
|
||||
#endif
|
||||
|
||||
#include <rtc.h>
|
||||
#include "plog/Formatters/FuncMessageFormatter.h"
|
||||
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#endif
|
||||
|
||||
using namespace rtc;
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
|
||||
#define CATCH(statement) \
|
||||
try { \
|
||||
statement; \
|
||||
} catch (const std::exception &e) { \
|
||||
PLOG_ERROR << e.what(); \
|
||||
return -1; \
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
|
||||
@ -64,21 +64,25 @@ void *getUserPointer(int id) {
|
||||
void setUserPointer(int i, void *ptr) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (ptr)
|
||||
userPointerMap.insert(std::make_pair(i, ptr));
|
||||
userPointerMap[i] = ptr;
|
||||
else
|
||||
userPointerMap.erase(i);
|
||||
}
|
||||
|
||||
shared_ptr<PeerConnection> getPeerConnection(int id) {
|
||||
std::lock_guard lock(mutex);
|
||||
auto it = peerConnectionMap.find(id);
|
||||
return it != peerConnectionMap.end() ? it->second : nullptr;
|
||||
if (auto it = peerConnectionMap.find(id); it != peerConnectionMap.end())
|
||||
return it->second;
|
||||
else
|
||||
throw std::invalid_argument("PeerConnection ID does not exist");
|
||||
}
|
||||
|
||||
shared_ptr<DataChannel> getDataChannel(int id) {
|
||||
std::lock_guard lock(mutex);
|
||||
auto it = dataChannelMap.find(id);
|
||||
return it != dataChannelMap.end() ? it->second : nullptr;
|
||||
if (auto it = dataChannelMap.find(id); it != dataChannelMap.end())
|
||||
return it->second;
|
||||
else
|
||||
throw std::invalid_argument("DataChannel ID does not exist");
|
||||
}
|
||||
|
||||
int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
|
||||
@ -95,27 +99,27 @@ int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
|
||||
return dc;
|
||||
}
|
||||
|
||||
bool erasePeerConnection(int pc) {
|
||||
void erasePeerConnection(int pc) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (peerConnectionMap.erase(pc) == 0)
|
||||
return false;
|
||||
throw std::invalid_argument("PeerConnection ID does not exist");
|
||||
userPointerMap.erase(pc);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool eraseDataChannel(int dc) {
|
||||
void eraseDataChannel(int dc) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (dataChannelMap.erase(dc) == 0)
|
||||
return false;
|
||||
throw std::invalid_argument("DataChannel ID does not exist");
|
||||
userPointerMap.erase(dc);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
shared_ptr<WebSocket> getWebSocket(int id) {
|
||||
std::lock_guard lock(mutex);
|
||||
auto it = webSocketMap.find(id);
|
||||
return it != webSocketMap.end() ? it->second : nullptr;
|
||||
if (auto it = webSocketMap.find(id); it != webSocketMap.end())
|
||||
return it->second;
|
||||
else
|
||||
throw std::invalid_argument("WebSocket ID does not exist");
|
||||
}
|
||||
|
||||
int emplaceWebSocket(shared_ptr<WebSocket> ptr) {
|
||||
@ -125,12 +129,11 @@ int emplaceWebSocket(shared_ptr<WebSocket> ptr) {
|
||||
return ws;
|
||||
}
|
||||
|
||||
bool eraseWebSocket(int ws) {
|
||||
void eraseWebSocket(int ws) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (webSocketMap.erase(ws) == 0)
|
||||
return false;
|
||||
throw std::invalid_argument("WebSocket ID does not exist");
|
||||
userPointerMap.erase(ws);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -142,380 +145,425 @@ shared_ptr<Channel> getChannel(int id) {
|
||||
if (auto it = webSocketMap.find(id); it != webSocketMap.end())
|
||||
return it->second;
|
||||
#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
|
||||
|
||||
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); }
|
||||
|
||||
int rtcCreatePeerConnection(const rtcConfiguration *config) {
|
||||
Configuration c;
|
||||
for (int i = 0; i < config->iceServersCount; ++i)
|
||||
c.iceServers.emplace_back(string(config->iceServers[i]));
|
||||
return WRAP({
|
||||
Configuration c;
|
||||
for (int i = 0; i < config->iceServersCount; ++i)
|
||||
c.iceServers.emplace_back(string(config->iceServers[i]));
|
||||
|
||||
if (config->portRangeBegin || config->portRangeEnd) {
|
||||
c.portRangeBegin = config->portRangeBegin;
|
||||
c.portRangeEnd = config->portRangeEnd;
|
||||
}
|
||||
if (config->portRangeBegin || config->portRangeEnd) {
|
||||
c.portRangeBegin = config->portRangeBegin;
|
||||
c.portRangeEnd = config->portRangeEnd;
|
||||
}
|
||||
|
||||
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
|
||||
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
|
||||
});
|
||||
}
|
||||
|
||||
int rtcDeletePeerConnection(int pc) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
peerConnection->onDataChannel(nullptr);
|
||||
peerConnection->onLocalDescription(nullptr);
|
||||
peerConnection->onLocalCandidate(nullptr);
|
||||
peerConnection->onStateChange(nullptr);
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
|
||||
peerConnection->onDataChannel(nullptr);
|
||||
peerConnection->onLocalDescription(nullptr);
|
||||
peerConnection->onLocalCandidate(nullptr);
|
||||
peerConnection->onStateChange(nullptr);
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
|
||||
erasePeerConnection(pc);
|
||||
return 0;
|
||||
erasePeerConnection(pc);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcCreateDataChannel(int pc, const char *label) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
|
||||
void *ptr = getUserPointer(pc);
|
||||
rtcSetUserPointer(dc, ptr);
|
||||
return dc;
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
|
||||
rtcSetUserPointer(dc, getUserPointer(pc));
|
||||
return dc;
|
||||
});
|
||||
}
|
||||
|
||||
int rtcDeleteDataChannel(int dc) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
return WRAP({
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
dataChannel->onOpen(nullptr);
|
||||
dataChannel->onClosed(nullptr);
|
||||
dataChannel->onError(nullptr);
|
||||
dataChannel->onMessage(nullptr);
|
||||
dataChannel->onBufferedAmountLow(nullptr);
|
||||
dataChannel->onAvailable(nullptr);
|
||||
|
||||
dataChannel->onOpen(nullptr);
|
||||
dataChannel->onClosed(nullptr);
|
||||
dataChannel->onError(nullptr);
|
||||
dataChannel->onMessage(nullptr);
|
||||
dataChannel->onBufferedAmountLow(nullptr);
|
||||
dataChannel->onAvailable(nullptr);
|
||||
|
||||
eraseDataChannel(dc);
|
||||
return 0;
|
||||
eraseDataChannel(dc);
|
||||
});
|
||||
}
|
||||
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
int rtcCreateWebSocket(const char *url) {
|
||||
auto ws = std::make_shared<WebSocket>();
|
||||
ws->open(url);
|
||||
return emplaceWebSocket(ws);
|
||||
}
|
||||
|
||||
int rtcDeleteWebsocket(int ws) {
|
||||
auto webSocket = getWebSocket(ws);
|
||||
if (!webSocket)
|
||||
return -1;
|
||||
|
||||
webSocket->onOpen(nullptr);
|
||||
webSocket->onClosed(nullptr);
|
||||
webSocket->onError(nullptr);
|
||||
webSocket->onMessage(nullptr);
|
||||
webSocket->onBufferedAmountLow(nullptr);
|
||||
webSocket->onAvailable(nullptr);
|
||||
|
||||
eraseWebSocket(ws);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
|
||||
int dc = emplaceDataChannel(dataChannel);
|
||||
void *ptr = getUserPointer(pc);
|
||||
rtcSetUserPointer(dc, ptr);
|
||||
cb(dc, ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onDataChannel(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
|
||||
cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalDescription(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
|
||||
cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalCandidate(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
|
||||
cb(static_cast<rtcState>(state), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onStateChange(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
|
||||
cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
CATCH(peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}))
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (auto addr = peerConnection->localAddress()) {
|
||||
size = std::min(size_t(size - 1), addr->size());
|
||||
std::copy(addr->data(), addr->data() + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (auto addr = peerConnection->remoteAddress()) {
|
||||
size = std::min(size_t(size - 1), addr->size());
|
||||
std::copy(addr->data(), addr->data() + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
if (!size)
|
||||
return 0;
|
||||
|
||||
string label = dataChannel->label();
|
||||
size = std::min(size_t(size - 1), label.size());
|
||||
std::copy(label.data(), label.data() + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
int rtcSetOpenCallback(int id, openCallbackFunc cb) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
channel->onOpen([id, cb]() { cb(getUserPointer(id)); });
|
||||
else
|
||||
channel->onOpen(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetClosedCallback(int id, closedCallbackFunc cb) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
channel->onClosed([id, cb]() { cb(getUserPointer(id)); });
|
||||
else
|
||||
channel->onClosed(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetErrorCallback(int id, errorCallbackFunc cb) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
channel->onError([id, cb](const string &error) { cb(error.c_str(), getUserPointer(id)); });
|
||||
else
|
||||
channel->onError(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetMessageCallback(int id, messageCallbackFunc cb) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
channel->onMessage(
|
||||
[id, cb](const binary &b) {
|
||||
cb(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(id));
|
||||
},
|
||||
[id, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(id)); });
|
||||
else
|
||||
channel->onMessage(nullptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSendMessage(int id, const char *data, int size) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
if (size >= 0) {
|
||||
auto b = reinterpret_cast<const byte *>(data);
|
||||
CATCH(channel->send(binary(b, b + size)));
|
||||
return size;
|
||||
} else {
|
||||
string str(data);
|
||||
int len = str.size();
|
||||
CATCH(channel->send(std::move(str)));
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
int rtcGetBufferedAmount(int id) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
CATCH(return int(channel->bufferedAmount()));
|
||||
}
|
||||
|
||||
int rtcSetBufferedAmountLowThreshold(int id, int amount) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
CATCH(channel->setBufferedAmountLowThreshold(size_t(amount)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetBufferedAmountLowCallback(int id, bufferedAmountLowCallbackFunc cb) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
channel->onBufferedAmountLow([id, cb]() { cb(getUserPointer(id)); });
|
||||
else
|
||||
channel->onBufferedAmountLow(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcGetAvailableAmount(int id) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
CATCH(return int(channel->availableAmount()));
|
||||
}
|
||||
|
||||
int rtcSetAvailableCallback(int id, availableCallbackFunc cb) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
channel->onOpen([id, cb]() { cb(getUserPointer(id)); });
|
||||
else
|
||||
channel->onOpen(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcReceiveMessage(int id, char *buffer, int *size) {
|
||||
auto channel = getChannel(id);
|
||||
if (!channel)
|
||||
return -1;
|
||||
|
||||
if (!size)
|
||||
return -1;
|
||||
|
||||
CATCH({
|
||||
auto message = channel->receive();
|
||||
if (!message)
|
||||
return 0;
|
||||
|
||||
return std::visit( //
|
||||
overloaded{ //
|
||||
[&](const binary &b) {
|
||||
*size = std::min(*size, int(b.size()));
|
||||
auto data = reinterpret_cast<const char *>(b.data());
|
||||
std::copy(data, data + *size, buffer);
|
||||
return *size;
|
||||
},
|
||||
[&](const string &s) {
|
||||
int len = std::min(*size - 1, int(s.size()));
|
||||
if (len >= 0) {
|
||||
std::copy(s.data(), s.data() + len, buffer);
|
||||
buffer[len] = '\0';
|
||||
}
|
||||
*size = -(len + 1);
|
||||
return len + 1;
|
||||
}},
|
||||
*message);
|
||||
return WRAP({
|
||||
auto ws = std::make_shared<WebSocket>();
|
||||
ws->open(url);
|
||||
return emplaceWebSocket(ws);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcDeleteWebsocket(int ws) {
|
||||
return WRAP({
|
||||
auto webSocket = getWebSocket(ws);
|
||||
webSocket->onOpen(nullptr);
|
||||
webSocket->onClosed(nullptr);
|
||||
webSocket->onError(nullptr);
|
||||
webSocket->onMessage(nullptr);
|
||||
webSocket->onBufferedAmountLow(nullptr);
|
||||
webSocket->onAvailable(nullptr);
|
||||
|
||||
eraseWebSocket(ws);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (cb)
|
||||
peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
|
||||
int dc = emplaceDataChannel(dataChannel);
|
||||
void *ptr = getUserPointer(pc);
|
||||
rtcSetUserPointer(dc, ptr);
|
||||
cb(dc, ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onDataChannel(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (cb)
|
||||
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
|
||||
cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalDescription(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (cb)
|
||||
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
|
||||
cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalCandidate(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (cb)
|
||||
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
|
||||
cb(static_cast<rtcState>(state), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onStateChange(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (cb)
|
||||
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
|
||||
cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (!sdp)
|
||||
throw std::invalid_argument("Unexpected null pointer");
|
||||
|
||||
peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""});
|
||||
});
|
||||
}
|
||||
|
||||
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (!cand)
|
||||
throw std::invalid_argument("Unexpected null pointer");
|
||||
|
||||
peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""});
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer");
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
|
||||
if (auto addr = peerConnection->localAddress()) {
|
||||
const char *data = addr->data();
|
||||
size = std::min(size - 1, int(addr->size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer");
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
|
||||
if (auto addr = peerConnection->remoteAddress()) {
|
||||
const char *data = addr->data();
|
||||
size = std::min(size - 1, int(addr->size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return int(size + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer");
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
|
||||
string label = dataChannel->label();
|
||||
const char *data = label.data();
|
||||
size = std::min(size - 1, int(label.size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return int(size + 1);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
if (cb)
|
||||
channel->onOpen([id, cb]() { cb(getUserPointer(id)); });
|
||||
else
|
||||
channel->onOpen(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
if (cb)
|
||||
channel->onClosed([id, cb]() { cb(getUserPointer(id)); });
|
||||
else
|
||||
channel->onClosed(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
if (cb)
|
||||
channel->onError(
|
||||
[id, cb](const string &error) { cb(error.c_str(), getUserPointer(id)); });
|
||||
else
|
||||
channel->onError(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
if (cb)
|
||||
channel->onMessage(
|
||||
[id, cb](const binary &b) {
|
||||
cb(reinterpret_cast<const char *>(b.data()), int(b.size()), getUserPointer(id));
|
||||
},
|
||||
[id, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(id)); });
|
||||
else
|
||||
channel->onMessage(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSendMessage(int id, const char *data, int size) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
|
||||
if (!data)
|
||||
throw std::invalid_argument("Unexpected null pointer");
|
||||
|
||||
if (size >= 0) {
|
||||
auto b = reinterpret_cast<const byte *>(data);
|
||||
channel->send(binary(b, b + size));
|
||||
return size;
|
||||
} else {
|
||||
string str(data);
|
||||
int len = int(str.size());
|
||||
channel->send(std::move(str));
|
||||
return len;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetBufferedAmount(int id) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
return int(channel->bufferedAmount());
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetBufferedAmountLowThreshold(int id, int amount) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
channel->setBufferedAmountLowThreshold(size_t(amount));
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
if (cb)
|
||||
channel->onBufferedAmountLow([id, cb]() { cb(getUserPointer(id)); });
|
||||
else
|
||||
channel->onBufferedAmountLow(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetAvailableAmount(int id) {
|
||||
return WRAP({ return int(getChannel(id)->availableAmount()); });
|
||||
}
|
||||
|
||||
int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
if (cb)
|
||||
channel->onOpen([id, cb]() { cb(getUserPointer(id)); });
|
||||
else
|
||||
channel->onOpen(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcReceiveMessage(int id, char *buffer, int *size) {
|
||||
return WRAP({
|
||||
auto channel = getChannel(id);
|
||||
|
||||
if (!buffer || !size)
|
||||
throw std::invalid_argument("Unexpected null pointer");
|
||||
|
||||
if (auto message = channel->receive())
|
||||
return std::visit( //
|
||||
overloaded{ //
|
||||
[&](const binary &b) {
|
||||
*size = std::min(*size, int(b.size()));
|
||||
auto data = reinterpret_cast<const char *>(b.data());
|
||||
std::copy(data, data + *size, buffer);
|
||||
return 1;
|
||||
},
|
||||
[&](const string &s) {
|
||||
int len = std::min(*size - 1, int(s.size()));
|
||||
if (len >= 0) {
|
||||
std::copy(s.data(), s.data() + len, buffer);
|
||||
buffer[len] = '\0';
|
||||
}
|
||||
*size = -(len + 1);
|
||||
return 1;
|
||||
}},
|
||||
*message);
|
||||
else
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
void rtcPreload() { rtc::Preload(); }
|
||||
void rtcCleanup() { rtc::Cleanup(); }
|
||||
|
@ -61,6 +61,20 @@ void SctpTransport::Init() {
|
||||
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_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() {
|
||||
@ -158,11 +172,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
|
||||
// realistic RTTs, therefore we increase it to 1MiB for better performance.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685
|
||||
int bufSize = 1024 * 1024;
|
||||
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &bufSize, sizeof(bufSize)))
|
||||
int bufferSize = 1024 * 1024;
|
||||
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)))
|
||||
throw std::runtime_error("Could not set SCTP recv buffer size, 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=" +
|
||||
std::to_string(errno));
|
||||
|
||||
@ -248,7 +262,7 @@ bool SctpTransport::send(message_ptr message) {
|
||||
return true;
|
||||
|
||||
mSendQueue.push(message);
|
||||
updateBufferedAmount(message->stream, message_size_func(message));
|
||||
updateBufferedAmount(uint16_t(message->stream), long(message_size_func(message)));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -265,7 +279,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
|
||||
// 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.
|
||||
{
|
||||
if (!mWrittenOnce) { // test the atomic boolean is not set first to prevent a lock contention
|
||||
std::unique_lock lock(mWriteMutex);
|
||||
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || state() != State::Connected; });
|
||||
}
|
||||
@ -288,7 +302,7 @@ bool SctpTransport::trySendQueue() {
|
||||
if (!trySendMessage(message))
|
||||
return false;
|
||||
mSendQueue.pop();
|
||||
updateBufferedAmount(message->stream, -message_size_func(message));
|
||||
updateBufferedAmount(uint16_t(message->stream), -long(message_size_func(message)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -310,7 +324,7 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
||||
ppid = PPID_CONTROL;
|
||||
break;
|
||||
case Message::Reset:
|
||||
sendReset(message->stream);
|
||||
sendReset(uint16_t(message->stream));
|
||||
return true;
|
||||
default:
|
||||
// Ignore
|
||||
@ -362,18 +376,20 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
||||
ret = usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0);
|
||||
}
|
||||
|
||||
if (ret >= 0) {
|
||||
PLOG_VERBOSE << "SCTP sent size=" << message->size();
|
||||
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
|
||||
mBytesSent += message->size();
|
||||
return true;
|
||||
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
PLOG_VERBOSE << "SCTP sending not possible";
|
||||
return false;
|
||||
} else {
|
||||
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();
|
||||
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
|
||||
mBytesSent += message->size();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
|
||||
@ -385,7 +401,13 @@ void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
|
||||
else
|
||||
it->second = amount;
|
||||
|
||||
mBufferedAmountCallback(streamId, amount);
|
||||
mSendMutex.unlock();
|
||||
try {
|
||||
mBufferedAmountCallback(streamId, amount);
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << "SCTP buffered amount callback: " << e.what();
|
||||
}
|
||||
mSendMutex.lock();
|
||||
}
|
||||
|
||||
void SctpTransport::sendReset(uint16_t streamId) {
|
||||
@ -421,37 +443,51 @@ bool SctpTransport::safeFlush() {
|
||||
return true;
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "SCTP flush: " << e.what();
|
||||
PLOG_WARNING << "SCTP flush: " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data,
|
||||
size_t len, struct sctp_rcvinfo info, int flags) {
|
||||
int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*addr*/,
|
||||
const byte *data, size_t len, struct sctp_rcvinfo info, int flags) {
|
||||
try {
|
||||
PLOG_VERBOSE << "Handle recv, len=" << len;
|
||||
if (!len)
|
||||
return -1;
|
||||
return 0; // Ignore
|
||||
|
||||
// This is valid because SCTP_FRAGMENT_INTERLEAVE is set to level 0
|
||||
// so partial messages and notifications may not be interleaved.
|
||||
if (flags & MSG_EOR) {
|
||||
if (!mPartialRecv.empty()) {
|
||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
||||
data = mPartialRecv.data();
|
||||
len = mPartialRecv.size();
|
||||
}
|
||||
// Message/Notification is complete, process it
|
||||
if (flags & MSG_NOTIFICATION)
|
||||
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
|
||||
// therefore partial notifications and messages need to be handled separately.
|
||||
if (flags & MSG_NOTIFICATION) {
|
||||
// SCTP event notification
|
||||
if (flags & MSG_EOR) {
|
||||
if (!mPartialNotification.empty()) {
|
||||
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
|
||||
data = mPartialNotification.data();
|
||||
len = mPartialNotification.size();
|
||||
}
|
||||
// Notification is complete, process it
|
||||
processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
|
||||
else
|
||||
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
|
||||
mPartialNotification.clear();
|
||||
} else {
|
||||
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
|
||||
}
|
||||
|
||||
mPartialRecv.clear();
|
||||
} else {
|
||||
// Message/Notification is not complete
|
||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
||||
// 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) {
|
||||
PLOG_ERROR << "SCTP recv: " << e.what();
|
||||
return -1;
|
||||
@ -464,13 +500,14 @@ int SctpTransport::handleSend(size_t free) {
|
||||
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 {
|
||||
PLOG_VERBOSE << "Handle write, len=" << len;
|
||||
|
||||
std::unique_lock lock(mWriteMutex);
|
||||
if (!outgoing(make_message(data, data + len)))
|
||||
return -1;
|
||||
|
||||
mWritten = true;
|
||||
mWrittenOnce = true;
|
||||
mWrittenCondition.notify_all();
|
||||
|
@ -97,9 +97,10 @@ private:
|
||||
std::mutex mWriteMutex;
|
||||
std::condition_variable mWrittenCondition;
|
||||
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
|
||||
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
|
||||
|
@ -21,6 +21,7 @@
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
|
||||
#include <exception>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
@ -59,7 +60,7 @@ int SelectInterrupter::prepare(fd_set &readfds, fd_set &writefds) {
|
||||
if (mDummySock == INVALID_SOCKET)
|
||||
mDummySock = ::socket(AF_INET, SOCK_DGRAM, 0);
|
||||
FD_SET(mDummySock, &readfds);
|
||||
return SOCK_TO_INT(mDummySock) + 1;
|
||||
return SOCKET_TO_INT(mDummySock) + 1;
|
||||
#else
|
||||
int ret;
|
||||
do {
|
||||
@ -150,7 +151,7 @@ void TcpTransport::connect(const string &hostname, const string &service) {
|
||||
|
||||
for (auto p = result; p; p = p->ai_next) {
|
||||
try {
|
||||
connect(p->ai_addr, p->ai_addrlen);
|
||||
connect(p->ai_addr, socklen_t(p->ai_addrlen));
|
||||
|
||||
PLOG_INFO << "Connected to " << hostname << ":" << service;
|
||||
freeaddrinfo(result);
|
||||
@ -201,7 +202,7 @@ void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
|
||||
|
||||
// Initiate connection
|
||||
int ret = ::connect(mSock, addr, addrlen);
|
||||
if (ret < 0 && errno != EINPROGRESS) {
|
||||
if (ret < 0 && sockerrno != SEINPROGRESS && sockerrno != SEWOULDBLOCK) {
|
||||
std::ostringstream msg;
|
||||
msg << "TCP connection to " << node << ":" << serv << " failed, errno=" << sockerrno;
|
||||
throw std::runtime_error(msg.str());
|
||||
@ -226,7 +227,7 @@ void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
|
||||
|
||||
int error = 0;
|
||||
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");
|
||||
|
||||
if (error != 0) {
|
||||
@ -271,14 +272,14 @@ bool TcpTransport::trySendMessage(message_ptr &message) {
|
||||
auto data = reinterpret_cast<const char *>(message->data());
|
||||
auto size = message->size();
|
||||
while (size) {
|
||||
#ifdef __APPLE__
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
int flags = 0;
|
||||
#else
|
||||
int flags = MSG_NOSIGNAL;
|
||||
#endif
|
||||
int len = ::send(mSock, data, size, flags);
|
||||
int len = ::send(mSock, data, int(size), flags);
|
||||
if (len < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
|
||||
message = make_message(message->end() - size, message->end());
|
||||
return false;
|
||||
} else {
|
||||
@ -335,7 +336,7 @@ void TcpTransport::runLoop() {
|
||||
char buffer[bufferSize];
|
||||
int len = ::recv(mSock, buffer, bufferSize, 0);
|
||||
if (len < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
|
||||
continue;
|
||||
} else {
|
||||
throw std::runtime_error("Connection lost");
|
||||
|
@ -48,6 +48,11 @@ gnutls_datum_t make_datum(char *data, size_t size);
|
||||
|
||||
#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/bio.h>
|
||||
|
@ -269,9 +269,19 @@ TlsTransport::TlsTransport(shared_ptr<TcpTransport> lower, string host, state_ca
|
||||
SSL_CTX_set_quiet_shutdown(mCtx, 1);
|
||||
SSL_CTX_set_info_callback(mCtx, InfoCallback);
|
||||
|
||||
SSL_CTX_set_default_verify_paths(mCtx);
|
||||
SSL_CTX_set_verify(mCtx, SSL_VERIFY_PEER, NULL);
|
||||
SSL_CTX_set_verify_depth(mCtx, 4);
|
||||
// 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_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)))
|
||||
throw std::runtime_error("Failed to create SSL instance");
|
||||
@ -337,7 +347,7 @@ bool TlsTransport::send(message_ptr message) {
|
||||
if (message->size() == 0)
|
||||
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))
|
||||
return false;
|
||||
|
||||
@ -393,7 +403,7 @@ void TlsTransport::runRecvLoop() {
|
||||
|
||||
message_ptr message = *next;
|
||||
if (message->size() > 0)
|
||||
BIO_write(mInBio, message->data(), message->size()); // Input
|
||||
BIO_write(mInBio, message->data(), int(message->size())); // Input
|
||||
else
|
||||
recv(message); // Pass zero-sized messages through
|
||||
}
|
||||
|
@ -26,7 +26,6 @@
|
||||
|
||||
#if RTC_ENABLE_WEBSOCKET
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace rtc {
|
||||
|
@ -50,7 +50,7 @@ using std::to_integer;
|
||||
using std::to_string;
|
||||
|
||||
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,
|
||||
message_callback recvCallback, state_callback stateCallback)
|
||||
@ -145,12 +145,12 @@ void WsTransport::close() {
|
||||
bool WsTransport::sendHttpRequest() {
|
||||
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);
|
||||
|
||||
binary key(16);
|
||||
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 +
|
||||
" HTTP/1.1\r\n"
|
||||
@ -283,7 +283,7 @@ size_t WsTransport::readFrame(byte *buffer, size_t size, Frame &frame) {
|
||||
cur += 4;
|
||||
}
|
||||
|
||||
if (end - cur < frame.length)
|
||||
if (size_t(end - cur) < frame.length)
|
||||
return 0;
|
||||
|
||||
frame.payload = cur;
|
||||
@ -292,7 +292,7 @@ size_t WsTransport::readFrame(byte *buffer, size_t size, Frame &frame) {
|
||||
frame.payload[i] ^= maskingKey[i % 4];
|
||||
cur += frame.length;
|
||||
|
||||
return cur - buffer;
|
||||
return size_t(cur - buffer);
|
||||
}
|
||||
|
||||
void WsTransport::recvFrame(const Frame &frame) {
|
||||
@ -378,13 +378,13 @@ bool WsTransport::sendFrame(const Frame &frame) {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
byte *maskingKey = reinterpret_cast<byte *>(cur);
|
||||
|
||||
auto u = reinterpret_cast<uint8_t *>(maskingKey);
|
||||
std::generate(u, u + 4, generator);
|
||||
std::generate(u, u + 4, [&]() { return uint8_t(generator()); });
|
||||
cur += 4;
|
||||
|
||||
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 <cstdbool>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@ -136,7 +135,7 @@ static void deletePeer(Peer *peer) {
|
||||
int test_capi_main() {
|
||||
int attempts;
|
||||
|
||||
rtcInitLogger(RTC_LOG_DEBUG);
|
||||
rtcInitLogger(RTC_LOG_DEBUG, nullptr);
|
||||
|
||||
// Create peer 1
|
||||
rtcConfiguration config1;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
@ -63,6 +64,7 @@ void test_connectivity() {
|
||||
});
|
||||
|
||||
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
||||
|
||||
pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "Gathering state 1: " << state << endl;
|
||||
});
|
||||
@ -84,6 +86,7 @@ void test_connectivity() {
|
||||
});
|
||||
|
||||
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
||||
|
||||
pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "Gathering state 2: " << state << endl;
|
||||
});
|
||||
@ -91,13 +94,16 @@ void test_connectivity() {
|
||||
shared_ptr<DataChannel> dc2;
|
||||
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
||||
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)) {
|
||||
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");
|
||||
@ -105,6 +111,7 @@ void test_connectivity() {
|
||||
auto dc1 = wdc1.lock();
|
||||
if (!dc1)
|
||||
return;
|
||||
|
||||
cout << "DataChannel 1: Open" << endl;
|
||||
dc1->send("Hello from 1");
|
||||
});
|
||||
@ -115,14 +122,15 @@ void test_connectivity() {
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
if (pc1->state() != PeerConnection::State::Connected &&
|
||||
pc2->state() != PeerConnection::State::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");
|
||||
|
||||
if (auto addr = pc1->localAddress())
|
||||
|
@ -16,13 +16,27 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace chrono_literals;
|
||||
|
||||
void test_connectivity();
|
||||
void test_capi();
|
||||
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) {
|
||||
try {
|
||||
@ -51,5 +65,13 @@ int main(int argc, char **argv) {
|
||||
return -1;
|
||||
}
|
||||
#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;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user