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: on:
push: push:
branches: branches:
@ -7,7 +7,7 @@ on:
branches: branches:
- master - master
jobs: jobs:
build-ubuntu: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -31,9 +31,6 @@ jobs:
run: git submodule update --init --recursive run: git submodule update --init --recursive
- name: cmake - name: cmake
run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=1 run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=1
env:
# hack to bypass EPERM issue on sendto()
CFLAGS: -DJUICE_ENABLE_ADDRS_LOCALHOST
- name: make - name: make
run: (cd build; make -j2) run: (cd build; make -j2)
- name: test - name: test

View File

@ -1,4 +1,4 @@
name: Build and test with libnice name: Build with libnice
on: on:
push: push:
branches: branches:
@ -7,7 +7,7 @@ on:
branches: branches:
- master - master
jobs: jobs:
build-ubuntu: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -1,4 +1,4 @@
name: Build and test with OpenSSL name: Build with OpenSSL
on: on:
push: push:
branches: branches:
@ -7,7 +7,7 @@ on:
branches: branches:
- master - master
jobs: jobs:
build-ubuntu: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -34,9 +34,25 @@ jobs:
env: env:
OPENSSL_ROOT_DIR: /usr/local/opt/openssl OPENSSL_ROOT_DIR: /usr/local/opt/openssl
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
# hack to bypass EPERM issue on sendto()
CFLAGS: -DJUICE_ENABLE_ADDRS_LOCALHOST
- name: make - name: make
run: (cd build; make -j2) run: (cd build; make -j2)
- name: test - name: test
run: ./build/tests run: ./build/tests
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: ilammy/msvc-dev-cmd@v1
- name: install packages
run: choco install openssl
- name: submodules
run: git submodule update --init --recursive
- name: cmake
run: cmake -B build -G "NMake Makefiles" -DUSE_JUICE=1 -DUSE_GNUTLS=0
- name: nmake
run: |
cd build
nmake
- name: test
run: build/tests.exe

2
.gitmodules vendored
View File

@ -1,6 +1,6 @@
[submodule "deps/plog"] [submodule "deps/plog"]
path = deps/plog path = deps/plog
url = https://github.com/SergiusTheBest/plog url = https://github.com/paullouisageneau/plog
[submodule "usrsctp"] [submodule "usrsctp"]
path = deps/usrsctp path = deps/usrsctp
url = https://github.com/sctplab/usrsctp.git url = https://github.com/sctplab/usrsctp.git

View File

@ -1,12 +1,14 @@
cmake_minimum_required(VERSION 3.7) cmake_minimum_required(VERSION 3.7)
project(libdatachannel project(libdatachannel
DESCRIPTION "WebRTC DataChannels Library" DESCRIPTION "WebRTC Data Channels Library"
VERSION 0.6.0 VERSION 0.6.5
LANGUAGES CXX) LANGUAGES CXX)
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF) option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
option(USE_JUICE "Use libjuice instead of libnice" OFF) option(USE_JUICE "Use libjuice instead of libnice" OFF)
option(RTC_ENABLE_WEBSOCKET "Build WebSocket support" ON) option(USE_SRTP "Enable SRTP for media support" OFF)
option(NO_WEBSOCKET "Disable WebSocket support" OFF)
option(NO_EXAMPLES "Disable examples" OFF)
if(USE_GNUTLS) if(USE_GNUTLS)
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON) option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
@ -21,6 +23,8 @@ if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN) add_definitions(-DWIN32_LEAN_AND_MEAN)
if (MSVC) if (MSVC)
add_definitions(-DNOMINMAX) add_definitions(-DNOMINMAX)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
endif() endif()
endif() endif()
@ -40,6 +44,8 @@ set(LIBDATACHANNEL_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/processor.cpp
) )
set(LIBDATACHANNEL_WEBSOCKET_SOURCES set(LIBDATACHANNEL_WEBSOCKET_SOURCES
@ -74,12 +80,12 @@ set(TESTS_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/websocket.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/websocket.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/benchmark.cpp
) )
set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_package(SRTP)
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW) set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
add_subdirectory(deps/plog) add_subdirectory(deps/plog)
@ -96,7 +102,14 @@ endif()
add_library(Usrsctp::Usrsctp ALIAS usrsctp) add_library(Usrsctp::Usrsctp ALIAS usrsctp)
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static) add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
if (RTC_ENABLE_WEBSOCKET) if (NO_WEBSOCKET)
add_library(datachannel SHARED
${LIBDATACHANNEL_SOURCES})
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
${LIBDATACHANNEL_SOURCES})
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
else()
add_library(datachannel SHARED add_library(datachannel SHARED
${LIBDATACHANNEL_SOURCES} ${LIBDATACHANNEL_SOURCES}
${LIBDATACHANNEL_WEBSOCKET_SOURCES}) ${LIBDATACHANNEL_WEBSOCKET_SOURCES})
@ -105,13 +118,6 @@ if (RTC_ENABLE_WEBSOCKET)
${LIBDATACHANNEL_WEBSOCKET_SOURCES}) ${LIBDATACHANNEL_WEBSOCKET_SOURCES})
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1) target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=1)
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1) target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=1)
else()
add_library(datachannel SHARED
${LIBDATACHANNEL_SOURCES})
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
${LIBDATACHANNEL_SOURCES})
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_WEBSOCKET=0)
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_WEBSOCKET=0)
endif() endif()
set_target_properties(datachannel PROPERTIES set_target_properties(datachannel PROPERTIES
@ -138,7 +144,8 @@ if(WIN32)
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2 target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
endif() endif()
if(SRTP_FOUND) if(USE_SRTP)
find_package(SRTP REQUIRED)
if(NOT TARGET SRTP::SRTP) if(NOT TARGET SRTP::SRTP)
add_library(SRTP::SRTP UNKNOWN IMPORTED) add_library(SRTP::SRTP UNKNOWN IMPORTED)
set_target_properties(SRTP::SRTP PROPERTIES set_target_properties(SRTP::SRTP PROPERTIES
@ -160,10 +167,10 @@ if (USE_GNUTLS)
if(NOT TARGET GnuTLS::GnuTLS) if(NOT TARGET GnuTLS::GnuTLS)
add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED) add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED)
set_target_properties(GnuTLS::GnuTLS PROPERTIES set_target_properties(GnuTLS::GnuTLS PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${GNUTLS_INCLUDE_DIRS} INTERFACE_INCLUDE_DIRECTORIES "${GNUTLS_INCLUDE_DIRS}"
INTERFACE_COMPILE_DEFINITIONS ${GNUTLS_DEFINITIONS} INTERFACE_COMPILE_DEFINITIONS "${GNUTLS_DEFINITIONS}"
IMPORTED_LINK_INTERFACE_LANGUAGES C IMPORTED_LINK_INTERFACE_LANGUAGES C
IMPORTED_LOCATION ${GNUTLS_LIBRARIES}) IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
endif() endif()
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1) target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1) target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
@ -204,12 +211,32 @@ set_target_properties(datachannel-tests PROPERTIES
CXX_STANDARD 17) CXX_STANDARD 17)
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests) set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(datachannel-tests datachannel nlohmann_json::nlohmann_json) if(WIN32)
target_link_libraries(datachannel-tests datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-tests datachannel)
endif()
# Benchmark
add_executable(datachannel-benchmark test/benchmark.cpp)
set_target_properties(datachannel-benchmark PROPERTIES
VERSION ${PROJECT_VERSION}
CXX_STANDARD 17)
set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
if(WIN32)
target_link_libraries(datachannel-benchmark datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-benchmark datachannel)
endif()
# Examples # Examples
set(JSON_BuildTests OFF CACHE INTERNAL "") if(NOT NO_EXAMPLES)
add_subdirectory(deps/json) set(JSON_BuildTests OFF CACHE INTERNAL "")
add_subdirectory(examples/client) add_subdirectory(deps/json)
add_subdirectory(examples/copy-paste) add_subdirectory(examples/client)
add_subdirectory(examples/copy-paste-capi) add_subdirectory(examples/copy-paste)
add_subdirectory(examples/copy-paste-capi)
endif()

216
Jamfile
View File

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

View File

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

View File

