Compare commits

..

166 Commits

Author SHA1 Message Date
c93f44f132 Bumped version to 0.6.5 2020-07-10 19:42:54 +02:00
517b69043f Merge pull request #120 from paullouisageneau/workaround-usrsctp-send-after-close
Better workaround for usrsctp send after closing/unregistering
2020-07-10 18:24:31 +02:00
b04db3a744 Better workaround for usrsctp send after closing/unregistering 2020-07-10 18:15:19 +02:00
36090a24e4 Updated usrsctp 2020-07-10 17:30:16 +02:00
6b75b3e227 Re-added usrsctp_deregister_address call mistakenly removed in #116 2020-07-10 17:05:41 +02:00
afed83f5f0 Merge pull request #116 from paullouisageneau/workaround-usrsctp-send-after-close
Workaround for usrsctp send after close
2020-07-10 14:33:16 +02:00
75c42592bf Unregister usrsctp send callback before closing 2020-07-10 14:13:39 +02:00
b35bfbeb0a Merge pull request #119 from paullouisageneau/fix-jamfile
Jamfile: Use different build directories for different builds
2020-07-10 14:10:03 +02:00
e481e896cb Added NO_EXAMPLES cmake flag 2020-07-08 18:38:25 +02:00
202467928a Changed build directory name to build-[crypto]-[variant] 2020-07-08 18:25:32 +02:00
71650ce163 Use different build directories for different builds 2020-07-08 17:58:26 +02:00
706a8b7160 Merge pull request #118 from paullouisageneau/threadpool
Enhance thread management
2020-07-08 17:57:50 +02:00
6f419a32ea Increased test timings for destruction 2020-07-08 17:42:23 +02:00
f5ff042d62 Check for re-init in destructor 2020-07-08 17:37:57 +02:00
822b2e6558 Simplified Init with a recursive mutex 2020-07-08 17:34:32 +02:00
5b251af1d7 Added initialized flag 2020-07-08 17:08:54 +02:00
5b56291b67 Fixed init lock on destruction 2020-07-08 15:58:20 +02:00
777f5a8dfe Revised deinitialization 2020-07-08 15:30:50 +02:00
971e6e8b91 Fixed init tokens handling 2020-07-08 11:39:53 +02:00
a3fb52c173 Fixed TCP transport thread interruption 2020-07-08 03:24:38 +02:00
db00253c18 Made Processor keep an init token to prevent early threadpool join 2020-07-08 02:46:19 +02:00
dd2967b0e1 Fixed Init critical section 2020-07-08 02:36:00 +02:00
cc4e215067 Install OpenSSL in Windows workflow 2020-07-08 01:52:34 +02:00
bbeed01eb0 Added comments 2020-07-08 01:52:26 +02:00
aecc2b8fda Added Processor and finished ThreadPool integration 2020-07-08 01:33:54 +02:00
d60e18d963 Made ThreadPool a singleton 2020-07-07 20:18:09 +02:00
e41019a1f0 Added ThreadPool 2020-07-07 19:59:18 +02:00
5825e44fc8 Fixed compilation warning 2020-07-06 12:25:03 +02:00
dc9a8114bc Merge pull request #114 from aldenml/fix-no-deprecated
fixed compilation when using openssl compiled with no-deprecated
2020-07-05 23:17:01 +02:00
3e827f9798 fixed compilation when using openssl compiled with no-deprecated 2020-07-04 18:57:50 -04:00
0a6b263bc3 Merge pull request #113 from paullouisageneau/fix-capi-userptr
Fix potential callback call with null user pointer in C API
2020-07-02 20:32:24 +02:00
e02c30027b Pass actual size of string in message callback 2020-07-02 20:09:03 +02:00
c4380ebcc4 Fixed potential null user pointer in callbacks 2020-07-02 20:08:54 +02:00
add0649335 Merge pull request #110 from paullouisageneau/keep-mline-order
Keep m-line order in description
2020-07-01 11:35:38 +02:00
cd28340de3 Keep m-line order in description 2020-07-01 11:24:03 +02:00
c675aedb83 Merge pull request #109 from paullouisageneau/fix-mid-handling
Fix mid handling in description
2020-07-01 00:17:41 +02:00
e32d139056 Moved data after non-data media in description 2020-07-01 00:13:12 +02:00
a790161168 Properly set remote data mid 2020-07-01 00:01:01 +02:00
2697ef0d76 Bumped version to 0.6.4 2020-06-29 21:09:02 +02:00
5044aedbec Updated libjuice to v0.4.4 2020-06-29 21:08:39 +02:00
44c90c1cb4 Set gnutls linking to shared in Jamfile 2020-06-29 21:05:34 +02:00
be79c68540 Merge pull request #108 from paullouisageneau/fix-srtp-transport
Fix optional SRTP transport
2020-06-29 21:03:44 +02:00
226a927df1 Implemented RTP and RTCP demultiplexing 2020-06-29 20:39:31 +02:00
4e1b9bb3c2 Fixed SRTP/DTLS demultiplexing 2020-06-29 09:48:42 +02:00
8bc016cc08 Added some logging 2020-06-29 09:48:42 +02:00
5afbe10d01 Changed to two SRTP sessions and introduced srtp_add_stream() 2020-06-29 09:48:42 +02:00
8df07ca68d Fixed serverSalt 2020-06-29 09:48:42 +02:00
884bd2316e Fixed error checking for OpenSSL 2020-06-29 09:48:42 +02:00
dadecce709 Fixed compilation for OpenSSL 2020-06-29 09:48:42 +02:00
103935bdd5 Introduced postCreation method to DTLS-SRTP 2020-06-29 09:48:42 +02:00
62e6954949 Use dedicated method to send media 2020-06-29 09:48:42 +02:00
3ac2d155cc Set DtlsSrtpTransport to bypass before handshake 2020-06-29 09:48:42 +02:00
6d8788c2a1 Updated libjuice 2020-06-29 09:47:53 +02:00
603dd01b87 Merge pull request #107 from Kyrio/fix-termux-include
Add the right header for IPPROTO_* on platforms such as Termux
2020-06-29 09:43:34 +02:00
8091508428 Add the right header for IPPROTO_* on some platforms 2020-06-29 01:08:34 +02:00
b38f63f077 Renamed PeerConnection::send() to sendMedia() 2020-06-26 18:02:07 +02:00
d87539937e Merge pull request #105 from paullouisageneau/fix-after-free-sctp-incoming
Fix use-after-free in SctpTransport::incoming()
2020-06-26 11:10:33 +02:00
eb09cadded Reordered SctpTransport::shutdown() and mLower->onRecv(nullptr) 2020-06-25 22:26:15 +02:00
79e0c62321 Merge pull request #103 from paullouisageneau/srtp-flag
Add USE_SRTP flag
2020-06-25 11:03:41 +02:00
ef38777129 Added USE_SRTP flag and renamed NO_WEBSOCKET flag 2020-06-25 10:59:12 +02:00
313f081061 Merge pull request #102 from paullouisageneau/fix-getaddrinfo-leak
Fix getaddrinfo() leak in Candidate::resolve()
2020-06-25 09:26:18 +02:00
6108b05e0d Fixed missing freeaddrinfo() on early exit 2020-06-25 09:20:05 +02:00
f68601b45f Updated libjuice 2020-06-24 15:30:15 +02:00
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
60 changed files with 2217 additions and 1054 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,25 @@ 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: install packages
run: choco install openssl
- name: submodules
run: git submodule update --init --recursive
- name: cmake
run: cmake -B build -G "NMake Makefiles" -DUSE_JUICE=1 -DUSE_GNUTLS=0
- name: nmake
run: |
cd build
nmake
- name: test
run: build/tests.exe

