Compare commits

..

103 Commits

Author SHA1 Message Date
b94f2ab339 Bumped version to 0.6.3 2020-06-24 14:27:36 +02:00
9c79c8516b Updated libjuice to v0.4.3 2020-06-24 14:27:19 +02:00
ad03549f8c Updated libjuice 2020-06-24 13:38:56 +02:00
5cc8eb1ce6 Moved CleanupCertificateCache() to rtc::Cleanup() 2020-06-24 09:09:50 +02:00
d19ff754b2 Merge pull request #100 from paullouisageneau/fix-plog
Change plog to fork
2020-06-23 23:07:36 +02:00
fb0f903e2e Added includes after plog update 2020-06-23 23:03:09 +02:00
6628297580 Changed plog to fork https://github.com/paullouisageneau/plog 2020-06-23 22:51:43 +02:00
ccc05b9999 Added libjuice dependency on nettle if using gnutls 2020-06-23 19:18:35 +02:00
fb2f480f92 Change dir to build before cmake rather than using option -B 2020-06-23 15:03:28 +02:00
cca0ac859a Explicit . source directory for cmake 2020-06-23 12:21:24 +02:00
9fbc7c6ea8 Updated libjuice 2020-06-23 12:13:59 +02:00
83d65c805a Hint OpenSSL for libjuice cmake build 2020-06-23 09:28:27 +02:00
0a5cef331d Fixed target name usrsctp-static 2020-06-22 19:36:15 +02:00
7c1714f83c Changed MSVC build mode to Release and removed OpenSSL < 1.1 2020-06-22 18:55:19 +02:00
f3024d0552 Fixed compilation on MSVC 2020-06-22 14:59:34 +02:00
bcd1972270 Updated Readme 2020-06-22 12:14:29 +02:00
57501b1739 Updated Readme 2020-06-22 12:08:11 +02:00
cfe8a0e9c6 Fixed defines for MSVC 2020-06-21 20:21:00 +02:00
10061b3d4b Replaced dtls-id by tls-id according to draft-ietf-mmusic-dtls-sdp-32 2020-06-21 18:21:29 +02:00
52959ee700 Refactored media map access to prevent unused variables 2020-06-21 18:20:51 +02:00
0e86d6e3f1 Fixed cmake calls in Jamfile 2020-06-21 17:56:03 +02:00
62675e4c21 Merge pull request #97 from paullouisageneau/usrsctp-fix-notifications
Fix SCTP interleaved notifications
2020-06-21 17:51:12 +02:00
eafe86e4c0 Merge branch 'master' into usrsctp-fix-notifications 2020-06-21 17:35:08 +02:00
a2b2126930 Added comment about SCTP_FRAGMENT_INTERLEAVE 2020-06-21 17:32:55 +02:00
b9a663de75 Fixed compilation warning for MSVC 2020-06-20 23:35:58 +02:00
f5faeba7b0 Fixed compilation warning for MSVC 2020-06-20 23:29:47 +02:00
98f02c5195 Fixed Jamfile 2020-06-20 21:09:33 +02:00
d8ab5b4820 Added MSVC support to Jamfile 2020-06-20 15:54:26 +02:00
b569e78c02 Changed Jamfile to use cmake 2020-06-20 15:36:11 +02:00
4eac16e053 Updated usrsctp 2020-06-20 15:12:28 +02:00
bb13b4bd37 Renamed mPartialData to mPartialMessage 2020-06-20 12:46:03 +02:00
aabb435fc7 Separate SCTP partial notification and partial data 2020-06-20 12:40:21 +02:00
3d7764c1e9 Merge pull request #95 from paullouisageneau/msvc
Add workflow for MSVC
2020-06-20 11:21:44 +02:00
37687c3cd6 Removed finished flag 2020-06-20 11:16:49 +02:00
63f10303e0 Disable server verification on Windows 2020-06-20 11:05:22 +02:00
d656d739f3 Disable server verification if system CA certificates not found 2020-06-20 10:55:49 +02:00
dbc706b69d Fixed socket error code EINPROGRESS 2020-06-20 10:36:04 +02:00
1985088f4f Renamed socket errno constants 2020-06-20 10:10:22 +02:00
ccbaa8beda Updated libjuice 2020-06-20 10:09:30 +02:00
642d304af9 Second fix for EWOULDBLOCK on Windows 2020-06-19 19:12:22 +02:00
df2102497d Fixed compilation 2020-06-19 18:51:01 +02:00
ad676815bd Fixed EWOULDBLOCK errno on Windows 2020-06-19 18:48:19 +02:00
4bd40799fd Fixed compilation for MSVC 2020-06-19 18:33:55 +02:00
4d1d1fa6fe Renamed workflows 2020-06-19 18:20:07 +02:00
50f61b19aa Moved C++ tests and examples to static lib on Windows 2020-06-19 18:17:53 +02:00
b7dbe7cdd9 Added RTC_EXPORT directive on C API 2020-06-19 18:07:12 +02:00
7bfd731ed3 Fixed compilation with MSVC 2020-06-19 16:19:49 +02:00
4da8b8c9a3 Renamed build-ubuntu to build-linux 2020-06-19 15:31:13 +02:00
0382067d92 Fixed compilation with MSVC 2020-06-19 15:29:25 +02:00
325230404a Added build with MSVC on Windows 2020-06-19 14:03:54 +02:00
2a721015f8 Updated libjuice 2020-06-19 11:56:19 +02:00
5752b17a6f Merge pull request #94 from paullouisageneau/capi-log-callback
Optional log callback in C API
2020-06-17 23:54:12 +02:00
878d15b746 Added optional callback to C API rtcInitLogger() 2020-06-17 16:29:56 +02:00
26d240e3ba Prefixed callback types 2020-06-17 15:20:46 +02:00
ca7fc8b26f Merge pull request #93 from paullouisageneau/fix-setuserpointer
Fix setUserPointer() not replacing pointer
2020-06-15 21:25:46 +02:00
35cb9ea4be Fixed setUserPointer() not replacing pointer 2020-06-15 21:21:41 +02:00
ec42736a2f Merge pull request #92 from paullouisageneau/fix-clang-analyser
Prevent clang analyser inner pointer used after reallocation
2020-06-15 16:09:16 +02:00
b4f7c506da Added consistent checks on size parameter 2020-06-15 16:04:15 +02:00
a577bb9004 Fixed clang analyser inner pointer used after reallocation 2020-06-15 15:54:54 +02:00
bfd858ab13 Removed localhost candidates hack for macOS 2020-06-14 12:40:14 +02:00
18fe326090 Updated libjuice 2020-06-14 12:39:18 +02:00
384454b293 Bumped version to 0.6.2 2020-06-12 20:00:29 +02:00
34db6ae673 Made description mutexes non-recursive and fix deadlock 2020-06-12 19:59:44 +02:00
31c88b0783 Bumped version to v0.6.1 2020-06-12 18:13:49 +02:00
c502b1f207 Updated libjuice to v0.4.2 2020-06-12 18:12:27 +02:00
858e181be1 Merge branch 'dev' 2020-06-12 18:07:10 +02:00
d00c73e993 Changed error constants to defines 2020-06-12 17:49:29 +02:00
9403818a12 Fixed description 2020-06-12 17:46:04 +02:00
b233e655cc Updated libjuice 2020-06-12 17:43:33 +02:00
b625519c4a Merge pull request #90 from paullouisageneau/more-sctp-tuning
SCTP optimizations
2020-06-12 13:03:46 +00:00
82e604b869 Fixed benchmark 2020-06-12 14:42:48 +02:00
60169cc676 Prevent write lock contention in SCTP transport 2020-06-12 13:59:52 +02:00
ee0139402a Enable SCTP NR-SACKs and reduce SACK delay to 20ms 2020-06-12 13:59:43 +02:00
661d6827c6 Fixed typo 2020-06-11 22:02:26 +02:00
5a331f1087 Added JSEP 2020-06-11 21:26:57 +02:00
1aedbddc55 Merge pull request #88 from paullouisageneau/sctp-tuning
Tune SCTP
2020-06-11 15:10:56 +00:00
35d58bb4e5 Merge pull request #89 from paullouisageneau/fix-compilation-win32
Fix compilation for Windows
2020-06-11 13:08:34 +00:00
0da5985ef6 Fixed compilation for Windows 2020-06-11 14:57:08 +02:00
8440c085ca Fix set_target_properties wrong number of arguments error 2020-06-11 11:02:25 +02:00
b68ccb4d71 Lock remote description only when necessary 2020-06-11 10:44:47 +02:00
8e3ec73ca6 Lowered goodput test threshold 2020-06-11 09:46:52 +02:00
c29de9dd1e Optimized libjuice 2020-06-10 23:21:27 +02:00
2ca3b07938 Added GnuTLS explicit initialization 2020-06-10 19:24:21 +02:00
679263c9f7 Change SCTP congestion control to H-TCP 2020-06-10 18:55:18 +02:00
9d635feb30 Increase SCTP max chunks on queue 2020-06-10 18:29:19 +02:00
fc29073577 Increase the initial window size to 10 MTUs 2020-06-10 18:28:26 +02:00
83bb6878f7 Changed SCTP congestion control to HighSpeed TCP 2020-06-10 18:18:48 +02:00
672124aa29 Updated libjuice to increase UDP buffer size 2020-06-10 18:14:38 +02:00
1e734906d3 Merge pull request #87 from paullouisageneau/fix-capi
Properly catch exceptions in C API
2020-06-10 15:03:14 +00:00
d0695aa9cb Properly catch exceptions in C API 2020-06-10 16:54:32 +02:00
3a941367b8 Fixed dependabot.yml 2020-06-09 13:32:06 +02:00
52dcae6453 Create dependabot.yml 2020-06-09 13:30:48 +02:00
22a1c56863 Added optional preloading 2020-06-09 11:17:09 +02:00
74c5cbcf9f Some fixes for tests 2020-06-09 10:48:49 +02:00
5b2c0cbc08 Fixed compilation 2020-06-09 00:09:43 +02:00
d853bb59c3 Enhancements to tests 2020-06-09 00:04:23 +02:00
c30927b6fa Updated libjuice 2020-06-08 23:28:03 +02:00
3a72adf8c8 Removed cleanup 2020-06-08 16:03:28 +02:00
767d719563 Merge pull request #86 from paullouisageneau/benchmark
Added benchmark
2020-06-08 13:58:21 +00:00
af87e5a1b8 Fixed unsafe smart_ptr access from thread 2020-06-08 15:48:58 +02:00
c83196ee40 Lowered threshold for CI 2020-06-08 15:40:37 +02:00
dfcae8f4fd Added benchmark 2020-06-08 15:34:15 +02:00
64be7a62f4 Fixed potential deadlock when sending from buffer low callback 2020-06-08 15:29:47 +02:00
45 changed files with 1437 additions and 838 deletions