@ -1,7 +1,6 @@
# libdatachannel - C/C++ WebRTC Data Channels # libdatachannel - C/C++ WebRTC Data Channels
libdatachannel is a standalone implementation of WebRTC Data Channels and WebSockets in C++17 with C bindings for POSIX platforms (including Linux and Apple macOS) and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC and WebSocket browser API, in order to ease the design of cross-environment applications. libdatachannel is a standalone implementation of WebRTC Data Channels and WebSockets in C++17 with C bindings for POSIX platforms (including Linux and Apple macOS) and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. The interface consists of simplified versions of the JavaScript WebRTC and WebSocket APIs present in browsers, in order to ease the design of cross-environment applications.
It can be compiled with multiple backends: It can be compiled with multiple backends:
- The security layer can be provided through [GnuTLS](https://www.gnutls.org/) or [OpenSSL](https://www.openssl.org/). - The security layer can be provided through [GnuTLS](https://www.gnutls.org/) or [OpenSSL](https://www.openssl.org/).
- The connectivity for WebRTC can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice). - The connectivity for WebRTC can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice).
@ -26,6 +25,7 @@ Protocol stack:
Features: Features:
- Full IPv6 support - Full IPv6 support
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21)) - Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
- JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26))
- Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04)) - Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
- TURN relaying ([RFC5766](https://tools.ietf.org/html/rfc5766)) with [libnice](https://github.com/libnice/libnice) as ICE backend - TURN relaying ([RFC5766](https://tools.ietf.org/html/rfc5766)) with [libnice](https://github.com/libnice/libnice) as ICE backend
- SRTP media transport ([RFC3711](https://tools.ietf.org/html/rfc3711)) with [libSRTP](https://github.com/cisco/libsrtp) - SRTP media transport ([RFC3711](https://tools.ietf.org/html/rfc3711)) with [libSRTP](https://github.com/cisco/libsrtp)
@ -44,31 +44,55 @@ Features:
## Dependencies ## Dependencies
Dependencies:
- GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/ - GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/
Optional: Optional dependencies:
- libnice: https://nice.freedesktop.org/ (substituable with libjuice) - libnice: https://nice.freedesktop.org/ (substituable with libjuice)
- libSRTP: https://github.com/cisco/libsrtp - libSRTP: https://github.com/cisco/libsrtp (only necessary for media transport)
Submodules: Submodules:
- libjuice: https://github.com/paullouisageneau/libjuice - libjuice: https://github.com/paullouisageneau/libjuice
- usrsctp: https://github.com/sctplab/usrsctp - usrsctp: https://github.com/sctplab/usrsctp
## Building ## Building
### Building with CMake (preferred)
### Clone repository and submodules
```bash ```bash
$ git clone https://github.com/paullouisageneau/libdatachannel.git
$ cd libdatachannel
$ git submodule update --init --recursive $ git submodule update --init --recursive
$ mkdir build
$ cd build
$ cmake -DUSE_JUICE=1 -DUSE_GNUTLS=1 ..
$ make
``` ```
### Building directly with Make ### Building with CMake
The CMake library targets `libdatachannel` and `libdatachannel-static` respectively correspond to the shared and static libraries. On Windows, the DLL resulting from the shared library build only exposes the C API, use the static library for the C++ API. The default target will build tests and examples.
#### POSIX-compliant operating systems (including Linux and Apple macOS)
```bash
$ cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=1
$ cd build
$ make -j2
```
#### Microsoft Windows with MinGW cross-compilation
```bash
$ cmake -B build -DUSE_JUICE=1 -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
$ cd build
$ make -j2
```
#### Microsoft Windows with Microsoft Visual C++
```bash
$ cmake -B build -G "NMake Makefiles" -DUSE_JUICE=1
$ cd build
$ nmake
```
### Building directly with Make (Linux only)
```bash ```bash
$ git submodule update --init --recursive
$ make USE_JUICE=1 USE_GNUTLS=1 $ make USE_JUICE=1 USE_GNUTLS=1
``` ```

2
deps/libjuice vendored

2
deps/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 set_target_properties(datachannel-client PROPERTIES
CXX_STANDARD 17 CXX_STANDARD 17
OUTPUT_NAME client) OUTPUT_NAME client)
if(WIN32)
target_link_libraries(datachannel-client datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-client datachannel)
endif()
target_link_libraries(datachannel-client datachannel nlohmann_json) target_link_libraries(datachannel-client datachannel nlohmann_json)

View File

@ -212,7 +212,7 @@ string randomId(size_t length) {
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
string id(length, '0'); string id(length, '0');
default_random_engine rng(random_device{}()); default_random_engine rng(random_device{}());
uniform_int_distribution<int> dist(0, characters.size() - 1); uniform_int_distribution<int> dist(0, int(characters.size() - 1));
generate(id.begin(), id.end(), [&]() { return characters.at(dist(rng)); }); generate(id.begin(), id.end(), [&]() { return characters.at(dist(rng)); });
return id; return id;
} }

View File

@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.5.1)
project(offerer C) project(offerer C)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "-Wall -g -O2")
add_executable(datachannel-copy-paste-capi-offerer offerer.c) add_executable(datachannel-copy-paste-capi-offerer offerer.c)
set_target_properties(datachannel-copy-paste-capi-offerer PROPERTIES set_target_properties(datachannel-copy-paste-capi-offerer PROPERTIES
@ -13,3 +12,4 @@ add_executable(datachannel-copy-paste-capi-answerer answerer.c)
set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
OUTPUT_NAME answerer) OUTPUT_NAME answerer)
target_link_libraries(datachannel-copy-paste-capi-answerer datachannel) target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)

View File

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

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

View File

@ -4,11 +4,19 @@ add_executable(datachannel-copy-paste-offerer offerer.cpp)
set_target_properties(datachannel-copy-paste-offerer PROPERTIES set_target_properties(datachannel-copy-paste-offerer PROPERTIES
CXX_STANDARD 17 CXX_STANDARD 17
OUTPUT_NAME offerer) OUTPUT_NAME offerer)
target_link_libraries(datachannel-copy-paste-offerer datachannel) if(WIN32)
target_link_libraries(datachannel-copy-paste-offerer datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-copy-paste-offerer datachannel)
endif()
add_executable(datachannel-copy-paste-answerer answerer.cpp) add_executable(datachannel-copy-paste-answerer answerer.cpp)
set_target_properties(datachannel-copy-paste-answerer PROPERTIES set_target_properties(datachannel-copy-paste-answerer PROPERTIES
CXX_STANDARD 17 CXX_STANDARD 17
OUTPUT_NAME answerer) OUTPUT_NAME answerer)
target_link_libraries(datachannel-copy-paste-answerer datachannel) if(WIN32)
target_link_libraries(datachannel-copy-paste-answerer datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-copy-paste-answerer datachannel)
endif()

View File

@ -108,8 +108,8 @@ template <typename Iterator> bool DataChannel::sendBuffer(Iterator first, Iterat
auto message = std::make_shared<Message>(size); auto message = std::make_shared<Message>(size);
auto pos = message->begin(); auto pos = message->begin();
for (Iterator it = first; it != last; ++it) { for (Iterator it = first; it != last; ++it) {
auto [bytes, size] = to_bytes(*it); auto [bytes, len] = to_bytes(*it);
pos = std::copy(bytes, bytes + size, pos); pos = std::copy(bytes, bytes + len, pos);
} }
return outgoing(message); return outgoing(message);
} }

View File

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

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 DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
const int THREADPOOL_SIZE = 4; // Number of threads in the global thread pool
// overloaded helper // overloaded helper
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>; template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;

View File

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

View File

@ -41,6 +41,7 @@
namespace rtc { namespace rtc {
class Certificate; class Certificate;
class Processor;
class IceTransport; class IceTransport;
class DtlsTransport; class DtlsTransport;
class SctpTransport; class SctpTransport;
@ -101,7 +102,7 @@ public:
// Media // Media
bool hasMedia() const; bool hasMedia() const;
void sendMedia(const binary &packet); void sendMedia(const binary &packet);
void send(const byte *packet, size_t size); void sendMedia(const byte *packet, size_t size);
void onMedia(std::function<void(const binary &packet)> callback); void onMedia(std::function<void(const binary &packet)> callback);
@ -139,13 +140,13 @@ private:
void outgoingMedia(message_ptr message); void outgoingMedia(message_ptr message);
const init_token mInitToken = Init::Token();
const Configuration mConfig; const Configuration mConfig;
const future_certificate_ptr mCertificate; const future_certificate_ptr mCertificate;
const std::unique_ptr<Processor> mProcessor;
init_token mInitToken = Init::Token();
std::optional<Description> mLocalDescription, mRemoteDescription; std::optional<Description> mLocalDescription, mRemoteDescription;
mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex; mutable std::mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
std::shared_ptr<IceTransport> mIceTransport; std::shared_ptr<IceTransport> mIceTransport;
std::shared_ptr<DtlsTransport> mDtlsTransport; std::shared_ptr<DtlsTransport> mDtlsTransport;

View File

@ -23,9 +23,11 @@
extern "C" { extern "C" {
#endif #endif
#include <stdint.h> #ifdef _WIN32
#define RTC_EXPORT __declspec(dllexport)
// libdatachannel C API #else
#define RTC_EXPORT
#endif
#ifndef RTC_ENABLE_MEDIA #ifndef RTC_ENABLE_MEDIA
#define RTC_ENABLE_MEDIA 1 #define RTC_ENABLE_MEDIA 1
@ -35,6 +37,10 @@ extern "C" {
#define RTC_ENABLE_WEBSOCKET 1 #define RTC_ENABLE_WEBSOCKET 1
#endif #endif
#include <stdint.h>
// libdatachannel C API
typedef enum { typedef enum {
RTC_NEW = 0, RTC_NEW = 0,
RTC_CONNECTING = 1, RTC_CONNECTING = 1,
@ -60,6 +66,10 @@ typedef enum { // Don't change, it must match plog severity
RTC_LOG_VERBOSE = 6 RTC_LOG_VERBOSE = 6
} rtcLogLevel; } rtcLogLevel;
#define RTC_ERR_SUCCESS 0
#define RTC_ERR_INVALID -1 // invalid argument
#define RTC_ERR_FAILURE -2 // runtime error
typedef struct { typedef struct {
const char **iceServers; const char **iceServers;
int iceServersCount; int iceServersCount;
@ -67,70 +77,72 @@ typedef struct {
uint16_t portRangeEnd; uint16_t portRangeEnd;
} rtcConfiguration; } rtcConfiguration;
typedef void (*dataChannelCallbackFunc)(int dc, void *ptr); typedef void (*rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
typedef void (*descriptionCallbackFunc)(const char *sdp, const char *type, void *ptr); typedef void (*rtcDataChannelCallbackFunc)(int dc, void *ptr);
typedef void (*candidateCallbackFunc)(const char *cand, const char *mid, void *ptr); typedef void (*rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
typedef void (*stateChangeCallbackFunc)(rtcState state, void *ptr); typedef void (*rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
typedef void (*gatheringStateCallbackFunc)(rtcGatheringState state, void *ptr); typedef void (*rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
typedef void (*openCallbackFunc)(void *ptr); typedef void (*rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
typedef void (*closedCallbackFunc)(void *ptr); typedef void (*rtcOpenCallbackFunc)(void *ptr);
typedef void (*errorCallbackFunc)(const char *error, void *ptr); typedef void (*rtcClosedCallbackFunc)(void *ptr);
typedef void (*messageCallbackFunc)(const char *message, int size, void *ptr); typedef void (*rtcErrorCallbackFunc)(const char *error, void *ptr);
typedef void (*bufferedAmountLowCallbackFunc)(void *ptr); typedef void (*rtcMessageCallbackFunc)(const char *message, int size, void *ptr);
typedef void (*availableCallbackFunc)(void *ptr); typedef void (*rtcBufferedAmountLowCallbackFunc)(void *ptr);
typedef void (*rtcAvailableCallbackFunc)(void *ptr);
// Log // Log
void rtcInitLogger(rtcLogLevel level); RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb); // NULL cb to log to stdout
// User pointer // User pointer
void rtcSetUserPointer(int id, void *ptr); RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
// PeerConnection // PeerConnection
int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id RTC_EXPORT int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id
int rtcDeletePeerConnection(int pc); RTC_EXPORT int rtcDeletePeerConnection(int pc);
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb); RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb); RTC_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb);
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb); RTC_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb); RTC_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb); RTC_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type); RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid); RTC_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
int rtcGetLocalAddress(int pc, char *buffer, int size); RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
int rtcGetRemoteAddress(int pc, char *buffer, int size); RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
// DataChannel // DataChannel
int rtcCreateDataChannel(int pc, const char *label); // returns dc id RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
int rtcDeleteDataChannel(int dc); RTC_EXPORT int rtcDeleteDataChannel(int dc);
int rtcGetDataChannelLabel(int dc, char *buffer, int size); RTC_EXPORT int rtcGetDataChannelLabel(int dc, char *buffer, int size);
// WebSocket // WebSocket
#if RTC_ENABLE_WEBSOCKET #if RTC_ENABLE_WEBSOCKET
int rtcCreateWebSocket(const char *url); // returns ws id RTC_EXPORT int rtcCreateWebSocket(const char *url); // returns ws id
int rtcDeleteWebsocket(int ws); RTC_EXPORT int rtcDeleteWebsocket(int ws);
#endif #endif
// DataChannel and WebSocket common API // DataChannel and WebSocket common API
int rtcSetOpenCallback(int id, openCallbackFunc cb); RTC_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb);
int rtcSetClosedCallback(int id, closedCallbackFunc cb); RTC_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb);
int rtcSetErrorCallback(int id, errorCallbackFunc cb); RTC_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb);
int rtcSetMessageCallback(int id, messageCallbackFunc cb); RTC_EXPORT int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb);
int rtcSendMessage(int id, const char *data, int size); RTC_EXPORT int rtcSendMessage(int id, const char *data, int size);
int rtcGetBufferedAmount(int id); // total size buffered to send RTC_EXPORT int rtcGetBufferedAmount(int id); // total size buffered to send
int rtcSetBufferedAmountLowThreshold(int id, int amount); RTC_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount);
int rtcSetBufferedAmountLowCallback(int id, bufferedAmountLowCallbackFunc cb); RTC_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb);
// DataChannel and WebSocket common extended API // DataChannel and WebSocket common extended API
int rtcGetAvailableAmount(int id); // total size available to receive RTC_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive
int rtcSetAvailableCallback(int id, availableCallbackFunc cb); RTC_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb);
int rtcReceiveMessage(int id, char *buffer, int *size); RTC_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size);
// Cleanup // Optional preload and cleanup
void rtcCleanup(); RTC_EXPORT void rtcPreload(void);
RTC_EXPORT void rtcCleanup(void);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View File