2
.gitmodules vendored
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,12 +1,14 @@
cmake_minimum_required(VERSION 3.7)
project(libdatachannel
DESCRIPTION "WebRTC DataChannels Library"
VERSION 0.6.0
DESCRIPTION "WebRTC Data Channels Library"
VERSION 0.6.5
LANGUAGES CXX)
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
option(USE_JUICE "Use libjuice instead of libnice" OFF)
option(RTC_ENABLE_WEBSOCKET "Build WebSocket support" ON)
option(USE_SRTP "Enable SRTP for media support" OFF)
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
option(NO_EXAMPLES "Disable examples" OFF)
if(USE_GNUTLS)
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
@ -21,6 +23,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()
@ -40,6 +44,8 @@ set(LIBDATACHANNEL_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.cpp
)
set(LIBDATACHANNEL_WEBSOCKET_SOURCES
@ -74,12 +80,12 @@ 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)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED)
find_package(SRTP)
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
add_subdirectory(deps/plog)
@ -96,7 +102,14 @@ endif()
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
if (RTC_ENABLE_WEBSOCKET)
if (NO_WEBSOCKET)
add_library(datachannel SHARED
${LIBDATACHANNEL_SOURCES})
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
${LIBDATACHANNEL_SOURCES})
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
else()
add_library(datachannel SHARED
${LIBDATACHANNEL_SOURCES}
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
@ -105,13 +118,6 @@ if (RTC_ENABLE_WEBSOCKET)
${LIBDATACHANNEL_WEBSOCKET_SOURCES})
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1)
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
else()
add_library(datachannel SHARED
${LIBDATACHANNEL_SOURCES})
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
${LIBDATACHANNEL_SOURCES})
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
endif()
set_target_properties(datachannel PROPERTIES
@ -138,7 +144,8 @@ if(WIN32)
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
endif()
if(SRTP_FOUND)
if(USE_SRTP)
find_package(SRTP REQUIRED)
if(NOT TARGET SRTP::SRTP)
add_library(SRTP::SRTP UNKNOWN IMPORTED)
set_target_properties(SRTP::SRTP PROPERTIES
@ -160,10 +167,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,12 +211,32 @@ 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 "")
add_subdirectory(deps/json)
add_subdirectory(examples/client)
add_subdirectory(examples/copy-paste)
add_subdirectory(examples/copy-paste-capi)
if(NOT NO_EXAMPLES)
set(JSON_BuildTests OFF CACHE INTERNAL "")
add_subdirectory(deps/json)
add_subdirectory(examples/client)
add_subdirectory(examples/copy-paste)
add_subdirectory(examples/copy-paste-capi)
endif()

216
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/<link>shared
<gnutls>off:<library>ssl
<gnutls>off:<library>crypto
: # default build
<link>static
: # usage requirements
<include>./include
<library>/libdatachannel//plog
<cxxflags>-pthread
<toolset>gcc:<cxxflags>"-Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
<toolset>clang:<cxxflags>"-Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
<toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
<toolset>clang:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
;
feature gnutls : off on : composite propagated ;
feature.compose <gnutls>off
: <define>USE_GNUTLS=0 ;
feature.compose <gnutls>on
: <define>USE_GNUTLS=1 ;
alias plog
: # no sources
: # no build requirements
@ -45,7 +51,16 @@ alias usrsctp
: # no default build
: # usage requirements
<include>./deps/usrsctp/usrsctplib
<library>libusrsctp.a
<library>libusrsctp.a
;
alias usrsctp
: # no sources
: <toolset>msvc
: # no default build
: # usage requirements
<include>./deps/usrsctp/usrsctplib
<library>usrsctp.lib
;
alias juice
@ -54,35 +69,192 @@ alias juice
: # no default build
: # usage requirements
<include>./deps/libjuice/include
<library>libjuice.a
<library>libjuice-static.a
<gnutls>on:<library>nettle
;
alias juice
: # no sources
: <toolset>msvc
: # no default build
: # usage requirements
<include>./deps/libjuice/include
<library>juice-static.lib
;
make libusrsctp.a : : @make_libusrsctp ;
make usrsctp.lib : : @make_libusrsctp_msvc ;
rule make_libusrsctp ( targets * : sources * : properties * )
{
local VARIANT = [ feature.get-values <variant> : $(properties) ] ;
VARIANT on $(targets) = $(VARIANT) ;
if <gnutls>on in $(properties)
{ BUILD_DIR on $(targets) = "build-gnutls-$(VARIANT)" ; }
else
{ BUILD_DIR on $(targets) = "build-openssl-$(VARIANT)" ; }
}
actions make_libusrsctp
{
(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_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC" .. && make -j2 usrsctp-static)
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
}
rule make_libusrsctp_msvc ( targets * : sources * : properties * )
{
local VARIANT = [ feature.get-values <variant> : $(properties) ] ;
VARIANT on $(targets) = $(VARIANT) ;
if <gnutls>on in $(properties)
{ BUILD_DIR on $(targets) = "build-gnutls-$(VARIANT)" ; }
else
{ BUILD_DIR on $(targets) = "build-openssl-$(VARIANT)" ; }
}
actions make_libusrsctp_msvc
{
SET OLDD=%CD%
cd $(CWD)/deps/usrsctp
mkdir $(BUILD_SIR)
cd $(BUILD_DIR)
cmake -G "Visual Studio 16 2019" ..
msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
cd %OLDD%
cp $(CWD)/deps/usrsctp/build/usrsctplib/Release/usrsctp.lib $(<)
}
make libjuice.a : : @make_libjuice ;
make libjuice-static.a : : @make_libjuice ;
make juice-static.lib : : @make_libjuice_msvc ;
rule make_libjuice ( targets * : sources * : properties * )
{
if <crypto>gnutls in $(properties)
local VARIANT = [ feature.get-values <variant> : $(properties) ] ;
VARIANT on $(targets) = $(VARIANT) ;
if <gnutls>on in $(properties)
{
MAKEOPTS on $(targets) = "USE_NETTLE=1" ;
BUILD_DIR on $(targets) = "build-gnutls-$(VARIANT)" ;
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=1" ;
}
else
{
MAKEOPTS on $(targets) = "USE_NETTLE=0" ;
local OPENSSL_INCLUDE = [ feature.get-values <openssl-include> : $(properties) ] ;
if <target-os>darwin in $(properties) && $(OPENSSL_INCLUDE) = ""
{
# on macOS, default to pick up openssl from the homebrew installation
# brew install openssl
OPENSSL_INCLUDE = /usr/local/opt/openssl/include ;
}
BUILD_DIR on $(targets) = "build-openssl-$(VARIANT)" ;
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=0" ;
if $(OPENSSL_INCLUDE) != ""
{ CMAKEOPTS on $(targets) += " -DOPENSSL_ROOT_DIR=$(OPENSSL_INCLUDE)/.." ; }
}
}
actions make_libjuice
{
(cd $(CWD)/deps/libjuice && make $(MAKEOPTS))
cp $(CWD)/deps/libjuice/libjuice.a $(<)
(cd $(CWD)/deps/libjuice && mkdir $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_BUILD_TYPE=$(VARIANT) $(CMAKEOPTS) .. && make -j2 juice-static)
cp $(CWD)/deps/libjuice/$(BUILD_DIR)/libjuice-static.a $(<)
}
rule make_libjuice_msvc ( targets * : sources * : properties * )
{
local VARIANT = [ feature.get-values <variant> : $(properties) ] ;
VARIANT on $(targets) = $(VARIANT) ;
if <gnutls>on in $(properties)
{
BUILD_DIR on $(targets) += "build-gnutls-$(VARIANT)" ;
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=1" ;
}
else
{
BUILD_DIR on $(targets) += "build-openssl-$(VARIANT)" ;
CMAKEOPTS on $(targets) = "-DUSE_NETTLE=0" ;
}
}
actions make_libjuice_msvc
{
SET OLDD=%CD%
cd $(CWD)/deps/libjuice
mkdir $(BUILD_DIR)
cd $(BUILD_DIR)
cmake -G "Visual Studio 16 2019" $(CMAKEOPTS) ..
msbuild libjuice.sln /property:Configuration=$(VARIANT)
cd %OLDD%
cp $(CWD)/deps/libjuice/$(BUILD_DIR)/Release/juice-static.lib $(<)
}
# the search path to pick up the openssl libraries from. This is the <search>
# property of those libraries
rule openssl-lib-path ( properties * )
{
local OPENSSL_LIB = [ feature.get-values <openssl-lib> : $(properties) ] ;
if <target-os>darwin in $(properties) && $(OPENSSL_LIB) = ""
{
# on macOS, default to pick up openssl from the homebrew installation
# brew install openssl
OPENSSL_LIB = /usr/local/opt/openssl/lib ;
}
else if <target-os>windows in $(properties) && $(OPENSSL_LIB) = ""
{
# on windows, assume openssl is installed to c:\OpenSSL-Win32
if <address-model>64 in $(properties)
{ OPENSSL_LIB = c:\\OpenSSL-Win64\\lib ; }
else
{ OPENSSL_LIB = c:\\OpenSSL-Win32\\lib ; }
}
local result ;
result += <search>$(OPENSSL_LIB) ;
return $(result) ;
}
# the include path to pick up openssl headers from. This is the
# usage-requirement for the openssl-related libraries
rule openssl-include-path ( properties * )
{
local OPENSSL_INCLUDE = [ feature.get-values <openssl-include> : $(properties) ] ;
if <target-os>darwin in $(properties) && $(OPENSSL_INCLUDE) = ""
{
# on macOS, default to pick up openssl from the homebrew installation
# brew install openssl
OPENSSL_INCLUDE = /usr/local/opt/openssl/include ;
}
else if <target-os>windows in $(properties) && $(OPENSSL_INCLUDE) = ""
{
# on windows, assume openssl is installed to c:\OpenSSL-Win32
if <address-model>64 in $(properties)
{ OPENSSL_INCLUDE = c:\\OpenSSL-Win64\\include ; }
else
{ OPENSSL_INCLUDE = c:\\OpenSSL-Win32\\include ; }
}
local result ;
result += <include>$(OPENSSL_INCLUDE) ;
return $(result) ;
}
# libraries for OpenSSL on Windows
lib advapi32 : : <name>advapi32 ;
lib user32 : : <name>user32 ;
lib shell32 : : <name>shell32 ;
lib gdi32 : : <name>gdi32 ;
lib bcrypt : : <name>bcrypt ;
lib z : : <link>shared <name>z ;
alias ssl-deps : advapi32 user32 shell32 gdi32 ;
# OpenSSL on Windows
lib crypto : ssl-deps : <toolset>msvc <openssl-version>1.1 <name>libcrypto
<conditional>@openssl-lib-path : : <conditional>@openssl-include-path ;
lib ssl : ssl-deps : <toolset>msvc <openssl-version>1.1 <name>libssl <use>crypto
<conditional>@openssl-lib-path : : <conditional>@openssl-include-path ;
# OpenSSL on other platforms
lib crypto : : <name>crypto <use>z <conditional>@openssl-lib-path : :
<conditional>@openssl-include-path ;
lib ssl : : <name>ssl <use>crypto <conditional>@openssl-lib-path : :
<conditional>@openssl-include-path ;
# GnuTLS
lib gnutls : : <link>shared <name>gnutls ;
lib nettle : : <link>shared <name>nettle ;

View File

@ -38,22 +38,22 @@ else
LIBS+=glib-2.0 gobject-2.0 nice
endif
RTC_ENABLE_MEDIA ?= 0
ifneq ($(RTC_ENABLE_MEDIA), 0)
USE_SRTP ?= 0
ifneq ($(USE_SRTP), 0)
CPPFLAGS+=-DRTC_ENABLE_MEDIA=1
LIBS+=srtp
else
CPPFLAGS+=-DRTC_ENABLE_MEDIA=0
endif
RTC_ENABLE_WEBSOCKET ?= 1
ifneq ($(RTC_ENABLE_WEBSOCKET), 0)
NO_WEBSOCKET ?= 0
ifeq ($(NO_WEBSOCKET), 0)
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=1
else
CPPFLAGS+=-DRTC_ENABLE_WEBSOCKET=0
endif
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))

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,42 +41,32 @@ typedef struct {
bool connected;
} Peer;
Peer *peer = NULL;
static void dataChannelCallback(int dc, void *ptr);
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
static void candidateCallback(const char *cand, const char *mid, void *ptr);
static void stateChangeCallback(rtcState state, void *ptr);
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
static void closedCallback(void *ptr);
static void messageCallback(const char *message, int size, void *ptr);
static void deletePeer(Peer *peer);
int all_space(const char *str);
char* state_print(rtcState state);
char* rtcGatheringState_print(rtcState state);
char *rtcGatheringState_print(rtcGatheringState state);
int all_space(const char *str);
int main(int argc, char **argv) {
rtcInitLogger(RTC_LOG_DEBUG);
rtcInitLogger(RTC_LOG_DEBUG, NULL);
// Create peer
rtcConfiguration config;
memset(&config, 0, sizeof(config));
// Create peer
rtcConfiguration config;
memset(&config, 0, sizeof(config));
Peer *peer = (Peer *)malloc(sizeof(Peer));
if (!peer) {
printf("Error allocating memory for peer\n");
deletePeer(peer);
}
Peer *peer = (Peer *)malloc(sizeof(Peer));
if (!peer) {
fprintf(stderr, "Error allocating memory for peer\n");
return -1;
}
memset(peer, 0, sizeof(Peer));
printf("Peer created\n");
@ -86,17 +83,12 @@ int main(int argc, char **argv) {
rtcSetUserPointer(peer->dc, NULL);
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
sleep(1);
bool exit = false;
bool exit = false;
while (!exit) {
printf("\n");
printf("***************************************************************************************\n");
// << endl
printf("* 0: Exit /"
" 1: Enter remote description /"
" 2: Enter remote candidate /"
@ -106,16 +98,16 @@ int main(int argc, char **argv) {
int command = -1;
int c;
// int check_scan
if (scanf("%d", &command)) {
}else {
break;
}
while ((c = getchar()) != '\n' && c != EOF) { }
if (!scanf("%d", &command)) {
break;
}
fflush(stdin);
switch (command) {
while ((c = getchar()) != '\n' && c != EOF) {
}
fflush(stdin);
switch (command) {
case 0: {
exit = true;
break;
@ -123,8 +115,6 @@ int main(int argc, char **argv) {
case 1: {
// Parse Description
printf("[Description]: ");
char *line = NULL;
size_t len = 0;
size_t read = 0;
@ -151,12 +141,12 @@ int main(int argc, char **argv) {
rtcAddRemoteCandidate(peer->pc, candidate, "0");
free(candidate);
}else {
printf("Error reading line\n");
break;
}
} else {
printf("Error reading line\n");
break;
}
break;
break;
}
case 3: {
// Send Message
@ -171,11 +161,11 @@ int main(int argc, char **argv) {
if(getline(&message, &message_size, stdin)) {
rtcSendMessage(peer->dc, message, -1);
free(message);
}else {
printf("Error reading line\n");
break;
}
break;
} else {
printf("Error reading line\n");
break;
}
break;
}
case 4: {
// Connection Info
@ -200,12 +190,10 @@ int main(int argc, char **argv) {
}
}
deletePeer(peer);
return 0;
}
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
// Peer *peer = (Peer *)ptr;
printf("Description %s:\n%s\n", "answerer", sdp);
@ -229,22 +217,19 @@ static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
}
static void closedCallback(void *ptr) {
Peer *peer = (Peer *)ptr;
peer->connected = false;
}
static void messageCallback(const char *message, int size, void *ptr) {
// Peer *peer = (Peer *)ptr;
if (size < 0) { // negative size indicates a null-terminated string
printf("Message %s: %s\n", "answerer", message);
} else {
printf("Message %s: [binary of size %d]\n", "answerer", size);
}
}
static void deletePeer(Peer *peer) {
if (peer) {
if (peer->dc)
@ -264,63 +249,61 @@ static void dataChannelCallback(int dc, void *ptr) {
char buffer[256];
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
}
char *state_print(rtcState state) {
char *str = NULL;
switch (state) {
case RTC_NEW:
str = "RTC_NEW";
break;
case RTC_CONNECTING:
str = "RTC_CONNECTING";
break;
case RTC_CONNECTED:
str = "RTC_CONNECTED";
break;
case RTC_DISCONNECTED:
str = "RTC_DISCONNECTED";
break;
case RTC_FAILED:
str = "RTC_FAILED";
break;
case RTC_CLOSED:
str = "RTC_CLOSED";
break;
default:
break;
}
return str;
}
char *rtcGatheringState_print(rtcGatheringState state) {
char *str = NULL;
switch (state) {
case RTC_GATHERING_NEW:
str = "RTC_GATHERING_NEW";
break;
case RTC_GATHERING_INPROGRESS:
str = "RTC_GATHERING_INPROGRESS";
break;
case RTC_GATHERING_COMPLETE:
str = "RTC_GATHERING_COMPLETE";
break;
default:
break;
}
return str;
}
int all_space(const char *str) {
while (*str) {
if (!isspace(*str++)) {
return 0;
}
}
return 1;
}
char* state_print(rtcState state) {
char *str = NULL;
switch (state) {
case RTC_NEW:
str = "RTC_NEW";
break;
case RTC_CONNECTING:
str = "RTC_CONNECTING";
break;
case RTC_CONNECTED:
str = "RTC_CONNECTED";
break;
case RTC_DISCONNECTED:
str = "RTC_DISCONNECTED";
break;
case RTC_FAILED:
str = "RTC_FAILED";
break;
case RTC_CLOSED:
str = "RTC_CLOSED";
break;
default:
break;
}
return str;
}
char* rtcGatheringState_print(rtcState state) {
char* str = NULL;
switch (state) {
case RTC_GATHERING_NEW:
str = "RTC_GATHERING_NEW";
break;
case RTC_GATHERING_INPROGRESS:
str = "RTC_GATHERING_INPROGRESS";
break;
case RTC_GATHERING_COMPLETE:
str = "RTC_GATHERING_COMPLETE";
break;
default:
break;
}
return str;
while (*str) {
if (!isspace(*str++)) {
return 0;
}
}
return 1;
}

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,41 +41,33 @@ typedef struct {
bool connected;
} Peer;
Peer *peer = NULL;
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
static void candidateCallback(const char *cand, const char *mid, void *ptr);
static void stateChangeCallback(rtcState state, void *ptr);
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
static void openCallback(void *ptr);
static void closedCallback(void *ptr);
static void messageCallback(const char *message, int size, void *ptr);
static void deletePeer(Peer *peer);
char *state_print(rtcState state);
char *rtcGatheringState_print(rtcGatheringState state);
int all_space(const char *str);
int main(int argc, char **argv){
rtcInitLogger(RTC_LOG_DEBUG);
rtcInitLogger(RTC_LOG_DEBUG, NULL);
// Create peer
rtcConfiguration config;
memset(&config, 0, sizeof(config));
// Create peer
rtcConfiguration config;
memset(&config, 0, sizeof(config));
Peer *peer = (Peer *)malloc(sizeof(Peer));
if (!peer) {
printf("Error allocating memory for peer\n");
deletePeer(peer);
}
memset(peer, 0, sizeof(Peer));
Peer *peer = (Peer *)malloc(sizeof(Peer));
if (!peer) {
fprintf(stderr, "Error allocating memory for peer\n");
return -1;
}
memset(peer, 0, sizeof(Peer));
printf("Peer created\n");
@ -85,25 +81,17 @@ int main(int argc, char **argv){
// Since this is the offere, we will create a datachannel
peer->dc = rtcCreateDataChannel(peer->pc, "test");
rtcSetOpenCallback(peer->dc, openCallback);
rtcSetClosedCallback(peer->dc, closedCallback);
rtcSetMessageCallback(peer->dc, messageCallback);
sleep(1);
sleep(1);
bool exit = false;
bool exit = false;
while (!exit) {
printf("\n");
printf("***************************************************************************************\n");
// << endl
printf("* 0: Exit /"
" 1: Enter remote description /"
" 2: Enter remote candidate /"
@ -113,13 +101,14 @@ int main(int argc, char **argv){
int command = -1;
int c;
if (scanf("%d", &command)) {
}else {
break;
}
while ((c = getchar()) != '\n' && c != EOF) { }
fflush(stdin);
if (!scanf("%d", &command)) {
break;
}
while ((c = getchar()) != '\n' && c != EOF) {
}
fflush(stdin);
switch (command) {
case 0: {
@ -210,11 +199,6 @@ int main(int argc, char **argv){
return 0;
}
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
// Peer *peer = (Peer *)ptr;
printf("Description %s:\n%s\n", "offerer", sdp);
@ -238,22 +222,17 @@ static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
}
static void openCallback(void *ptr) {
Peer *peer = (Peer *)ptr;
peer->connected = true;
char buffer[256];
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
}
static void closedCallback(void *ptr) {
Peer *peer = (Peer *)ptr;
peer->connected = false;
}
static void messageCallback(const char *message, int size, void *ptr) {
@ -264,6 +243,7 @@ static void messageCallback(const char *message, int size, void *ptr) {
printf("Message %s: [binary of size %d]\n", "offerer", size);
}
}
static void deletePeer(Peer *peer) {
if (peer) {
if (peer->dc)
@ -274,61 +254,59 @@ static void deletePeer(Peer *peer) {
}
}
char *state_print(rtcState state) {
char *str = NULL;
switch (state) {
case RTC_NEW:
str = "RTC_NEW";
break;
case RTC_CONNECTING:
str = "RTC_CONNECTING";
break;
case RTC_CONNECTED:
str = "RTC_CONNECTED";
break;
case RTC_DISCONNECTED:
str = "RTC_DISCONNECTED";
break;
case RTC_FAILED:
str = "RTC_FAILED";
break;
case RTC_CLOSED:
str = "RTC_CLOSED";
break;
default:
break;
}
return str;
}
char *rtcGatheringState_print(rtcGatheringState state) {
char *str = NULL;
switch (state) {
case RTC_GATHERING_NEW:
str = "RTC_GATHERING_NEW";
break;
case RTC_GATHERING_INPROGRESS:
str = "RTC_GATHERING_INPROGRESS";
break;
case RTC_GATHERING_COMPLETE:
str = "RTC_GATHERING_COMPLETE";
break;
default:
break;
}
return str;
}
int all_space(const char *str) {
while (*str) {
if (!isspace(*str++)) {
return 0;
}
}
return 1;
}
char* state_print(rtcState state) {
char *str = NULL;
switch (state) {
case RTC_NEW:
str = "RTC_NEW";
break;
case RTC_CONNECTING:
str = "RTC_CONNECTING";
break;
case RTC_CONNECTED:
str = "RTC_CONNECTED";
break;
case RTC_DISCONNECTED:
str = "RTC_DISCONNECTED";
break;
case RTC_FAILED:
str = "RTC_FAILED";
break;
case RTC_CLOSED:
str = "RTC_CLOSED";
break;
default:
break;
}
return str;
}
char* rtcGatheringState_print(rtcState state) {
char* str = NULL;
switch (state) {
case RTC_GATHERING_NEW:
str = "RTC_GATHERING_NEW";
break;
case RTC_GATHERING_INPROGRESS:
str = "RTC_GATHERING_INPROGRESS";
break;
case RTC_GATHERING_COMPLETE:
str = "RTC_GATHERING_COMPLETE";
break;
default:
break;
}
return str;
while (*str) {
if (!isspace(*str++)) {
return 0;
}
}
return 1;
}

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)
target_link_libraries(datachannel-copy-paste-offerer datachannel)
if(WIN32)
target_link_libraries(datachannel-copy-paste-offerer datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-copy-paste-offerer datachannel)
endif()
add_executable(datachannel-copy-paste-answerer answerer.cpp)
set_target_properties(datachannel-copy-paste-answerer PROPERTIES
CXX_STANDARD 17
OUTPUT_NAME answerer)
target_link_libraries(datachannel-copy-paste-answerer datachannel)
if(WIN32)
target_link_libraries(datachannel-copy-paste-answerer datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-copy-paste-answerer datachannel)
endif()

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

@ -49,6 +49,7 @@ public:
bool ended() const;
void hintType(Type type);
void setDataMid(string mid);
void setFingerprint(string fingerprint);
void setSctpPort(uint16_t port);
void setMaxMessageSize(size_t size);
@ -86,7 +87,7 @@ private:
string mid;
std::vector<string> attributes;
};
std::map<string, Media> mMedia; // by mid
std::map<int, Media> mMedia; // by m-line index
// Candidates
std::vector<Candidate> mCandidates;

View File

@ -64,6 +64,8 @@ const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
const int THREADPOOL_SIZE = 4; // Number of threads in the global thread pool
// overloaded helper
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;

View File

@ -25,12 +25,12 @@
namespace rtc {
class Init;
using init_token = std::shared_ptr<Init>;
using init_token = std::shared_ptr<void>;
class Init {
public:
static init_token Token();
static void Preload();
static void Cleanup();
~Init();
@ -38,11 +38,13 @@ public:
private:
Init();
static std::weak_ptr<Init> Weak;
static init_token Global;
static std::mutex Mutex;
static std::weak_ptr<void> Weak;
static std::shared_ptr<void> *Global;
static bool Initialized;
static std::recursive_mutex Mutex;
};
inline void Preload() { Init::Preload(); }
inline void Cleanup() { Init::Cleanup(); }
} // namespace rtc

View File

@ -41,6 +41,7 @@
namespace rtc {
class Certificate;
class Processor;
class IceTransport;
class DtlsTransport;
class SctpTransport;
@ -101,7 +102,7 @@ public:
// Media
bool hasMedia() const;
void sendMedia(const binary &packet);
void send(const byte *packet, size_t size);
void sendMedia(const byte *packet, size_t size);
void onMedia(std::function<void(const binary &packet)> callback);
@ -139,13 +140,13 @@ private:
void outgoingMedia(message_ptr message);
const init_token mInitToken = Init::Token();
const Configuration mConfig;
const future_certificate_ptr mCertificate;
init_token mInitToken = Init::Token();
const std::unique_ptr<Processor> mProcessor;
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

@ -28,6 +28,7 @@
#else
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#include <sys/types.h>
@ -98,8 +99,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;
@ -107,8 +108,9 @@ bool Candidate::resolve(ResolveMode mode) {
oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
oss << left;
mCandidate = oss.str();
mIsResolved = true;
PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
return mIsResolved = true;
break;
}
}
}
@ -116,7 +118,7 @@ bool Candidate::resolve(ResolveMode mode) {
freeaddrinfo(result);
}
return false;
return mIsResolved;
}
bool Candidate::isResolved() const { return mIsResolved; }

View File

@ -17,6 +17,7 @@
*/
#include "certificate.hpp"
#include "threadpool.hpp"
#include <cassert>
#include <chrono>
@ -132,14 +133,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)
@ -201,8 +202,8 @@ certificate_ptr make_certificate_impl(string commonName) {
auto *commonNameBytes =
reinterpret_cast<unsigned char *>(const_cast<char *>(commonName.c_str()));
if (!X509_gmtime_adj(X509_get_notBefore(x509.get()), 3600 * -1) ||
!X509_gmtime_adj(X509_get_notAfter(x509.get()), 3600 * 24 * 365) ||
if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 3600 * -1) ||
!X509_gmtime_adj(X509_getm_notAfter(x509.get()), 3600 * 24 * 365) ||
!X509_set_version(x509.get(), 1) || !X509_set_pubkey(x509.get(), pkey.get()) ||
!BN_pseudo_rand(serial_number.get(), serialSize, 0, 0) ||
!BN_to_ASN1_INTEGER(serial_number.get(), X509_get_serialNumber(x509.get())) ||
@ -230,19 +231,6 @@ namespace rtc {
namespace {
// Helper function roughly equivalent to std::async with policy std::launch::async
// since std::async might be unreliable on some platforms (e.g. Mingw32 on Windows)
template <class F, class... Args>
std::future<std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>> thread_call(F &&f,
Args &&... args) {
using R = std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>;
std::packaged_task<R()> task(std::bind(f, std::forward<Args>(args)...));
std::future<R> future = task.get_future();
std::thread t(std::move(task));
t.detach();
return future;
}
static std::unordered_map<string, future_certificate_ptr> CertificateCache;
static std::mutex CertificateCacheMutex;
@ -254,7 +242,7 @@ future_certificate_ptr make_certificate(string commonName) {
if (auto it = CertificateCache.find(commonName); it != CertificateCache.end())
return it->second;
auto future = thread_call(make_certificate_impl, commonName);
auto future = ThreadPool::Instance().enqueue(make_certificate_impl, commonName);
auto shared = future.share();
CertificateCache.emplace(std::move(commonName), shared);
return shared;

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

@ -96,7 +96,7 @@ void DataChannel::close() {
mIsClosed = true;
if (mIsOpen.exchange(false))
if (auto transport = mSctpTransport.lock())
transport->close(mStream);
transport->closeStream(mStream);
mSctpTransport.reset();
resetCallbacks();
@ -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));
@ -62,6 +63,7 @@ Description::Description(const string &sdp, Type type, Role role)
std::istringstream ss(sdp);
std::optional<Media> currentMedia;
int mlineIndex = 0;
bool finished;
do {
string line;
@ -75,7 +77,9 @@ Description::Description(const string &sdp, Type type, Role role)
if (currentMedia->type == "application")
mData.mid = currentMedia->mid;
else
mMedia.emplace(currentMedia->mid, std::move(*currentMedia));
mMedia.emplace(mlineIndex, std::move(*currentMedia));
++mlineIndex;
} else if (line.find(" ICE/SDP") != string::npos) {
PLOG_WARNING << "SDP \"m=\" line has no corresponding mid, ignoring";
@ -112,7 +116,8 @@ Description::Description(const string &sdp, Type type, Role role)
if (match_prefix(value, "sha-256 ")) {
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;
}
@ -161,6 +166,8 @@ void Description::hintType(Type type) {
}
}
void Description::setDataMid(string mid) { mData.mid = mid; }
void Description::setFingerprint(string fingerprint) {
mFingerprint.emplace(std::move(fingerprint));
}
@ -185,11 +192,8 @@ 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);
else
PLOG_WARNING << "Media mid \"" << mid << "\" is the same as data mid, ignoring";
for (auto p : source.mMedia)
mMedia.emplace(p);
}
Description::operator string() const { return generateSdp("\r\n"); }
@ -210,47 +214,60 @@ 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;
sdp << " " << mData.mid << eol;
for (int i = 0; i < int(mMedia.size() + 1); ++i)
if (auto it = mMedia.find(i); it != mMedia.end())
sdp << ' ' << it->second.mid;
else
sdp << ' ' << mData.mid;
sdp << eol;
// Data
const string dataDescription = "UDP/DTLS/SCTP webrtc-datachannel";
sdp << "m=application" << ' ' << (!mMedia.empty() ? 0 : 9) << ' ' << dataDescription << eol;
sdp << "c=IN IP4 0.0.0.0" << eol;
if (!mMedia.empty())
sdp << "a=bundle-only" << eol;
sdp << "a=mid:" << mData.mid << eol;
if (mData.sctpPort)
sdp << "a=sctp-port:" << *mData.sctpPort << eol;
if (mData.maxMessageSize)
sdp << "a=max-message-size:" << *mData.maxMessageSize << eol;
sdp << "a=msid-semantic: WMS" << eol;
// Non-data media
if (!mMedia.empty()) {
// Lip-sync
sdp << "a=group:LS";
for (const auto &[mid, _] : mMedia)
sdp << " " << mid;
for (const auto &p : mMedia)
sdp << " " << p.second.mid;
sdp << eol;
}
// Descriptions and attributes
for (const auto &[_, media] : mMedia) {
// Descriptions and attributes
for (int i = 0; i < int(mMedia.size() + 1); ++i) {
if (auto it = mMedia.find(i); it != mMedia.end()) {
// Non-data media
const auto &media = it->second;
sdp << "m=" << media.type << ' ' << 0 << ' ' << media.description << eol;
sdp << "c=IN IP4 0.0.0.0" << eol;
sdp << "a=bundle-only" << eol;
sdp << "a=mid:" << media.mid << eol;
for (const auto &attr : media.attributes)
sdp << "a=" << attr << eol;
} else {
// Data
const string description = "UDP/DTLS/SCTP webrtc-datachannel";
sdp << "m=application" << ' ' << (!mMedia.empty() ? 0 : 9) << ' ' << description << eol;
sdp << "c=IN IP4 0.0.0.0" << eol;
if (!mMedia.empty())
sdp << "a=bundle-only" << eol;
sdp << "a=mid:" << mData.mid << eol;
if (mData.sctpPort)
sdp << "a=sctp-port:" << *mData.sctpPort << eol;
if (mData.maxMessageSize)
sdp << "a=max-message-size:" << *mData.maxMessageSize << eol;
}
}
// Common
sdp << "a=ice-options:trickle" << eol;
if (!mEnded)
sdp << "a=ice-options:trickle" << eol;
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

@ -43,50 +43,86 @@ DtlsSrtpTransport::DtlsSrtpTransport(std::shared_ptr<IceTransport> lower,
std::move(stateChangeCallback)),
mSrtpRecvCallback(std::move(srtpRecvCallback)) { // distinct from Transport recv callback
PLOG_DEBUG << "Initializing SRTP transport";
PLOG_DEBUG << "Initializing DTLS-SRTP transport";
#if USE_GNUTLS
PLOG_DEBUG << "Initializing DTLS-SRTP transport (GnuTLS)";
gnutls::check(gnutls_srtp_set_profile(mSession, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80),
"Failed to set SRTP profile");
#else
PLOG_DEBUG << "Initializing DTLS-SRTP transport (OpenSSL)";
openssl::check(SSL_set_tlsext_use_srtp(mSsl, "SRTP_AES128_CM_SHA1_80"),
"Failed to set SRTP profile");
#endif
if (srtp_err_status_t err = srtp_create(&mSrtpIn, nullptr)) {
throw std::runtime_error("SRTP create failed, status=" + to_string(static_cast<int>(err)));
}
if (srtp_err_status_t err = srtp_create(&mSrtpOut, nullptr)) {
srtp_dealloc(mSrtpIn);
throw std::runtime_error("SRTP create failed, status=" + to_string(static_cast<int>(err)));
}
}
DtlsSrtpTransport::~DtlsSrtpTransport() {
stop();
if (mCreated)
srtp_dealloc(mSrtp);
srtp_dealloc(mSrtpIn);
srtp_dealloc(mSrtpOut);
}
bool DtlsSrtpTransport::send(message_ptr message) {
bool DtlsSrtpTransport::sendMedia(message_ptr message) {
if (!message)
return false;
if (!mInitDone) {
PLOG_WARNING << "SRTP media sent before keys are derived";
return false;
}
int size = message->size();
PLOG_VERBOSE << "Send size=" << size;
// srtp_protect() assumes that it can write SRTP_MAX_TRAILER_LEN (for the authentication tag)
// into the location in memory immediately following the RTP packet.
// The RTP header has a minimum size of 12 bytes
if (size < 12)
throw std::runtime_error("RTP/RTCP packet too short");
// srtp_protect() and srtp_protect_rtcp() assume that they can write SRTP_MAX_TRAILER_LEN (for
// the authentication tag) into the location in memory immediately following the RTP packet.
message->resize(size + SRTP_MAX_TRAILER_LEN);
if (srtp_err_status_t err = srtp_protect(mSrtp, message->data(), &size)) {
if (err == srtp_err_status_replay_fail)
throw std::runtime_error("SRTP packet is a replay");
else
throw std::runtime_error("SRTP protect error, status=" +
to_string(static_cast<int>(err)));
uint8_t value2 = to_integer<uint8_t>(*(message->begin() + 1)) & 0x7F;
PLOG_VERBOSE << "Demultiplexing SRTCP and SRTP with RTP payload type, value="
<< unsigned(value2);
// RFC 5761 Multiplexing RTP and RTCP 4. Distinguishable RTP and RTCP Packets
// It is RECOMMENDED to follow the guidelines in the RTP/AVP profile for the choice of RTP
// payload type values, with the additional restriction that payload type values in the
// range 64-95 MUST NOT be used. Specifically, dynamic RTP payload types SHOULD be chosen in
// the range 96-127 where possible. Values below 64 MAY be used if that is insufficient
// [...]
if (value2 >= 64 && value2 <= 95) { // Range 64-95 (inclusive) MUST be RTCP
if (srtp_err_status_t err = srtp_protect_rtcp(mSrtpOut, message->data(), &size)) {
if (err == srtp_err_status_replay_fail)
throw std::runtime_error("SRTCP packet is a replay");
else
throw std::runtime_error("SRTCP protect error, status=" +
to_string(static_cast<int>(err)));
}
PLOG_VERBOSE << "Protected SRTCP packet, size=" << size;
} else {
if (srtp_err_status_t err = srtp_protect(mSrtpOut, message->data(), &size)) {
if (err == srtp_err_status_replay_fail)
throw std::runtime_error("SRTP packet is a replay");
else
throw std::runtime_error("SRTP protect error, status=" +
to_string(static_cast<int>(err)));
}
PLOG_VERBOSE << "Protected SRTP packet, size=" << size;
}
PLOG_VERBOSE << "Protected SRTP packet, size=" << size;
message->resize(size);
outgoing(message);
return true;
}
void DtlsSrtpTransport::incoming(message_ptr message) {
if (!mInitDone) {
// Bypas
DtlsTransport::incoming(message);
return;
}
int size = message->size();
if (size == 0)
return;
@ -95,49 +131,80 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
// The process for demultiplexing a packet is as follows. The receiver looks at the first byte
// of the packet. [...] If the value is in between 128 and 191 (inclusive), then the packet is
// RTP (or RTCP [...]). If the value is between 20 and 63 (inclusive), the packet is DTLS.
uint8_t value = to_integer<uint8_t>(*message->begin());
uint8_t value1 = to_integer<uint8_t>(*message->begin());
PLOG_VERBOSE << "Demultiplexing DTLS and SRTP/SRTCP with first byte, value="
<< unsigned(value1);
if (value >= 128 && value <= 192) {
if (value1 >= 20 && value1 <= 63) {
PLOG_VERBOSE << "Incoming DTLS packet, size=" << size;
DtlsTransport::incoming(message);
} else if (value >= 20 && value <= 64) {
PLOG_VERBOSE << "Incoming SRTP packet, size=" << size;
if (srtp_err_status_t err = srtp_unprotect(mSrtp, message->data(), &size)) {
if (err == srtp_err_status_replay_fail)
PLOG_WARNING << "Incoming SRTP packet is a replay";
else
PLOG_WARNING << "SRTP unprotect error, status=" << err;
} else if (value1 >= 128 && value1 <= 191) {
// The RTP header has a minimum size of 12 bytes
if (size < 12) {
PLOG_WARNING << "Incoming SRTP/SRTCP packet too short, size=" << size;
return;
}
PLOG_VERBOSE << "Unprotected SRTP packet, size=" << size;
uint8_t value2 = to_integer<uint8_t>(*(message->begin() + 1)) & 0x7F;
PLOG_VERBOSE << "Demultiplexing SRTCP and SRTP with RTP payload type, value="
<< unsigned(value2);
// See RFC 5761 reference above
if (value2 >= 64 && value2 <= 95) { // Range 64-95 (inclusive) MUST be RTCP
PLOG_VERBOSE << "Incoming SRTCP packet, size=" << size;
if (srtp_err_status_t err = srtp_unprotect_rtcp(mSrtpIn, message->data(), &size)) {
if (err == srtp_err_status_replay_fail)
PLOG_WARNING << "Incoming SRTCP packet is a replay";
else
PLOG_WARNING << "SRTCP unprotect error, status=" << err;
return;
}
PLOG_VERBOSE << "Unprotected SRTCP packet, size=" << size;
} else {
PLOG_VERBOSE << "Incoming SRTP packet, size=" << size;
if (srtp_err_status_t err = srtp_unprotect(mSrtpIn, message->data(), &size)) {
if (err == srtp_err_status_replay_fail)
PLOG_WARNING << "Incoming SRTP packet is a replay";
else
PLOG_WARNING << "SRTP unprotect error, status=" << err;
return;
}
PLOG_VERBOSE << "Unprotected SRTP packet, size=" << size;
}
message->resize(size);
mSrtpRecvCallback(message);
} else {
PLOG_WARNING << "Unknown packet type, value=" << value << ", size=" << size;
PLOG_WARNING << "Unknown packet type, value=" << unsigned(value1) << ", size=" << size;
}
}
void DtlsSrtpTransport::postCreation() {
#if USE_GNUTLS
PLOG_DEBUG << "Setting SRTP profile (GnuTLS)";
gnutls::check(gnutls_srtp_set_profile(mSession, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80),
"Failed to set SRTP profile");
#else
PLOG_DEBUG << "Setting SRTP profile (OpenSSL)";
// returns 0 on success, 1 on error
if (SSL_set_tlsext_use_srtp(mSsl, "SRTP_AES128_CM_SHA1_80"), "Failed to set SRTP profile")
throw std::runtime_error("Failed to set SRTP profile: " + openssl::error_string(ERR_get_error()));
#endif
}
void DtlsSrtpTransport::postHandshake() {
if (mCreated)
if (mInitDone)
return;
srtp_policy_t inbound = {};
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
inbound.ssrc.type = ssrc_any_inbound;
srtp_policy_t outbound = {};
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp);
outbound.ssrc.type = ssrc_any_outbound;
const size_t materialLen = SRTP_AES_ICM_128_KEY_LEN_WSALT * 2;
unsigned char material[materialLen];
const unsigned char *clientKey, *clientSalt, *serverKey, *serverSalt;
#if USE_GNUTLS
PLOG_INFO << "Deriving SRTP keying material (GnuTLS)";
gnutls_datum_t clientKeyDatum, clientSaltDatum, serverKeyDatum, serverSaltDatum;
gnutls::check(gnutls_srtp_get_keys(mSession, material, materialLen, &clientKeyDatum,
&clientSaltDatum, &serverKeyDatum, &serverSaltDatum),
@ -160,18 +227,23 @@ void DtlsSrtpTransport::postHandshake() {
serverKey = reinterpret_cast<const unsigned char *>(serverKeyDatum.data);
serverSalt = reinterpret_cast<const unsigned char *>(serverSaltDatum.data);
#else
// This provides the client write master key, the server write master key, the client write
// master salt and the server write master salt in that order.
PLOG_INFO << "Deriving SRTP keying material (OpenSSL)";
// The extractor provides the client write master key, the server write master key, the client
// write master salt and the server write master salt in that order.
const string label = "EXTRACTOR-dtls_srtp";
openssl::check(SSL_export_keying_material(mSsl, material, materialLen, label.c_str(),
label.size(), nullptr, 0, 0),
"Failed to derive SRTP keys");
// returns 1 on success, 0 or -1 on failure (OpenSSL API is a complete mess...)
if (SSL_export_keying_material(mSsl, material, materialLen, label.c_str(), label.size(),
nullptr, 0, 0) <= 0)
throw std::runtime_error("Failed to derive SRTP keys: " +
openssl::error_string(ERR_get_error()));
clientKey = material;
clientSalt = clientKey + SRTP_AES_128_KEY_LEN;
serverKey = material + SRTP_AES_ICM_128_KEY_LEN_WSALT;
serverSalt = serverSalt + SRTP_AES_128_KEY_LEN;
serverSalt = serverKey + SRTP_AES_128_KEY_LEN;
#endif
unsigned char clientSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
@ -182,22 +254,31 @@ void DtlsSrtpTransport::postHandshake() {
std::memcpy(serverSessionKey, serverKey, SRTP_AES_128_KEY_LEN);
std::memcpy(serverSessionKey + SRTP_AES_128_KEY_LEN, serverSalt, SRTP_SALT_LEN);
if (mIsClient) {
inbound.key = serverSessionKey;
outbound.key = clientSessionKey;
} else {
inbound.key = clientSessionKey;
outbound.key = serverSessionKey;
}
srtp_policy_t inbound = {};
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
inbound.ssrc.type = ssrc_any_inbound;
inbound.ssrc.value = 0;
inbound.key = mIsClient ? serverSessionKey : clientSessionKey;
inbound.next = nullptr;
srtp_policy_t *policies = &inbound;
inbound.next = &outbound;
if (srtp_err_status_t err = srtp_add_stream(mSrtpIn, &inbound))
throw std::runtime_error("SRTP add inbound stream failed, status=" +
to_string(static_cast<int>(err)));
srtp_policy_t outbound = {};
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp);
outbound.ssrc.type = ssrc_any_outbound;
outbound.ssrc.value = 0;
outbound.key = mIsClient ? clientSessionKey : serverSessionKey;
outbound.next = nullptr;
if (srtp_err_status_t err = srtp_create(&mSrtp, policies))
throw std::runtime_error("SRTP create failed, status=" + to_string(static_cast<int>(err)));
if (srtp_err_status_t err = srtp_add_stream(mSrtpOut, &outbound))
throw std::runtime_error("SRTP add outbound stream failed, status=" +
to_string(static_cast<int>(err)));
mCreated = true;
mInitDone = true;
}
} // namespace rtc

View File

@ -38,16 +38,17 @@ public:
state_callback stateChangeCallback);
~DtlsSrtpTransport();
bool send(message_ptr message) override;
bool sendMedia(message_ptr message);
private:
void incoming(message_ptr message) override;
void postCreation() override;
void postHandshake() override;
message_callback mSrtpRecvCallback;
srtp_t mSrtp;
bool mCreated = false;
srtp_t mSrtpIn, mSrtpOut;
bool mInitDone = false;
};
} // namespace rtc

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)
@ -79,6 +85,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr cer
gnutls_transport_set_pull_function(mSession, ReadCallback);
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
postCreation();
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
registerIncoming();
@ -131,6 +139,10 @@ void DtlsTransport::incoming(message_ptr message) {
mIncomingQueue.push(message);
}
void DtlsTransport::postCreation() {
// Dummy
}
void DtlsTransport::postHandshake() {
// Dummy
}
@ -302,7 +314,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 +339,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
openssl::check(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed");
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 +350,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 +400,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);
}
@ -398,6 +414,10 @@ void DtlsTransport::incoming(message_ptr message) {
mIncomingQueue.push(message);
}
void DtlsTransport::postCreation() {
// Dummy
}
void DtlsTransport::postHandshake() {
// Dummy
}
@ -418,11 +438,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 +456,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 +469,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 +508,7 @@ void DtlsTransport::runRecvLoop() {
}
}
int DtlsTransport::CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx) {
int DtlsTransport::CertificateCallback(int /*preverify_ok*/, X509_STORE_CTX *ctx) {
SSL *ssl =
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
DtlsTransport *t =
@ -537,7 +557,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

@ -52,6 +52,7 @@ public:
protected:
virtual void incoming(message_ptr message) override;
virtual void postCreation();
virtual void postHandshake();
void runRecvLoop();

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

@ -21,6 +21,7 @@
#include "certificate.hpp"
#include "dtlstransport.hpp"
#include "sctptransport.hpp"
#include "threadpool.hpp"
#include "tls.hpp"
#if RTC_ENABLE_WEBSOCKET
@ -39,33 +40,21 @@ using std::shared_ptr;
namespace rtc {
std::weak_ptr<Init> Init::Weak;
init_token Init::Global;
std::mutex Init::Mutex;
namespace {
init_token Init::Token() {
std::lock_guard lock(Mutex);
void doInit() {
PLOG_DEBUG << "Global initialization";
if (!Global) {
if (auto token = Weak.lock())
Global = token;
else
Global = shared_ptr<Init>(new Init());
}
return Global;
}
void Init::Cleanup() { Global.reset(); }
Init::Init() {
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
#endif
ThreadPool::Instance().spawn(THREADPOOL_SIZE);
#if USE_GNUTLS
// Nothing to do
// Nothing to do
#else
openssl::init();
#endif
@ -80,8 +69,12 @@ Init::Init() {
#endif
}
Init::~Init() {
void doCleanup() {
PLOG_DEBUG << "Global cleanup";
ThreadPool::Instance().join();
CleanupCertificateCache();
SctpTransport::Cleanup();
DtlsTransport::Cleanup();
#if RTC_ENABLE_WEBSOCKET
@ -96,5 +89,57 @@ Init::~Init() {
#endif
}
} // namespace
std::weak_ptr<void> Init::Weak;
std::shared_ptr<void> *Init::Global = nullptr;
bool Init::Initialized = false;
std::recursive_mutex Init::Mutex;
init_token Init::Token() {
std::unique_lock lock(Mutex);
if (auto token = Weak.lock())
return token;
delete Global;
Global = new shared_ptr<void>(new Init());
Weak = *Global;
return *Global;
}
void Init::Preload() {
std::unique_lock lock(Mutex);
auto token = Token();
if (!Global)
Global = new shared_ptr<void>(token);
PLOG_DEBUG << "Preloading certificate";
make_certificate().wait();
}
void Init::Cleanup() {
std::unique_lock lock(Mutex);
delete Global;
Global = nullptr;
}
Init::Init() {
// Mutex is locked by Token() here
if (!std::exchange(Initialized, true))
doInit();
}
Init::~Init() {
std::thread t([]() {
// We need to lock Mutex ourselves
std::unique_lock lock(Mutex);
if (Global)
return;
if (std::exchange(Initialized, false))
doCleanup();
});
t.detach();
}
} // namespace rtc

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

@ -18,9 +18,12 @@
#include "peerconnection.hpp"
#include "certificate.hpp"
#include "include.hpp"
#include "processor.hpp"
#include "threadpool.hpp"
#include "dtlstransport.hpp"
#include "icetransport.hpp"
#include "include.hpp"
#include "sctptransport.hpp"
#if RTC_ENABLE_MEDIA
@ -39,9 +42,12 @@ using std::weak_ptr;
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
PeerConnection::PeerConnection(const Configuration &config)
: mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New),
mGatheringState(GatheringState::New) {
: mConfig(config), mCertificate(make_certificate()), mProcessor(std::make_unique<Processor>()),
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 +95,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,18 +139,16 @@ 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 {
// OK, we might need a lookup, do it asynchronously
// We don't use the thread pool because we have no control on the timeout
weak_ptr<IceTransport> weakIceTransport{iceTransport};
std::thread t([weakIceTransport, candidate]() mutable {
if (candidate.resolve(Candidate::ResolveMode::Lookup))
@ -151,6 +157,9 @@ void PeerConnection::addRemoteCandidate(Candidate candidate) {
});
t.detach();
}
std::lock_guard lock(mRemoteDescriptionMutex);
mRemoteDescription->addCandidate(candidate);
}
std::optional<string> PeerConnection::localAddress() const {
@ -222,7 +231,7 @@ void PeerConnection::sendMedia(const binary &packet) {
outgoingMedia(make_message(packet.begin(), packet.end(), Message::Binary));
}
void PeerConnection::send(const byte *packet, size_t size) {
void PeerConnection::sendMedia(const byte *packet, size_t size) {
outgoingMedia(make_message(packet, packet + size, Message::Binary));
}
@ -239,7 +248,7 @@ void PeerConnection::outgoingMedia(message_ptr message) {
if (!transport)
throw std::runtime_error("PeerConnection is not open");
std::dynamic_pointer_cast<DtlsSrtpTransport>(transport)->send(message);
std::dynamic_pointer_cast<DtlsSrtpTransport>(transport)->sendMedia(message);
#else
PLOG_WARNING << "Ignoring sent media (not compiled with SRTP support)";
#endif
@ -440,21 +449,18 @@ void PeerConnection::closeTransports() {
auto sctp = std::atomic_exchange(&mSctpTransport, decltype(mSctpTransport)(nullptr));
auto dtls = std::atomic_exchange(&mDtlsTransport, decltype(mDtlsTransport)(nullptr));
auto ice = std::atomic_exchange(&mIceTransport, decltype(mIceTransport)(nullptr));
if (sctp || dtls || ice) {
std::thread t([sctp, dtls, ice, token = mInitToken]() mutable {
if (sctp)
sctp->stop();
if (dtls)
dtls->stop();
if (ice)
ice->stop();
ThreadPool::Instance().enqueue([sctp, dtls, ice]() mutable {
if (sctp)
sctp->stop();
if (dtls)
dtls->stop();
if (ice)
ice->stop();
sctp.reset();
dtls.reset();
ice.reset();
});
t.detach();
}
sctp.reset();
dtls.reset();
ice.reset();
});
}
void PeerConnection::endLocalCandidates() {
@ -478,7 +484,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);
@ -497,7 +503,7 @@ void PeerConnection::forwardMessage(message_ptr message) {
mDataChannels.insert(std::make_pair(message->stream, channel));
} else {
// Invalid, close the DataChannel
sctpTransport->close(message->stream);
sctpTransport->closeStream(message->stream);
return;
}
}
@ -589,18 +595,26 @@ void PeerConnection::remoteCloseDataChannels() {
void PeerConnection::processLocalDescription(Description description) {
std::optional<uint16_t> remoteSctpPort;
if (auto remote = remoteDescription())
remoteSctpPort = remote->sctpPort();
std::optional<string> remoteDataMid;
if (auto remote = remoteDescription()) {
remoteDataMid = remote->dataMid();
remoteSctpPort = remote->sctpPort();
}
auto certificate = mCertificate.get(); // wait for certificate if not ready
std::lock_guard lock(mLocalDescriptionMutex);
mLocalDescription.emplace(std::move(description));
mLocalDescription->setFingerprint(certificate->fingerprint());
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
{
std::lock_guard lock(mLocalDescriptionMutex);
mLocalDescription.emplace(std::move(description));
if (remoteDataMid)
mLocalDescription->setDataMid(*remoteDataMid);
mLocalDescriptionCallback(*mLocalDescription);
mLocalDescription->setFingerprint(certificate->fingerprint());
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
}
mProcessor->enqueue([this]() { mLocalDescriptionCallback(*mLocalDescription); });
}
void PeerConnection::processLocalCandidate(Candidate candidate) {
@ -610,7 +624,8 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
mLocalDescription->addCandidate(candidate);
mLocalCandidateCallback(candidate);
mProcessor->enqueue(
[this, candidate = std::move(candidate)]() { mLocalCandidateCallback(candidate); });
}
void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
@ -618,7 +633,8 @@ void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
if (!dataChannel)
return;
mDataChannelCallback(dataChannel);
mProcessor->enqueue(
[this, dataChannel = std::move(dataChannel)]() { mDataChannelCallback(dataChannel); });
}
bool PeerConnection::changeState(State state) {
@ -632,13 +648,13 @@ bool PeerConnection::changeState(State state) {
} while (!mState.compare_exchange_weak(current, state));
mStateChangeCallback(state);
mProcessor->enqueue([this, state]() { mStateChangeCallback(state); });
return true;
}
bool PeerConnection::changeGatheringState(GatheringState state) {
if (mGatheringState.exchange(state) != state)
mGatheringStateChangeCallback(state);
mProcessor->enqueue([this, state] { mGatheringStateChangeCallback(state); });
return true;
}
@ -651,13 +667,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
}

44
src/processor.cpp Normal file
View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "processor.hpp"
namespace rtc {
Processor::~Processor() { join(); }
void Processor::join() {
std::unique_lock lock(mMutex);
mCondition.wait(lock, [this]() { return !mPending && mTasks.empty(); });
}
void Processor::schedule() {
std::unique_lock lock(mMutex);
if (mTasks.empty()) {
// No more tasks
mPending = false;
mCondition.notify_all();
return;
}
ThreadPool::Instance().enqueue(std::move(mTasks.front()));
mTasks.pop();
}
} // namespace rtc

92
src/processor.hpp Normal file
View File

@ -0,0 +1,92 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef RTC_PROCESSOR_H
#define RTC_PROCESSOR_H
#include "include.hpp"
#include "init.hpp"
#include "threadpool.hpp"
#include <condition_variable>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
namespace rtc {
// Processed tasks in order by delegating them to the thread pool
class Processor final {
public:
Processor() = default;
~Processor();
Processor(const Processor &) = delete;
Processor &operator=(const Processor &) = delete;
Processor(Processor &&) = delete;
Processor &operator=(Processor &&) = delete;
void join();
template <class F, class... Args>
auto enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...>;
protected:
void schedule();
// Keep an init token
const init_token mInitToken = Init::Token();
std::queue<std::function<void()>> mTasks;
bool mPending = false; // true iff a task is pending in the thread pool
mutable std::mutex mMutex;
std::condition_variable mCondition;
};
template <class F, class... Args>
auto Processor::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
std::unique_lock lock(mMutex);
using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
auto task = std::make_shared<std::packaged_task<R()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<R> result = task->get_future();
auto bundle = [this, task = std::move(task)]() {
try {
(*task)();
} catch (const std::exception &e) {
PLOG_WARNING << "Unhandled exception in task: " << e.what();
}
schedule(); // chain the next task
};
if (!mPending) {
ThreadPool::Instance().enqueue(std::move(bundle));
mPending = true;
} else {
mTasks.emplace(std::move(bundle));
}
return result;
}
} // namespace rtc
#endif

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;
@ -55,36 +55,38 @@ std::unordered_map<int, void *> userPointerMap;
std::mutex mutex;
int lastId = 0;
void *getUserPointer(int id) {
std::optional<void *> getUserPointer(int id) {
std::lock_guard lock(mutex);
auto it = userPointerMap.find(id);
return it != userPointerMap.end() ? it->second : nullptr;
return it != userPointerMap.end() ? std::make_optional(it->second) : nullopt;
}
void setUserPointer(int i, void *ptr) {
std::lock_guard lock(mutex);
if (ptr)
userPointerMap.insert(std::make_pair(i, ptr));
else
userPointerMap.erase(i);
userPointerMap[i] = ptr;
}
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) {
std::lock_guard lock(mutex);
int pc = ++lastId;
peerConnectionMap.emplace(std::make_pair(pc, ptr));
userPointerMap.emplace(std::make_pair(pc, nullptr));
return pc;
}
@ -92,45 +94,46 @@ int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
std::lock_guard lock(mutex);
int dc = ++lastId;
dataChannelMap.emplace(std::make_pair(dc, ptr));
userPointerMap.emplace(std::make_pair(dc, nullptr));
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) {
std::lock_guard lock(mutex);
int ws = ++lastId;
webSocketMap.emplace(std::make_pair(ws, ptr));
userPointerMap.emplace(std::make_pair(ws, nullptr));
return ws;
}
bool eraseWebSocket(int ws) {
void eraseWebSocket(int ws) {
std::lock_guard lock(mutex);
if (webSocketMap.erase(ws) == 0)
return false;
throw std::invalid_argument("WebSocket ID does not exist");
userPointerMap.erase(ws);
return true;
}
#endif
@ -142,380 +145,449 @@ shared_ptr<Channel> getChannel(int id) {
if (auto it = webSocketMap.find(id); it != webSocketMap.end())
return it->second;
#endif
return nullptr;
throw std::invalid_argument("DataChannel or WebSocket ID does not exist");
}
template <typename F> int wrap(F func) {
try {
return int(func());
} catch (const std::invalid_argument &e) {
PLOG_ERROR << e.what();
return RTC_ERR_INVALID;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
return RTC_ERR_FAILURE;
}
}
#define WRAP(statement) \
wrap([&]() { \
statement; \
return RTC_ERR_SUCCESS; \
})
class plog_appender : public plog::IAppender {
public:
plog_appender(rtcLogCallbackFunc cb = nullptr) { set_callback(cb); }
void set_callback(rtcLogCallbackFunc cb) {
std::lock_guard lock(mutex);
callback = cb;
}
void write(const plog::Record &record) override {
plog::Severity severity = record.getSeverity();
auto formatted = plog::FuncMessageFormatter::format(record);
formatted.pop_back(); // remove newline
#ifdef _WIN32
using convert_type = std::codecvt_utf8<wchar_t>;
std::wstring_convert<convert_type, wchar_t> converter;
std::string str = converter.to_bytes(formatted);
#else
std::string str = formatted;
#endif
std::lock_guard lock(mutex);
if (callback)
callback(static_cast<rtcLogLevel>(record.getSeverity()), str.c_str());
else
std::cout << plog::severityToString(severity) << " " << str << std::endl;
}
private:
rtcLogCallbackFunc callback;
};
} // namespace
void rtcInitLogger(rtcLogLevel level) { InitLogger(static_cast<LogLevel>(level)); }
void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
static std::optional<plog_appender> appender;
if (appender)
appender->set_callback(cb);
else if (cb)
appender.emplace(plog_appender(cb));
InitLogger(static_cast<plog::Severity>(level), appender ? &appender.value() : nullptr);
}
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
int rtcCreatePeerConnection(const rtcConfiguration *config) {
Configuration c;
for (int i = 0; i < config->iceServersCount; ++i)
c.iceServers.emplace_back(string(config->iceServers[i]));
return WRAP({
Configuration c;
for (int i = 0; i < config->iceServersCount; ++i)
c.iceServers.emplace_back(string(config->iceServers[i]));
if (config->portRangeBegin || config->portRangeEnd) {
c.portRangeBegin = config->portRangeBegin;
c.portRangeEnd = config->portRangeEnd;
}
if (config->portRangeBegin || config->portRangeEnd) {
c.portRangeBegin = config->portRangeBegin;
c.portRangeEnd = config->portRangeEnd;
}
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
});
}
int rtcDeletePeerConnection(int pc) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
return WRAP({
auto peerConnection = getPeerConnection(pc);
peerConnection->onDataChannel(nullptr);
peerConnection->onLocalDescription(nullptr);
peerConnection->onLocalCandidate(nullptr);
peerConnection->onStateChange(nullptr);
peerConnection->onGatheringStateChange(nullptr);
peerConnection->onDataChannel(nullptr);
peerConnection->onLocalDescription(nullptr);
peerConnection->onLocalCandidate(nullptr);
peerConnection->onStateChange(nullptr);
peerConnection->onGatheringStateChange(nullptr);
erasePeerConnection(pc);
return 0;
erasePeerConnection(pc);
});
}
int rtcCreateDataChannel(int pc, const char *label) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
void *ptr = getUserPointer(pc);
rtcSetUserPointer(dc, ptr);
return dc;
return WRAP({
auto peerConnection = getPeerConnection(pc);
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
if (auto ptr = getUserPointer(pc))
rtcSetUserPointer(dc, *ptr);
return dc;
});
}
int rtcDeleteDataChannel(int dc) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
return WRAP({
auto dataChannel = getDataChannel(dc);
dataChannel->onOpen(nullptr);
dataChannel->onClosed(nullptr);
dataChannel->onError(nullptr);
dataChannel->onMessage(nullptr);
dataChannel->onBufferedAmountLow(nullptr);
dataChannel->onAvailable(nullptr);
dataChannel->onOpen(nullptr);
dataChannel->onClosed(nullptr);
dataChannel->onError(nullptr);
dataChannel->onMessage(nullptr);
dataChannel->onBufferedAmountLow(nullptr);
dataChannel->onAvailable(nullptr);
eraseDataChannel(dc);
return 0;
eraseDataChannel(dc);
});
}
#if RTC_ENABLE_WEBSOCKET
int rtcCreateWebSocket(const char *url) {
auto ws = std::make_shared<WebSocket>();
ws->open(url);
return emplaceWebSocket(ws);
}
int rtcDeleteWebsocket(int ws) {
auto webSocket = getWebSocket(ws);
if (!webSocket)
return -1;
webSocket->onOpen(nullptr);
webSocket->onClosed(nullptr);
webSocket->onError(nullptr);
webSocket->onMessage(nullptr);
webSocket->onBufferedAmountLow(nullptr);
webSocket->onAvailable(nullptr);
eraseWebSocket(ws);
return 0;
}
#endif
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
int dc = emplaceDataChannel(dataChannel);
void *ptr = getUserPointer(pc);
rtcSetUserPointer(dc, ptr);
cb(dc, ptr);
});
else
peerConnection->onDataChannel(nullptr);
return 0;
}
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
});
else
peerConnection->onLocalDescription(nullptr);
return 0;
}
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
});
else
peerConnection->onLocalCandidate(nullptr);
return 0;
}
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
cb(static_cast<rtcState>(state), getUserPointer(pc));
});
else
peerConnection->onStateChange(nullptr);
return 0;
}
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
});
else
peerConnection->onGatheringStateChange(nullptr);
return 0;
}
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
return 0;
}
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
CATCH(peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}))
return 0;
}
int rtcGetLocalAddress(int pc, char *buffer, int size) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (auto addr = peerConnection->localAddress()) {
size = std::min(size_t(size - 1), addr->size());
std::copy(addr->data(), addr->data() + size, buffer);
buffer[size] = '\0';
return size + 1;
}
return -1;
}
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (auto addr = peerConnection->remoteAddress()) {
size = std::min(size_t(size - 1), addr->size());
std::copy(addr->data(), addr->data() + size, buffer);
buffer[size] = '\0';
return size + 1;
}
return -1;
}
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (!size)
return 0;
string label = dataChannel->label();
size = std::min(size_t(size - 1), label.size());
std::copy(label.data(), label.data() + size, buffer);
buffer[size] = '\0';
return size + 1;
}
int rtcSetOpenCallback(int id, openCallbackFunc cb) {
auto channel = getChannel(id);
if (!channel)
return -1;
if (cb)
channel->onOpen([id, cb]() { cb(getUserPointer(id)); });
else
channel->onOpen(nullptr);
return 0;
}
int rtcSetClosedCallback(int id, closedCallbackFunc cb) {
auto channel = getChannel(id);
if (!channel)
return -1;
if (cb)
channel->onClosed([id, cb]() { cb(getUserPointer(id)); });
else
channel->onClosed(nullptr);
return 0;
}
int rtcSetErrorCallback(int id, errorCallbackFunc cb) {
auto channel = getChannel(id);
if (!channel)
return -1;
if (cb)
channel->onError([id, cb](const string &error) { cb(error.c_str(), getUserPointer(id)); });
else
channel->onError(nullptr);
return 0;
}
int rtcSetMessageCallback(int id, messageCallbackFunc cb) {
auto channel = getChannel(id);
if (!channel)
return -1;
if (cb)
channel->onMessage(
[id, cb](const binary &b) {
cb(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(id));
},
[id, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(id)); });
else
channel->onMessage(nullptr);
return 0;
}
int rtcSendMessage(int id, const char *data, int size) {
auto channel = getChannel(id);
if (!channel)
return -1;
if (size >= 0) {
auto b = reinterpret_cast<const byte *>(data);
CATCH(channel->send(binary(b, b + size)));
return size;
} else {
string str(data);
int len = str.size();
CATCH(channel->send(std::move(str)));
return len;
}
}
int rtcGetBufferedAmount(int id) {
auto channel = getChannel(id);
if (!channel)
return -1;
CATCH(return int(channel->bufferedAmount()));
}
int rtcSetBufferedAmountLowThreshold(int id, int amount) {
auto channel = getChannel(id);
if (!channel)
return -1;
CATCH(channel->setBufferedAmountLowThreshold(size_t(amount)));
return 0;
}
int rtcSetBufferedAmountLowCallback(int id, bufferedAmountLowCallbackFunc cb) {
auto channel = getChannel(id);
if (!channel)
return -1;
if (cb)
channel->onBufferedAmountLow([id, cb]() { cb(getUserPointer(id)); });
else
channel->onBufferedAmountLow(nullptr);
return 0;
}
int rtcGetAvailableAmount(int id) {
auto channel = getChannel(id);
if (!channel)
return -1;
CATCH(return int(channel->availableAmount()));
}
int rtcSetAvailableCallback(int id, availableCallbackFunc cb) {
auto channel = getChannel(id);
if (!channel)
return -1;
if (cb)
channel->onOpen([id, cb]() { cb(getUserPointer(id)); });
else
channel->onOpen(nullptr);
return 0;
}
int rtcReceiveMessage(int id, char *buffer, int *size) {
auto channel = getChannel(id);
if (!channel)
return -1;
if (!size)
return -1;
CATCH({
auto message = channel->receive();
if (!message)
return 0;
return std::visit( //
overloaded{ //
[&](const binary &b) {
*size = std::min(*size, int(b.size()));
auto data = reinterpret_cast<const char *>(b.data());
std::copy(data, data + *size, buffer);
return *size;
},
[&](const string &s) {
int len = std::min(*size - 1, int(s.size()));
if (len >= 0) {
std::copy(s.data(), s.data() + len, buffer);
buffer[len] = '\0';
}
*size = -(len + 1);
return len + 1;
}},
*message);
return WRAP({
auto ws = std::make_shared<WebSocket>();
ws->open(url);
return emplaceWebSocket(ws);
});
}
int rtcDeleteWebsocket(int ws) {
return WRAP({
auto webSocket = getWebSocket(ws);
webSocket->onOpen(nullptr);
webSocket->onClosed(nullptr);
webSocket->onError(nullptr);
webSocket->onMessage(nullptr);
webSocket->onBufferedAmountLow(nullptr);
webSocket->onAvailable(nullptr);
eraseWebSocket(ws);
});
}
#endif
int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (cb)
peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
int dc = emplaceDataChannel(dataChannel);
if (auto ptr = getUserPointer(pc)) {
rtcSetUserPointer(dc, *ptr);
cb(dc, *ptr);
}
});
else
peerConnection->onDataChannel(nullptr);
});
}
int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (cb)
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
if (auto ptr = getUserPointer(pc))
cb(string(desc).c_str(), desc.typeString().c_str(), *ptr);
});
else
peerConnection->onLocalDescription(nullptr);
});
}
int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (cb)
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
if (auto ptr = getUserPointer(pc))
cb(cand.candidate().c_str(), cand.mid().c_str(), *ptr);
});
else
peerConnection->onLocalCandidate(nullptr);
});
}
int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (cb)
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
if (auto ptr = getUserPointer(pc))
cb(static_cast<rtcState>(state), *ptr);
});
else
peerConnection->onStateChange(nullptr);
});
}
int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (cb)
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
if (auto ptr = getUserPointer(pc))
cb(static_cast<rtcGatheringState>(state), *ptr);
});
else
peerConnection->onGatheringStateChange(nullptr);
});
}
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (!sdp)
throw std::invalid_argument("Unexpected null pointer");
peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""});
});
}
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (!cand)
throw std::invalid_argument("Unexpected null pointer");
peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""});
});
}
int rtcGetLocalAddress(int pc, char *buffer, int size) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (!buffer)
throw std::invalid_argument("Unexpected null pointer");
if (size <= 0)
return 0;
if (auto addr = peerConnection->localAddress()) {
const char *data = addr->data();
size = std::min(size - 1, int(addr->size()));
std::copy(data, data + size, buffer);
buffer[size] = '\0';
return size + 1;
}
});
}
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
return WRAP({
auto peerConnection = getPeerConnection(pc);
if (!buffer)
throw std::invalid_argument("Unexpected null pointer");
if (size <= 0)
return 0;
if (auto addr = peerConnection->remoteAddress()) {
const char *data = addr->data();
size = std::min(size - 1, int(addr->size()));
std::copy(data, data + size, buffer);
buffer[size] = '\0';
return int(size + 1);
}
});
}
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
return WRAP({
auto dataChannel = getDataChannel(dc);
if (!buffer)
throw std::invalid_argument("Unexpected null pointer");
if (size <= 0)
return 0;
string label = dataChannel->label();
const char *data = label.data();
size = std::min(size - 1, int(label.size()));
std::copy(data, data + size, buffer);
buffer[size] = '\0';
return int(size + 1);
});
}
int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
return WRAP({
auto channel = getChannel(id);
if (cb)
channel->onOpen([id, cb]() {
if (auto ptr = getUserPointer(id))
cb(*ptr);
});
else
channel->onOpen(nullptr);
});
}
int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
return WRAP({
auto channel = getChannel(id);
if (cb)
channel->onClosed([id, cb]() {
if (auto ptr = getUserPointer(id))
cb(*ptr);
});
else
channel->onClosed(nullptr);
});
}
int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
return WRAP({
auto channel = getChannel(id);
if (cb)
channel->onError([id, cb](const string &error) {
if (auto ptr = getUserPointer(id))
cb(error.c_str(), *ptr);
});
else
channel->onError(nullptr);
});
}
int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
return WRAP({
auto channel = getChannel(id);
if (cb)
channel->onMessage(
[id, cb](const binary &b) {
if (auto ptr = getUserPointer(id))
cb(reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
},
[id, cb](const string &s) {
if (auto ptr = getUserPointer(id))
cb(s.c_str(), -int(s.size() + 1), *ptr);
});
else
channel->onMessage(nullptr);
});
}
int rtcSendMessage(int id, const char *data, int size) {
return WRAP({
auto channel = getChannel(id);
if (!data)
throw std::invalid_argument("Unexpected null pointer");
if (size >= 0) {
auto b = reinterpret_cast<const byte *>(data);
channel->send(binary(b, b + size));
return size;
} else {
string str(data);
int len = int(str.size());
channel->send(std::move(str));
return len;
}
});
}
int rtcGetBufferedAmount(int id) {
return WRAP({
auto channel = getChannel(id);
return int(channel->bufferedAmount());
});
}
int rtcSetBufferedAmountLowThreshold(int id, int amount) {
return WRAP({
auto channel = getChannel(id);
channel->setBufferedAmountLowThreshold(size_t(amount));
});
}
int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb) {
return WRAP({
auto channel = getChannel(id);
if (cb)
channel->onBufferedAmountLow([id, cb]() {
if (auto ptr = getUserPointer(id))
cb(*ptr);
});
else
channel->onBufferedAmountLow(nullptr);
});
}
int rtcGetAvailableAmount(int id) {
return WRAP({ return int(getChannel(id)->availableAmount()); });
}
int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
return WRAP({
auto channel = getChannel(id);
if (cb)
channel->onOpen([id, cb]() {
if (auto ptr = getUserPointer(id))
cb(*ptr);
});
else
channel->onOpen(nullptr);
});
}
int rtcReceiveMessage(int id, char *buffer, int *size) {
return WRAP({
auto channel = getChannel(id);
if (!buffer || !size)
throw std::invalid_argument("Unexpected null pointer");
if (auto message = channel->receive())
return std::visit( //
overloaded{ //
[&](const binary &b) {
*size = std::min(*size, int(b.size()));
auto data = reinterpret_cast<const char *>(b.data());
std::copy(data, data + *size, buffer);
return 1;
},
[&](const string &s) {
int len = std::min(*size - 1, int(s.size()));
if (len >= 0) {
std::copy(s.data(), s.data() + len, buffer);
buffer[len] = '\0';
}
*size = -(len + 1);
return 1;
}},
*message);
else
return 0;
});
}
void rtcPreload() { rtc::Preload(); }
void rtcCleanup() { rtc::Cleanup(); }