6
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "examples/web"
schedule:
interval: "weekly"

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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 "")

192
Jamfile
View File

@ -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
@ -48,41 +54,183 @@ alias usrsctp
<library>libusrsctp.a
;
alias usrsctp
: # no sources
: <toolset>msvc
: # no default build
: # usage requirements
<include>./deps/usrsctp/usrsctplib
<library>usrsctp.lib
;
alias juice
: # no sources
: # no build requirements
: # 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
else {
local OPENSSL_INCLUDE = [ feature.get-values <openssl-include> : $(properties) ] ;
if <target-os>darwin in $(properties) && $(OPENSSL_INCLUDE) = ""
{
MAKEOPTS on $(targets) = "USE_NETTLE=0" ;
# 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 ;

View File

@ -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/plog vendored

Submodule deps/plog updated: 47883f0609...afb6f6f0e8

2
deps/usrsctp vendored

View File

@ -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)

View File

@ -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;
}

View File

@ -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)

View File

@ -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,30 +41,22 @@ 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;
@ -65,10 +64,8 @@ int main(int argc, char **argv) {
Peer *peer = (Peer *)malloc(sizeof(Peer));
if (!peer) {
printf("Error allocating memory for peer\n");
deletePeer(peer);
fprintf(stderr, "Error allocating memory for peer\n");
return -1;
}
memset(peer, 0, sizeof(Peer));
@ -86,17 +83,12 @@ int main(int argc, char **argv) {
rtcSetUserPointer(peer->dc, NULL);
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
sleep(1);
bool exit = false;
while (!exit) {
printf("\n");
printf("***************************************************************************************\n");
// << endl
printf("* 0: Exit /"
" 1: Enter remote description /"
" 2: Enter remote candidate /"
@ -106,15 +98,15 @@ int main(int argc, char **argv) {
int command = -1;
int c;
// int check_scan
if (scanf("%d", &command)) {
}else {
if (!scanf("%d", &command)) {
break;
}
while ((c = getchar()) != '\n' && c != EOF) { }
while ((c = getchar()) != '\n' && c != EOF) {
}
fflush(stdin);
switch (command) {
case 0: {
exit = true;
@ -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;
@ -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,16 +249,6 @@ 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);
}
int all_space(const char *str) {
while (*str) {
if (!isspace(*str++)) {
return 0;
}
}
return 1;
}
char *state_print(rtcState state) {
@ -302,10 +277,9 @@ char* state_print(rtcState state) {
}
return str;
}
char* rtcGatheringState_print(rtcState state) {
char *rtcGatheringState_print(rtcGatheringState state) {
char *str = NULL;
switch (state) {
case RTC_GATHERING_NEW:
@ -322,5 +296,14 @@ char* rtcGatheringState_print(rtcState state) {
}
return str;
}
int all_space(const char *str) {
while (*str) {
if (!isspace(*str++)) {
return 0;
}
}
return 1;
}

View 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;
}

View File

@ -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,28 +41,22 @@ 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;
@ -66,10 +64,8 @@ int main(int argc, char **argv){
Peer *peer = (Peer *)malloc(sizeof(Peer));
if (!peer) {
printf("Error allocating memory for peer\n");
deletePeer(peer);
fprintf(stderr, "Error allocating memory for peer\n");
return -1;
}
memset(peer, 0, sizeof(Peer));
@ -85,25 +81,17 @@ int main(int argc, char **argv){
// Since this is the offere, we will create a datachannel
peer->dc = rtcCreateDataChannel(peer->pc, "test");
rtcSetOpenCallback(peer->dc, openCallback);
rtcSetClosedCallback(peer->dc, closedCallback);
rtcSetMessageCallback(peer->dc, messageCallback);
sleep(1);
bool exit = false;
while (!exit) {
printf("\n");
printf("***************************************************************************************\n");
// << endl
printf("* 0: Exit /"
" 1: Enter remote description /"
" 2: Enter remote candidate /"
@ -113,12 +101,13 @@ int main(int argc, char **argv){
int command = -1;
int c;
if (scanf("%d", &command)) {
}else {
if (!scanf("%d", &command)) {
break;
}
while ((c = getchar()) != '\n' && c != EOF) { }
while ((c = getchar()) != '\n' && c != EOF) {
}
fflush(stdin);
switch (command) {
@ -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,16 +254,6 @@ static void deletePeer(Peer *peer) {
}
}
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) {
@ -310,10 +280,9 @@ char* state_print(rtcState state) {
}
return str;
}
char* rtcGatheringState_print(rtcState state) {
char *rtcGatheringState_print(rtcGatheringState state) {
char *str = NULL;
switch (state) {
case RTC_GATHERING_NEW:
@ -330,5 +299,14 @@ char* rtcGatheringState_print(rtcState state) {
}
return str;
}
int all_space(const char *str) {
while (*str) {
if (!isspace(*str++)) {
return 0;
}
}
return 1;
}

View File

@ -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)
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)
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()

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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"

View File

@ -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];

View File

@ -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;

View File

@ -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));

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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);
}
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
}

View File

@ -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,16 +145,75 @@ 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) {
return WRAP({
Configuration c;
for (int i = 0; i < config->iceServersCount; ++i)
c.iceServers.emplace_back(string(config->iceServers[i]));
@ -162,13 +224,12 @@ int rtcCreatePeerConnection(const rtcConfiguration *config) {
}
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
});
}
int rtcDeletePeerConnection(int pc) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
peerConnection->onDataChannel(nullptr);
peerConnection->onLocalDescription(nullptr);
peerConnection->onLocalCandidate(nullptr);
@ -176,25 +237,21 @@ int rtcDeletePeerConnection(int pc) {
peerConnection->onGatheringStateChange(nullptr);
erasePeerConnection(pc);
return 0;
});
}
int rtcCreateDataChannel(int pc, const char *label) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
void *ptr = getUserPointer(pc);
rtcSetUserPointer(dc, ptr);
rtcSetUserPointer(dc, getUserPointer(pc));
return dc;
});
}
int rtcDeleteDataChannel(int dc) {
return WRAP({
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
dataChannel->onOpen(nullptr);
dataChannel->onClosed(nullptr);
dataChannel->onError(nullptr);
@ -203,21 +260,21 @@ int rtcDeleteDataChannel(int dc) {
dataChannel->onAvailable(nullptr);
eraseDataChannel(dc);
return 0;
});
}
#if RTC_ENABLE_WEBSOCKET
int rtcCreateWebSocket(const char *url) {
return WRAP({
auto ws = std::make_shared<WebSocket>();
ws->open(url);
return emplaceWebSocket(ws);
});
}
int rtcDeleteWebsocket(int ws) {
return WRAP({
auto webSocket = getWebSocket(ws);
if (!webSocket)
return -1;
webSocket->onOpen(nullptr);
webSocket->onClosed(nullptr);
webSocket->onError(nullptr);
@ -226,16 +283,13 @@ int rtcDeleteWebsocket(int ws) {
webSocket->onAvailable(nullptr);
eraseWebSocket(ws);
return 0;
});
}
#endif
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb) {
int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
int dc = emplaceDataChannel(dataChannel);
@ -245,265 +299,256 @@ int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb) {
});
else
peerConnection->onDataChannel(nullptr);
return 0;
});
}
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
return WRAP({
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) {
int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
return WRAP({
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) {
int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
return WRAP({
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) {
int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) {
return WRAP({
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) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
return 0;
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 (!peerConnection)
return -1;
CATCH(peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}))
return 0;
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 (!peerConnection)
return -1;
if (!buffer)
throw std::invalid_argument("Unexpected null pointer");
if (size <= 0)
return 0;
if (auto addr = peerConnection->localAddress()) {
size = std::min(size_t(size - 1), addr->size());
std::copy(addr->data(), addr->data() + size, buffer);
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;
}
return -1;
});
}
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (!buffer)
throw std::invalid_argument("Unexpected null pointer");
if (size <= 0)
return 0;
if (auto addr = peerConnection->remoteAddress()) {
size = std::min(size_t(size - 1), addr->size());
std::copy(addr->data(), addr->data() + size, buffer);
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;
return int(size + 1);
}
return -1;
});
}
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
return WRAP({
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (!size)
if (!buffer)
throw std::invalid_argument("Unexpected null pointer");
if (size <= 0)
return 0;
string label = dataChannel->label();
size = std::min(size_t(size - 1), label.size());
std::copy(label.data(), label.data() + size, buffer);
const char *data = label.data();
size = std::min(size - 1, int(label.size()));
std::copy(data, data + size, buffer);
buffer[size] = '\0';
return size + 1;
return int(size + 1);
});
}
int rtcSetOpenCallback(int id, openCallbackFunc cb) {
int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
return WRAP({
auto channel = getChannel(id);
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) {
int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
return WRAP({
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) {
int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
return WRAP({
auto channel = getChannel(id);
if (!channel)
return -1;
if (cb)
channel->onError([id, cb](const string &error) { cb(error.c_str(), getUserPointer(id)); });
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) {
int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
return WRAP({
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));
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);
return 0;
});
}
int rtcSendMessage(int id, const char *data, int size) {
return WRAP({
auto channel = getChannel(id);
if (!channel)
return -1;
if (!data)
throw std::invalid_argument("Unexpected null pointer");
if (size >= 0) {
auto b = reinterpret_cast<const byte *>(data);
CATCH(channel->send(binary(b, b + size)));
channel->send(binary(b, b + size));
return size;
} else {
string str(data);
int len = str.size();
CATCH(channel->send(std::move(str)));
int len = int(str.size());
channel->send(std::move(str));
return len;
}
});
}
int rtcGetBufferedAmount(int id) {
return WRAP({
auto channel = getChannel(id);
if (!channel)
return -1;
CATCH(return int(channel->bufferedAmount()));
return int(channel->bufferedAmount());
});
}
int rtcSetBufferedAmountLowThreshold(int id, int amount) {
return WRAP({
auto channel = getChannel(id);
if (!channel)
return -1;
CATCH(channel->setBufferedAmountLowThreshold(size_t(amount)));
return 0;
channel->setBufferedAmountLowThreshold(size_t(amount));
});
}
int rtcSetBufferedAmountLowCallback(int id, bufferedAmountLowCallbackFunc cb) {
int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb) {
return WRAP({
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()));
return WRAP({ return int(getChannel(id)->availableAmount()); });
}
int rtcSetAvailableCallback(int id, availableCallbackFunc cb) {
int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
return WRAP({
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) {
return WRAP({
auto channel = getChannel(id);
if (!channel)
return -1;
if (!size)
return -1;
CATCH({
auto message = channel->receive();
if (!message)
return 0;
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 *size;
return 1;
},
[&](const string &s) {
int len = std::min(*size - 1, int(s.size()));
@ -512,10 +557,13 @@ int rtcReceiveMessage(int id, char *buffer, int *size) {
buffer[len] = '\0';
}
*size = -(len + 1);
return len + 1;
return 1;
}},
*message);
else
return 0;
});
}
void rtcPreload() { rtc::Preload(); }
void rtcCleanup() { rtc::Cleanup(); }

View File

@ -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) {
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;
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
PLOG_VERBOSE << "SCTP sending not possible";
return false;
} else {
PLOG_ERROR << "SCTP sending failed, errno=" << errno;
throw std::runtime_error("Sending failed, errno=" + std::to_string(errno));
}
}
void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
@ -385,7 +401,13 @@ void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
else
it->second = 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.
// 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 (!mPartialRecv.empty()) {
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
data = mPartialRecv.data();
len = mPartialRecv.size();
if (!mPartialNotification.empty()) {
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
data = mPartialNotification.data();
len = mPartialNotification.size();
}
// Message/Notification is complete, process it
if (flags & MSG_NOTIFICATION)
// 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)));
mPartialRecv.clear();
mPartialNotification.clear();
} else {
// Message/Notification is not complete
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
}
} else {
// SCTP message
if (flags & MSG_EOR) {
if (!mPartialMessage.empty()) {
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
data = mPartialMessage.data();
len = mPartialMessage.size();
}
// Message is complete, process it
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
mPartialMessage.clear();
} else {
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
}
}
} catch (const std::exception &e) {
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();

View File

@ -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;

View File

@ -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");

View File

@ -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>

View File

@ -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_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
}

View File

@ -26,7 +26,6 @@
#if RTC_ENABLE_WEBSOCKET
#include <mutex>
#include <thread>
namespace rtc {

View File

@ -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
View 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

View File

@ -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;

View File

@ -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())

View File

@ -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;
}