@ -42,7 +42,7 @@ string to_base64(const binary &data) {
i += 3; i += 3;
} }
int left = data.size() - i; int left = int(data.size() - i);
if (left) { if (left) {
auto d0 = to_integer<uint8_t>(data[i]); auto d0 = to_integer<uint8_t>(data[i]);
out += tab[d0 >> 2]; out += tab[d0 >> 2];

View File

@ -28,6 +28,7 @@
#else #else
#include <netdb.h> #include <netdb.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h>
#endif #endif
#include <sys/types.h> #include <sys/types.h>
@ -98,8 +99,8 @@ bool Candidate::resolve(ResolveMode mode) {
// Rewrite the candidate // Rewrite the candidate
char nodebuffer[MAX_NUMERICNODE_LEN]; char nodebuffer[MAX_NUMERICNODE_LEN];
char servbuffer[MAX_NUMERICSERV_LEN]; char servbuffer[MAX_NUMERICSERV_LEN];
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN, if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
servbuffer, MAX_NUMERICSERV_LEN, MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
NI_NUMERICHOST | NI_NUMERICSERV) == 0) { NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
const char sp{' '}; const char sp{' '};
std::ostringstream oss; std::ostringstream oss;
@ -107,8 +108,9 @@ bool Candidate::resolve(ResolveMode mode) {
oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type; oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
oss << left; oss << left;
mCandidate = oss.str(); mCandidate = oss.str();
mIsResolved = true;
PLOG_VERBOSE << "Resolved candidate: " << mCandidate; PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
return mIsResolved = true; break;
} }
} }
} }
@ -116,7 +118,7 @@ bool Candidate::resolve(ResolveMode mode) {
freeaddrinfo(result); freeaddrinfo(result);
} }
return false; return mIsResolved;
} }
bool Candidate::isResolved() const { return mIsResolved; } bool Candidate::isResolved() const { return mIsResolved; }

View File

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

View File

@ -61,7 +61,7 @@ string make_fingerprint(X509 *x509);
using certificate_ptr = std::shared_ptr<Certificate>; using certificate_ptr = std::shared_ptr<Certificate>;
using future_certificate_ptr = std::shared_future<certificate_ptr>; using future_certificate_ptr = std::shared_future<certificate_ptr>;
future_certificate_ptr make_certificate(string commonName); // cached future_certificate_ptr make_certificate(string commonName = "libdatachannel"); // cached
void CleanupCertificateCache(); void CleanupCertificateCache();

View File