View File

@ -50,6 +50,9 @@ using std::shared_ptr;
namespace rtc {
std::unordered_set<SctpTransport *> SctpTransport::Instances;
std::shared_mutex SctpTransport::InstancesMutex;
void SctpTransport::Init() {
usrsctp_init(0, &SctpTransport::WriteCallback, nullptr);
usrsctp_sysctl_set_sctp_ecn_enable(0);
@ -61,6 +64,20 @@ void SctpTransport::Init() {
usrsctp_sysctl_set_sctp_rto_initial_default(1 * 1000); // ms
usrsctp_sysctl_set_sctp_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() {
@ -78,6 +95,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
PLOG_DEBUG << "Initializing SCTP transport";
usrsctp_register_address(this);
{
std::unique_lock lock(InstancesMutex);
Instances.insert(this);
}
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
&SctpTransport::SendCallback, 0, this);
if (!mSock)
@ -158,11 +180,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
// The default send and receive window size of usrsctp is 256KiB, which is too small for
// 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));
@ -172,14 +194,21 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
SctpTransport::~SctpTransport() {
stop();
if (mSock)
usrsctp_close(mSock);
close();
usrsctp_deregister_address(this);
{
std::unique_lock lock(InstancesMutex);
Instances.erase(this);
}
}
bool SctpTransport::stop() {
// Transport::stop() will unregister incoming() from the lower layer, therefore we need to make
// sure the thread from lower layers is not blocked in incoming() by the WrittenOnce condition.
mWrittenOnce = true;
mWrittenCondition.notify_all();
if (!Transport::stop())
return false;
@ -190,6 +219,13 @@ bool SctpTransport::stop() {
return true;
}
void SctpTransport::close() {
if (mSock) {
usrsctp_close(mSock);
mSock = nullptr;
}
}
void SctpTransport::connect() {
if (!mSock)
return;
@ -226,9 +262,7 @@ void SctpTransport::shutdown() {
PLOG_WARNING << "SCTP shutdown failed, errno=" << errno;
}
// close() abort the connection when linger is disabled, call it now
usrsctp_close(mSock);
mSock = nullptr;
close();
PLOG_INFO << "SCTP disconnected";
changeState(State::Disconnected);
@ -248,11 +282,11 @@ 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;
}
void SctpTransport::close(unsigned int stream) {
void SctpTransport::closeStream(unsigned int stream) {
send(make_message(0, Message::Reset, uint16_t(stream)));
}
@ -265,7 +299,7 @@ void SctpTransport::incoming(message_ptr message) {
// There could be a race condition here where we receive the remote INIT before the local one is
// 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 +322,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 +344,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 +396,20 @@ bool SctpTransport::trySendMessage(message_ptr message) {
ret = usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0);
}
if (ret >= 0) {
PLOG_VERBOSE << "SCTP sent size=" << message->size();
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
mBytesSent += message->size();
return true;
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
PLOG_VERBOSE << "SCTP sending not possible";
return false;
} else {
if (ret < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
PLOG_VERBOSE << "SCTP sending not possible";
return false;
}
PLOG_ERROR << "SCTP sending failed, errno=" << errno;
throw std::runtime_error("Sending failed, errno=" + std::to_string(errno));
}
PLOG_VERBOSE << "SCTP sent size=" << message->size();
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
mBytesSent += message->size();
return true;
}
void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
@ -385,7 +421,13 @@ void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
else
it->second = amount;
mBufferedAmountCallback(streamId, amount);
mSendMutex.unlock();
try {
mBufferedAmountCallback(streamId, amount);
} catch (const std::exception &e) {
PLOG_DEBUG << "SCTP buffered amount callback: " << e.what();
}
mSendMutex.lock();
}
void SctpTransport::sendReset(uint16_t streamId) {
@ -421,37 +463,51 @@ bool SctpTransport::safeFlush() {
return true;
} catch (const std::exception &e) {
PLOG_ERROR << "SCTP flush: " << e.what();
PLOG_WARNING << "SCTP flush: " << e.what();
return false;
}
}
int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data,
size_t len, struct sctp_rcvinfo info, int flags) {
int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*addr*/,
const byte *data, size_t len, struct sctp_rcvinfo info, int flags) {
try {
PLOG_VERBOSE << "Handle recv, len=" << len;
if (!len)
return -1;
return 0; // Ignore
// This is valid because SCTP_FRAGMENT_INTERLEAVE is set to level 0
// so partial messages and notifications may not be interleaved.
if (flags & MSG_EOR) {
if (!mPartialRecv.empty()) {
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
data = mPartialRecv.data();
len = mPartialRecv.size();
}
// Message/Notification is complete, process it
if (flags & MSG_NOTIFICATION)
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
// therefore partial notifications and messages need to be handled separately.
if (flags & MSG_NOTIFICATION) {
// SCTP event notification
if (flags & MSG_EOR) {
if (!mPartialNotification.empty()) {
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
data = mPartialNotification.data();
len = mPartialNotification.size();
}
// Notification is complete, process it
processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
else
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
mPartialNotification.clear();
} else {
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
}
mPartialRecv.clear();
} else {
// Message/Notification is not complete
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
// SCTP message
if (flags & MSG_EOR) {
if (!mPartialMessage.empty()) {
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
data = mPartialMessage.data();
len = mPartialMessage.size();
}
// Message is complete, process it
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
mPartialMessage.clear();
} else {
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
}
}
} catch (const std::exception &e) {
PLOG_ERROR << "SCTP recv: " << e.what();
return -1;
@ -464,13 +520,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 {
std::unique_lock lock(mWriteMutex);
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();
@ -591,7 +648,7 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
if (flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
for (int i = 0; i < count; ++i) {
uint16_t streamId = reset_event.strreset_stream_list[i];
close(streamId);
closeStream(streamId);
}
}
if (flags & SCTP_STREAM_RESET_INCOMING_SSN) {
@ -649,7 +706,15 @@ int SctpTransport::SendCallback(struct socket *sock, uint32_t sb_free) {
auto sconn = reinterpret_cast<struct sockaddr_conn *>(&paddrinfo.spinfo_address);
void *ptr = sconn->sconn_addr;
return static_cast<SctpTransport *>(ptr)->handleSend(size_t(sb_free));
auto *transport = static_cast<SctpTransport *>(ptr);
// Workaround for sctplab/usrsctp#405: Send callback is invoked on already closed socket
// https://github.com/sctplab/usrsctp/issues/405
std::shared_lock lock(InstancesMutex);
if (Instances.find(transport) == Instances.end())
return -1;
return transport->handleSend(size_t(sb_free));
}
int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) {

View File

@ -28,6 +28,8 @@
#include <functional>
#include <map>
#include <mutex>
#include <shared_mutex>
#include <unordered_set>
#include "usrsctp.h"
@ -46,7 +48,7 @@ public:
bool stop() override;
bool send(message_ptr message) override; // false if buffered
void close(unsigned int stream);
void closeStream(unsigned int stream);
void flush();
// Stats
@ -70,6 +72,7 @@ private:
void connect();
void shutdown();
void close();
void incoming(message_ptr message) override;
bool trySendQueue();
@ -97,9 +100,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;
@ -108,6 +112,9 @@ private:
struct sctp_rcvinfo recv_info, int flags, void *user_data);
static int SendCallback(struct socket *sock, uint32_t sb_free);
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
static std::unordered_set<SctpTransport *> Instances;
static std::shared_mutex InstancesMutex;
};
} // namespace rtc

View File

@ -21,6 +21,7 @@
#if RTC_ENABLE_WEBSOCKET
#include <exception>
#ifndef _WIN32
#include <fcntl.h>
#include <unistd.h>
@ -37,8 +38,8 @@ SelectInterrupter::SelectInterrupter() {
throw std::runtime_error("Failed to create pipe");
::fcntl(pipefd[0], F_SETFL, O_NONBLOCK);
::fcntl(pipefd[1], F_SETFL, O_NONBLOCK);
mPipeOut = pipefd[0]; // read
mPipeIn = pipefd[1]; // write
mPipeOut = pipefd[1]; // read
mPipeIn = pipefd[0]; // write
#endif
}
@ -59,13 +60,10 @@ 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 {
char dummy;
ret = ::read(mPipeIn, &dummy, 1);
} while (ret > 0);
char dummy;
::read(mPipeIn, &dummy, 1);
FD_SET(mPipeIn, &readfds);
return mPipeIn + 1;
#endif
@ -106,6 +104,7 @@ bool TcpTransport::stop() {
}
bool TcpTransport::send(message_ptr message) {
std::unique_lock lock(mSockMutex);
if (state() != State::Connected)
return false;
@ -125,6 +124,7 @@ void TcpTransport::incoming(message_ptr message) {
}
bool TcpTransport::outgoing(message_ptr message) {
// mSockMutex must be locked
// If nothing is pending, try to send directly
// It's safe because if the queue is empty, the thread is not sending
if (mSendQueue.empty() && trySendMessage(message))
@ -150,7 +150,7 @@ void TcpTransport::connect(const string &hostname, const string &service) {
for (auto p = result; p; p = p->ai_next) {
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);
@ -173,6 +173,7 @@ void TcpTransport::connect(const string &hostname, const string &service) {
}
void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
std::unique_lock lock(mSockMutex);
try {
char node[MAX_NUMERICNODE_LEN];
char serv[MAX_NUMERICSERV_LEN];
@ -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) {
@ -247,15 +248,18 @@ void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
}
void TcpTransport::close() {
std::unique_lock lock(mSockMutex);
if (mSock != INVALID_SOCKET) {
PLOG_DEBUG << "Closing TCP socket";
::closesocket(mSock);
mSock = INVALID_SOCKET;
}
changeState(State::Disconnected);
interruptSelect();
}
bool TcpTransport::trySendQueue() {
// mSockMutex must be locked
while (auto next = mSendQueue.peek()) {
auto message = *next;
if (!trySendMessage(message)) {
@ -268,17 +272,18 @@ bool TcpTransport::trySendQueue() {
}
bool TcpTransport::trySendMessage(message_ptr &message) {
// mSockMutex must be locked
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 {
@ -313,13 +318,22 @@ void TcpTransport::runLoop() {
changeState(State::Connected);
while (true) {
std::unique_lock lock(mSockMutex);
if (mSock == INVALID_SOCKET)
break;
fd_set readfds, writefds;
int n = prepareSelect(readfds, writefds);
struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
lock.unlock();
int ret = ::select(n, &readfds, &writefds, NULL, &tv);
lock.lock();
if (mSock == INVALID_SOCKET)
break;
if (ret < 0) {
throw std::runtime_error("Failed to wait on socket");
} else if (ret == 0) {
@ -335,7 +349,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

@ -78,6 +78,7 @@ private:
string mHostname, mService;
socket_t mSock = INVALID_SOCKET;
std::mutex mSockMutex;
std::thread mThread;
SelectInterrupter mInterrupter;
Queue<message_ptr> mSendQueue;

81
src/threadpool.cpp Normal file
View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "threadpool.hpp"
namespace rtc {
ThreadPool &ThreadPool::Instance() {
static ThreadPool instance;
return instance;
}
ThreadPool::~ThreadPool() { join(); }
int ThreadPool::count() const {
std::unique_lock lock(mWorkersMutex);
return mWorkers.size();
}
void ThreadPool::spawn(int count) {
std::unique_lock lock(mWorkersMutex);
mJoining = false;
while (count-- > 0)
mWorkers.emplace_back(std::bind(&ThreadPool::run, this));
}
void ThreadPool::join() {
std::unique_lock lock(mWorkersMutex);
mJoining = true;
mCondition.notify_all();
for (auto &w : mWorkers)
w.join();
mWorkers.clear();
}
void ThreadPool::run() {
while (runOne()) {
}
}
bool ThreadPool::runOne() {
if (auto task = dequeue()) {
try {
task();
} catch (const std::exception &e) {
PLOG_WARNING << "Unhandled exception in task: " << e.what();
}
return true;
}
return false;
}
std::function<void()> ThreadPool::dequeue() {
std::unique_lock lock(mMutex);
mCondition.wait(lock, [this]() { return !mTasks.empty() || mJoining; });
if (mTasks.empty())
return nullptr;
auto task = std::move(mTasks.front());
mTasks.pop();
return task;
}
} // namespace rtc

87
src/threadpool.hpp Normal file
View File

@ -0,0 +1,87 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef RTC_THREADPOOL_H
#define RTC_THREADPOOL_H
#include "include.hpp"
#include "init.hpp"
#include <condition_variable>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <stdexcept>
#include <thread>
#include <vector>
namespace rtc {
template <class F, class... Args>
using invoke_future_t = std::future<std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>>;
class ThreadPool final {
public:
static ThreadPool &Instance();
ThreadPool(const ThreadPool &) = delete;
ThreadPool &operator=(const ThreadPool &) = delete;
ThreadPool(ThreadPool &&) = delete;
ThreadPool &operator=(ThreadPool &&) = delete;
int count() const;
void spawn(int count = 1);
void join();
void run();
bool runOne();
template <class F, class... Args>
auto enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...>;
protected:
ThreadPool() = default;
~ThreadPool();
std::function<void()> dequeue(); // returns null function if joining
std::vector<std::thread> mWorkers;
std::queue<std::function<void()>> mTasks;
std::atomic<bool> mJoining = false;
mutable std::mutex mMutex, mWorkersMutex;
std::condition_variable mCondition;
};
template <class F, class... Args>
auto ThreadPool::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
std::unique_lock lock(mMutex);
using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
auto task = std::make_shared<std::packaged_task<R()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<R> result = task->get_future();
mTasks.emplace([task = std::move(task), token = Init::Token()]() { return (*task)(); });
mCondition.notify_one();
return result;
}
} // namespace rtc
#endif

View File

@ -86,9 +86,8 @@ void init() {
std::lock_guard lock(mutex);
if (!done) {
OPENSSL_init_ssl(0, NULL);
SSL_load_error_strings();
ERR_load_crypto_strings();
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
done = true;
}
}

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>
@ -55,6 +60,8 @@ gnutls_datum_t make_datum(char *data, size_t size);
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/bn.h>
#ifndef BIO_EOF
#define BIO_EOF -1

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_verify(mCtx, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_verify_depth(mCtx, 4);
// SSL_CTX_set_default_verify_paths() does nothing on Windows
#ifndef _WIN32
if (SSL_CTX_set_default_verify_paths(mCtx)) {
#else
if (false) {
#endif
PLOG_INFO << "SSL root CA certificates available, server verification enabled";
SSL_CTX_set_verify(mCtx, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_verify_depth(mCtx, 4);
} else {
PLOG_WARNING << "SSL root CA certificates unavailable, server verification disabled";
SSL_CTX_set_verify(mCtx, SSL_VERIFY_NONE, NULL);
}
if (!(mSsl = SSL_new(mCtx)))
throw std::runtime_error("Failed to create SSL instance");
@ -337,7 +347,7 @@ bool TlsTransport::send(message_ptr message) {
if (message->size() == 0)
return true;
int ret = SSL_write(mSsl, message->data(), message->size());
int ret = SSL_write(mSsl, message->data(), int(message->size()));
if (!openssl::check(mSsl, ret))
return false;
@ -393,7 +403,7 @@ void TlsTransport::runRecvLoop() {
message_ptr message = *next;
if (message->size() > 0)
BIO_write(mInBio, message->data(), message->size()); // Input
BIO_write(mInBio, message->data(), int(message->size())); // Input
else
recv(message); // Pass zero-sized messages through
}

View File

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

View File

@ -41,12 +41,17 @@ public:
virtual ~Transport() {
stop();
if (mLower)
mLower->onRecv(nullptr); // doing it on stop could cause a deadlock
}
virtual bool stop() {
return !mShutdown.exchange(true);
if (mShutdown.exchange(true))
return false;
// We don't want incoming() to be called by the lower layer anymore
if (mLower)
mLower->onRecv(nullptr);
return true;
}
void registerIncoming() {

View File

@ -18,8 +18,9 @@
#if RTC_ENABLE_WEBSOCKET
#include "include.hpp"
#include "websocket.hpp"
#include "include.hpp"
#include "threadpool.hpp"
#include "tcptransport.hpp"
#include "tlstransport.hpp"
@ -301,21 +302,18 @@ void WebSocket::closeTransports() {
auto ws = std::atomic_exchange(&mWsTransport, decltype(mWsTransport)(nullptr));
auto tls = std::atomic_exchange(&mTlsTransport, decltype(mTlsTransport)(nullptr));
auto tcp = std::atomic_exchange(&mTcpTransport, decltype(mTcpTransport)(nullptr));
if (ws || tls || tcp) {
std::thread t([ws, tls, tcp, token = mInitToken]() mutable {
if (ws)
ws->stop();
if (tls)
tls->stop();
if (tcp)
tcp->stop();
ThreadPool::Instance().enqueue([ws, tls, tcp]() mutable {
if (ws)
ws->stop();
if (tls)
tls->stop();
if (tcp)
tcp->stop();
ws.reset();
tls.reset();
tcp.reset();
});
t.detach();
}
ws.reset();
tls.reset();
tcp.reset();
});
}
} // 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;
@ -201,7 +200,7 @@ int test_capi_main() {
// You may call rtcCleanup() when finished to free static resources
rtcCleanup();
sleep(1);
sleep(2);
printf("Success\n");
return 0;

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())
@ -142,7 +150,7 @@ void test_connectivity() {
// You may call rtc::Cleanup() when finished to free static resources
rtc::Cleanup();
this_thread::sleep_for(1s);
this_thread::sleep_for(2s);
cout << "Success" << endl;
}

View File

@ -16,13 +16,28 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <chrono>
#include <iostream>
#include <thread>
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 +66,16 @@ 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;
std::this_thread::sleep_for(2s);
return -1;
}
std::this_thread::sleep_for(2s);
return 0;
}

View File

@ -72,7 +72,7 @@ void test_websocket() {
throw runtime_error("Expected message not received");
ws->close();
this_thread::sleep_for(1s);
this_thread::sleep_for(2s);
// You may call rtc::Cleanup() when finished to free static resources
rtc::Cleanup();