@ -96,7 +96,7 @@ void DataChannel::close() {
mIsClosed = true; mIsClosed = true;
if (mIsOpen.exchange(false)) if (mIsOpen.exchange(false))
if (auto transport = mSctpTransport.lock()) if (auto transport = mSctpTransport.lock())
transport->close(mStream); transport->closeStream(mStream);
mSctpTransport.reset(); mSctpTransport.reset();
resetCallbacks(); resetCallbacks();
@ -186,8 +186,8 @@ void DataChannel::open(shared_ptr<SctpTransport> transport) {
open.channelType = mReliability->type; open.channelType = mReliability->type;
open.priority = htons(0); open.priority = htons(0);
open.reliabilityParameter = htonl(reliabilityParameter); open.reliabilityParameter = htonl(reliabilityParameter);
open.labelLength = htons(mLabel.size()); open.labelLength = htons(uint16_t(mLabel.size()));
open.protocolLength = htons(mProtocol.size()); open.protocolLength = htons(uint16_t(mProtocol.size()));
auto end = reinterpret_cast<char *>(buffer.data() + sizeof(OpenMessage)); auto end = reinterpret_cast<char *>(buffer.data() + sizeof(OpenMessage));
std::copy(mLabel.begin(), mLabel.end(), end); std::copy(mLabel.begin(), mLabel.end(), end);

View File

@ -26,6 +26,7 @@
using std::size_t; using std::size_t;
using std::string; using std::string;
using std::chrono::system_clock;
namespace { namespace {
@ -54,7 +55,7 @@ Description::Description(const string &sdp, Type type, Role role)
mData.mid = "data"; mData.mid = "data";
hintType(type); hintType(type);
auto seed = std::chrono::system_clock::now().time_since_epoch().count(); auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
std::default_random_engine generator(seed); std::default_random_engine generator(seed);
std::uniform_int_distribution<uint32_t> uniform; std::uniform_int_distribution<uint32_t> uniform;
mSessionId = std::to_string(uniform(generator)); mSessionId = std::to_string(uniform(generator));
@ -62,6 +63,7 @@ Description::Description(const string &sdp, Type type, Role role)
std::istringstream ss(sdp); std::istringstream ss(sdp);
std::optional<Media> currentMedia; std::optional<Media> currentMedia;
int mlineIndex = 0;
bool finished; bool finished;
do { do {
string line; string line;
@ -75,7 +77,9 @@ Description::Description(const string &sdp, Type type, Role role)
if (currentMedia->type == "application") if (currentMedia->type == "application")
mData.mid = currentMedia->mid; mData.mid = currentMedia->mid;
else else
mMedia.emplace(currentMedia->mid, std::move(*currentMedia)); mMedia.emplace(mlineIndex, std::move(*currentMedia));
++mlineIndex;
} else if (line.find(" ICE/SDP") != string::npos) { } else if (line.find(" ICE/SDP") != string::npos) {
PLOG_WARNING << "SDP \"m=\" line has no corresponding mid, ignoring"; PLOG_WARNING << "SDP \"m=\" line has no corresponding mid, ignoring";
@ -112,7 +116,8 @@ Description::Description(const string &sdp, Type type, Role role)
if (match_prefix(value, "sha-256 ")) { if (match_prefix(value, "sha-256 ")) {
mFingerprint = value.substr(8); mFingerprint = value.substr(8);
std::transform(mFingerprint->begin(), mFingerprint->end(), std::transform(mFingerprint->begin(), mFingerprint->end(),
mFingerprint->begin(), [](char c) { return std::toupper(c); }); mFingerprint->begin(),
[](char c) { return char(std::toupper(c)); });
} else { } else {
PLOG_WARNING << "Unknown SDP fingerprint type: " << value; PLOG_WARNING << "Unknown SDP fingerprint type: " << value;
} }
@ -161,6 +166,8 @@ void Description::hintType(Type type) {
} }
} }
void Description::setDataMid(string mid) { mData.mid = mid; }
void Description::setFingerprint(string fingerprint) { void Description::setFingerprint(string fingerprint) {
mFingerprint.emplace(std::move(fingerprint)); mFingerprint.emplace(std::move(fingerprint));
} }
@ -185,11 +192,8 @@ std::vector<Candidate> Description::extractCandidates() {
bool Description::hasMedia() const { return !mMedia.empty(); } bool Description::hasMedia() const { return !mMedia.empty(); }
void Description::addMedia(const Description &source) { void Description::addMedia(const Description &source) {
for (auto [mid, media] : source.mMedia) for (auto p : source.mMedia)
if (mid != mData.mid) mMedia.emplace(p);
mMedia.emplace(mid, media);
else
PLOG_WARNING << "Media mid \"" << mid << "\" is the same as data mid, ignoring";
} }
Description::operator string() const { return generateSdp("\r\n"); } Description::operator string() const { return generateSdp("\r\n"); }
@ -210,47 +214,60 @@ string Description::generateSdp(const string &eol) const {
// see Negotiating Media Multiplexing Using the Session Description Protocol // see Negotiating Media Multiplexing Using the Session Description Protocol
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54 // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54
sdp << "a=group:BUNDLE"; sdp << "a=group:BUNDLE";
for (const auto &[mid, _] : mMedia) for (int i = 0; i < int(mMedia.size() + 1); ++i)
sdp << " " << mid; if (auto it = mMedia.find(i); it != mMedia.end())
sdp << " " << mData.mid << eol; sdp << ' ' << it->second.mid;
else
sdp << ' ' << mData.mid;
sdp << eol;
// Data sdp << "a=msid-semantic: WMS" << eol;
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;
// Non-data media // Non-data media
if (!mMedia.empty()) { if (!mMedia.empty()) {
// Lip-sync // Lip-sync
sdp << "a=group:LS"; sdp << "a=group:LS";
for (const auto &[mid, _] : mMedia) for (const auto &p : mMedia)
sdp << " " << mid; sdp << " " << p.second.mid;
sdp << eol; sdp << eol;
}
// Descriptions and attributes // Descriptions and attributes
for (const auto &[_, media] : mMedia) { 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 << "m=" << media.type << ' ' << 0 << ' ' << media.description << eol;
sdp << "c=IN IP4 0.0.0.0" << eol; sdp << "c=IN IP4 0.0.0.0" << eol;
sdp << "a=bundle-only" << eol; sdp << "a=bundle-only" << eol;
sdp << "a=mid:" << media.mid << eol; sdp << "a=mid:" << media.mid << eol;
for (const auto &attr : media.attributes) for (const auto &attr : media.attributes)
sdp << "a=" << attr << eol; 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 // Common
sdp << "a=ice-options:trickle" << eol; if (!mEnded)
sdp << "a=ice-options:trickle" << eol;
sdp << "a=ice-ufrag:" << mIceUfrag << eol; sdp << "a=ice-ufrag:" << mIceUfrag << eol;
sdp << "a=ice-pwd:" << mIcePwd << eol; sdp << "a=ice-pwd:" << mIcePwd << eol;
sdp << "a=setup:" << roleToString(mRole) << eol; sdp << "a=setup:" << roleToString(mRole) << eol;
sdp << "a=dtls-id:1" << eol; sdp << "a=tls-id:1" << eol;
if (mFingerprint) if (mFingerprint)
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol; sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;

View File

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

View File

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

View File

@ -24,6 +24,14 @@
#include <exception> #include <exception>
#include <iostream> #include <iostream>
#if !USE_GNUTLS
#ifdef _WIN32
#include <winsock2.h> // for timeval
#else
#include <sys/time.h> // for timeval
#endif
#endif
using namespace std::chrono; using namespace std::chrono;
using std::shared_ptr; using std::shared_ptr;
@ -36,12 +44,10 @@ namespace rtc {
#if USE_GNUTLS #if USE_GNUTLS
void DtlsTransport::Init() { void DtlsTransport::Init() {
// Nothing to do gnutls_global_init(); // optional
} }
void DtlsTransport::Cleanup() { void DtlsTransport::Cleanup() { gnutls_global_deinit(); }
// Nothing to do
}
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate, DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
verifier_callback verifierCallback, state_callback stateChangeCallback) verifier_callback verifierCallback, state_callback stateChangeCallback)
@ -79,6 +85,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr cer
gnutls_transport_set_pull_function(mSession, ReadCallback); gnutls_transport_set_pull_function(mSession, ReadCallback);
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback); gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
postCreation();
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this); mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
registerIncoming(); registerIncoming();
@ -131,6 +139,10 @@ void DtlsTransport::incoming(message_ptr message) {
mIncomingQueue.push(message); mIncomingQueue.push(message);
} }
void DtlsTransport::postCreation() {
// Dummy
}
void DtlsTransport::postHandshake() { void DtlsTransport::postHandshake() {
// Dummy // Dummy
} }
@ -302,7 +314,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)"; PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
try { try {
if (!(mCtx = SSL_CTX_new(DTLS_method()))) mCtx = SSL_CTX_new(DTLS_method());
if (!mCtx)
throw std::runtime_error("Failed to create SSL context"); throw std::runtime_error("Failed to create SSL context");
openssl::check(SSL_CTX_set_cipher_list(mCtx, "ALL:!LOW:!EXP:!RC4:!MD5:@STRENGTH"), openssl::check(SSL_CTX_set_cipher_list(mCtx, "ALL:!LOW:!EXP:!RC4:!MD5:@STRENGTH"),
@ -326,7 +339,8 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
openssl::check(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed"); openssl::check(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed");
if (!(mSsl = SSL_new(mCtx))) mSsl = SSL_new(mCtx);
if (!mSsl)
throw std::runtime_error("Failed to create SSL instance"); throw std::runtime_error("Failed to create SSL instance");
SSL_set_ex_data(mSsl, TransportExIndex, this); SSL_set_ex_data(mSsl, TransportExIndex, this);
@ -336,7 +350,9 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
else else
SSL_set_accept_state(mSsl); SSL_set_accept_state(mSsl);
if (!(mInBio = BIO_new(BIO_s_mem())) || !(mOutBio = BIO_new(BioMethods))) mInBio = BIO_new(BIO_s_mem());
mOutBio = BIO_new(BioMethods);
if (!mInBio || !mOutBio)
throw std::runtime_error("Failed to create BIO"); throw std::runtime_error("Failed to create BIO");
BIO_set_mem_eof_return(mInBio, BIO_EOF); BIO_set_mem_eof_return(mInBio, BIO_EOF);
@ -384,7 +400,7 @@ bool DtlsTransport::send(message_ptr message) {
PLOG_VERBOSE << "Send size=" << message->size(); PLOG_VERBOSE << "Send size=" << message->size();
int ret = SSL_write(mSsl, message->data(), message->size()); int ret = SSL_write(mSsl, message->data(), int(message->size()));
return openssl::check(mSsl, ret); return openssl::check(mSsl, ret);
} }
@ -398,6 +414,10 @@ void DtlsTransport::incoming(message_ptr message) {
mIncomingQueue.push(message); mIncomingQueue.push(message);
} }
void DtlsTransport::postCreation() {
// Dummy
}
void DtlsTransport::postHandshake() { void DtlsTransport::postHandshake() {
// Dummy // Dummy
} }
@ -418,11 +438,11 @@ void DtlsTransport::runRecvLoop() {
// Process pending messages // Process pending messages
while (!mIncomingQueue.empty()) { while (!mIncomingQueue.empty()) {
auto message = *mIncomingQueue.pop(); auto message = *mIncomingQueue.pop();
BIO_write(mInBio, message->data(), message->size()); BIO_write(mInBio, message->data(), int(message->size()));
if (state() == State::Connecting) { if (state() == State::Connecting) {
// Continue the handshake // Continue the handshake
int ret = SSL_do_handshake(mSsl); ret = SSL_do_handshake(mSsl);
if (!openssl::check(mSsl, ret, "Handshake failed")) if (!openssl::check(mSsl, ret, "Handshake failed"))
break; break;
@ -436,7 +456,7 @@ void DtlsTransport::runRecvLoop() {
postHandshake(); postHandshake();
} }
} else { } else {
int ret = SSL_read(mSsl, buffer, bufferSize); ret = SSL_read(mSsl, buffer, bufferSize);
if (!openssl::check(mSsl, ret)) if (!openssl::check(mSsl, ret))
break; break;
@ -449,7 +469,7 @@ void DtlsTransport::runRecvLoop() {
std::optional<milliseconds> duration; std::optional<milliseconds> duration;
if (state() == State::Connecting) { if (state() == State::Connecting) {
// Warning: This function breaks the usual return value convention // Warning: This function breaks the usual return value convention
int ret = DTLSv1_handle_timeout(mSsl); ret = DTLSv1_handle_timeout(mSsl);
if (ret < 0) { if (ret < 0) {
throw std::runtime_error("Handshake timeout"); // write BIO can't fail throw std::runtime_error("Handshake timeout"); // write BIO can't fail
} else if (ret > 0) { } else if (ret > 0) {
@ -488,7 +508,7 @@ void DtlsTransport::runRecvLoop() {
} }
} }
int DtlsTransport::CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx) { int DtlsTransport::CertificateCallback(int /*preverify_ok*/, X509_STORE_CTX *ctx) {
SSL *ssl = SSL *ssl =
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
DtlsTransport *t = DtlsTransport *t =
@ -537,7 +557,7 @@ int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
return inl; // can't fail return inl; // can't fail
} }
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) { long DtlsTransport::BioMethodCtrl(BIO * /*bio*/, int cmd, long /*num*/, void * /*ptr*/) {
switch (cmd) { switch (cmd) {
case BIO_CTRL_FLUSH: case BIO_CTRL_FLUSH:
return 1; return 1;

View File

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

View File

@ -40,6 +40,7 @@ using namespace std::chrono_literals;
using std::shared_ptr; using std::shared_ptr;
using std::weak_ptr; using std::weak_ptr;
using std::chrono::system_clock;
#if USE_JUICE #if USE_JUICE
@ -69,7 +70,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
// Randomize servers order // Randomize servers order
std::vector<IceServer> servers = config.iceServers; std::vector<IceServer> servers = config.iceServers;
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed)); std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
// Pick a STUN server (TURN support is not implemented in libjuice yet) // Pick a STUN server (TURN support is not implemented in libjuice yet)
@ -82,7 +83,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
mStunHostname = server.hostname; mStunHostname = server.hostname;
mStunService = server.service; mStunService = server.service;
jconfig.stun_server_host = mStunHostname.c_str(); jconfig.stun_server_host = mStunHostname.c_str();
jconfig.stun_server_port = std::stoul(mStunService); jconfig.stun_server_port = uint16_t(std::stoul(mStunService));
break; break;
} }
} }
@ -206,7 +207,7 @@ void IceTransport::processCandidate(const string &candidate) {
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); } void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
void IceTransport::StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr) { void IceTransport::StateChangeCallback(juice_agent_t *, juice_state_t state, void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr); auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try { try {
iceTransport->processStateChange(static_cast<unsigned int>(state)); iceTransport->processStateChange(static_cast<unsigned int>(state));
@ -215,7 +216,7 @@ void IceTransport::StateChangeCallback(juice_agent_t *agent, juice_state_t state
} }
} }
void IceTransport::CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr) { void IceTransport::CandidateCallback(juice_agent_t *, const char *sdp, void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr); auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try { try {
iceTransport->processCandidate(sdp); iceTransport->processCandidate(sdp);
@ -224,7 +225,7 @@ void IceTransport::CandidateCallback(juice_agent_t *agent, const char *sdp, void
} }
} }
void IceTransport::GatheringDoneCallback(juice_agent_t *agent, void *user_ptr) { void IceTransport::GatheringDoneCallback(juice_agent_t *, void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr); auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try { try {
iceTransport->processGatheringDone(); iceTransport->processGatheringDone();
@ -233,8 +234,7 @@ void IceTransport::GatheringDoneCallback(juice_agent_t *agent, void *user_ptr) {
} }
} }
void IceTransport::RecvCallback(juice_agent_t *agent, const char *data, size_t size, void IceTransport::RecvCallback(juice_agent_t *, const char *data, size_t size, void *user_ptr) {
void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr); auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try { try {
PLOG_VERBOSE << "Incoming size=" << size; PLOG_VERBOSE << "Incoming size=" << size;
@ -337,7 +337,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
// Randomize order // Randomize order
std::vector<IceServer> servers = config.iceServers; std::vector<IceServer> servers = config.iceServers;
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed)); std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
// Add one STUN server // Add one STUN server

View File

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

View File

@ -19,6 +19,8 @@
#include "log.hpp" #include "log.hpp"
#include "plog/Appenders/ColorConsoleAppender.h" #include "plog/Appenders/ColorConsoleAppender.h"
#include "plog/Formatters/TxtFormatter.h"
#include "plog/Init.h"
#include "plog/Log.h" #include "plog/Log.h"
#include "plog/Logger.h" #include "plog/Logger.h"

View File

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

44
src/processor.cpp Normal file
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 * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -18,32 +18,32 @@
#include "include.hpp" #include "include.hpp"
#include "datachannel.hpp" #include "rtc.h"
#include "peerconnection.hpp"
#include "datachannel.hpp"
#include "log.hpp"
#include "peerconnection.hpp"
#if RTC_ENABLE_WEBSOCKET #if RTC_ENABLE_WEBSOCKET
#include "websocket.hpp" #include "websocket.hpp"
#endif #endif
#include <rtc.h> #include "plog/Formatters/FuncMessageFormatter.h"
#include <exception> #include <exception>
#include <mutex> #include <mutex>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#ifdef _WIN32
#include <codecvt>
#include <locale>
#endif
using namespace rtc; using namespace rtc;
using std::shared_ptr; using std::shared_ptr;
using std::string; using std::string;
#define CATCH(statement) \
try { \
statement; \
} catch (const std::exception &e) { \
PLOG_ERROR << e.what(); \
return -1; \
}
namespace { namespace {
std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap; std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
@ -55,36 +55,38 @@ std::unordered_map<int, void *> userPointerMap;
std::mutex mutex; std::mutex mutex;
int lastId = 0; int lastId = 0;
void *getUserPointer(int id) { std::optional<void *> getUserPointer(int id) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
auto it = userPointerMap.find(id); auto it = userPointerMap.find(id);
return it != userPointerMap.end() ? it->second : nullptr; return it != userPointerMap.end() ? std::make_optional(it->second) : nullopt;
} }
void setUserPointer(int i, void *ptr) { void setUserPointer(int i, void *ptr) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
if (ptr) userPointerMap[i] = ptr;
userPointerMap.insert(std::make_pair(i, ptr));
else
userPointerMap.erase(i);
} }
shared_ptr<PeerConnection> getPeerConnection(int id) { shared_ptr<PeerConnection> getPeerConnection(int id) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
auto it = peerConnectionMap.find(id); if (auto it = peerConnectionMap.find(id); it != peerConnectionMap.end())
return it != peerConnectionMap.end() ? it->second : nullptr; return it->second;
else
throw std::invalid_argument("PeerConnection ID does not exist");
} }
shared_ptr<DataChannel> getDataChannel(int id) { shared_ptr<DataChannel> getDataChannel(int id) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
auto it = dataChannelMap.find(id); if (auto it = dataChannelMap.find(id); it != dataChannelMap.end())
return it != dataChannelMap.end() ? it->second : nullptr; return it->second;
else
throw std::invalid_argument("DataChannel ID does not exist");
} }
int emplacePeerConnection(shared_ptr<PeerConnection> ptr) { int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
int pc = ++lastId; int pc = ++lastId;
peerConnectionMap.emplace(std::make_pair(pc, ptr)); peerConnectionMap.emplace(std::make_pair(pc, ptr));
userPointerMap.emplace(std::make_pair(pc, nullptr));
return pc; return pc;
} }
@ -92,45 +94,46 @@ int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
int dc = ++lastId; int dc = ++lastId;
dataChannelMap.emplace(std::make_pair(dc, ptr)); dataChannelMap.emplace(std::make_pair(dc, ptr));
userPointerMap.emplace(std::make_pair(dc, nullptr));
return dc; return dc;
} }
bool erasePeerConnection(int pc) { void erasePeerConnection(int pc) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
if (peerConnectionMap.erase(pc) == 0) if (peerConnectionMap.erase(pc) == 0)
return false; throw std::invalid_argument("PeerConnection ID does not exist");
userPointerMap.erase(pc); userPointerMap.erase(pc);
return true;
} }
bool eraseDataChannel(int dc) { void eraseDataChannel(int dc) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
if (dataChannelMap.erase(dc) == 0) if (dataChannelMap.erase(dc) == 0)
return false; throw std::invalid_argument("DataChannel ID does not exist");
userPointerMap.erase(dc); userPointerMap.erase(dc);
return true;
} }
#if RTC_ENABLE_WEBSOCKET #if RTC_ENABLE_WEBSOCKET
shared_ptr<WebSocket> getWebSocket(int id) { shared_ptr<WebSocket> getWebSocket(int id) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
auto it = webSocketMap.find(id); if (auto it = webSocketMap.find(id); it != webSocketMap.end())
return it != webSocketMap.end() ? it->second : nullptr; return it->second;
else
throw std::invalid_argument("WebSocket ID does not exist");
} }
int emplaceWebSocket(shared_ptr<WebSocket> ptr) { int emplaceWebSocket(shared_ptr<WebSocket> ptr) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
int ws = ++lastId; int ws = ++lastId;
webSocketMap.emplace(std::make_pair(ws, ptr)); webSocketMap.emplace(std::make_pair(ws, ptr));
userPointerMap.emplace(std::make_pair(ws, nullptr));
return ws; return ws;
} }
bool eraseWebSocket(int ws) { void eraseWebSocket(int ws) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
if (webSocketMap.erase(ws) == 0) if (webSocketMap.erase(ws) == 0)
return false; throw std::invalid_argument("WebSocket ID does not exist");
userPointerMap.erase(ws); userPointerMap.erase(ws);
return true;
} }
#endif #endif
@ -142,380 +145,449 @@ shared_ptr<Channel> getChannel(int id) {
if (auto it = webSocketMap.find(id); it != webSocketMap.end()) if (auto it = webSocketMap.find(id); it != webSocketMap.end())
return it->second; return it->second;
#endif #endif
return nullptr; throw std::invalid_argument("DataChannel or WebSocket ID does not exist");
} }
template <typename F> int wrap(F func) {
try {
return int(func());
} catch (const std::invalid_argument &e) {
PLOG_ERROR << e.what();
return RTC_ERR_INVALID;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
return RTC_ERR_FAILURE;
}
}
#define WRAP(statement) \
wrap([&]() { \
statement; \
return RTC_ERR_SUCCESS; \
})
class plog_appender : public plog::IAppender {
public:
plog_appender(rtcLogCallbackFunc cb = nullptr) { set_callback(cb); }
void set_callback(rtcLogCallbackFunc cb) {
std::lock_guard lock(mutex);
callback = cb;
}
void write(const plog::Record &record) override {
plog::Severity severity = record.getSeverity();
auto formatted = plog::FuncMessageFormatter::format(record);
formatted.pop_back(); // remove newline
#ifdef _WIN32
using convert_type = std::codecvt_utf8<wchar_t>;
std::wstring_convert<convert_type, wchar_t> converter;
std::string str = converter.to_bytes(formatted);
#else
std::string str = formatted;
#endif
std::lock_guard lock(mutex);
if (callback)
callback(static_cast<rtcLogLevel>(record.getSeverity()), str.c_str());
else
std::cout << plog::severityToString(severity) << " " << str << std::endl;
}
private:
rtcLogCallbackFunc callback;
};
} // namespace } // namespace
void rtcInitLogger(rtcLogLevel level) { InitLogger(static_cast<LogLevel>(level)); } void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
static std::optional<plog_appender> appender;
if (appender)
appender->set_callback(cb);
else if (cb)
appender.emplace(plog_appender(cb));
InitLogger(static_cast<plog::Severity>(level), appender ? &appender.value() : nullptr);
}
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); } void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
int rtcCreatePeerConnection(const rtcConfiguration *config) { int rtcCreatePeerConnection(const rtcConfiguration *config) {
Configuration c; return WRAP({
for (int i = 0; i < config->iceServersCount; ++i) Configuration c;
c.iceServers.emplace_back(string(config->iceServers[i])); for (int i = 0; i < config->iceServersCount; ++i)
c.iceServers.emplace_back(string(config->iceServers[i]));
if (config->portRangeBegin || config->portRangeEnd) { if (config->portRangeBegin || config->portRangeEnd) {
c.portRangeBegin = config->portRangeBegin; c.portRangeBegin = config->portRangeBegin;
c.portRangeEnd = config->portRangeEnd; c.portRangeEnd = config->portRangeEnd;
} }
return emplacePeerConnection(std::make_shared<PeerConnection>(c)); return emplacePeerConnection(std::make_shared<PeerConnection>(c));
});
} }
int rtcDeletePeerConnection(int pc) { int rtcDeletePeerConnection(int pc) {
auto peerConnection = getPeerConnection(pc); return WRAP({
if (!peerConnection) auto peerConnection = getPeerConnection(pc);
return -1; peerConnection->onDataChannel(nullptr);
peerConnection->onLocalDescription(nullptr);
peerConnection->onLocalCandidate(nullptr);
peerConnection->onStateChange(nullptr);
peerConnection->onGatheringStateChange(nullptr);
peerConnection->onDataChannel(nullptr); erasePeerConnection(pc);
peerConnection->onLocalDescription(nullptr); });
peerConnection->onLocalCandidate(nullptr);
peerConnection->onStateChange(nullptr);
peerConnection->onGatheringStateChange(nullptr);
erasePeerConnection(pc);
return 0;
} }
int rtcCreateDataChannel(int pc, const char *label) { int rtcCreateDataChannel(int pc, const char *label) {
auto peerConnection = getPeerConnection(pc); return WRAP({
if (!peerConnection) auto peerConnection = getPeerConnection(pc);
return -1; int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
if (auto ptr = getUserPointer(pc))
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label))); rtcSetUserPointer(dc, *ptr);
void *ptr = getUserPointer(pc); return dc;
rtcSetUserPointer(dc, ptr); });
return dc;
} }
int rtcDeleteDataChannel(int dc) { int rtcDeleteDataChannel(int dc) {
auto dataChannel = getDataChannel(dc); return WRAP({
if (!dataChannel) auto dataChannel = getDataChannel(dc);
return -1; dataChannel->onOpen(nullptr);
dataChannel->onClosed(nullptr);
dataChannel->onError(nullptr);
dataChannel->onMessage(nullptr);
dataChannel->onBufferedAmountLow(nullptr);
dataChannel->onAvailable(nullptr);
dataChannel->onOpen(nullptr); eraseDataChannel(dc);
dataChannel->onClosed(nullptr); });
dataChannel->onError(nullptr);
dataChannel->onMessage(nullptr);
dataChannel->onBufferedAmountLow(nullptr);
dataChannel->onAvailable(nullptr);
eraseDataChannel(dc);
return 0;
} }
#if RTC_ENABLE_WEBSOCKET #if RTC_ENABLE_WEBSOCKET
int rtcCreateWebSocket(const char *url) { int rtcCreateWebSocket(const char *url) {
auto ws = std::make_shared<WebSocket>(); return WRAP({
ws->open(url); auto ws = std::make_shared<WebSocket>();
return emplaceWebSocket(ws); 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);
}); });
} }
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(); } void rtcCleanup() { rtc::Cleanup(); }

View File

@ -50,6 +50,9 @@ using std::shared_ptr;
namespace rtc { namespace rtc {
std::unordered_set<SctpTransport *> SctpTransport::Instances;
std::shared_mutex SctpTransport::InstancesMutex;
void SctpTransport::Init() { void SctpTransport::Init() {
usrsctp_init(0, &SctpTransport::WriteCallback, nullptr); usrsctp_init(0, &SctpTransport::WriteCallback, nullptr);
usrsctp_sysctl_set_sctp_ecn_enable(0); usrsctp_sysctl_set_sctp_ecn_enable(0);
@ -61,6 +64,20 @@ void SctpTransport::Init() {
usrsctp_sysctl_set_sctp_rto_initial_default(1 * 1000); // ms usrsctp_sysctl_set_sctp_rto_initial_default(1 * 1000); // ms
usrsctp_sysctl_set_sctp_init_rto_max_default(10 * 1000); // ms usrsctp_sysctl_set_sctp_init_rto_max_default(10 * 1000); // ms
usrsctp_sysctl_set_sctp_heartbeat_interval_default(10 * 1000); // ms usrsctp_sysctl_set_sctp_heartbeat_interval_default(10 * 1000); // ms
usrsctp_sysctl_set_sctp_max_chunks_on_queue(10 * 1024);
// Change congestion control from the default TCP Reno (RFC 2581) to H-TCP
usrsctp_sysctl_set_sctp_default_cc_module(SCTP_CC_HTCP);
// Enable Non-Renegable Selective Acknowledgments (NR-SACKs)
usrsctp_sysctl_set_sctp_nrsack_enable(1);
// Increase the initial window size to 10 MTUs (RFC 6928)
usrsctp_sysctl_set_sctp_initial_cwnd(10);
// Reduce SACK delay from the default 200ms to 20ms
usrsctp_sysctl_set_sctp_delayed_sack_time_default(20); // ms
} }
void SctpTransport::Cleanup() { void SctpTransport::Cleanup() {
@ -78,6 +95,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
PLOG_DEBUG << "Initializing SCTP transport"; PLOG_DEBUG << "Initializing SCTP transport";
usrsctp_register_address(this); usrsctp_register_address(this);
{
std::unique_lock lock(InstancesMutex);
Instances.insert(this);
}
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback, mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
&SctpTransport::SendCallback, 0, this); &SctpTransport::SendCallback, 0, this);
if (!mSock) if (!mSock)
@ -158,11 +180,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
// The default send and receive window size of usrsctp is 256KiB, which is too small for // The default send and receive window size of usrsctp is 256KiB, which is too small for
// realistic RTTs, therefore we increase it to 1MiB for better performance. // realistic RTTs, therefore we increase it to 1MiB for better performance.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685
int bufSize = 1024 * 1024; int bufferSize = 1024 * 1024;
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &bufSize, sizeof(bufSize))) if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)))
throw std::runtime_error("Could not set SCTP recv buffer size, errno=" + throw std::runtime_error("Could not set SCTP recv buffer size, errno=" +
std::to_string(errno)); std::to_string(errno));
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_SNDBUF, &bufSize, sizeof(bufSize))) if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)))
throw std::runtime_error("Could not set SCTP send buffer size, errno=" + throw std::runtime_error("Could not set SCTP send buffer size, errno=" +
std::to_string(errno)); std::to_string(errno));
@ -172,14 +194,21 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
SctpTransport::~SctpTransport() { SctpTransport::~SctpTransport() {
stop(); stop();
close();
if (mSock)
usrsctp_close(mSock);
usrsctp_deregister_address(this); usrsctp_deregister_address(this);
{
std::unique_lock lock(InstancesMutex);
Instances.erase(this);
}
} }
bool SctpTransport::stop() { bool SctpTransport::stop() {
// Transport::stop() will unregister incoming() from the lower layer, therefore we need to make
// sure the thread from lower layers is not blocked in incoming() by the WrittenOnce condition.
mWrittenOnce = true;
mWrittenCondition.notify_all();
if (!Transport::stop()) if (!Transport::stop())
return false; return false;
@ -190,6 +219,13 @@ bool SctpTransport::stop() {
return true; return true;
} }
void SctpTransport::close() {
if (mSock) {
usrsctp_close(mSock);
mSock = nullptr;
}
}
void SctpTransport::connect() { void SctpTransport::connect() {
if (!mSock) if (!mSock)
return; return;
@ -226,9 +262,7 @@ void SctpTransport::shutdown() {
PLOG_WARNING << "SCTP shutdown failed, errno=" << errno; PLOG_WARNING << "SCTP shutdown failed, errno=" << errno;
} }
// close() abort the connection when linger is disabled, call it now close();
usrsctp_close(mSock);
mSock = nullptr;
PLOG_INFO << "SCTP disconnected"; PLOG_INFO << "SCTP disconnected";
changeState(State::Disconnected); changeState(State::Disconnected);
@ -248,11 +282,11 @@ bool SctpTransport::send(message_ptr message) {
return true; return true;
mSendQueue.push(message); mSendQueue.push(message);
updateBufferedAmount(message->stream, message_size_func(message)); updateBufferedAmount(uint16_t(message->stream), long(message_size_func(message)));
return false; return false;
} }
void SctpTransport::close(unsigned int stream) { void SctpTransport::closeStream(unsigned int stream) {
send(make_message(0, Message::Reset, uint16_t(stream))); send(make_message(0, Message::Reset, uint16_t(stream)));
} }
@ -265,7 +299,7 @@ void SctpTransport::incoming(message_ptr message) {
// There could be a race condition here where we receive the remote INIT before the local one is // There could be a race condition here where we receive the remote INIT before the local one is
// sent, which would result in the connection being aborted. Therefore, we need to wait for data // sent, which would result in the connection being aborted. Therefore, we need to wait for data
// to be sent on our side (i.e. the local INIT) before proceeding. // to be sent on our side (i.e. the local INIT) before proceeding.
{ if (!mWrittenOnce) { // test the atomic boolean is not set first to prevent a lock contention
std::unique_lock lock(mWriteMutex); std::unique_lock lock(mWriteMutex);
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || state() != State::Connected; }); mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || state() != State::Connected; });
} }
@ -288,7 +322,7 @@ bool SctpTransport::trySendQueue() {
if (!trySendMessage(message)) if (!trySendMessage(message))
return false; return false;
mSendQueue.pop(); mSendQueue.pop();
updateBufferedAmount(message->stream, -message_size_func(message)); updateBufferedAmount(uint16_t(message->stream), -long(message_size_func(message)));
} }
return true; return true;
} }
@ -310,7 +344,7 @@ bool SctpTransport::trySendMessage(message_ptr message) {
ppid = PPID_CONTROL; ppid = PPID_CONTROL;
break; break;
case Message::Reset: case Message::Reset:
sendReset(message->stream); sendReset(uint16_t(message->stream));
return true; return true;
default: default:
// Ignore // Ignore
@ -362,18 +396,20 @@ bool SctpTransport::trySendMessage(message_ptr message) {
ret = usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0); ret = usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0);
} }
if (ret >= 0) { if (ret < 0) {
PLOG_VERBOSE << "SCTP sent size=" << message->size(); if (errno == EWOULDBLOCK || errno == EAGAIN) {
if (message->type == Message::Type::Binary || message->type == Message::Type::String) PLOG_VERBOSE << "SCTP sending not possible";
mBytesSent += message->size(); return false;
return true; }
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
PLOG_VERBOSE << "SCTP sending not possible";
return false;
} else {
PLOG_ERROR << "SCTP sending failed, errno=" << errno; PLOG_ERROR << "SCTP sending failed, errno=" << errno;
throw std::runtime_error("Sending failed, errno=" + std::to_string(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) { void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
@ -385,7 +421,13 @@ void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
else else
it->second = amount; it->second = amount;
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) { void SctpTransport::sendReset(uint16_t streamId) {
@ -421,37 +463,51 @@ bool SctpTransport::safeFlush() {
return true; return true;
} catch (const std::exception &e) { } catch (const std::exception &e) {
PLOG_ERROR << "SCTP flush: " << e.what(); PLOG_WARNING << "SCTP flush: " << e.what();
return false; return false;
} }
} }
int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data, int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*addr*/,
size_t len, struct sctp_rcvinfo info, int flags) { const byte *data, size_t len, struct sctp_rcvinfo info, int flags) {
try { try {
PLOG_VERBOSE << "Handle recv, len=" << len; PLOG_VERBOSE << "Handle recv, len=" << len;
if (!len) if (!len)
return -1; return 0; // Ignore
// This is valid because SCTP_FRAGMENT_INTERLEAVE is set to level 0 // SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
// so partial messages and notifications may not be interleaved. // therefore partial notifications and messages need to be handled separately.
if (flags & MSG_EOR) { if (flags & MSG_NOTIFICATION) {
if (!mPartialRecv.empty()) { // SCTP event notification
mPartialRecv.insert(mPartialRecv.end(), data, data + len); if (flags & MSG_EOR) {
data = mPartialRecv.data(); if (!mPartialNotification.empty()) {
len = mPartialRecv.size(); mPartialNotification.insert(mPartialNotification.end(), data, data + len);
} data = mPartialNotification.data();
// Message/Notification is complete, process it len = mPartialNotification.size();
if (flags & MSG_NOTIFICATION) }
// Notification is complete, process it
processNotification(reinterpret_cast<const union sctp_notification *>(data), len); processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
else mPartialNotification.clear();
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid))); } else {
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
}
mPartialRecv.clear();
} else { } else {
// Message/Notification is not complete // SCTP message
mPartialRecv.insert(mPartialRecv.end(), data, data + len); if (flags & MSG_EOR) {
if (!mPartialMessage.empty()) {
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
data = mPartialMessage.data();
len = mPartialMessage.size();
}
// Message is complete, process it
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
mPartialMessage.clear();
} else {
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
}
} }
} catch (const std::exception &e) { } catch (const std::exception &e) {
PLOG_ERROR << "SCTP recv: " << e.what(); PLOG_ERROR << "SCTP recv: " << e.what();
return -1; return -1;
@ -464,13 +520,14 @@ int SctpTransport::handleSend(size_t free) {
return safeFlush() ? 0 : -1; return safeFlush() ? 0 : -1;
} }
int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df) { int SctpTransport::handleWrite(byte *data, size_t len, uint8_t /*tos*/, uint8_t /*set_df*/) {
try { try {
std::unique_lock lock(mWriteMutex);
PLOG_VERBOSE << "Handle write, len=" << len; PLOG_VERBOSE << "Handle write, len=" << len;
std::unique_lock lock(mWriteMutex);
if (!outgoing(make_message(data, data + len))) if (!outgoing(make_message(data, data + len)))
return -1; return -1;
mWritten = true; mWritten = true;
mWrittenOnce = true; mWrittenOnce = true;
mWrittenCondition.notify_all(); mWrittenCondition.notify_all();
@ -591,7 +648,7 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
if (flags & SCTP_STREAM_RESET_OUTGOING_SSN) { if (flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
uint16_t streamId = reset_event.strreset_stream_list[i]; uint16_t streamId = reset_event.strreset_stream_list[i];
close(streamId); closeStream(streamId);
} }
} }
if (flags & SCTP_STREAM_RESET_INCOMING_SSN) { if (flags & SCTP_STREAM_RESET_INCOMING_SSN) {
@ -649,7 +706,15 @@ int SctpTransport::SendCallback(struct socket *sock, uint32_t sb_free) {
auto sconn = reinterpret_cast<struct sockaddr_conn *>(&paddrinfo.spinfo_address); auto sconn = reinterpret_cast<struct sockaddr_conn *>(&paddrinfo.spinfo_address);
void *ptr = sconn->sconn_addr; void *ptr = sconn->sconn_addr;
return static_cast<SctpTransport *>(ptr)->handleSend(size_t(sb_free)); auto *transport = static_cast<SctpTransport *>(ptr);
// Workaround for sctplab/usrsctp#405: Send callback is invoked on already closed socket
// https://github.com/sctplab/usrsctp/issues/405
std::shared_lock lock(InstancesMutex);
if (Instances.find(transport) == Instances.end())
return -1;
return transport->handleSend(size_t(sb_free));
} }
int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) { int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) {

View File

@ -28,6 +28,8 @@
#include <functional> #include <functional>
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <shared_mutex>
#include <unordered_set>
#include "usrsctp.h" #include "usrsctp.h"
@ -46,7 +48,7 @@ public:
bool stop() override; bool stop() override;
bool send(message_ptr message) override; // false if buffered bool send(message_ptr message) override; // false if buffered
void close(unsigned int stream); void closeStream(unsigned int stream);
void flush(); void flush();
// Stats // Stats
@ -70,6 +72,7 @@ private:
void connect(); void connect();
void shutdown(); void shutdown();
void close();
void incoming(message_ptr message) override; void incoming(message_ptr message) override;
bool trySendQueue(); bool trySendQueue();
@ -97,9 +100,10 @@ private:
std::mutex mWriteMutex; std::mutex mWriteMutex;
std::condition_variable mWrittenCondition; std::condition_variable mWrittenCondition;
std::atomic<bool> mWritten = false; // written outside lock std::atomic<bool> mWritten = false; // written outside lock
bool mWrittenOnce = false; std::atomic<bool> mWrittenOnce = false; // same
binary mPartialRecv, mPartialStringData, mPartialBinaryData; binary mPartialMessage, mPartialNotification;
binary mPartialStringData, mPartialBinaryData;
// Stats // Stats
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0; std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
@ -108,6 +112,9 @@ private:
struct sctp_rcvinfo recv_info, int flags, void *user_data); struct sctp_rcvinfo recv_info, int flags, void *user_data);
static int SendCallback(struct socket *sock, uint32_t sb_free); static int SendCallback(struct socket *sock, uint32_t sb_free);
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df); static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
static std::unordered_set<SctpTransport *> Instances;
static std::shared_mutex InstancesMutex;
}; };
} // namespace rtc } // namespace rtc

View File

@ -21,6 +21,7 @@
#if RTC_ENABLE_WEBSOCKET #if RTC_ENABLE_WEBSOCKET
#include <exception> #include <exception>
#ifndef _WIN32 #ifndef _WIN32
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
@ -37,8 +38,8 @@ SelectInterrupter::SelectInterrupter() {
throw std::runtime_error("Failed to create pipe"); throw std::runtime_error("Failed to create pipe");
::fcntl(pipefd[0], F_SETFL, O_NONBLOCK); ::fcntl(pipefd[0], F_SETFL, O_NONBLOCK);
::fcntl(pipefd[1], F_SETFL, O_NONBLOCK); ::fcntl(pipefd[1], F_SETFL, O_NONBLOCK);
mPipeOut = pipefd[0]; // read mPipeOut = pipefd[1]; // read
mPipeIn = pipefd[1]; // write mPipeIn = pipefd[0]; // write
#endif #endif
} }
@ -59,13 +60,10 @@ int SelectInterrupter::prepare(fd_set &readfds, fd_set &writefds) {
if (mDummySock == INVALID_SOCKET) if (mDummySock == INVALID_SOCKET)
mDummySock = ::socket(AF_INET, SOCK_DGRAM, 0); mDummySock = ::socket(AF_INET, SOCK_DGRAM, 0);
FD_SET(mDummySock, &readfds); FD_SET(mDummySock, &readfds);
return SOCK_TO_INT(mDummySock) + 1; return SOCKET_TO_INT(mDummySock) + 1;
#else #else
int ret; char dummy;
do { ::read(mPipeIn, &dummy, 1);
char dummy;
ret = ::read(mPipeIn, &dummy, 1);
} while (ret > 0);
FD_SET(mPipeIn, &readfds); FD_SET(mPipeIn, &readfds);
return mPipeIn + 1; return mPipeIn + 1;
#endif #endif
@ -106,6 +104,7 @@ bool TcpTransport::stop() {
} }
bool TcpTransport::send(message_ptr message) { bool TcpTransport::send(message_ptr message) {
std::unique_lock lock(mSockMutex);
if (state() != State::Connected) if (state() != State::Connected)
return false; return false;
@ -125,6 +124,7 @@ void TcpTransport::incoming(message_ptr message) {
} }
bool TcpTransport::outgoing(message_ptr message) { bool TcpTransport::outgoing(message_ptr message) {
// mSockMutex must be locked
// If nothing is pending, try to send directly // If nothing is pending, try to send directly
// It's safe because if the queue is empty, the thread is not sending // It's safe because if the queue is empty, the thread is not sending
if (mSendQueue.empty() && trySendMessage(message)) if (mSendQueue.empty() && trySendMessage(message))
@ -150,7 +150,7 @@ void TcpTransport::connect(const string &hostname, const string &service) {
for (auto p = result; p; p = p->ai_next) { for (auto p = result; p; p = p->ai_next) {
try { try {
connect(p->ai_addr, p->ai_addrlen); connect(p->ai_addr, socklen_t(p->ai_addrlen));
PLOG_INFO << "Connected to " << hostname << ":" << service; PLOG_INFO << "Connected to " << hostname << ":" << service;
freeaddrinfo(result); freeaddrinfo(result);
@ -173,6 +173,7 @@ void TcpTransport::connect(const string &hostname, const string &service) {
} }
void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) { void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
std::unique_lock lock(mSockMutex);
try { try {
char node[MAX_NUMERICNODE_LEN]; char node[MAX_NUMERICNODE_LEN];
char serv[MAX_NUMERICSERV_LEN]; char serv[MAX_NUMERICSERV_LEN];
@ -201,7 +202,7 @@ void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
// Initiate connection // Initiate connection
int ret = ::connect(mSock, addr, addrlen); int ret = ::connect(mSock, addr, addrlen);
if (ret < 0 && errno != EINPROGRESS) { if (ret < 0 && sockerrno != SEINPROGRESS && sockerrno != SEWOULDBLOCK) {
std::ostringstream msg; std::ostringstream msg;
msg << "TCP connection to " << node << ":" << serv << " failed, errno=" << sockerrno; msg << "TCP connection to " << node << ":" << serv << " failed, errno=" << sockerrno;
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
@ -226,7 +227,7 @@ void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
int error = 0; int error = 0;
socklen_t errorlen = sizeof(error); socklen_t errorlen = sizeof(error);
if (::getsockopt(mSock, SOL_SOCKET, SO_ERROR, &error, &errorlen) != 0) if (::getsockopt(mSock, SOL_SOCKET, SO_ERROR, (char *)&error, &errorlen) != 0)
throw std::runtime_error("Failed to get socket error code"); throw std::runtime_error("Failed to get socket error code");
if (error != 0) { if (error != 0) {
@ -247,15 +248,18 @@ void TcpTransport::connect(const sockaddr *addr, socklen_t addrlen) {
} }
void TcpTransport::close() { void TcpTransport::close() {
std::unique_lock lock(mSockMutex);
if (mSock != INVALID_SOCKET) { if (mSock != INVALID_SOCKET) {
PLOG_DEBUG << "Closing TCP socket"; PLOG_DEBUG << "Closing TCP socket";
::closesocket(mSock); ::closesocket(mSock);
mSock = INVALID_SOCKET; mSock = INVALID_SOCKET;
} }
changeState(State::Disconnected); changeState(State::Disconnected);
interruptSelect();
} }
bool TcpTransport::trySendQueue() { bool TcpTransport::trySendQueue() {
// mSockMutex must be locked
while (auto next = mSendQueue.peek()) { while (auto next = mSendQueue.peek()) {
auto message = *next; auto message = *next;
if (!trySendMessage(message)) { if (!trySendMessage(message)) {
@ -268,17 +272,18 @@ bool TcpTransport::trySendQueue() {
} }
bool TcpTransport::trySendMessage(message_ptr &message) { bool TcpTransport::trySendMessage(message_ptr &message) {
// mSockMutex must be locked
auto data = reinterpret_cast<const char *>(message->data()); auto data = reinterpret_cast<const char *>(message->data());
auto size = message->size(); auto size = message->size();
while (size) { while (size) {
#ifdef __APPLE__ #if defined(__APPLE__) || defined(_WIN32)
int flags = 0; int flags = 0;
#else #else
int flags = MSG_NOSIGNAL; int flags = MSG_NOSIGNAL;
#endif #endif
int len = ::send(mSock, data, size, flags); int len = ::send(mSock, data, int(size), flags);
if (len < 0) { if (len < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) { if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
message = make_message(message->end() - size, message->end()); message = make_message(message->end() - size, message->end());
return false; return false;
} else { } else {
@ -313,13 +318,22 @@ void TcpTransport::runLoop() {
changeState(State::Connected); changeState(State::Connected);
while (true) { while (true) {
std::unique_lock lock(mSockMutex);
if (mSock == INVALID_SOCKET)
break;
fd_set readfds, writefds; fd_set readfds, writefds;
int n = prepareSelect(readfds, writefds); int n = prepareSelect(readfds, writefds);
struct timeval tv; struct timeval tv;
tv.tv_sec = 10; tv.tv_sec = 10;
tv.tv_usec = 0; tv.tv_usec = 0;
lock.unlock();
int ret = ::select(n, &readfds, &writefds, NULL, &tv); int ret = ::select(n, &readfds, &writefds, NULL, &tv);
lock.lock();
if (mSock == INVALID_SOCKET)
break;
if (ret < 0) { if (ret < 0) {
throw std::runtime_error("Failed to wait on socket"); throw std::runtime_error("Failed to wait on socket");
} else if (ret == 0) { } else if (ret == 0) {
@ -335,7 +349,7 @@ void TcpTransport::runLoop() {
char buffer[bufferSize]; char buffer[bufferSize];
int len = ::recv(mSock, buffer, bufferSize, 0); int len = ::recv(mSock, buffer, bufferSize, 0);
if (len < 0) { if (len < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) { if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
continue; continue;
} else { } else {
throw std::runtime_error("Connection lost"); throw std::runtime_error("Connection lost");

View File

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

81
src/threadpool.cpp Normal file
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); std::lock_guard lock(mutex);
if (!done) { if (!done) {
OPENSSL_init_ssl(0, NULL); OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
SSL_load_error_strings(); OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
ERR_load_crypto_strings();
done = true; done = true;
} }
} }

View File

@ -48,6 +48,11 @@ gnutls_datum_t make_datum(char *data, size_t size);
#else // USE_GNUTLS==0 #else // USE_GNUTLS==0
#ifdef _WIN32
// Include winsock2.h header first since OpenSSL may include winsock.h
#include <winsock2.h>
#endif
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/bio.h> #include <openssl/bio.h>
@ -55,6 +60,8 @@ gnutls_datum_t make_datum(char *data, size_t size);
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/pem.h> #include <openssl/pem.h>
#include <openssl/x509.h> #include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/bn.h>
#ifndef BIO_EOF #ifndef BIO_EOF
#define BIO_EOF -1 #define BIO_EOF -1

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

View File

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

View File

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

View File

@ -18,8 +18,9 @@
#if RTC_ENABLE_WEBSOCKET #if RTC_ENABLE_WEBSOCKET
#include "include.hpp"
#include "websocket.hpp" #include "websocket.hpp"
#include "include.hpp"
#include "threadpool.hpp"
#include "tcptransport.hpp" #include "tcptransport.hpp"
#include "tlstransport.hpp" #include "tlstransport.hpp"
@ -301,21 +302,18 @@ void WebSocket::closeTransports() {
auto ws = std::atomic_exchange(&mWsTransport, decltype(mWsTransport)(nullptr)); auto ws = std::atomic_exchange(&mWsTransport, decltype(mWsTransport)(nullptr));
auto tls = std::atomic_exchange(&mTlsTransport, decltype(mTlsTransport)(nullptr)); auto tls = std::atomic_exchange(&mTlsTransport, decltype(mTlsTransport)(nullptr));
auto tcp = std::atomic_exchange(&mTcpTransport, decltype(mTcpTransport)(nullptr)); auto tcp = std::atomic_exchange(&mTcpTransport, decltype(mTcpTransport)(nullptr));
if (ws || tls || tcp) { ThreadPool::Instance().enqueue([ws, tls, tcp]() mutable {
std::thread t([ws, tls, tcp, token = mInitToken]() mutable { if (ws)
if (ws) ws->stop();
ws->stop(); if (tls)
if (tls) tls->stop();
tls->stop(); if (tcp)
if (tcp) tcp->stop();
tcp->stop();
ws.reset(); ws.reset();
tls.reset(); tls.reset();
tcp.reset(); tcp.reset();
}); });
t.detach();
}
} }
} // namespace rtc } // namespace rtc

View File

@ -50,7 +50,7 @@ using std::to_integer;
using std::to_string; using std::to_string;
using random_bytes_engine = using random_bytes_engine =
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char>; std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned short>;
WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string path, WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string path,
message_callback recvCallback, state_callback stateCallback) message_callback recvCallback, state_callback stateCallback)
@ -145,12 +145,12 @@ void WsTransport::close() {
bool WsTransport::sendHttpRequest() { bool WsTransport::sendHttpRequest() {
changeState(State::Connecting); changeState(State::Connecting);
auto seed = system_clock::now().time_since_epoch().count(); auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
random_bytes_engine generator(seed); random_bytes_engine generator(seed);
binary key(16); binary key(16);
auto k = reinterpret_cast<uint8_t *>(key.data()); auto k = reinterpret_cast<uint8_t *>(key.data());
std::generate(k, k + key.size(), generator); std::generate(k, k + key.size(), [&]() { return uint8_t(generator()); });
const string request = "GET " + mPath + const string request = "GET " + mPath +
" HTTP/1.1\r\n" " HTTP/1.1\r\n"
@ -283,7 +283,7 @@ size_t WsTransport::readFrame(byte *buffer, size_t size, Frame &frame) {
cur += 4; cur += 4;
} }
if (end - cur < frame.length) if (size_t(end - cur) < frame.length)
return 0; return 0;
frame.payload = cur; frame.payload = cur;
@ -292,7 +292,7 @@ size_t WsTransport::readFrame(byte *buffer, size_t size, Frame &frame) {
frame.payload[i] ^= maskingKey[i % 4]; frame.payload[i] ^= maskingKey[i % 4];
cur += frame.length; cur += frame.length;
return cur - buffer; return size_t(cur - buffer);
} }
void WsTransport::recvFrame(const Frame &frame) { void WsTransport::recvFrame(const Frame &frame) {
@ -378,13 +378,13 @@ bool WsTransport::sendFrame(const Frame &frame) {
} }
if (frame.mask) { if (frame.mask) {
auto seed = system_clock::now().time_since_epoch().count(); auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
random_bytes_engine generator(seed); random_bytes_engine generator(seed);
byte *maskingKey = reinterpret_cast<byte *>(cur); byte *maskingKey = reinterpret_cast<byte *>(cur);
auto u = reinterpret_cast<uint8_t *>(maskingKey); auto u = reinterpret_cast<uint8_t *>(maskingKey);
std::generate(u, u + 4, generator); std::generate(u, u + 4, [&]() { return uint8_t(generator()); });
cur += 4; cur += 4;
for (size_t i = 0; i < frame.length; ++i) for (size_t i = 0; i < frame.length; ++i)

192
test/benchmark.cpp Normal file
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 <rtc/rtc.h>
#include <cstdbool>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -136,7 +135,7 @@ static void deletePeer(Peer *peer) {
int test_capi_main() { int test_capi_main() {
int attempts; int attempts;
rtcInitLogger(RTC_LOG_DEBUG); rtcInitLogger(RTC_LOG_DEBUG, nullptr);
// Create peer 1 // Create peer 1
rtcConfiguration config1; rtcConfiguration config1;
@ -201,7 +200,7 @@ int test_capi_main() {
// You may call rtcCleanup() when finished to free static resources // You may call rtcCleanup() when finished to free static resources
rtcCleanup(); rtcCleanup();
sleep(1); sleep(2);
printf("Success\n"); printf("Success\n");
return 0; return 0;

View File

@ -18,6 +18,7 @@
#include "rtc/rtc.hpp" #include "rtc/rtc.hpp"
#include <atomic>
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
@ -63,6 +64,7 @@ void test_connectivity() {
}); });
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; }); pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
pc1->onGatheringStateChange([](PeerConnection::GatheringState state) { pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "Gathering state 1: " << state << endl; cout << "Gathering state 1: " << state << endl;
}); });
@ -84,6 +86,7 @@ void test_connectivity() {
}); });
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; }); pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
pc2->onGatheringStateChange([](PeerConnection::GatheringState state) { pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "Gathering state 2: " << state << endl; cout << "Gathering state 2: " << state << endl;
}); });
@ -91,13 +94,16 @@ void test_connectivity() {
shared_ptr<DataChannel> dc2; shared_ptr<DataChannel> dc2;
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) { pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl; cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
dc2 = dc;
dc2->onMessage([](const variant<binary, string> &message) { dc->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) { if (holds_alternative<string>(message)) {
cout << "Message 2: " << get<string>(message) << endl; cout << "Message 2: " << get<string>(message) << endl;
} }
}); });
dc2->send("Hello from 2");
dc->send("Hello from 2");
std::atomic_store(&dc2, dc);
}); });
auto dc1 = pc1->createDataChannel("test"); auto dc1 = pc1->createDataChannel("test");
@ -105,6 +111,7 @@ void test_connectivity() {
auto dc1 = wdc1.lock(); auto dc1 = wdc1.lock();
if (!dc1) if (!dc1)
return; return;
cout << "DataChannel 1: Open" << endl; cout << "DataChannel 1: Open" << endl;
dc1->send("Hello from 1"); dc1->send("Hello from 1");
}); });
@ -115,14 +122,15 @@ void test_connectivity() {
}); });
int attempts = 10; int attempts = 10;
while ((!dc2 || !dc2->isOpen() || !dc1->isOpen()) && attempts--) shared_ptr<DataChannel> adc2;
while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
this_thread::sleep_for(1s); this_thread::sleep_for(1s);
if (pc1->state() != PeerConnection::State::Connected && if (pc1->state() != PeerConnection::State::Connected &&
pc2->state() != PeerConnection::State::Connected) pc2->state() != PeerConnection::State::Connected)
throw runtime_error("PeerConnection is not connected"); throw runtime_error("PeerConnection is not connected");
if (!dc1->isOpen() || !dc2->isOpen()) if (!adc2 || !adc2->isOpen() || !dc1->isOpen())
throw runtime_error("DataChannel is not open"); throw runtime_error("DataChannel is not open");
if (auto addr = pc1->localAddress()) if (auto addr = pc1->localAddress())
@ -142,7 +150,7 @@ void test_connectivity() {
// You may call rtc::Cleanup() when finished to free static resources // You may call rtc::Cleanup() when finished to free static resources
rtc::Cleanup(); rtc::Cleanup();
this_thread::sleep_for(1s); this_thread::sleep_for(2s);
cout << "Success" << endl; cout << "Success" << endl;
} }

View File

@ -16,13 +16,28 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#include <chrono>
#include <iostream> #include <iostream>
#include <thread>
using namespace std; using namespace std;
using namespace chrono_literals;
void test_connectivity(); void test_connectivity();
void test_capi(); void test_capi();
void test_websocket(); void test_websocket();
size_t benchmark(chrono::milliseconds duration);
void test_benchmark() {
size_t goodput = benchmark(10s);
if (goodput == 0)
throw runtime_error("No data received");
const size_t threshold = 1000; // 1 MB/s;
if (goodput < threshold)
throw runtime_error("Goodput is too low");
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
try { try {
@ -51,5 +66,16 @@ int main(int argc, char **argv) {
return -1; return -1;
} }
#endif #endif
try {
cout << endl << "*** Running WebRTC benchmark..." << endl;
test_benchmark();
cout << "*** Finished WebRTC benchmark" << endl;
} catch (const exception &e) {
cerr << "WebRTC benchmark failed: " << e.what() << endl;
std::this_thread::sleep_for(2s);
return -1;
}
std::this_thread::sleep_for(2s);
return 0; return 0;
} }

View File

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