Compare commits

...

167 Commits

Author SHA1 Message Date
284db56615 Bumped version to 0.5.1 2020-05-31 20:43:51 +02:00
46a3d58cb8 Fixed candidate lookup type corruption 2020-05-31 18:06:02 +02:00
dd05e4b8ce Merge pull request #81 from paullouisageneau/workflow-macos
Add MacOS build to workflow
2020-05-28 16:07:57 +02:00
f57b860649 Changed to brew reinstall 2020-05-28 16:05:17 +02:00
24e7872695 Force OpenSSL version for brew 2020-05-28 15:46:54 +02:00
8348b70ee6 Fixed const qualifier issue with X509_NAME_add_entry_by_NID() 2020-05-28 15:42:44 +02:00
9a343d5301 Set OpenSSL env variables 2020-05-28 15:39:54 +02:00
c198ffd994 Added -j2 option to make 2020-05-28 15:35:58 +02:00
6eb92301fb Added macOS build to workflow 2020-05-28 15:33:55 +02:00
22e71bd663 Bumped version to 0.5.0 2020-05-27 11:26:11 +02:00
9436757f73 Updated libjuice to v0.4.0 2020-05-27 11:25:21 +02:00
b3bba4286b Updated libjuice 2020-05-27 11:05:48 +02:00
f8df667a14 Created separate workflows for different backends 2020-05-27 11:04:34 +02:00
c18b1738b0 Merge pull request #79 from paullouisageneau/async-certificate
Asynchronous certificate generation
2020-05-27 10:38:32 +02:00
96501a8a68 Clear certificate cache on cleanup 2020-05-27 10:32:29 +02:00
3be2b0427f Replaced std::async call with custom thread invocation 2020-05-27 10:23:29 +02:00
5ae311c50a Made dependency on Threads public 2020-05-27 10:23:01 +02:00
7a2e0c267d Set flag CMAKE_THREAD_PREFER_PTHREAD 2020-05-27 10:23:01 +02:00
5e59186757 Made certificate generation async to reduce init delay 2020-05-26 15:33:19 +02:00
c5c9e24a01 Visual Studio std::min,max fix (#77)
Visual Studio std::min,max fix
2020-05-22 22:30:24 +02:00
2c953e77a9 Cleaned up leftover namespace std 2020-05-22 14:59:20 +02:00
ddb9f99ed6 Fixed call to usrsctp_finish() in Cleanup() 2020-05-22 14:55:01 +02:00
3a737e940c Enhanced handling of usrsctp shutdown 2020-05-22 14:32:53 +02:00
258135d070 Fixed uninitialized mGatheringState 2020-05-20 22:42:41 +02:00
6302d995f7 Moved gnutls_bye() in thread 2020-05-19 21:24:44 +02:00
6a7296d40d Updated Jamfile to allow GnuTLS 2020-05-17 23:13:30 +02:00
88c88bbaf5 Moved SCTP compile definition 2020-05-17 19:49:03 +02:00
c525c4b3f8 Updated libjuice 2020-05-17 16:22:35 +02:00
2c58fd7659 Loosened write lock to prevent usrsctp deadlock 2020-05-15 16:42:31 +02:00
d0f91d5cf4 Unregister transport recv callback on destruction instead of on stop 2020-05-15 16:04:34 +02:00
90ce154e15 Updated libjuice 2020-05-15 15:06:32 +02:00
b956e45f33 Fixed name on license headers 2020-05-15 09:59:41 +02:00
be1013fe7a Added missing license headers 2020-05-14 21:13:53 +02:00
d123041180 Merge branch 'dev' 2020-05-14 21:07:22 +02:00
b5511f71a5 Updated libjuice to fix #71 2020-05-14 21:02:31 +02:00
8e08ba1a29 Merge pull request #65 from stevedanOgochu/master
Adds a p2p version in c
2020-05-14 16:31:38 +02:00
3713b520db Fixed compilation warning 2020-05-14 15:39:49 +02:00
8c03c24e03 Build on pull request 2020-05-14 15:09:21 +02:00
86b9bace53 Merge pull request #70 from paullouisageneau/fix-incoming-race
Fix possible race condition on Transport::incoming()
2020-05-14 14:57:14 +02:00
049d339554 Changed IceTransport::incoming() to default transport method 2020-05-14 12:28:24 +02:00
e86ecc2c97 Unregister recv callback before stopping nice agent 2020-05-14 12:21:24 +02:00
de51b9adc7 Fixed cmake warning 2020-05-14 10:52:32 +02:00
0f0047729b Small fixes to synchronized_callback 2020-05-14 10:51:30 +02:00
2729b247fa Fixed possible race condition on Transport::incoming() 2020-05-14 10:46:06 +02:00
32800c1c1c Update offerer.c 2020-05-13 21:17:25 +02:00
f153e2c795 Update answerer.c
Fixing format
2020-05-13 20:56:33 +02:00
cb79ec0023 Update offerer.c
Fixing format
2020-05-13 20:55:50 +02:00
3b98c9d1ec Update offerer.c 2020-05-13 20:27:16 +02:00
e075e9a7ec Update answerer.c 2020-05-13 20:24:29 +02:00
c89163610b Update answerer.c 2020-05-13 19:36:12 +02:00
c0edf3bfde Update offerer.c 2020-05-13 18:26:58 +02:00
c0470d813f Update offerer.c 2020-05-13 18:16:22 +02:00
a61c173b8c Update answerer.c 2020-05-13 18:15:08 +02:00
980c456de8 Adds a p2p version in c 2020-05-13 17:49:20 +02:00
8530b20dbe Updated libjuice 2020-05-13 12:50:31 +02:00
3db8f0473b Updated libjuice 2020-05-12 14:04:42 +02:00
9546834605 Merge pull request #61 from paullouisageneau/refactor-openssl
Manually handle OpenSSL handshake timeout
2020-05-06 15:31:42 +02:00
e97efaf38d Cleanup 2020-05-04 14:27:50 +02:00
61d0f6ef73 Changed GnuTLS timeouts in accordance 2020-05-04 14:01:34 +02:00
cea564ddb3 Handle handshake timeout manually for OpenSSL 2020-05-04 12:55:47 +02:00
738cbe78a0 More realiable tests 2020-05-04 12:18:04 +02:00
b9102a156a Refactored OpenSSL loop 2020-05-04 12:18:02 +02:00
306c1a3ab6 Updated libjuice 2020-05-04 09:52:25 +02:00
bbf7119c85 Merge pull request #59 from paullouisageneau/fix-openssl-handshake-timeout
Add error checking on DTLSv1_get_timeout()
2020-05-03 19:33:46 +02:00
d6de29f7e0 Added error checking on DTLSv1_get_timeout() 2020-05-03 16:40:23 +02:00
a40a89ced8 Updated libjuice 2020-05-03 16:32:35 +02:00
b81eb92f96 Merge pull request #57 from paullouisageneau/fix-openssl-write
Fix OpenSSL write failure under load
2020-05-02 23:01:20 +02:00
85dd5b067e Fixed write BIO failure on outgoing dropped 2020-05-02 22:50:29 +02:00
6e647e64b1 Merge pull request #55 from murat-dogan/master
define WIN32_LEAN_AND_MEAN in CMakeLists.txt
2020-05-01 15:05:35 +02:00
836c7c8504 define WIN32_LEAN_AND_MEAN in CMakeLists.txt 2020-05-01 14:26:42 +03:00
b2baabd76d Merge pull request #54 from murat-dogan/master
TurnTls as default relayType for turns
2020-04-28 18:21:27 +02:00
199db5f310 TurnTls as default relayType for turns 2020-04-28 18:40:28 +03:00
5dd8826bf9 Updated libjuice to v0.3.0 2020-04-28 15:46:06 +02:00
0f934aca8c Merge pull request #53 from murat-dogan/master
proxy support
2020-04-28 14:44:05 +02:00
3e7ee70b7e Add ProxyServer constructor 2020-04-28 15:14:55 +03:00
44361714a5 proxyServer param as optional 2020-04-28 14:36:37 +03:00
56bd8c98b3 proxy support 2020-04-27 19:06:43 +03:00
49d509f2d1 Updated libjuice 2020-04-27 11:02:35 +02:00
d446f49d5f Merge pull request #50 from murat-dogan/stats
Stats
2020-04-27 10:59:02 +02:00
070582d87a rtt as optional & delete const 2020-04-27 11:25:48 +03:00
9f4a265ef0 fix rtt & bytes received 2020-04-26 21:41:36 +03:00
2e33fef88d Merge branch 'master' of https://github.com/paullouisageneau/libdatachannel into stats 2020-04-26 21:13:25 +03:00
39392c52a7 Merge pull request #49 from murat-dogan/master
Do not free candidate memory
2020-04-26 19:00:30 +02:00
cd343cd9ea provide socket address 2020-04-26 19:34:52 +03:00
9f305a6b01 Do not free candidate memory 2020-04-26 17:17:34 +03:00
dee0074270 reviews 2020-04-26 17:16:12 +03:00
9e36b5f4d6 Merge branch 'master' of https://github.com/paullouisageneau/libdatachannel into stats 2020-04-26 16:46:17 +03:00
17ba9af2e1 Fixed compilation with libjuice 2020-04-26 15:07:15 +02:00
7c667cafee Merge pull request #47 from murat-dogan/master
Get Selected Candidate Pair Info
2020-04-26 15:00:06 +02:00
782efabaea pull upstream 2020-04-26 15:38:21 +03:00
011d1199a2 Merge branch 'master' of https://github.com/paullouisageneau/libdatachannel into stats 2020-04-26 15:37:24 +03:00
94561ec7e5 Stats initial commit 2020-04-26 15:33:30 +03:00
6173d18da4 Camel case fix 2020-04-26 14:42:06 +03:00
1226d99c72 Merge pull request #48 from paullouisageneau/port-range
Support for port range with libjuice
2020-04-26 12:23:12 +02:00
67218d8e23 Cleanup double iceServers example line 2020-04-26 12:16:33 +02:00
20d1a03380 Added support for port range with libjuice 2020-04-26 12:14:10 +02:00
dffca48e69 Change string types to enum 2020-04-26 12:44:12 +03:00
fc595fd1bb Get Selected Candidate Pair Info 2020-04-25 22:48:51 +03:00
076cf00b8f Updated libjuice 2020-04-22 10:43:13 +02:00
a78bc9cff3 Updated libjuice 2020-04-21 13:54:05 +02:00
9ed4386e0c Use weak pointers for state callbacks 2020-04-02 23:16:38 +02:00
89655ff749 Weak bind transport callbacks for safety 2020-03-31 17:55:23 +02:00
c767e82d64 Revised transports stop method 2020-03-31 16:57:10 +02:00
ed30fd9dfb Fixed data channels shared lock usage 2020-03-31 15:49:32 +02:00
c39a4ee6c5 More tolerant wait time for tests 2020-03-31 15:22:07 +02:00
e04113f3f1 Fixed state callback and revised synchronization and deletion 2020-03-31 14:59:50 +02:00
577d048844 Remove useless init mutex 2020-03-29 22:57:04 +02:00
70cb347f3b Fixed notifications handling by setting SCTP_FRAGMENT_INTERLEAVE to 0 2020-03-29 11:29:34 +02:00
89def5120b Updated libjuice 2020-03-26 17:05:22 +01:00
327085ac50 Updated libjuice to v0.2.9 2020-03-26 16:25:55 +01:00
a6502c95c5 Bumped version to 0.4.9 2020-03-26 16:13:02 +01:00
c717b65243 Made DataChannel only keep a weak reference on PeerConnection 2020-03-26 16:10:13 +01:00
80e2115a7b Cleaned up old WSAInit call for Win32 2020-03-26 15:26:32 +01:00
6881e85071 Moved all global initialization to Init singleton 2020-03-26 15:12:11 +01:00
e5539c02fe Do not remove closed data channel from peer connection 2020-03-26 12:20:09 +01:00
920189e2bb Fixed process notification switch and added verbose logging 2020-03-25 23:03:52 +01:00
1ea4fad7c8 Replaced flush() by safeFlush() in SCTP transport destructor 2020-03-25 18:54:36 +01:00
15e986ebfe Fixed buffered amount computation 2020-03-25 11:20:32 +01:00
ea8d1317ee Implemented DTLS retransmissions with OpenSSL 2020-03-24 17:21:22 +01:00
345e7ee9b0 Added -Wno-error=format-truncation to usrsctp compilation 2020-03-24 10:55:39 +01:00
3b15363db8 Added install directive to CMakeLists 2020-03-19 10:52:19 +01:00
de52f0101d Updated libjuice 2020-03-17 16:26:39 +01:00
a74f9419a0 Bumped version to 0.4.8 2020-03-16 15:06:32 +01:00
9d8394eddf Updated libjuice to v0.2.8 2020-03-16 15:05:37 +01:00
978d3e4d09 Added missing free 2020-03-10 13:59:14 +01:00
becdaaa25b Bumped version to 0.4.7 2020-03-10 12:28:25 +01:00
b6f2176be8 Merge pull request #41 from paullouisageneau/c-api
C API update and fixes
2020-03-10 11:09:22 +00:00
f7f83aa519 Added C API test link 2020-03-10 12:04:29 +01:00
64e8957c54 Removed -g 2020-03-10 12:01:31 +01:00
f3b3208367 Added shared mutex to protect data channels map 2020-03-10 12:00:27 +01:00
ed28460e80 Added local and remote address getters to C API 2020-03-10 12:00:27 +01:00
7b5b12617d Switched libjuice debug output as verbose 2020-03-10 12:00:27 +01:00
be04d8037e Added tests for C API 2020-03-10 12:00:27 +01:00
56198372fd Pass user pointer to data channel 2020-03-10 12:00:27 +01:00
29ffb34fe8 Added missing functions to C API 2020-03-10 12:00:27 +01:00
834ea9b041 Split and cleaned up tests 2020-03-10 12:00:27 +01:00
9441f78494 Added WSAStartup call in PeerConnection and cleaned up includes 2020-03-10 12:00:27 +01:00
3367eba4fe Moved log to its own header and prevented multiple log init 2020-03-10 12:00:27 +01:00
6507542a80 Updated libjuice to v0.2.7 2020-03-10 12:00:27 +01:00
fea3297a57 Merge pull request #40 from paullouisageneau/macos
MacOS support
2020-03-10 11:00:06 +00:00
f322ab00ec Fixed includes for MacOS 2020-03-05 16:17:20 +01:00
b6374b9d07 Updated libjuice to v0.2.6 2020-03-05 16:17:20 +01:00
70fd54804d Cleanup CMakeLists 2020-03-05 16:17:00 +01:00
ff268aee60 Renamed workflow 2020-03-04 16:28:44 +01:00
91a5c608d7 Fix build.yml 2020-03-04 16:15:29 +01:00
682be73eab Update build.yml 2020-03-04 16:13:36 +01:00
fd4a6fef7f Update build.yml 2020-03-04 16:10:48 +01:00
05a06f47b0 Update build.yml 2020-03-04 16:09:17 +01:00
8e3de8a07a Create build.yml 2020-03-04 16:07:53 +01:00
dc065add0b Bumped version to 0.4.5 2020-02-27 14:06:02 +01:00
e64d4049a6 Updated libjuice to v0.2.5 2020-02-27 14:05:33 +01:00
cb3bc85474 Fixed && instead of || when EWOULDBLOCK != EAGAIN #38 2020-02-26 14:45:39 +01:00
7af3da7872 Revised handling of path MTU discovery to exclude Mac OS 2020-02-26 09:15:42 +01:00
3c77d717d2 Explicitely added COMP-NULL to GnuTLS priorities 2020-02-25 00:17:06 +01:00
6f399945fe Updated libjuice 2020-02-25 00:02:05 +01:00
c8b14b1262 Change state to failed if a transport initialization fails 2020-02-24 23:53:10 +01:00
35d4455c4f Cleaned up tests and fixed SDP reading from console 2020-02-24 11:45:36 +01:00
7d21b4b42b Guess the description type from the context (useful for tests) 2020-02-24 11:39:11 +01:00
24e9e06c5a Bumped version to 0.4.4 2020-02-23 17:30:27 +01:00
443a19d8e7 Updated libjuice to v0.2.4 with better host candidates gathering 2020-02-23 17:24:47 +01:00
83de743924 Bumped version to 0.4.3 2020-02-21 12:39:27 +01:00
1dc1de4b86 Added platforms to Readme 2020-02-21 12:39:27 +01:00
8ca7722d48 Updated libjuice to v0.2.3 with Windows compilation support 2020-02-21 12:39:25 +01:00
3079072e63 For Win32, define _WIN32_WINNT to 0x0601 (Windows 7) if undefined 2020-02-21 00:04:57 +01:00
982d1c10e1 Merge pull request #35 from murat-dogan/master
Compile support on Windows with mingw-w64
2020-02-20 22:53:15 +00:00
50b22bbf3c delete win32 directive 2020-02-20 21:06:54 +03:00
93e153398f Compile support on Windows with mingw-w64 2020-02-20 14:55:21 +03:00
48 changed files with 3136 additions and 939 deletions

40
.github/workflows/build-gnutls.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: Build and test with GnuTLS
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build-ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: install packages
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev
- name: submodules
run: git submodule update --init --recursive
- name: cmake
run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=1
- name: make
run: (cd build; make -j2)
- name: test
run: ./build/tests
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: install packages
run: brew install gnutls nettle
- name: submodules
run: git submodule update --init --recursive
- name: cmake
run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=1
env:
# hack to bypass EPERM issue on sendto()
CFLAGS: -DJUICE_ENABLE_ADDRS_LOCALHOST
- name: make
run: (cd build; make -j2)
- name: test
run: ./build/tests

24
.github/workflows/build-nice.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Build and test with libnice
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build-ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: install packages
run: sudo apt update && sudo apt install libgnutls28-dev libnice-dev
- name: submodules
run: git submodule update --init --recursive
- name: cmake
run: cmake -B build -DUSE_JUICE=0 -DUSE_GNUTLS=1
- name: make
run: (cd build; make -j2)
- name: test
run: ./build/tests

42
.github/workflows/build-openssl.yml vendored Normal file
View File

@ -0,0 +1,42 @@
name: Build and test with OpenSSL
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build-ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: install packages
run: sudo apt update && sudo apt install libssl-dev
- name: submodules
run: git submodule update --init --recursive
- name: cmake
run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=0
- name: make
run: (cd build; make -j2)
- name: test
run: ./build/tests
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: install packages
run: brew reinstall openssl@1.1
- name: submodules
run: git submodule update --init --recursive
- name: cmake
run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=0
env:
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
# hack to bypass EPERM issue on sendto()
CFLAGS: -DJUICE_ENABLE_ADDRS_LOCALHOST
- name: make
run: (cd build; make -j2)
- name: test
run: ./build/tests

View File

@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 3.7) cmake_minimum_required (VERSION 3.7)
project (libdatachannel project (libdatachannel
DESCRIPTION "WebRTC DataChannels Library" DESCRIPTION "WebRTC DataChannels Library"
VERSION 0.4.2 VERSION 0.5.1
LANGUAGES CXX) LANGUAGES CXX)
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF) option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
@ -16,6 +16,13 @@ endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
if (MSVC)
add_definitions(-DNOMINMAX)
endif()
endif()
set(LIBDATACHANNEL_SOURCES set(LIBDATACHANNEL_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp
@ -25,13 +32,35 @@ set(LIBDATACHANNEL_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/init.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/log.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
${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
) )
set(LIBDATACHANNEL_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/include.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/init.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/log.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/message.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/peerconnection.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/queue.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
)
set(TESTS_SOURCES set(TESTS_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
) )
set(TESTS_OFFERER_SOURCES set(TESTS_OFFERER_SOURCES
@ -42,10 +71,19 @@ set(TESTS_ANSWERER_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp
) )
set(THREADS_PREFER_PTHREAD_FLAG ON) set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL) add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
if (MSYS OR MINGW)
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
target_compile_definitions(usrsctp-static PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
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)
@ -55,13 +93,11 @@ set_target_properties(datachannel PROPERTIES
CXX_STANDARD 17) CXX_STANDARD 17)
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc) target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include) target_link_libraries(datachannel PUBLIC Threads::Threads)
target_link_libraries(datachannel target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic)
Threads::Threads
Usrsctp::UsrsctpStatic
)
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES}) add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
set_target_properties(datachannel-static PROPERTIES set_target_properties(datachannel-static PROPERTIES
@ -69,13 +105,16 @@ set_target_properties(datachannel-static PROPERTIES
CXX_STANDARD 17) CXX_STANDARD 17)
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc) target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include) target_link_libraries(datachannel-static PUBLIC Threads::Threads)
target_link_libraries(datachannel-static target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
Threads::Threads
Usrsctp::UsrsctpStatic if(WIN32)
) target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
endif()
if (USE_GNUTLS) if (USE_GNUTLS)
find_package(GnuTLS REQUIRED) find_package(GnuTLS REQUIRED)
@ -88,34 +127,37 @@ if (USE_GNUTLS)
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_link_libraries(datachannel GnuTLS::GnuTLS) target_link_libraries(datachannel PRIVATE GnuTLS::GnuTLS)
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1) target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
target_link_libraries(datachannel-static GnuTLS::GnuTLS) target_link_libraries(datachannel-static PRIVATE GnuTLS::GnuTLS)
else() else()
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=0) target_compile_definitions(datachannel PRIVATE USE_GNUTLS=0)
target_link_libraries(datachannel OpenSSL::SSL) target_link_libraries(datachannel PRIVATE OpenSSL::SSL)
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=0) target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=0)
target_link_libraries(datachannel-static OpenSSL::SSL) target_link_libraries(datachannel-static PRIVATE OpenSSL::SSL)
endif() endif()
if (USE_JUICE) if (USE_JUICE)
add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL) add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
target_compile_definitions(datachannel PRIVATE USE_JUICE=1) target_compile_definitions(datachannel PRIVATE USE_JUICE=1)
target_link_libraries(datachannel LibJuice::LibJuiceStatic) target_link_libraries(datachannel PRIVATE LibJuice::LibJuiceStatic)
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=1) target_compile_definitions(datachannel-static PRIVATE USE_JUICE=1)
target_link_libraries(datachannel-static LibJuice::LibJuiceStatic) target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
else() else()
find_package(LibNice REQUIRED) find_package(LibNice REQUIRED)
target_compile_definitions(datachannel PRIVATE USE_JUICE=0) target_compile_definitions(datachannel PRIVATE USE_JUICE=0)
target_link_libraries(datachannel LibNice::LibNice) target_link_libraries(datachannel PRIVATE LibNice::LibNice)
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=0) target_compile_definitions(datachannel-static PRIVATE USE_JUICE=0)
target_link_libraries(datachannel-static LibNice::LibNice) target_link_libraries(datachannel-static PRIVATE LibNice::LibNice)
endif() endif()
add_library(LibDataChannel::LibDataChannel ALIAS datachannel) add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static) add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
install(TARGETS datachannel LIBRARY DESTINATION lib)
install(FILES ${LIBDATACHANNEL_HEADERS} DESTINATION include/rtc)
# Main Test # Main Test
add_executable(datachannel-tests ${TESTS_SOURCES}) add_executable(datachannel-tests ${TESTS_SOURCES})
set_target_properties(datachannel-tests PROPERTIES set_target_properties(datachannel-tests PROPERTIES
@ -138,6 +180,6 @@ add_executable(datachannel-answerer ${TESTS_ANSWERER_SOURCES})
set_target_properties(datachannel-answerer PROPERTIES set_target_properties(datachannel-answerer PROPERTIES
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
CXX_STANDARD 17) CXX_STANDARD 17)
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME datachannel) set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME answerer)
target_link_libraries(datachannel-answerer datachannel) target_link_libraries(datachannel-answerer datachannel)

27
Jamfile
View File

@ -1,3 +1,5 @@
import feature : feature ;
project libdatachannel ; project libdatachannel ;
path-constant CWD : . ; path-constant CWD : . ;
@ -5,21 +7,26 @@ lib libdatachannel
: # sources : # sources
[ glob ./src/*.cpp ] [ glob ./src/*.cpp ]
: # requirements : # requirements
<cxxstd>17
<include>./include/rtc <include>./include/rtc
<define>USE_GNUTLS=0
<define>USE_JUICE=1 <define>USE_JUICE=1
<cxxflags>"`pkg-config --cflags openssl`"
<library>/libdatachannel//usrsctp <library>/libdatachannel//usrsctp
<library>/libdatachannel//juice <library>/libdatachannel//juice
<library>/libdatachannel//plog
: # default build : # default build
<link>static <link>static
: # usage requirements : # usage requirements
<include>./include <include>./include
<library>/libdatachannel//plog <library>/libdatachannel//plog
<cxxflags>-pthread <cxxflags>-pthread
<linkflags>"`pkg-config --libs openssl`"
; ;
feature crypto : openssl gnutls : composite propagated ;
feature.compose <crypto>openssl
: <define>USE_GNUTLS=0 ;
feature.compose <crypto>gnutls
: <define>USE_GNUTLS=1 ;
alias plog alias plog
: # no sources : # no sources
: # no build requirements : # no build requirements
@ -57,9 +64,21 @@ actions make_libusrsctp
} }
make libjuice.a : : @make_libjuice ; make libjuice.a : : @make_libjuice ;
rule make_libjuice ( targets * : sources * : properties * )
{
if <crypto>gnutls in $(properties)
{
MAKEOPTS on $(targets) = "USE_NETTLE=1" ;
}
else
{
MAKEOPTS on $(targets) = "USE_NETTLE=0" ;
}
}
actions make_libjuice actions make_libjuice
{ {
(cd $(CWD)/deps/libjuice && make USE_NETTLE=0) (cd $(CWD)/deps/libjuice && make $(MAKEOPTS))
cp $(CWD)/deps/libjuice/libjuice.a $(<) cp $(CWD)/deps/libjuice/libjuice.a $(<)
} }

View File

@ -5,7 +5,7 @@ CXX=$(CROSS)g++
AR=$(CROSS)ar AR=$(CROSS)ar
RM=rm -f RM=rm -f
CXXFLAGS=-std=c++17 CXXFLAGS=-std=c++17
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member CPPFLAGS=-O2 -pthread -fPIC -Wall
LDFLAGS=-pthread LDFLAGS=-pthread
LIBS= LIBS=
LOCALLIBS=libusrsctp.a LOCALLIBS=libusrsctp.a
@ -44,6 +44,9 @@ LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
SRCS=$(shell printf "%s " src/*.cpp) SRCS=$(shell printf "%s " src/*.cpp)
OBJS=$(subst .cpp,.o,$(SRCS)) OBJS=$(subst .cpp,.o,$(SRCS))
TEST_SRCS=$(shell printf "%s " test/*.cpp)
TEST_OBJS=$(subst .cpp,.o,$(TEST_SRCS))
all: $(NAME).a $(NAME).so tests all: $(NAME).a $(NAME).so tests
src/%.o: src/%.cpp src/%.o: src/%.cpp
@ -60,8 +63,8 @@ $(NAME).a: $(OBJS)
$(NAME).so: $(LOCALLIBS) $(OBJS) $(NAME).so: $(LOCALLIBS) $(OBJS)
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS) $(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
tests: $(NAME).a test/main.o tests: $(NAME).a $(TEST_OBJS)
$(CXX) $(LDFLAGS) -o $@ test/main.o $(NAME).a $(LDLIBS) $(CXX) $(LDFLAGS) -o $@ $(TEST_OBJS) $(NAME).a $(LDLIBS)
clean: clean:
-$(RM) include/rtc/*.d *.d -$(RM) include/rtc/*.d *.d
@ -83,7 +86,7 @@ dist-clean: clean
libusrsctp.a: libusrsctp.a:
cd $(USRSCTP_DIR) && \ cd $(USRSCTP_DIR) && \
./bootstrap && \ ./bootstrap && \
./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS)" && \ ./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS) -Wno-error=format-truncation" && \
make make
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a . cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .

View File

@ -1,6 +1,6 @@
# libdatachannel - C/C++ WebRTC DataChannels # libdatachannel - C/C++ WebRTC DataChannels
libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings. 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 API, in order to ease the design of cross-environment applications. libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings for POSIX platforms 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 API, in order to ease the design of cross-environment applications.
This projet is originally inspired by [librtcdcpp](https://github.com/chadnickbok/librtcdcpp), however it is a complete rewrite from scratch, because the messy architecture of librtcdcpp made solving its implementation issues difficult. This projet is originally inspired by [librtcdcpp](https://github.com/chadnickbok/librtcdcpp), however it is a complete rewrite from scratch, because the messy architecture of librtcdcpp made solving its implementation issues difficult.
@ -79,11 +79,11 @@ MY_ON_RECV_CANDIDATE_FROM_REMOTE([pc](string candidate, string mid) {
### Observe the PeerConnection state ### Observe the PeerConnection state
```cpp ```cpp
pc->onStateChanged([](PeerConnection::State state) { pc->onStateChange([](PeerConnection::State state) {
cout << "State: " << state << endl; cout << "State: " << state << endl;
}); });
pc->onGatheringStateChanged([](PeerConnection::GatheringState state) { pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "Gathering state: " << state << endl; cout << "Gathering state: " << state << endl;
}); });
@ -114,5 +114,7 @@ pc->onDataChannel([&dc](shared_ptr<rtc::DataChannel> incoming) {
``` ```
See [test/main.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/main.cpp) for a complete local connection example. See [test/connectivity.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/connectivity.cpp) for a complete local connection example.
See [test/cpai.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/capi.cpp) for a C API example.

View File

@ -10,7 +10,7 @@ if (NOT TARGET LibNice::LibNice)
HINTS ${PC_LIBNICE_LIBDIR} ${PC_LIBNICE_LIBRARY_DIRS}) HINTS ${PC_LIBNICE_LIBDIR} ${PC_LIBNICE_LIBRARY_DIRS})
include(FindPackageHandleStandardArgs) include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libnice DEFAULT_MSG find_package_handle_standard_args(LibNice DEFAULT_MSG
LIBNICE_LIBRARY LIBNICE_INCLUDE_DIR) LIBNICE_LIBRARY LIBNICE_INCLUDE_DIR)
mark_as_advanced(LIBNICE_INCLUDE_DIR LIBNICE_LIBRARY) mark_as_advanced(LIBNICE_INCLUDE_DIR LIBNICE_LIBRARY)

2
deps/libjuice vendored

View File

@ -25,6 +25,15 @@
namespace rtc { namespace rtc {
enum class CandidateType { Host = 0, ServerReflexive, PeerReflexive, Relayed };
enum class CandidateTransportType { Udp = 0, TcpActive, TcpPassive, TcpSo };
struct CandidateInfo {
string address;
int port;
CandidateType type;
CandidateTransportType transportType;
};
class Candidate { class Candidate {
public: public:
Candidate(string candidate, string mid = ""); Candidate(string candidate, string mid = "");
@ -46,6 +55,8 @@ private:
} // namespace rtc } // namespace rtc
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate); std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type);
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType);
#endif #endif

View File

@ -31,12 +31,10 @@ class Channel {
public: public:
virtual void close() = 0; virtual void close() = 0;
virtual bool send(const std::variant<binary, string> &data) = 0; // returns false if buffered virtual bool send(const std::variant<binary, string> &data) = 0; // returns false if buffered
virtual std::optional<std::variant<binary, string>> receive() = 0; // only if onMessage unset
virtual bool isOpen() const = 0; virtual bool isOpen() const = 0;
virtual bool isClosed() const = 0; virtual bool isClosed() const = 0;
virtual size_t maxMessageSize() const; // max message size in a call to send
virtual size_t availableAmount() const; // total size available to receive
virtual size_t bufferedAmount() const; // total size buffered to send virtual size_t bufferedAmount() const; // total size buffered to send
void onOpen(std::function<void()> callback); void onOpen(std::function<void()> callback);
@ -47,11 +45,14 @@ public:
void onMessage(std::function<void(const binary &data)> binaryCallback, void onMessage(std::function<void(const binary &data)> binaryCallback,
std::function<void(const string &data)> stringCallback); std::function<void(const string &data)> stringCallback);
void onAvailable(std::function<void()> callback);
void onBufferedAmountLow(std::function<void()> callback); void onBufferedAmountLow(std::function<void()> callback);
void setBufferedAmountLowThreshold(size_t amount); void setBufferedAmountLowThreshold(size_t amount);
// Extended API
virtual std::optional<std::variant<binary, string>> receive() = 0; // only if onMessage unset
virtual size_t availableAmount() const; // total size available to receive
void onAvailable(std::function<void()> callback);
protected: protected:
virtual void triggerOpen(); virtual void triggerOpen();
virtual void triggerClosed(); virtual void triggerClosed();
@ -59,6 +60,8 @@ protected:
virtual void triggerAvailable(size_t count); virtual void triggerAvailable(size_t count);
virtual void triggerBufferedAmount(size_t amount); virtual void triggerBufferedAmount(size_t amount);
void resetCallbacks();
private: private:
synchronized_callback<> mOpenCallback; synchronized_callback<> mOpenCallback;
synchronized_callback<> mClosedCallback; synchronized_callback<> mClosedCallback;

View File

@ -51,8 +51,22 @@ struct IceServer {
RelayType relayType; RelayType relayType;
}; };
struct ProxyServer {
enum class Type { None = 0, Socks5, Http, Last = Http };
ProxyServer(Type type_, string ip_, uint16_t port_, string username_ = "",
string password_ = "");
Type type;
string ip;
uint16_t port;
string username;
string password;
};
struct Configuration { struct Configuration {
std::vector<IceServer> iceServers; std::vector<IceServer> iceServers;
std::optional<ProxyServer> proxyServer;
bool enableIceTcp = false; bool enableIceTcp = false;
uint16_t portRangeBegin = 1024; uint16_t portRangeBegin = 1024;
uint16_t portRangeEnd = 65535; uint16_t portRangeEnd = 65535;

View File

@ -38,42 +38,40 @@ class PeerConnection;
class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel { class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel {
public: public:
DataChannel(std::shared_ptr<PeerConnection> pc, unsigned int stream, string label, DataChannel(std::weak_ptr<PeerConnection> pc, unsigned int stream, string label,
string protocol, Reliability reliability); string protocol, Reliability reliability);
DataChannel(std::shared_ptr<PeerConnection> pc, std::shared_ptr<SctpTransport> transport, DataChannel(std::weak_ptr<PeerConnection> pc, std::weak_ptr<SctpTransport> transport,
unsigned int stream); unsigned int stream);
~DataChannel(); ~DataChannel();
void close(void) override;
bool send(const std::variant<binary, string> &data) override;
bool send(const byte *data, size_t size);
template <typename Buffer> bool sendBuffer(const Buffer &buf);
template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
std::optional<std::variant<binary, string>> receive() override;
bool isOpen(void) const override;
bool isClosed(void) const override;
size_t availableAmount() const override;
size_t maxMessageSize() const; // maximum message size in a call to send or sendBuffer
unsigned int stream() const; unsigned int stream() const;
string label() const; string label() const;
string protocol() const; string protocol() const;
Reliability reliability() const; Reliability reliability() const;
void close(void) override;
bool send(const std::variant<binary, string> &data) override;
bool send(const byte *data, size_t size);
template <typename Buffer> bool sendBuffer(const Buffer &buf);
template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
bool isOpen(void) const override;
bool isClosed(void) const override;
size_t maxMessageSize() const override;
// Extended API
size_t availableAmount() const override;
std::optional<std::variant<binary, string>> receive() override;
private: private:
void remoteClose(); void remoteClose();
void open(std::shared_ptr<SctpTransport> sctpTransport); void open(std::shared_ptr<SctpTransport> transport);
bool outgoing(mutable_message_ptr message); bool outgoing(mutable_message_ptr message);
void incoming(message_ptr message); void incoming(message_ptr message);
void processOpenMessage(message_ptr message); void processOpenMessage(message_ptr message);
const std::shared_ptr<PeerConnection> mPeerConnection; const std::weak_ptr<PeerConnection> mPeerConnection;
std::shared_ptr<SctpTransport> mSctpTransport; std::weak_ptr<SctpTransport> mSctpTransport;
unsigned int mStream; unsigned int mStream;
string mLabel; string mLabel;

View File

@ -35,6 +35,7 @@ public:
enum class Role { ActPass = 0, Passive = 1, Active = 2 }; enum class Role { ActPass = 0, Passive = 1, Active = 2 };
Description(const string &sdp, const string &typeString = ""); Description(const string &sdp, const string &typeString = "");
Description(const string &sdp, Type type);
Description(const string &sdp, Type type, Role role); Description(const string &sdp, Type type, Role role);
Type type() const; Type type() const;
@ -47,6 +48,7 @@ public:
std::optional<size_t> maxMessageSize() const; std::optional<size_t> maxMessageSize() const;
bool trickleEnabled() const; bool trickleEnabled() const;
void hintType(Type type);
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);

View File

@ -19,6 +19,14 @@
#ifndef RTC_INCLUDE_H #ifndef RTC_INCLUDE_H
#define RTC_INCLUDE_H #define RTC_INCLUDE_H
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0602
#endif
#endif
#include "log.hpp"
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <memory> #include <memory>
@ -27,9 +35,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "plog/Appenders/ColorConsoleAppender.h"
#include "plog/Log.h"
namespace rtc { namespace rtc {
using std::byte; using std::byte;
@ -44,8 +49,6 @@ using std::uint32_t;
using std::uint64_t; using std::uint64_t;
using std::uint8_t; using std::uint8_t;
// Constants
const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length
@ -53,29 +56,6 @@ 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
// Log
enum class LogLevel { // Don't change, it must match plog severity
None = 0,
Fatal = 1,
Error = 2,
Warning = 3,
Info = 4,
Debug = 5,
Verbose = 6
};
inline void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr) {
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
if (!appender)
appender = &consoleAppender;
plog::init(severity, appender);
PLOG_DEBUG << "Logger initialized";
}
inline void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
// Utils
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...>;
@ -83,11 +63,12 @@ template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
template <typename... P> class synchronized_callback { template <typename... P> class synchronized_callback {
public: public:
synchronized_callback() = default; synchronized_callback() = default;
synchronized_callback(std::function<void(P...)> func) { *this = std::move(func); };
~synchronized_callback() { *this = nullptr; } ~synchronized_callback() { *this = nullptr; }
synchronized_callback &operator=(std::function<void(P...)> func) { synchronized_callback &operator=(std::function<void(P...)> func) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
callback = func; callback = std::move(func);
return *this; return *this;
} }
@ -97,7 +78,10 @@ public:
callback(args...); callback(args...);
} }
operator bool() const { return callback ? true : false; } operator bool() const {
std::lock_guard lock(mutex);
return callback ? true : false;
}
private: private:
std::function<void(P...)> callback; std::function<void(P...)> callback;

50
include/rtc/init.hpp Normal file
View File

@ -0,0 +1,50 @@
/**
* 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_INIT_H
#define RTC_INIT_H
#include "include.hpp"
#include <mutex>
namespace rtc {
class Init;
using init_token = std::shared_ptr<Init>;
class Init {
public:
static init_token Token();
static void Cleanup();
~Init();
private:
Init();
static std::weak_ptr<Init> Weak;
static init_token Global;
static std::mutex Mutex;
};
inline void Cleanup() { Init::Cleanup(); }
} // namespace rtc
#endif

40
include/rtc/log.hpp Normal file
View File

@ -0,0 +1,40 @@
/**
* 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
*/
#ifndef RTC_LOG_H
#define RTC_LOG_H
#include "plog/Log.h"
namespace rtc {
enum class LogLevel { // Don't change, it must match plog severity
None = 0,
Fatal = 1,
Error = 2,
Warning = 3,
Info = 4,
Debug = 5,
Verbose = 6
};
void InitLogger(LogLevel level);
void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr);
}
#endif

View File

@ -28,9 +28,9 @@
namespace rtc { namespace rtc {
struct Message : binary { struct Message : binary {
enum Type { Binary, String, Control }; enum Type { Binary, String, Control, Reset };
Message(size_t size) : binary(size), type(Binary) {} Message(size_t size, Type type_ = Binary) : binary(size), type(type_) {}
template <typename Iterator> template <typename Iterator>
Message(Iterator begin_, Iterator end_, Type type_ = Binary) Message(Iterator begin_, Iterator end_, Type type_ = Binary)
@ -46,7 +46,7 @@ using mutable_message_ptr = std::shared_ptr<Message>;
using message_callback = std::function<void(message_ptr message)>; using message_callback = std::function<void(message_ptr message)>;
constexpr auto message_size_func = [](const message_ptr &m) -> size_t { constexpr auto message_size_func = [](const message_ptr &m) -> size_t {
return m->type != Message::Control ? m->size() : 0; return m->type == Message::Binary || m->type == Message::String ? m->size() : 0;
}; };
template <typename Iterator> template <typename Iterator>
@ -59,6 +59,15 @@ message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Mess
return message; return message;
} }
inline message_ptr make_message(size_t size, Message::Type type = Message::Binary,
unsigned int stream = 0,
std::shared_ptr<Reliability> reliability = nullptr) {
auto message = std::make_shared<Message>(size, type);
message->stream = stream;
message->reliability = reliability;
return message;
}
} // namespace rtc } // namespace rtc
#endif #endif

View File

@ -24,14 +24,17 @@
#include "datachannel.hpp" #include "datachannel.hpp"
#include "description.hpp" #include "description.hpp"
#include "include.hpp" #include "include.hpp"
#include "init.hpp"
#include "message.hpp" #include "message.hpp"
#include "reliability.hpp" #include "reliability.hpp"
#include "rtc.hpp" #include "rtc.hpp"
#include <atomic> #include <atomic>
#include <functional> #include <functional>
#include <future>
#include <list> #include <list>
#include <mutex> #include <mutex>
#include <shared_mutex>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
@ -42,6 +45,9 @@ class IceTransport;
class DtlsTransport; class DtlsTransport;
class SctpTransport; class SctpTransport;
using certificate_ptr = std::shared_ptr<Certificate>;
using future_certificate_ptr = std::shared_future<certificate_ptr>;
class PeerConnection : public std::enable_shared_from_this<PeerConnection> { class PeerConnection : public std::enable_shared_from_this<PeerConnection> {
public: public:
enum class State : int { enum class State : int {
@ -50,14 +56,13 @@ public:
Connected = RTC_CONNECTED, Connected = RTC_CONNECTED,
Disconnected = RTC_DISCONNECTED, Disconnected = RTC_DISCONNECTED,
Failed = RTC_FAILED, Failed = RTC_FAILED,
Closed = RTC_CLOSED, Closed = RTC_CLOSED
Destroying = RTC_DESTROYING
}; };
enum class GatheringState : int { enum class GatheringState : int {
New = RTC_GATHERING_NEW, New = RTC_GATHERING_NEW,
InProgress = RTC_GATHERING_INPROGRESS, InProgress = RTC_GATHERING_INPROGRESS,
Complete = RTC_GATHERING_COMPLETE, Complete = RTC_GATHERING_COMPLETE
}; };
PeerConnection(void); PeerConnection(void);
@ -86,15 +91,31 @@ public:
void onStateChange(std::function<void(State state)> callback); void onStateChange(std::function<void(State state)> callback);
void onGatheringStateChange(std::function<void(GatheringState state)> callback); void onGatheringStateChange(std::function<void(GatheringState state)> callback);
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
// Stats
void clearStats();
size_t bytesSent();
size_t bytesReceived();
std::optional<std::chrono::milliseconds> rtt();
private: private:
init_token mInitToken = Init::Token();
std::shared_ptr<IceTransport> initIceTransport(Description::Role role); std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
std::shared_ptr<DtlsTransport> initDtlsTransport(); std::shared_ptr<DtlsTransport> initDtlsTransport();
std::shared_ptr<SctpTransport> initSctpTransport(); std::shared_ptr<SctpTransport> initSctpTransport();
void closeTransports();
void endLocalCandidates(); void endLocalCandidates();
bool checkFingerprint(const std::string &fingerprint) const; bool checkFingerprint(const std::string &fingerprint) const;
void forwardMessage(message_ptr message); void forwardMessage(message_ptr message);
void forwardBufferedAmount(uint16_t stream, size_t amount); void forwardBufferedAmount(uint16_t stream, size_t amount);
std::shared_ptr<DataChannel> emplaceDataChannel(Description::Role role, const string &label,
const string &protocol,
const Reliability &reliability);
std::shared_ptr<DataChannel> findDataChannel(uint16_t stream);
void iterateDataChannels(std::function<void(std::shared_ptr<DataChannel> channel)> func); void iterateDataChannels(std::function<void(std::shared_ptr<DataChannel> channel)> func);
void openDataChannels(); void openDataChannels();
void closeDataChannels(); void closeDataChannels();
@ -103,11 +124,13 @@ private:
void processLocalDescription(Description description); void processLocalDescription(Description description);
void processLocalCandidate(Candidate candidate); void processLocalCandidate(Candidate candidate);
void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel); void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
void changeState(State state); bool changeState(State state);
void changeGatheringState(GatheringState state); bool changeGatheringState(GatheringState state);
void resetCallbacks();
const Configuration mConfig; const Configuration mConfig;
const std::shared_ptr<Certificate> mCertificate; const future_certificate_ptr mCertificate;
std::optional<Description> mLocalDescription, mRemoteDescription; std::optional<Description> mLocalDescription, mRemoteDescription;
mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex; mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
@ -115,9 +138,9 @@ private:
std::shared_ptr<IceTransport> mIceTransport; std::shared_ptr<IceTransport> mIceTransport;
std::shared_ptr<DtlsTransport> mDtlsTransport; std::shared_ptr<DtlsTransport> mDtlsTransport;
std::shared_ptr<SctpTransport> mSctpTransport; std::shared_ptr<SctpTransport> mSctpTransport;
std::recursive_mutex mInitMutex;
std::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels; std::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels;
std::shared_mutex mDataChannelsMutex;
std::atomic<State> mState; std::atomic<State> mState;
std::atomic<GatheringState> mGatheringState; std::atomic<GatheringState> mGatheringState;

View File

@ -41,12 +41,10 @@ public:
bool empty() const; bool empty() const;
size_t size() const; // elements size_t size() const; // elements
size_t amount() const; // amount size_t amount() const; // amount
void push(const T &element); void push(T element);
void push(T &&element);
std::optional<T> pop(); std::optional<T> pop();
std::optional<T> peek(); std::optional<T> peek();
void wait(); bool wait(const std::optional<std::chrono::milliseconds> &duration = nullopt);
void wait(const std::chrono::milliseconds &duration);
private: private:
const size_t mLimit; const size_t mLimit;
@ -88,9 +86,7 @@ template <typename T> size_t Queue<T>::amount() const {
return mAmount; return mAmount;
} }
template <typename T> void Queue<T>::push(const T &element) { push(T{element}); } template <typename T> void Queue<T>::push(T element) {
template <typename T> void Queue<T>::push(T &&element) {
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; }); mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
if (!mStopping) { if (!mStopping) {
@ -122,14 +118,14 @@ template <typename T> std::optional<T> Queue<T>::peek() {
} }
} }
template <typename T> void Queue<T>::wait() { template <typename T>
bool Queue<T>::wait(const std::optional<std::chrono::milliseconds> &duration) {
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
if (duration)
mPopCondition.wait_for(lock, *duration, [this]() { return !mQueue.empty() || mStopping; });
else
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; }); mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
} return !mStopping;
template <typename T> void Queue<T>::wait(const std::chrono::milliseconds &duration) {
std::unique_lock lock(mMutex);
mPopCondition.wait_for(lock, duration, [this]() { return !mQueue.empty() || mStopping; });
} }
} // namespace rtc } // namespace rtc

View File

@ -23,6 +23,8 @@
extern "C" { extern "C" {
#endif #endif
#include <stdint.h>
// libdatachannel C API // libdatachannel C API
typedef enum { typedef enum {
@ -31,15 +33,14 @@ typedef enum {
RTC_CONNECTED = 2, RTC_CONNECTED = 2,
RTC_DISCONNECTED = 3, RTC_DISCONNECTED = 3,
RTC_FAILED = 4, RTC_FAILED = 4,
RTC_CLOSED = 5, RTC_CLOSED = 5
RTC_DESTROYING = 6 // internal } rtcState;
} rtc_state_t;
typedef enum { typedef enum {
RTC_GATHERING_NEW = 0, RTC_GATHERING_NEW = 0,
RTC_GATHERING_INPROGRESS = 1, RTC_GATHERING_INPROGRESS = 1,
RTC_GATHERING_COMPLETE = 2 RTC_GATHERING_COMPLETE = 2
} rtc_gathering_state_t; } rtcGatheringState;
// Don't change, it must match plog severity // Don't change, it must match plog severity
typedef enum { typedef enum {
@ -50,32 +51,72 @@ typedef enum {
RTC_LOG_INFO = 4, RTC_LOG_INFO = 4,
RTC_LOG_DEBUG = 5, RTC_LOG_DEBUG = 5,
RTC_LOG_VERBOSE = 6 RTC_LOG_VERBOSE = 6
} rtc_log_level_t; } rtcLogLevel;
void rtcInitLogger(rtc_log_level_t level); typedef struct {
const char **iceServers;
int iceServersCount;
uint16_t portRangeBegin;
uint16_t portRangeEnd;
} rtcConfiguration;
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount); typedef void (*dataChannelCallbackFunc)(int dc, void *ptr);
void rtcDeletePeerConnection(int pc); typedef void (*descriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
int rtcCreateDataChannel(int pc, const char *label); typedef void (*candidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
void rtcDeleteDataChannel(int dc); typedef void (*stateChangeCallbackFunc)(rtcState state, void *ptr);
void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *)); typedef void (*gatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *, typedef void (*openCallbackFunc)(void *ptr);
void *)); typedef void (*closedCallbackFunc)(void *ptr);
void rtcSetLocalCandidateCallback(int pc, typedef void (*errorCallbackFunc)(const char *error, void *ptr);
void (*candidateCallback)(const char *, const char *, void *)); typedef void (*messageCallbackFunc)(const char *message, int size, void *ptr);
void rtcSetStateChangeCallback(int pc, void (*stateCallback)(rtc_state_t state, void *)); typedef void (*bufferedAmountLowCallbackFunc)(void *ptr);
void rtcSetGatheringStateChangeCallback(int pc, typedef void (*availableCallbackFunc)(void *ptr);
void (*gatheringStateCallback)(rtc_gathering_state_t state,
void *)); // Log
void rtcSetRemoteDescription(int pc, const char *sdp, const char *type); void rtcInitLogger(rtcLogLevel level);
void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid);
int rtcGetDataChannelLabel(int dc, char *data, int size); // User pointer
void rtcSetOpenCallback(int dc, void (*openCallback)(void *));
void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *));
void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *));
int rtcSendMessage(int dc, const char *data, int size);
void rtcSetUserPointer(int i, void *ptr); void rtcSetUserPointer(int i, void *ptr);
// PeerConnection
int rtcCreatePeerConnection(const rtcConfiguration *config);
int rtcDeletePeerConnection(int pc);
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb);
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb);
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb);
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb);
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb);
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
int rtcGetLocalAddress(int pc, char *buffer, int size);
int rtcGetRemoteAddress(int pc, char *buffer, int size);
// DataChannel
int rtcCreateDataChannel(int pc, const char *label);
int rtcDeleteDataChannel(int dc);
int rtcGetDataChannelLabel(int dc, char *buffer, int size);
int rtcSetOpenCallback(int dc, openCallbackFunc cb);
int rtcSetClosedCallback(int dc, closedCallbackFunc cb);
int rtcSetErrorCallback(int dc, errorCallbackFunc cb);
int rtcSetMessageCallback(int dc, messageCallbackFunc cb);
int rtcSendMessage(int dc, const char *data, int size);
int rtcGetBufferedAmount(int dc); // total size buffered to send
int rtcSetBufferedAmountLowThreshold(int dc, int amount);
int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb);
// DataChannel extended API
int rtcGetAvailableAmount(int dc); // total size available to receive
int rtcSetAvailableCallback(int dc, availableCallbackFunc cb);
int rtcReceiveMessage(int dc, char *buffer, int *size);
// Cleanup
void rtcCleanup();
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View File

@ -17,6 +17,10 @@
*/ */
// C++ API // C++ API
#include "include.hpp"
#include "init.hpp" // for rtc::Cleanup()
#include "log.hpp"
//
#include "datachannel.hpp" #include "datachannel.hpp"
#include "peerconnection.hpp" #include "peerconnection.hpp"

View File

@ -22,8 +22,14 @@
#include <array> #include <array>
#include <sstream> #include <sstream>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <netdb.h> #include <netdb.h>
#include <sys/socket.h> #include <sys/socket.h>
#endif
#include <sys/types.h> #include <sys/types.h>
using std::array; using std::array;
@ -54,12 +60,19 @@ bool Candidate::resolve(ResolveMode mode) {
if (mIsResolved) if (mIsResolved)
return true; return true;
PLOG_VERBOSE << "Resolving candidate (mode="
<< (mode == ResolveMode::Simple ? "simple" : "lookup")
<< "): " << mCandidate;
// See RFC 8445 for format // See RFC 8445 for format
std::stringstream ss(mCandidate); std::istringstream iss(mCandidate);
int component{0}, priority{0}; int component{0}, priority{0};
string foundation, transport, node, service, typ_, type; string foundation, transport, node, service, typ_, type;
if (ss >> foundation >> component >> transport >> priority && if (iss >> foundation >> component >> transport >> priority &&
ss >> node >> service >> typ_ >> type && typ_ == "typ") { iss >> node >> service >> typ_ >> type && typ_ == "typ") {
string left;
std::getline(iss, left);
// Try to resolve the node // Try to resolve the node
struct addrinfo hints = {}; struct addrinfo hints = {};
@ -88,15 +101,13 @@ bool Candidate::resolve(ResolveMode mode) {
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN, if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
servbuffer, MAX_NUMERICSERV_LEN, servbuffer, MAX_NUMERICSERV_LEN,
NI_NUMERICHOST | NI_NUMERICSERV) == 0) { NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
string left;
std::getline(ss, left);
const char sp{' '}; const char sp{' '};
ss.clear(); std::ostringstream oss;
ss << foundation << sp << component << sp << transport << sp << priority; oss << foundation << sp << component << sp << transport << sp << priority;
ss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type; oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
if (!left.empty()) oss << left;
ss << left; mCandidate = oss.str();
mCandidate = ss.str(); PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
return mIsResolved = true; return mIsResolved = true;
} }
} }
@ -125,3 +136,33 @@ Candidate::operator string() const {
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) { std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
return out << std::string(candidate); return out << std::string(candidate);
} }
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type) {
switch (type) {
case rtc::CandidateType::Host:
return out << "Host";
case rtc::CandidateType::PeerReflexive:
return out << "PeerReflexive";
case rtc::CandidateType::Relayed:
return out << "Relayed";
case rtc::CandidateType::ServerReflexive:
return out << "ServerReflexive";
default:
return out << "Unknown";
}
}
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType) {
switch (transportType) {
case rtc::CandidateTransportType::TcpActive:
return out << "TcpActive";
case rtc::CandidateTransportType::TcpPassive:
return out << "TcpPassive";
case rtc::CandidateTransportType::TcpSo:
return out << "TcpSo";
case rtc::CandidateTransportType::Udp:
return out << "Udp";
default:
return out << "Unknown";
}
}

View File

@ -141,14 +141,9 @@ string make_fingerprint(gnutls_x509_crt_t crt) {
return oss.str(); return oss.str();
} }
shared_ptr<Certificate> make_certificate(const string &commonName) { namespace {
static std::unordered_map<string, shared_ptr<Certificate>> cache;
static std::mutex cacheMutex;
std::lock_guard lock(cacheMutex);
if (auto it = cache.find(commonName); it != cache.end())
return it->second;
certificate_ptr make_certificate_impl(string commonName) {
std::unique_ptr<gnutls_x509_crt_t, decltype(&delete_crt)> crt(create_crt(), delete_crt); std::unique_ptr<gnutls_x509_crt_t, decltype(&delete_crt)> crt(create_crt(), delete_crt);
std::unique_ptr<gnutls_x509_privkey_t, decltype(&delete_privkey)> privkey(create_privkey(), std::unique_ptr<gnutls_x509_privkey_t, decltype(&delete_privkey)> privkey(create_privkey(),
delete_privkey); delete_privkey);
@ -174,11 +169,11 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
check_gnutls(gnutls_x509_crt_sign2(*crt, *crt, *privkey, GNUTLS_DIG_SHA256, 0), check_gnutls(gnutls_x509_crt_sign2(*crt, *crt, *privkey, GNUTLS_DIG_SHA256, 0),
"Unable to auto-sign certificate"); "Unable to auto-sign certificate");
auto certificate = std::make_shared<Certificate>(*crt, *privkey); return std::make_shared<Certificate>(*crt, *privkey);
cache.emplace(std::make_pair(commonName, certificate));
return certificate;
} }
} // namespace
} // namespace rtc } // namespace rtc
#else #else
@ -236,22 +231,9 @@ string make_fingerprint(X509 *x509) {
return oss.str(); return oss.str();
} }
namespace {
shared_ptr<Certificate> make_certificate(const string &commonName) { certificate_ptr make_certificate_impl(string commonName) {
static std::unordered_map<string, shared_ptr<Certificate>> cache;
static std::mutex cacheMutex;
std::lock_guard lock(cacheMutex);
if (auto it = cache.find(commonName); it != cache.end())
return it->second;
if (cache.empty()) {
// This is the first call to OpenSSL
OPENSSL_init_ssl(0, NULL);
SSL_load_error_strings();
ERR_load_crypto_strings();
}
shared_ptr<X509> x509(X509_new(), X509_free); shared_ptr<X509> x509(X509_new(), X509_free);
shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free); shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
@ -272,7 +254,8 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
throw std::runtime_error("Unable to generate key pair"); throw std::runtime_error("Unable to generate key pair");
const size_t serialSize = 16; const size_t serialSize = 16;
const auto *commonNameBytes = reinterpret_cast<const unsigned char *>(commonName.c_str()); auto *commonNameBytes =
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_get_notBefore(x509.get()), 3600 * -1) ||
!X509_gmtime_adj(X509_get_notAfter(x509.get()), 3600 * 24 * 365) || !X509_gmtime_adj(X509_get_notAfter(x509.get()), 3600 * 24 * 365) ||
@ -288,12 +271,54 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
if (!X509_sign(x509.get(), pkey.get(), EVP_sha256())) if (!X509_sign(x509.get(), pkey.get(), EVP_sha256()))
throw std::runtime_error("Unable to auto-sign certificate"); throw std::runtime_error("Unable to auto-sign certificate");
auto certificate = std::make_shared<Certificate>(x509, pkey); return std::make_shared<Certificate>(x509, pkey);
cache.emplace(std::make_pair(commonName, certificate));
return certificate;
} }
} // namespace
} // namespace rtc } // namespace rtc
#endif #endif
// Common for GnuTLS and OpenSSL
namespace rtc {
namespace {
// Helper function roughly equivalent to std::async with policy std::launch::async
// since std::async might be unreliable on some platforms (e.g. Mingw32 on Windows)
template <class F, class... Args>
std::future<std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>> thread_call(F &&f,
Args &&... args) {
using R = std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>;
std::packaged_task<R()> task(std::bind(f, std::forward<Args>(args)...));
std::future<R> future = task.get_future();
std::thread t(std::move(task));
t.detach();
return future;
}
static std::unordered_map<string, future_certificate_ptr> CertificateCache;
static std::mutex CertificateCacheMutex;
} // namespace
future_certificate_ptr make_certificate(string commonName) {
std::lock_guard lock(CertificateCacheMutex);
if (auto it = CertificateCache.find(commonName); it != CertificateCache.end())
return it->second;
auto future = thread_call(make_certificate_impl, commonName);
auto shared = future.share();
CertificateCache.emplace(std::move(commonName), shared);
return shared;
}
void CleanupCertificateCache() {
std::lock_guard lock(CertificateCacheMutex);
CertificateCache.clear();
}
} // namespace rtc

View File

@ -21,6 +21,7 @@
#include "include.hpp" #include "include.hpp"
#include <future>
#include <tuple> #include <tuple>
#if USE_GNUTLS #if USE_GNUTLS
@ -62,7 +63,12 @@ string make_fingerprint(gnutls_x509_crt_t crt);
string make_fingerprint(X509 *x509); string make_fingerprint(X509 *x509);
#endif #endif
std::shared_ptr<Certificate> make_certificate(const string &commonName); using certificate_ptr = std::shared_ptr<Certificate>;
using future_certificate_ptr = std::shared_future<certificate_ptr>;
future_certificate_ptr make_certificate(string commonName); // cached
void CleanupCertificateCache();
} // namespace rtc } // namespace rtc

View File

@ -18,10 +18,14 @@
#include "channel.hpp" #include "channel.hpp"
namespace {}
namespace rtc { namespace rtc {
size_t Channel::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
size_t Channel::bufferedAmount() const { return mBufferedAmount; }
size_t Channel::availableAmount() const { return 0; }
void Channel::onOpen(std::function<void()> callback) { void Channel::onOpen(std::function<void()> callback) {
mOpenCallback = callback; mOpenCallback = callback;
} }
@ -49,20 +53,16 @@ void Channel::onMessage(std::function<void(const binary &data)> binaryCallback,
}); });
} }
void Channel::onAvailable(std::function<void()> callback) {
mAvailableCallback = callback;
}
void Channel::onBufferedAmountLow(std::function<void()> callback) { void Channel::onBufferedAmountLow(std::function<void()> callback) {
mBufferedAmountLowCallback = callback; mBufferedAmountLowCallback = callback;
} }
size_t Channel::availableAmount() const { return 0; }
size_t Channel::bufferedAmount() const { return mBufferedAmount; }
void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; } void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; }
void Channel::onAvailable(std::function<void()> callback) {
mAvailableCallback = callback;
}
void Channel::triggerOpen() { mOpenCallback(); } void Channel::triggerOpen() { mOpenCallback(); }
void Channel::triggerClosed() { mClosedCallback(); } void Channel::triggerClosed() { mClosedCallback(); }
@ -88,5 +88,14 @@ void Channel::triggerBufferedAmount(size_t amount) {
mBufferedAmountLowCallback(); mBufferedAmountLowCallback();
} }
void Channel::resetCallbacks() {
mOpenCallback = nullptr;
mClosedCallback = nullptr;
mErrorCallback = nullptr;
mMessageCallback = nullptr;
mAvailableCallback = nullptr;
mBufferedAmountLowCallback = nullptr;
}
} // namespace rtc } // namespace rtc

View File

@ -38,16 +38,17 @@ IceServer::IceServer(const string &url) {
}); });
string scheme = opt[2].value_or("stun"); string scheme = opt[2].value_or("stun");
relayType = RelayType::TurnUdp;
if (scheme == "stun" || scheme == "STUN") if (scheme == "stun" || scheme == "STUN")
type = Type::Stun; type = Type::Stun;
else if (scheme == "turn" || scheme == "TURN") else if (scheme == "turn" || scheme == "TURN")
type = Type::Turn; type = Type::Turn;
else if (scheme == "turns" || scheme == "TURNS") else if (scheme == "turns" || scheme == "TURNS") {
type = Type::Turn; type = Type::Turn;
else relayType = RelayType::TurnTls;
} else
throw std::invalid_argument("Unknown ICE server protocol: " + scheme); throw std::invalid_argument("Unknown ICE server protocol: " + scheme);
relayType = RelayType::TurnUdp;
if (auto &query = opt[15]) { if (auto &query = opt[15]) {
if (query->find("transport=udp") != string::npos) if (query->find("transport=udp") != string::npos)
relayType = RelayType::TurnUdp; relayType = RelayType::TurnUdp;
@ -84,4 +85,7 @@ IceServer::IceServer(string hostname_, string service_, string username_, string
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Turn), : hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Turn),
username(std::move(username_)), password(std::move(password_)), relayType(relayType_) {} username(std::move(username_)), password(std::move(password_)), relayType(relayType_) {}
ProxyServer::ProxyServer(Type type_, string ip_, uint16_t port_, string username_, string password_)
: type(type_), ip(ip_), port(port_), username(username_), password(password_) {}
} // namespace rtc } // namespace rtc

View File

@ -21,9 +21,16 @@
#include "peerconnection.hpp" #include "peerconnection.hpp"
#include "sctptransport.hpp" #include "sctptransport.hpp"
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif
namespace rtc { namespace rtc {
using std::shared_ptr; using std::shared_ptr;
using std::weak_ptr;
// Messages for the DataChannel establishment protocol // Messages for the DataChannel establishment protocol
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09 // See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09
@ -60,16 +67,16 @@ struct CloseMessage {
const size_t RECV_QUEUE_LIMIT = 1024 * 1024; // 1 MiB const size_t RECV_QUEUE_LIMIT = 1024 * 1024; // 1 MiB
DataChannel::DataChannel(shared_ptr<PeerConnection> pc, unsigned int stream, string label, DataChannel::DataChannel(weak_ptr<PeerConnection> pc, unsigned int stream, string label,
string protocol, Reliability reliability) string protocol, Reliability reliability)
: mPeerConnection(std::move(pc)), mStream(stream), mLabel(std::move(label)), : mPeerConnection(pc), mStream(stream), mLabel(std::move(label)),
mProtocol(std::move(protocol)), mProtocol(std::move(protocol)),
mReliability(std::make_shared<Reliability>(std::move(reliability))), mReliability(std::make_shared<Reliability>(std::move(reliability))),
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {} mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
DataChannel::DataChannel(shared_ptr<PeerConnection> pc, shared_ptr<SctpTransport> transport, DataChannel::DataChannel(weak_ptr<PeerConnection> pc, weak_ptr<SctpTransport> transport,
unsigned int stream) unsigned int stream)
: mPeerConnection(std::move(pc)), mSctpTransport(transport), mStream(stream), : mPeerConnection(pc), mSctpTransport(transport), mStream(stream),
mReliability(std::make_shared<Reliability>()), mReliability(std::make_shared<Reliability>()),
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {} mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
@ -77,17 +84,29 @@ DataChannel::~DataChannel() {
close(); close();
} }
unsigned int DataChannel::stream() const { return mStream; }
string DataChannel::label() const { return mLabel; }
string DataChannel::protocol() const { return mProtocol; }
Reliability DataChannel::reliability() const { return *mReliability; }
void DataChannel::close() { void DataChannel::close() {
if (mIsOpen.exchange(false) && mSctpTransport)
mSctpTransport->reset(mStream);
mIsClosed = true; mIsClosed = true;
if (mIsOpen.exchange(false))
if (auto transport = mSctpTransport.lock())
transport->close(mStream);
mSctpTransport.reset(); mSctpTransport.reset();
resetCallbacks();
} }
void DataChannel::remoteClose() { void DataChannel::remoteClose() {
mIsOpen = false;
if (!mIsClosed.exchange(true)) if (!mIsClosed.exchange(true))
triggerClosed(); triggerClosed();
mIsOpen = false;
mSctpTransport.reset(); mSctpTransport.reset();
} }
@ -121,6 +140,9 @@ std::optional<std::variant<binary, string>> DataChannel::receive() {
string(reinterpret_cast<const char *>(message->data()), message->size())); string(reinterpret_cast<const char *>(message->data()), message->size()));
case Message::Binary: case Message::Binary:
return std::make_optional(std::move(*message)); return std::make_optional(std::move(*message));
default:
// Ignore
break;
} }
} }
@ -131,27 +153,20 @@ bool DataChannel::isOpen(void) const { return mIsOpen; }
bool DataChannel::isClosed(void) const { return mIsClosed; } bool DataChannel::isClosed(void) const { return mIsClosed; }
size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
size_t DataChannel::maxMessageSize() const { size_t DataChannel::maxMessageSize() const {
size_t max = DEFAULT_MAX_MESSAGE_SIZE; size_t max = DEFAULT_MAX_MESSAGE_SIZE;
if (auto description = mPeerConnection->remoteDescription()) if (auto pc = mPeerConnection.lock())
if (auto description = pc->remoteDescription())
if (auto maxMessageSize = description->maxMessageSize()) if (auto maxMessageSize = description->maxMessageSize())
return *maxMessageSize > 0 ? *maxMessageSize : LOCAL_MAX_MESSAGE_SIZE; return *maxMessageSize > 0 ? *maxMessageSize : LOCAL_MAX_MESSAGE_SIZE;
return std::min(max, LOCAL_MAX_MESSAGE_SIZE); return std::min(max, LOCAL_MAX_MESSAGE_SIZE);
} }
unsigned int DataChannel::stream() const { return mStream; } size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
string DataChannel::label() const { return mLabel; } void DataChannel::open(shared_ptr<SctpTransport> transport) {
mSctpTransport = transport;
string DataChannel::protocol() const { return mProtocol; }
Reliability DataChannel::reliability() const { return *mReliability; }
void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
mSctpTransport = sctpTransport;
uint8_t channelType = static_cast<uint8_t>(mReliability->type); uint8_t channelType = static_cast<uint8_t>(mReliability->type);
if (mReliability->unordered) if (mReliability->unordered)
@ -178,20 +193,24 @@ void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
std::copy(mLabel.begin(), mLabel.end(), end); std::copy(mLabel.begin(), mLabel.end(), end);
std::copy(mProtocol.begin(), mProtocol.end(), end + mLabel.size()); std::copy(mProtocol.begin(), mProtocol.end(), end + mLabel.size());
mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream)); transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
} }
bool DataChannel::outgoing(mutable_message_ptr message) { bool DataChannel::outgoing(mutable_message_ptr message) {
if (mIsClosed || !mSctpTransport) if (mIsClosed)
throw std::runtime_error("DataChannel is closed"); throw std::runtime_error("DataChannel is closed");
if (message->size() > maxMessageSize()) if (message->size() > maxMessageSize())
throw std::runtime_error("Message size exceeds limit"); throw std::runtime_error("Message size exceeds limit");
auto transport = mSctpTransport.lock();
if (!transport)
throw std::runtime_error("DataChannel has no transport");
// Before the ACK has been received on a DataChannel, all messages must be sent ordered // Before the ACK has been received on a DataChannel, all messages must be sent ordered
message->reliability = mIsOpen ? mReliability : nullptr; message->reliability = mIsOpen ? mReliability : nullptr;
message->stream = mStream; message->stream = mStream;
return mSctpTransport->send(message); return transport->send(message);
} }
void DataChannel::incoming(message_ptr message) { void DataChannel::incoming(message_ptr message) {
@ -230,6 +249,10 @@ void DataChannel::incoming(message_ptr message) {
} }
void DataChannel::processOpenMessage(message_ptr message) { void DataChannel::processOpenMessage(message_ptr message) {
auto transport = mSctpTransport.lock();
if (!transport)
throw std::runtime_error("DataChannel has no transport");
if (message->size() < sizeof(OpenMessage)) if (message->size() < sizeof(OpenMessage))
throw std::invalid_argument("DataChannel open message too small"); throw std::invalid_argument("DataChannel open message too small");
@ -266,7 +289,7 @@ void DataChannel::processOpenMessage(message_ptr message) {
auto &ack = *reinterpret_cast<AckMessage *>(buffer.data()); auto &ack = *reinterpret_cast<AckMessage *>(buffer.data());
ack.type = MESSAGE_ACK; ack.type = MESSAGE_ACK;
mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream)); transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
mIsOpen = true; mIsOpen = true;
triggerOpen(); triggerOpen();

View File

@ -45,12 +45,13 @@ inline void trim_end(string &str) {
namespace rtc { namespace rtc {
Description::Description(const string &sdp, const string &typeString) Description::Description(const string &sdp, const string &typeString)
: Description(sdp, stringToType(typeString), Description::Role::ActPass) {} : Description(sdp, stringToType(typeString)) {}
Description::Description(const string &sdp, Type type) : Description(sdp, type, Role::ActPass) {}
Description::Description(const string &sdp, Type type, Role role) Description::Description(const string &sdp, Type type, Role role)
: mType(type), mRole(role), mMid("0"), mIceUfrag("0"), mIcePwd("0"), mTrickle(true) { : mType(Type::Unspec), mRole(role), mMid("0"), mIceUfrag(""), mIcePwd(""), mTrickle(true) {
if (mType == Type::Answer && mRole == Role::ActPass) hintType(type);
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
auto seed = std::chrono::system_clock::now().time_since_epoch().count(); auto seed = std::chrono::system_clock::now().time_since_epoch().count();
std::default_random_engine generator(seed); std::default_random_engine generator(seed);
@ -109,6 +110,14 @@ std::optional<size_t> Description::maxMessageSize() const { return mMaxMessageSi
bool Description::trickleEnabled() const { return mTrickle; } bool Description::trickleEnabled() const { return mTrickle; }
void Description::hintType(Type type) {
if (mType == Type::Unspec) {
mType = type;
if (mType == Type::Answer && mRole == Role::ActPass)
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
}
}
void Description::setFingerprint(string fingerprint) { void Description::setFingerprint(string fingerprint) {
mFingerprint.emplace(std::move(fingerprint)); mFingerprint.emplace(std::move(fingerprint));
} }

View File

@ -55,17 +55,22 @@ static bool check_gnutls(int ret, const string &message = "GnuTLS error") {
namespace rtc { namespace rtc {
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate, void DtlsTransport::Init() {
verifier_callback verifierCallback, // Nothing to do
state_callback stateChangeCallback) }
void DtlsTransport::Cleanup() {
// Nothing to do
}
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
verifier_callback verifierCallback, state_callback stateChangeCallback)
: Transport(lower), mCertificate(certificate), mState(State::Disconnected), : Transport(lower), mCertificate(certificate), mState(State::Disconnected),
mVerifierCallback(std::move(verifierCallback)), mVerifierCallback(std::move(verifierCallback)),
mStateChangeCallback(std::move(stateChangeCallback)) { mStateChangeCallback(std::move(stateChangeCallback)) {
PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)"; PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)";
gnutls_certificate_set_verify_function(mCertificate->credentials(), CertificateCallback);
bool active = lower->role() == Description::Role::Active; bool active = lower->role() == Description::Role::Active;
unsigned int flags = GNUTLS_DATAGRAM | (active ? GNUTLS_CLIENT : GNUTLS_SERVER); unsigned int flags = GNUTLS_DATAGRAM | (active ? GNUTLS_CLIENT : GNUTLS_SERVER);
check_gnutls(gnutls_init(&mSession, flags)); check_gnutls(gnutls_init(&mSession, flags));
@ -73,17 +78,19 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU. // RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
// Therefore, the DTLS layer MUST NOT use any compression algorithm. // Therefore, the DTLS layer MUST NOT use any compression algorithm.
// See https://tools.ietf.org/html/rfc8261#section-5 // See https://tools.ietf.org/html/rfc8261#section-5
const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL"; const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL:+COMP-NULL";
const char *err_pos = NULL; const char *err_pos = NULL;
check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos), check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
"Unable to set TLS priorities"); "Unable to set TLS priorities");
gnutls_certificate_set_verify_function(mCertificate->credentials(), CertificateCallback);
check_gnutls( check_gnutls(
gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate->credentials())); gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate->credentials()));
gnutls_dtls_set_mtu(mSession, 1280 - 40 - 8); // min MTU over UDP/IPv6 (only for handshake) gnutls_dtls_set_timeouts(mSession,
gnutls_dtls_set_timeouts(mSession, 400, 60000); 1000, // 1s retransmission timeout recommended by RFC 6347
gnutls_handshake_set_timeout(mSession, 60000); 30000); // 30s total timeout
gnutls_handshake_set_timeout(mSession, 30000);
gnutls_session_set_ptr(mSession, this); gnutls_session_set_ptr(mSession, this);
gnutls_transport_set_ptr(mSession, this); gnutls_transport_set_ptr(mSession, this);
@ -92,6 +99,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback); gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this); mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
registerIncoming();
} }
DtlsTransport::~DtlsTransport() { DtlsTransport::~DtlsTransport() {
@ -102,15 +110,14 @@ DtlsTransport::~DtlsTransport() {
DtlsTransport::State DtlsTransport::state() const { return mState; } DtlsTransport::State DtlsTransport::state() const { return mState; }
void DtlsTransport::stop() { bool DtlsTransport::stop() {
Transport::stop(); if (!Transport::stop())
return false;
if (mRecvThread.joinable()) {
PLOG_DEBUG << "Stopping DTLS recv thread"; PLOG_DEBUG << "Stopping DTLS recv thread";
mIncomingQueue.stop(); mIncomingQueue.stop();
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
mRecvThread.join(); mRecvThread.join();
} return true;
} }
bool DtlsTransport::send(message_ptr message) { bool DtlsTransport::send(message_ptr message) {
@ -131,10 +138,13 @@ bool DtlsTransport::send(message_ptr message) {
} }
void DtlsTransport::incoming(message_ptr message) { void DtlsTransport::incoming(message_ptr message) {
if (message) if (!message) {
mIncomingQueue.push(message);
else
mIncomingQueue.stop(); mIncomingQueue.stop();
return;
}
PLOG_VERBOSE << "Incoming size=" << message->size();
mIncomingQueue.push(message);
} }
void DtlsTransport::changeState(State state) { void DtlsTransport::changeState(State state) {
@ -148,6 +158,7 @@ void DtlsTransport::runRecvLoop() {
// Handshake loop // Handshake loop
try { try {
changeState(State::Connecting); changeState(State::Connecting);
gnutls_dtls_set_mtu(mSession, 1280 - 40 - 8); // min MTU over UDP/IPv6
int ret; int ret;
do { do {
@ -171,6 +182,7 @@ void DtlsTransport::runRecvLoop() {
// Receive loop // Receive loop
try { try {
PLOG_INFO << "DTLS handshake done";
changeState(State::Connected); changeState(State::Connected);
const size_t bufferSize = maxMtu; const size_t bufferSize = maxMtu;
@ -203,6 +215,8 @@ void DtlsTransport::runRecvLoop() {
PLOG_ERROR << "DTLS recv: " << e.what(); PLOG_ERROR << "DTLS recv: " << e.what();
} }
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
PLOG_INFO << "DTLS disconnected"; PLOG_INFO << "DTLS disconnected";
changeState(State::Disconnected); changeState(State::Disconnected);
recv(nullptr); recv(nullptr);
@ -262,10 +276,8 @@ ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size
int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) { int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
DtlsTransport *t = static_cast<DtlsTransport *>(ptr); DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
if (ms != GNUTLS_INDEFINITE_TIMEOUT) t->mIncomingQueue.wait(ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms))
t->mIncomingQueue.wait(milliseconds(ms)); : nullopt);
else
t->mIncomingQueue.wait();
return !t->mIncomingQueue.empty() ? 1 : 0; return !t->mIncomingQueue.empty() ? 1 : 0;
} }
@ -323,7 +335,7 @@ BIO_METHOD *DtlsTransport::BioMethods = NULL;
int DtlsTransport::TransportExIndex = -1; int DtlsTransport::TransportExIndex = -1;
std::mutex DtlsTransport::GlobalMutex; std::mutex DtlsTransport::GlobalMutex;
void DtlsTransport::GlobalInit() { void DtlsTransport::Init() {
std::lock_guard lock(GlobalMutex); std::lock_guard lock(GlobalMutex);
if (!BioMethods) { if (!BioMethods) {
BioMethods = BIO_meth_new(BIO_TYPE_BIO, "DTLS writer"); BioMethods = BIO_meth_new(BIO_TYPE_BIO, "DTLS writer");
@ -339,6 +351,10 @@ void DtlsTransport::GlobalInit() {
} }
} }
void DtlsTransport::Cleanup() {
// Nothing to do
}
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate, DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
verifier_callback verifierCallback, state_callback stateChangeCallback) verifier_callback verifierCallback, state_callback stateChangeCallback)
: Transport(lower), mCertificate(certificate), mState(State::Disconnected), : Transport(lower), mCertificate(certificate), mState(State::Disconnected),
@ -346,7 +362,6 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
mStateChangeCallback(std::move(stateChangeCallback)) { mStateChangeCallback(std::move(stateChangeCallback)) {
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)"; PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
GlobalInit();
if (!(mCtx = SSL_CTX_new(DTLS_method()))) if (!(mCtx = SSL_CTX_new(DTLS_method())))
throw std::runtime_error("Unable to create SSL context"); throw std::runtime_error("Unable to create SSL context");
@ -376,7 +391,6 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
throw std::runtime_error("Unable to create SSL instance"); throw std::runtime_error("Unable to create SSL instance");
SSL_set_ex_data(mSsl, TransportExIndex, this); SSL_set_ex_data(mSsl, TransportExIndex, this);
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
if (lower->role() == Description::Role::Active) if (lower->role() == Description::Role::Active)
SSL_set_connect_state(mSsl); SSL_set_connect_state(mSsl);
@ -396,6 +410,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
SSL_set_tmp_ecdh(mSsl, ecdh.get()); SSL_set_tmp_ecdh(mSsl, ecdh.get());
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this); mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
registerIncoming();
} }
DtlsTransport::~DtlsTransport() { DtlsTransport::~DtlsTransport() {
@ -405,16 +420,15 @@ DtlsTransport::~DtlsTransport() {
SSL_CTX_free(mCtx); SSL_CTX_free(mCtx);
} }
void DtlsTransport::stop() { bool DtlsTransport::stop() {
Transport::stop(); if (!Transport::stop())
return false;
if (mRecvThread.joinable()) {
PLOG_DEBUG << "Stopping DTLS recv thread"; PLOG_DEBUG << "Stopping DTLS recv thread";
mIncomingQueue.stop(); mIncomingQueue.stop();
mRecvThread.join(); mRecvThread.join();
SSL_shutdown(mSsl); SSL_shutdown(mSsl);
} return true;
} }
DtlsTransport::State DtlsTransport::state() const { return mState; } DtlsTransport::State DtlsTransport::state() const { return mState; }
@ -432,10 +446,13 @@ bool DtlsTransport::send(message_ptr message) {
} }
void DtlsTransport::incoming(message_ptr message) { void DtlsTransport::incoming(message_ptr message) {
if (message) if (!message) {
mIncomingQueue.push(message);
else
mIncomingQueue.stop(); mIncomingQueue.stop();
return;
}
PLOG_VERBOSE << "Incoming size=" << message->size();
mIncomingQueue.push(message);
} }
void DtlsTransport::changeState(State state) { void DtlsTransport::changeState(State state) {
@ -447,35 +464,71 @@ void DtlsTransport::runRecvLoop() {
const size_t maxMtu = 4096; const size_t maxMtu = 4096;
try { try {
changeState(State::Connecting); changeState(State::Connecting);
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
SSL_do_handshake(mSsl); // Initiate the handshake
int ret = SSL_do_handshake(mSsl);
check_openssl_ret(mSsl, ret, "Handshake failed");
const size_t bufferSize = maxMtu; const size_t bufferSize = maxMtu;
byte buffer[bufferSize]; byte buffer[bufferSize];
while (auto next = mIncomingQueue.pop()) { while (true) {
auto message = *next; // Process pending messages
while (!mIncomingQueue.empty()) {
auto message = *mIncomingQueue.pop();
BIO_write(mInBio, message->data(), message->size()); BIO_write(mInBio, message->data(), message->size());
if (mState == State::Connecting) {
// Continue the handshake
int ret = SSL_do_handshake(mSsl);
if (!check_openssl_ret(mSsl, ret, "Handshake failed"))
break;
if (SSL_is_init_finished(mSsl)) {
// RFC 8261: DTLS MUST support sending messages larger than the current path
// MTU See https://tools.ietf.org/html/rfc8261#section-5
SSL_set_mtu(mSsl, maxMtu + 1);
PLOG_INFO << "DTLS handshake done";
changeState(State::Connected);
}
} else {
int ret = SSL_read(mSsl, buffer, bufferSize); int ret = SSL_read(mSsl, buffer, bufferSize);
if (!check_openssl_ret(mSsl, ret)) if (!check_openssl_ret(mSsl, ret))
break; break;
if (ret > 0)
recv(make_message(buffer, buffer + ret));
}
}
auto decrypted = ret > 0 ? make_message(buffer, buffer + ret) : nullptr; // No more messages pending, retransmit and rearm timeout if connecting
std::optional<milliseconds> duration;
if (mState == State::Connecting) { if (mState == State::Connecting) {
if (unsigned long err = ERR_get_error()) // Warning: This function breaks the usual return value convention
throw std::runtime_error("handshake failed: " + openssl_error_string(err)); int ret = DTLSv1_handle_timeout(mSsl);
if (ret < 0) {
throw std::runtime_error("Handshake timeout"); // write BIO can't fail
} else if (ret > 0) {
LOG_VERBOSE << "OpenSSL did DTLS retransmit";
}
if (SSL_is_init_finished(mSsl)) { struct timeval timeout = {};
changeState(State::Connected); if (mState == State::Connecting && DTLSv1_get_timeout(mSsl, &timeout)) {
duration = milliseconds(timeout.tv_sec * 1000 + timeout.tv_usec / 1000);
// RFC 8261: DTLS MUST support sending messages larger than the current path MTU // Also handle handshake timeout manually because OpenSSL actually doesn't...
// See https://tools.ietf.org/html/rfc8261#section-5 // OpenSSL backs off exponentially in base 2 starting from the recommended 1s
SSL_set_mtu(mSsl, maxMtu + 1); // so this allows for 5 retransmissions and fails after roughly 30s.
if (duration > 30s) {
throw std::runtime_error("Handshake timeout");
} else {
LOG_VERBOSE << "OpenSSL DTLS retransmit timeout is " << duration->count()
<< "ms";
}
} }
} }
if (decrypted) if (!mIncomingQueue.wait(duration))
recv(decrypted); break; // queue is stopped
} }
} catch (const std::exception &e) { } catch (const std::exception &e) {
PLOG_ERROR << "DTLS recv: " << e.what(); PLOG_ERROR << "DTLS recv: " << e.what();
@ -486,7 +539,7 @@ void DtlsTransport::runRecvLoop() {
changeState(State::Disconnected); changeState(State::Disconnected);
recv(nullptr); recv(nullptr);
} else { } else {
PLOG_INFO << "DTLS handshake failed"; PLOG_ERROR << "DTLS handshake failed";
changeState(State::Failed); changeState(State::Failed);
} }
} }
@ -536,7 +589,8 @@ int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
if (!transport) if (!transport)
return -1; return -1;
auto b = reinterpret_cast<const byte *>(in); auto b = reinterpret_cast<const byte *>(in);
return transport->outgoing(make_message(b, b + inl)) ? inl : 0; transport->outgoing(make_message(b, b + inl));
return inl; // can't fail
} }
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) { long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) {

View File

@ -43,18 +43,21 @@ class IceTransport;
class DtlsTransport : public Transport { class DtlsTransport : public Transport {
public: public:
static void Init();
static void Cleanup();
enum class State { Disconnected, Connecting, Connected, Failed }; enum class State { Disconnected, Connecting, Connected, Failed };
using verifier_callback = std::function<bool(const std::string &fingerprint)>; using verifier_callback = std::function<bool(const std::string &fingerprint)>;
using state_callback = std::function<void(State state)>; using state_callback = std::function<void(State state)>;
DtlsTransport(std::shared_ptr<IceTransport> lower, std::shared_ptr<Certificate> certificate, DtlsTransport(std::shared_ptr<IceTransport> lower, certificate_ptr certificate,
verifier_callback verifierCallback, state_callback stateChangeCallback); verifier_callback verifierCallback, state_callback stateChangeCallback);
~DtlsTransport(); ~DtlsTransport();
State state() const; State state() const;
void stop() override; bool stop() override;
bool send(message_ptr message) override; // false if dropped bool send(message_ptr message) override; // false if dropped
private: private:
@ -62,7 +65,7 @@ private:
void changeState(State state); void changeState(State state);
void runRecvLoop(); void runRecvLoop();
const std::shared_ptr<Certificate> mCertificate; const certificate_ptr mCertificate;
Queue<message_ptr> mIncomingQueue; Queue<message_ptr> mIncomingQueue;
std::atomic<State> mState; std::atomic<State> mState;
@ -87,7 +90,6 @@ private:
static int TransportExIndex; static int TransportExIndex;
static std::mutex GlobalMutex; static std::mutex GlobalMutex;
static void GlobalInit();
static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx); static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx);
static void InfoCallback(const SSL *ssl, int where, int ret); static void InfoCallback(const SSL *ssl, int where, int ret);

View File

@ -18,16 +18,24 @@
#include "icetransport.hpp" #include "icetransport.hpp"
#include "configuration.hpp" #include "configuration.hpp"
#include "transport.hpp"
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <iostream> #include <iostream>
#include <random> #include <random>
#include <sstream> #include <sstream>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif
#include <sys/types.h>
using namespace std::chrono_literals; using namespace std::chrono_literals;
using std::shared_ptr; using std::shared_ptr;
@ -65,7 +73,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); unsigned seed = std::chrono::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 // Pick a STUN server (TURN support is not implemented in libjuice yet)
for (auto &server : servers) { for (auto &server : servers) {
if (!server.hostname.empty() && server.type == IceServer::Type::Stun) { if (!server.hostname.empty() && server.type == IceServer::Type::Stun) {
if (server.service.empty()) if (server.service.empty())
@ -79,7 +87,12 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
} }
} }
// TURN support is not implemented yet // Port range
if (config.portRangeBegin > 1024 ||
(config.portRangeEnd != 0 && config.portRangeEnd != 65535)) {
jconfig.local_port_range_begin = config.portRangeBegin;
jconfig.local_port_range_end = config.portRangeEnd;
}
// Create agent // Create agent
mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy); mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy);
@ -89,8 +102,8 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
IceTransport::~IceTransport() { stop(); } IceTransport::~IceTransport() { stop(); }
void IceTransport::stop() { bool IceTransport::stop() {
// Nothing to do return Transport::stop();
} }
Description::Role IceTransport::role() const { return mRole; } Description::Role IceTransport::role() const { return mRole; }
@ -109,9 +122,6 @@ void IceTransport::setRemoteDescription(const Description &description) {
mRole = description.role() == Description::Role::Active ? Description::Role::Passive mRole = description.role() == Description::Role::Active ? Description::Role::Passive
: Description::Role::Active; : Description::Role::Active;
mMid = description.mid(); mMid = description.mid();
// TODO
// mTrickleTimeout = description.trickleEnabled() ? 30s : 0s;
if (juice_set_remote_description(mAgent.get(), string(description).c_str()) < 0) if (juice_set_remote_description(mAgent.get(), string(description).c_str()) < 0)
throw std::runtime_error("Failed to parse remote SDP"); throw std::runtime_error("Failed to parse remote SDP");
} }
@ -158,12 +168,6 @@ bool IceTransport::send(message_ptr message) {
return outgoing(message); return outgoing(message);
} }
void IceTransport::incoming(message_ptr message) { recv(message); }
void IceTransport::incoming(const byte *data, int size) {
incoming(make_message(data, data + size));
}
bool IceTransport::outgoing(message_ptr message) { bool IceTransport::outgoing(message_ptr message) {
return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()), return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()),
message->size()) >= 0; message->size()) >= 0;
@ -220,7 +224,9 @@ void IceTransport::RecvCallback(juice_agent_t *agent, const char *data, size_t s
void *user_ptr) { void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr); auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try { try {
iceTransport->incoming(reinterpret_cast<const byte *>(data), size); PLOG_VERBOSE << "Incoming size=" << size;
auto b = reinterpret_cast<const byte *>(data);
iceTransport->incoming(make_message(b, b + size));
} catch (const std::exception &e) { } catch (const std::exception &e) {
PLOG_WARNING << e.what(); PLOG_WARNING << e.what();
} }
@ -241,11 +247,8 @@ void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
case JUICE_LOG_LEVEL_INFO: case JUICE_LOG_LEVEL_INFO:
severity = plog::info; severity = plog::info;
break; break;
case JUICE_LOG_LEVEL_DEBUG:
severity = plog::debug;
break;
default: default:
severity = plog::verbose; severity = plog::verbose; // libjuice debug as verbose
break; break;
} }
PLOG(severity) << "juice: " << message; PLOG(severity) << "juice: " << message;
@ -308,6 +311,18 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr); g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr); g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr);
// Proxy
if (config.proxyServer.has_value()) {
ProxyServer proxyServer = config.proxyServer.value();
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-type", proxyServer.type, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-ip", proxyServer.ip.c_str(), nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-port", proxyServer.port, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-username", proxyServer.username.c_str(),
nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-password", proxyServer.password.c_str(),
nullptr);
}
// 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(); unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
@ -422,16 +437,22 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
IceTransport::~IceTransport() { stop(); } IceTransport::~IceTransport() { stop(); }
void IceTransport::stop() { bool IceTransport::stop() {
if (mTimeoutId) { if (mTimeoutId) {
g_source_remove(mTimeoutId); g_source_remove(mTimeoutId);
mTimeoutId = 0; mTimeoutId = 0;
} }
if (mMainLoopThread.joinable()) {
if (!Transport::stop())
return false;
PLOG_DEBUG << "Stopping ICE thread"; PLOG_DEBUG << "Stopping ICE thread";
nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(mMainLoop.get()),
NULL, NULL);
nice_agent_remove_stream(mNiceAgent.get(), mStreamId);
g_main_loop_quit(mMainLoop.get()); g_main_loop_quit(mMainLoop.get());
mMainLoopThread.join(); mMainLoopThread.join();
} return true;
} }
Description::Role IceTransport::role() const { return mRole; } Description::Role IceTransport::role() const { return mRole; }
@ -515,12 +536,6 @@ bool IceTransport::send(message_ptr message) {
return outgoing(message); return outgoing(message);
} }
void IceTransport::incoming(message_ptr message) { recv(message); }
void IceTransport::incoming(const byte *data, int size) {
incoming(make_message(data, data + size));
}
bool IceTransport::outgoing(message_ptr message) { bool IceTransport::outgoing(message_ptr message) {
return nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(), return nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(),
reinterpret_cast<const char *>(message->data())) >= 0; reinterpret_cast<const char *>(message->data())) >= 0;
@ -608,7 +623,9 @@ void IceTransport::RecvCallback(NiceAgent *agent, guint streamId, guint componen
gchar *buf, gpointer userData) { gchar *buf, gpointer userData) {
auto iceTransport = static_cast<rtc::IceTransport *>(userData); auto iceTransport = static_cast<rtc::IceTransport *>(userData);
try { try {
iceTransport->incoming(reinterpret_cast<byte *>(buf), len); PLOG_VERBOSE << "Incoming size=" << len;
auto b = reinterpret_cast<byte *>(buf);
iceTransport->incoming(make_message(b, b + len));
} catch (const std::exception &e) { } catch (const std::exception &e) {
PLOG_WARNING << e.what(); PLOG_WARNING << e.what();
} }
@ -644,6 +661,58 @@ void IceTransport::LogCallback(const gchar *logDomain, GLogLevelFlags logLevel,
PLOG(severity) << "nice: " << message; PLOG(severity) << "nice: " << message;
} }
bool IceTransport::getSelectedCandidatePair(CandidateInfo *localInfo, CandidateInfo *remoteInfo) {
NiceCandidate *local, *remote;
gboolean result = nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote);
if (!result)
return false;
char ipaddr[INET6_ADDRSTRLEN];
nice_address_to_string(&local->addr, ipaddr);
localInfo->address = std::string(ipaddr);
localInfo->port = nice_address_get_port(&local->addr);
localInfo->type = IceTransport::NiceTypeToCandidateType(local->type);
localInfo->transportType =
IceTransport::NiceTransportTypeToCandidateTransportType(local->transport);
nice_address_to_string(&remote->addr, ipaddr);
remoteInfo->address = std::string(ipaddr);
remoteInfo->port = nice_address_get_port(&remote->addr);
remoteInfo->type = IceTransport::NiceTypeToCandidateType(remote->type);
remoteInfo->transportType =
IceTransport::NiceTransportTypeToCandidateTransportType(remote->transport);
return true;
}
const CandidateType IceTransport::NiceTypeToCandidateType(NiceCandidateType type) {
switch (type) {
case NiceCandidateType::NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
return CandidateType::PeerReflexive;
case NiceCandidateType::NICE_CANDIDATE_TYPE_RELAYED:
return CandidateType::Relayed;
case NiceCandidateType::NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
return CandidateType::ServerReflexive;
default:
return CandidateType::Host;
}
}
const CandidateTransportType
IceTransport::NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type) {
switch (type) {
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE:
return CandidateTransportType::TcpActive;
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE:
return CandidateTransportType::TcpPassive;
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_SO:
return CandidateTransportType::TcpSo;
default:
return CandidateTransportType::Udp;
}
}
} // namespace rtc } // namespace rtc
#endif #endif

View File

@ -56,6 +56,8 @@ public:
Completed = NICE_COMPONENT_STATE_READY, Completed = NICE_COMPONENT_STATE_READY,
Failed = NICE_COMPONENT_STATE_FAILED, Failed = NICE_COMPONENT_STATE_FAILED,
}; };
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
#endif #endif
enum class GatheringState { New = 0, InProgress = 1, Complete = 2 }; enum class GatheringState { New = 0, InProgress = 1, Complete = 2 };
@ -79,12 +81,10 @@ public:
std::optional<string> getLocalAddress() const; std::optional<string> getLocalAddress() const;
std::optional<string> getRemoteAddress() const; std::optional<string> getRemoteAddress() const;
void stop() override; bool stop() override;
bool send(message_ptr message) override; // false if dropped bool send(message_ptr message) override; // false if dropped
private: private:
void incoming(message_ptr message) override;
void incoming(const byte *data, int size);
bool outgoing(message_ptr message) override; bool outgoing(message_ptr message) override;
void changeState(State state); void changeState(State state);
@ -133,6 +133,8 @@ private:
static gboolean TimeoutCallback(gpointer userData); static gboolean TimeoutCallback(gpointer userData);
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
gpointer user_data); gpointer user_data);
static const CandidateType NiceTypeToCandidateType(NiceCandidateType type);
static const CandidateTransportType NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type);
#endif #endif
}; };

88
src/init.cpp Normal file
View File

@ -0,0 +1,88 @@
/**
* 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 "init.hpp"
#include "certificate.hpp"
#include "dtlstransport.hpp"
#include "sctptransport.hpp"
#ifdef _WIN32
#include <winsock2.h>
#endif
#if USE_GNUTLS
// Nothing to do
#else
#include <openssl/err.h>
#include <openssl/ssl.h>
#endif
using std::shared_ptr;
namespace rtc {
std::weak_ptr<Init> Init::Weak;
init_token Init::Global;
std::mutex Init::Mutex;
init_token Init::Token() {
std::lock_guard lock(Mutex);
if (!Global) {
if (auto token = Weak.lock())
Global = token;
else
Global = shared_ptr<Init>(new Init());
}
return Global;
}
void Init::Cleanup() { Global.reset(); }
Init::Init() {
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
#endif
#if USE_GNUTLS
// Nothing to do
#else
OPENSSL_init_ssl(0, NULL);
SSL_load_error_strings();
ERR_load_crypto_strings();
#endif
DtlsTransport::Init();
SctpTransport::Init();
}
Init::~Init() {
CleanupCertificateCache();
DtlsTransport::Cleanup();
SctpTransport::Cleanup();
#ifdef _WIN32
WSACleanup();
#endif
}
} // namespace rtc

42
src/log.cpp Normal file
View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2019-2020 Paul-Louis Ageneau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* 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 "log.hpp"
#include "plog/Appenders/ColorConsoleAppender.h"
#include "plog/Log.h"
#include "plog/Logger.h"
namespace rtc {
void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
void InitLogger(plog::Severity severity, plog::IAppender *appender) {
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
static plog::Logger<0> *logger = nullptr;
if (!logger) {
logger = &plog::init(severity, appender ? appender : &consoleAppender);
PLOG_DEBUG << "Logger initialized";
} else {
logger->setMaxSeverity(severity);
if (appender)
logger->addAppender(appender);
}
}
}

View File

@ -24,6 +24,7 @@
#include "sctptransport.hpp" #include "sctptransport.hpp"
#include <iostream> #include <iostream>
#include <thread>
namespace rtc { namespace rtc {
@ -32,34 +33,34 @@ using namespace std::placeholders;
using std::shared_ptr; using std::shared_ptr;
using std::weak_ptr; using std::weak_ptr;
template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t, Args &&... _args) {
return [bound = std::bind(f, t, _args...), weak_this = t->weak_from_this()](auto &&... args) {
if (auto shared_this = weak_this.lock())
bound(args...);
};
}
template <typename F, typename T, typename... Args>
auto weak_bind_verifier(F &&f, T *t, Args &&... _args) {
return [bound = std::bind(f, t, _args...), weak_this = t->weak_from_this()](auto &&... args) {
if (auto shared_this = weak_this.lock())
return bound(args...);
else
return false;
};
}
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("libdatachannel")), mState(State::New),
mGatheringState(GatheringState::New) {}
PeerConnection::~PeerConnection() { PeerConnection::~PeerConnection() { close(); }
changeState(State::Destroying);
close();
mSctpTransport.reset();
mDtlsTransport.reset();
mIceTransport.reset();
}
void PeerConnection::close() { void PeerConnection::close() {
// Close DataChannels
closeDataChannels(); closeDataChannels();
mDataChannels.clear(); closeTransports();
// Close Transports
for (int i = 0; i < 2; ++i) { // Make sure a transport wasn't spawn behind our back
if (auto transport = std::atomic_load(&mSctpTransport))
transport->stop();
if (auto transport = std::atomic_load(&mDtlsTransport))
transport->stop();
if (auto transport = std::atomic_load(&mIceTransport))
transport->stop();
}
changeState(State::Closed);
} }
const Configuration *PeerConnection::config() const { return &mConfig; } const Configuration *PeerConnection::config() const { return &mConfig; }
@ -79,9 +80,10 @@ std::optional<Description> PeerConnection::remoteDescription() const {
} }
void PeerConnection::setRemoteDescription(Description description) { void PeerConnection::setRemoteDescription(Description description) {
std::lock_guard lock(mRemoteDescriptionMutex); description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
auto remoteCandidates = description.extractCandidates(); auto remoteCandidates = description.extractCandidates();
std::lock_guard lock(mRemoteDescriptionMutex);
mRemoteDescription.emplace(std::move(description)); mRemoteDescription.emplace(std::move(description));
auto iceTransport = std::atomic_load(&mIceTransport); auto iceTransport = std::atomic_load(&mIceTransport);
@ -100,12 +102,16 @@ void PeerConnection::setRemoteDescription(Description description) {
if (!sctpTransport && iceTransport->role() == Description::Role::Active) { if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
// Since we assumed passive role during DataChannel creation, we need to shift the // Since we assumed passive role during DataChannel creation, we need to shift the
// stream numbers by one to shift them from odd to even. // stream numbers by one to shift them from odd to even.
std::unique_lock lock(mDataChannelsMutex); // we are going to swap the container
decltype(mDataChannels) newDataChannels; decltype(mDataChannels) newDataChannels;
iterateDataChannels([&](shared_ptr<DataChannel> channel) { auto it = mDataChannels.begin();
while (it != mDataChannels.end()) {
auto channel = it->second.lock();
if (channel->stream() % 2 == 1) if (channel->stream() % 2 == 1)
channel->mStream -= 1; channel->mStream -= 1;
newDataChannels.emplace(channel->stream(), channel); newDataChannels.emplace(channel->stream(), channel);
}); ++it;
}
std::swap(mDataChannels, newDataChannels); std::swap(mDataChannels, newDataChannels);
} }
} }
@ -157,19 +163,7 @@ shared_ptr<DataChannel> PeerConnection::createDataChannel(const string &label,
auto iceTransport = std::atomic_load(&mIceTransport); auto iceTransport = std::atomic_load(&mIceTransport);
auto role = iceTransport ? iceTransport->role() : Description::Role::Passive; auto role = iceTransport ? iceTransport->role() : Description::Role::Passive;
// The active side must use streams with even identifiers, whereas the passive side must use auto channel = emplaceDataChannel(role, label, protocol, reliability);
// streams with odd identifiers.
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
while (mDataChannels.find(stream) != mDataChannels.end()) {
stream += 2;
if (stream >= 65535)
throw std::runtime_error("Too many DataChannels");
}
auto channel =
std::make_shared<DataChannel>(shared_from_this(), stream, label, protocol, reliability);
mDataChannels.insert(std::make_pair(stream, channel));
if (!iceTransport) { if (!iceTransport) {
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of // RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
@ -209,13 +203,16 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
} }
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) { shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
std::lock_guard lock(mInitMutex); try {
if (auto transport = std::atomic_load(&mIceTransport)) if (auto transport = std::atomic_load(&mIceTransport))
return transport; return transport;
auto transport = std::make_shared<IceTransport>( auto transport = std::make_shared<IceTransport>(
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1), mConfig, role, weak_bind(&PeerConnection::processLocalCandidate, this, _1),
[this](IceTransport::State state) { [this, weak_this = weak_from_this()](IceTransport::State state) {
auto shared_this = weak_this.lock();
if (!shared_this)
return;
switch (state) { switch (state) {
case IceTransport::State::Connecting: case IceTransport::State::Connecting:
changeState(State::Connecting); changeState(State::Connecting);
@ -234,7 +231,10 @@ shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role
break; break;
} }
}, },
[this](IceTransport::GatheringState state) { [this, weak_this = weak_from_this()](IceTransport::GatheringState state) {
auto shared_this = weak_this.lock();
if (!shared_this)
return;
switch (state) { switch (state) {
case IceTransport::GatheringState::InProgress: case IceTransport::GatheringState::InProgress:
changeGatheringState(GatheringState::InProgress); changeGatheringState(GatheringState::InProgress);
@ -248,19 +248,35 @@ shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role
break; break;
} }
}); });
std::atomic_store(&mIceTransport, transport); std::atomic_store(&mIceTransport, transport);
if (mState == State::Closed) {
mIceTransport.reset();
transport->stop();
throw std::runtime_error("Connection is closed");
}
return transport; return transport;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
changeState(State::Failed);
throw std::runtime_error("ICE transport initialization failed");
}
} }
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() { shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
std::lock_guard lock(mInitMutex); try {
if (auto transport = std::atomic_load(&mDtlsTransport)) if (auto transport = std::atomic_load(&mDtlsTransport))
return transport; return transport;
auto certificate = mCertificate.get();
auto lower = std::atomic_load(&mIceTransport); auto lower = std::atomic_load(&mIceTransport);
auto transport = std::make_shared<DtlsTransport>( auto transport = std::make_shared<DtlsTransport>(
lower, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1), lower, certificate, weak_bind_verifier(&PeerConnection::checkFingerprint, this, _1),
[this](DtlsTransport::State state) { [this, weak_this = weak_from_this()](DtlsTransport::State state) {
auto shared_this = weak_this.lock();
if (!shared_this)
return;
switch (state) { switch (state) {
case DtlsTransport::State::Connected: case DtlsTransport::State::Connected:
initSctpTransport(); initSctpTransport();
@ -276,21 +292,36 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
break; break;
} }
}); });
std::atomic_store(&mDtlsTransport, transport); std::atomic_store(&mDtlsTransport, transport);
if (mState == State::Closed) {
mDtlsTransport.reset();
transport->stop();
throw std::runtime_error("Connection is closed");
}
return transport; return transport;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
changeState(State::Failed);
throw std::runtime_error("DTLS transport initialization failed");
}
} }
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() { shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
std::lock_guard lock(mInitMutex); try {
if (auto transport = std::atomic_load(&mSctpTransport)) if (auto transport = std::atomic_load(&mSctpTransport))
return transport; return transport;
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT); uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
auto lower = std::atomic_load(&mDtlsTransport); auto lower = std::atomic_load(&mDtlsTransport);
auto transport = std::make_shared<SctpTransport>( auto transport = std::make_shared<SctpTransport>(
lower, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1), lower, sctpPort, weak_bind(&PeerConnection::forwardMessage, this, _1),
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2), weak_bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
[this](SctpTransport::State state) { [this, weak_this = weak_from_this()](SctpTransport::State state) {
auto shared_this = weak_this.lock();
if (!shared_this)
return;
switch (state) { switch (state) {
case SctpTransport::State::Connected: case SctpTransport::State::Connected:
changeState(State::Connected); changeState(State::Connected);
@ -309,8 +340,48 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
break; break;
} }
}); });
std::atomic_store(&mSctpTransport, transport); std::atomic_store(&mSctpTransport, transport);
if (mState == State::Closed) {
mSctpTransport.reset();
transport->stop();
throw std::runtime_error("Connection is closed");
}
return transport; return transport;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
changeState(State::Failed);
throw std::runtime_error("SCTP transport initialization failed");
}
}
void PeerConnection::closeTransports() {
// Change state to sink state Closed to block init methods
changeState(State::Closed);
// Reset callbacks now that state is changed
resetCallbacks();
// Pass the references to a thread, allowing to terminate a transport from its own thread
auto sctp = std::atomic_exchange(&mSctpTransport, decltype(mSctpTransport)(nullptr));
auto dtls = std::atomic_exchange(&mDtlsTransport, decltype(mDtlsTransport)(nullptr));
auto ice = std::atomic_exchange(&mIceTransport, decltype(mIceTransport)(nullptr));
if (sctp || dtls || ice) {
std::thread t([sctp, dtls, ice]() mutable {
if (sctp)
sctp->stop();
if (dtls)
dtls->stop();
if (ice)
ice->stop();
sctp.reset();
dtls.reset();
ice.reset();
});
t.detach();
}
} }
void PeerConnection::endLocalCandidates() { void PeerConnection::endLocalCandidates() {
@ -334,14 +405,7 @@ void PeerConnection::forwardMessage(message_ptr message) {
return; return;
} }
shared_ptr<DataChannel> channel; auto channel = findDataChannel(message->stream);
if (auto it = mDataChannels.find(message->stream); it != mDataChannels.end()) {
channel = it->second.lock();
if (!channel || channel->isClosed()) {
mDataChannels.erase(it);
channel = nullptr;
}
}
auto iceTransport = std::atomic_load(&mIceTransport); auto iceTransport = std::atomic_load(&mIceTransport);
auto sctpTransport = std::atomic_load(&mSctpTransport); auto sctpTransport = std::atomic_load(&mSctpTransport);
@ -355,12 +419,12 @@ void PeerConnection::forwardMessage(message_ptr message) {
message->stream % 2 == remoteParity) { message->stream % 2 == remoteParity) {
channel = channel =
std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream); std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
channel->onOpen(std::bind(&PeerConnection::triggerDataChannel, this, channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
weak_ptr<DataChannel>{channel})); weak_ptr<DataChannel>{channel}));
mDataChannels.insert(std::make_pair(message->stream, channel)); mDataChannels.insert(std::make_pair(message->stream, channel));
} else { } else {
// Invalid, close the DataChannel by resetting the stream // Invalid, close the DataChannel
sctpTransport->reset(message->stream); sctpTransport->close(message->stream);
return; return;
} }
} }
@ -369,32 +433,68 @@ void PeerConnection::forwardMessage(message_ptr message) {
} }
void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) { void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
shared_ptr<DataChannel> channel; if (auto channel = findDataChannel(stream))
if (auto it = mDataChannels.find(stream); it != mDataChannels.end()) { channel->triggerBufferedAmount(amount);
channel = it->second.lock();
if (!channel || channel->isClosed()) {
mDataChannels.erase(it);
channel = nullptr;
}
} }
if (channel) shared_ptr<DataChannel> PeerConnection::emplaceDataChannel(Description::Role role,
channel->triggerBufferedAmount(amount); const string &label,
const string &protocol,
const Reliability &reliability) {
// The active side must use streams with even identifiers, whereas the passive side must use
// streams with odd identifiers.
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
std::unique_lock lock(mDataChannelsMutex); // we are going to emplace
unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
while (mDataChannels.find(stream) != mDataChannels.end()) {
stream += 2;
if (stream >= 65535)
throw std::runtime_error("Too many DataChannels");
}
auto channel =
std::make_shared<DataChannel>(shared_from_this(), stream, label, protocol, reliability);
mDataChannels.emplace(std::make_pair(stream, channel));
return channel;
}
shared_ptr<DataChannel> PeerConnection::findDataChannel(uint16_t stream) {
std::shared_lock lock(mDataChannelsMutex); // read-only
if (auto it = mDataChannels.find(stream); it != mDataChannels.end())
if (auto channel = it->second.lock())
return channel;
return nullptr;
} }
void PeerConnection::iterateDataChannels( void PeerConnection::iterateDataChannels(
std::function<void(shared_ptr<DataChannel> channel)> func) { std::function<void(shared_ptr<DataChannel> channel)> func) {
// Iterate
{
std::shared_lock lock(mDataChannelsMutex); // read-only
auto it = mDataChannels.begin(); auto it = mDataChannels.begin();
while (it != mDataChannels.end()) { while (it != mDataChannels.end()) {
auto channel = it->second.lock(); auto channel = it->second.lock();
if (!channel || channel->isClosed()) { if (channel && !channel->isClosed())
func(channel);
++it;
}
}
// Cleanup
{
std::unique_lock lock(mDataChannelsMutex); // we are going to erase
auto it = mDataChannels.begin();
while (it != mDataChannels.end()) {
if (!it->second.lock()) {
it = mDataChannels.erase(it); it = mDataChannels.erase(it);
continue; continue;
} }
func(channel);
++it; ++it;
} }
} }
}
void PeerConnection::openDataChannels() { void PeerConnection::openDataChannels() {
if (auto transport = std::atomic_load(&mSctpTransport)) if (auto transport = std::atomic_load(&mSctpTransport))
@ -414,9 +514,11 @@ void PeerConnection::processLocalDescription(Description description) {
if (auto remote = remoteDescription()) if (auto remote = remoteDescription())
remoteSctpPort = remote->sctpPort(); remoteSctpPort = remote->sctpPort();
auto certificate = mCertificate.get(); // wait for certificate if not ready
std::lock_guard lock(mLocalDescriptionMutex); std::lock_guard lock(mLocalDescriptionMutex);
mLocalDescription.emplace(std::move(description)); mLocalDescription.emplace(std::move(description));
mLocalDescription->setFingerprint(mCertificate->fingerprint()); mLocalDescription->setFingerprint(certificate->fingerprint());
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT)); mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE); mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
@ -441,21 +543,72 @@ void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
mDataChannelCallback(dataChannel); mDataChannelCallback(dataChannel);
} }
void PeerConnection::changeState(State state) { bool PeerConnection::changeState(State state) {
State current; State current;
do { do {
current = mState.load(); current = mState.load();
if (current == state || current == State::Destroying) if (current == state)
return; return true;
if (current == State::Closed)
return false;
} while (!mState.compare_exchange_weak(current, state)); } while (!mState.compare_exchange_weak(current, state));
if (state != State::Destroying)
mStateChangeCallback(state); mStateChangeCallback(state);
return true;
} }
void PeerConnection::changeGatheringState(GatheringState state) { bool PeerConnection::changeGatheringState(GatheringState state) {
if (mGatheringState.exchange(state) != state) if (mGatheringState.exchange(state) != state)
mGatheringStateChangeCallback(state); mGatheringStateChangeCallback(state);
return true;
}
void PeerConnection::resetCallbacks() {
// Unregister all callbacks
mDataChannelCallback = nullptr;
mLocalDescriptionCallback = nullptr;
mLocalCandidateCallback = nullptr;
mStateChangeCallback = nullptr;
mGatheringStateChangeCallback = nullptr;
}
bool PeerConnection::getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote) {
#if not USE_JUICE
auto iceTransport = std::atomic_load(&mIceTransport);
return iceTransport->getSelectedCandidatePair(local, remote);
#else
PLOG_WARNING << "getSelectedCandidatePair is not implemented for libjuice";
return false;
#endif
}
void PeerConnection::clearStats() {
auto sctpTransport = std::atomic_load(&mSctpTransport);
if (sctpTransport)
return sctpTransport->clearStats();
}
size_t PeerConnection::bytesSent() {
auto sctpTransport = std::atomic_load(&mSctpTransport);
if (sctpTransport)
return sctpTransport->bytesSent();
return 0;
}
size_t PeerConnection::bytesReceived() {
auto sctpTransport = std::atomic_load(&mSctpTransport);
if (sctpTransport)
return sctpTransport->bytesReceived();
return 0;
}
std::optional<std::chrono::milliseconds> PeerConnection::rtt() {
auto sctpTransport = std::atomic_load(&mSctpTransport);
if (sctpTransport)
return sctpTransport->rtt();
PLOG_WARNING << "Could not load sctpTransport";
return std::nullopt;
} }
} // namespace rtc } // namespace rtc
@ -482,9 +635,6 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &st
case State::Closed: case State::Closed:
str = "closed"; str = "closed";
break; break;
case State::Destroying:
str = "destroying";
break;
default: default:
str = "unknown"; str = "unknown";
break; break;
@ -511,4 +661,3 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::Gathering
} }
return out << str; return out << str;
} }

View File

@ -22,195 +22,433 @@
#include <rtc.h> #include <rtc.h>
#include <exception>
#include <mutex>
#include <unordered_map> #include <unordered_map>
#include <utility>
#include <plog/Appenders/ColorConsoleAppender.h>
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;
std::unordered_map<int, shared_ptr<DataChannel>> dataChannelMap; std::unordered_map<int, shared_ptr<DataChannel>> dataChannelMap;
std::unordered_map<int, void *> userPointerMap; std::unordered_map<int, void *> userPointerMap;
std::mutex mutex;
int lastId = 0; int lastId = 0;
void *getUserPointer(int id) { void *getUserPointer(int id) {
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() ? it->second : nullptr;
} }
} // namespace void setUserPointer(int i, void *ptr) {
std::lock_guard lock(mutex);
void rtcInitLogger(rtc_log_level_t level) { InitLogger(static_cast<LogLevel>(level)); } if (ptr)
userPointerMap.insert(std::make_pair(i, ptr));
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount) { else
Configuration config; userPointerMap.erase(i);
for (int i = 0; i < iceServersCount; ++i) {
config.iceServers.emplace_back(IceServer(string(iceServers[i])));
} }
shared_ptr<PeerConnection> getPeerConnection(int id) {
std::lock_guard lock(mutex);
auto it = peerConnectionMap.find(id);
return it != peerConnectionMap.end() ? it->second : nullptr;
}
shared_ptr<DataChannel> getDataChannel(int id) {
std::lock_guard lock(mutex);
auto it = dataChannelMap.find(id);
return it != dataChannelMap.end() ? it->second : nullptr;
}
int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
std::lock_guard lock(mutex);
int pc = ++lastId; int pc = ++lastId;
peerConnectionMap.emplace(std::make_pair(pc, std::make_shared<PeerConnection>(config))); peerConnectionMap.emplace(std::make_pair(pc, ptr));
return pc; return pc;
} }
void rtcDeletePeerConnection(int pc) { peerConnectionMap.erase(pc); } int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
std::lock_guard lock(mutex);
int rtcCreateDataChannel(int pc, const char *label) {
auto it = peerConnectionMap.find(pc);
if (it == peerConnectionMap.end())
return 0;
auto dataChannel = it->second->createDataChannel(string(label));
int dc = ++lastId; int dc = ++lastId;
dataChannelMap.emplace(std::make_pair(dc, dataChannel)); dataChannelMap.emplace(std::make_pair(dc, ptr));
return dc; return dc;
} }
void rtcDeleteDataChannel(int dc) { dataChannelMap.erase(dc); } bool erasePeerConnection(int pc) {
std::lock_guard lock(mutex);
if (peerConnectionMap.erase(pc) == 0)
return false;
userPointerMap.erase(pc);
return true;
}
void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *)) { bool eraseDataChannel(int dc) {
auto it = peerConnectionMap.find(pc); std::lock_guard lock(mutex);
if (it == peerConnectionMap.end()) if (dataChannelMap.erase(dc) == 0)
return; return false;
userPointerMap.erase(dc);
return true;
}
it->second->onDataChannel([pc, dataChannelCallback](std::shared_ptr<DataChannel> dataChannel) { } // namespace
int dc = ++lastId;
dataChannelMap.emplace(std::make_pair(dc, dataChannel)); void rtcInitLogger(rtcLogLevel level) { InitLogger(static_cast<LogLevel>(level)); }
dataChannelCallback(dc, getUserPointer(pc));
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
int rtcCreatePeerConnection(const rtcConfiguration *config) {
Configuration c;
for (int i = 0; i < config->iceServersCount; ++i)
c.iceServers.emplace_back(string(config->iceServers[i]));
if (config->portRangeBegin || config->portRangeEnd) {
c.portRangeBegin = config->portRangeBegin;
c.portRangeEnd = config->portRangeEnd;
}
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
}
int rtcDeletePeerConnection(int pc) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
peerConnection->onDataChannel(nullptr);
peerConnection->onLocalDescription(nullptr);
peerConnection->onLocalCandidate(nullptr);
peerConnection->onStateChange(nullptr);
peerConnection->onGatheringStateChange(nullptr);
erasePeerConnection(pc);
return 0;
}
int rtcCreateDataChannel(int pc, const char *label) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
void *ptr = getUserPointer(pc);
rtcSetUserPointer(dc, ptr);
return dc;
}
int rtcDeleteDataChannel(int dc) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
dataChannel->onOpen(nullptr);
dataChannel->onClosed(nullptr);
dataChannel->onError(nullptr);
dataChannel->onMessage(nullptr);
dataChannel->onBufferedAmountLow(nullptr);
dataChannel->onAvailable(nullptr);
eraseDataChannel(dc);
return 0;
}
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;
} }
void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *, int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
void *)) { auto peerConnection = getPeerConnection(pc);
auto it = peerConnectionMap.find(pc); if (!peerConnection)
if (it == peerConnectionMap.end()) return -1;
return;
it->second->onLocalDescription([pc, descriptionCallback](const Description &description) { if (cb)
descriptionCallback(string(description).c_str(), description.typeString().c_str(), peerConnection->onLocalDescription([pc, cb](const Description &desc) {
getUserPointer(pc)); cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
}); });
else
peerConnection->onLocalDescription(nullptr);
return 0;
} }
void rtcSetLocalCandidateCallback(int pc, int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) {
void (*candidateCallback)(const char *, const char *, void *)) { auto peerConnection = getPeerConnection(pc);
auto it = peerConnectionMap.find(pc); if (!peerConnection)
if (it == peerConnectionMap.end()) return -1;
return;
it->second->onLocalCandidate([pc, candidateCallback](const Candidate &candidate) { if (cb)
candidateCallback(candidate.candidate().c_str(), candidate.mid().c_str(), peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
getUserPointer(pc)); cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
}); });
else
peerConnection->onLocalCandidate(nullptr);
return 0;
} }
void rtcSetStateChangeCallback(int pc, void (*stateCallback)(rtc_state_t state, void *)) { int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) {
auto it = peerConnectionMap.find(pc); auto peerConnection = getPeerConnection(pc);
if (it == peerConnectionMap.end()) if (!peerConnection)
return; return -1;
it->second->onStateChange([pc, stateCallback](PeerConnection::State state) { if (cb)
stateCallback(static_cast<rtc_state_t>(state), getUserPointer(pc)); peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
cb(static_cast<rtcState>(state), getUserPointer(pc));
}); });
else
peerConnection->onStateChange(nullptr);
return 0;
} }
void rtcSetGatheringStateChangeCallback(int pc, int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) {
void (*gatheringStateCallback)(rtc_gathering_state_t state, auto peerConnection = getPeerConnection(pc);
void *)) { if (!peerConnection)
auto it = peerConnectionMap.find(pc); return -1;
if (it == peerConnectionMap.end())
return;
it->second->onGatheringStateChange( if (cb)
[pc, gatheringStateCallback](PeerConnection::GatheringState state) { peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
gatheringStateCallback(static_cast<rtc_gathering_state_t>(state), getUserPointer(pc)); cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
}); });
else
peerConnection->onGatheringStateChange(nullptr);
return 0;
} }
void rtcSetRemoteDescription(int pc, const char *sdp, const char *type) { int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
auto it = peerConnectionMap.find(pc); auto peerConnection = getPeerConnection(pc);
if (it == peerConnectionMap.end()) if (!peerConnection)
return; return -1;
it->second->setRemoteDescription(Description(string(sdp), type ? string(type) : "")); CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
return 0;
} }
void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid) { int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
auto it = peerConnectionMap.find(pc); auto peerConnection = getPeerConnection(pc);
if (it == peerConnectionMap.end()) if (!peerConnection)
return; return -1;
it->second->addRemoteCandidate(Candidate(string(candidate), mid ? string(mid) : "")); 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) { int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
auto it = dataChannelMap.find(dc); auto dataChannel = getDataChannel(dc);
if (it == dataChannelMap.end()) if (!dataChannel)
return 0; return -1;
if (!size) if (!size)
return 0; return 0;
string label = it->second->label(); string label = dataChannel->label();
size = std::min(size_t(size - 1), label.size()); size = std::min(size_t(size - 1), label.size());
std::copy(label.data(), label.data() + size, buffer); std::copy(label.data(), label.data() + size, buffer);
buffer[size] = '\0'; buffer[size] = '\0';
return size + 1; return size + 1;
} }
void rtcSetOpenCallback(int dc, void (*openCallback)(void *)) { int rtcSetOpenCallback(int dc, openCallbackFunc cb) {
auto it = dataChannelMap.find(dc); auto dataChannel = getDataChannel(dc);
if (it == dataChannelMap.end()) if (!dataChannel)
return; return -1;
it->second->onOpen([dc, openCallback]() { openCallback(getUserPointer(dc)); }); if (cb)
dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); });
else
dataChannel->onOpen(nullptr);
return 0;
} }
void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *)) { int rtcSetClosedCallback(int dc, closedCallbackFunc cb) {
auto it = dataChannelMap.find(dc); auto dataChannel = getDataChannel(dc);
if (it == dataChannelMap.end()) if (!dataChannel)
return; return -1;
it->second->onError([dc, errorCallback](const string &error) { if (cb)
errorCallback(error.c_str(), getUserPointer(dc)); dataChannel->onClosed([dc, cb]() { cb(getUserPointer(dc)); });
}); else
dataChannel->onClosed(nullptr);
return 0;
} }
void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *)) { int rtcSetErrorCallback(int dc, errorCallbackFunc cb) {
auto it = dataChannelMap.find(dc); auto dataChannel = getDataChannel(dc);
if (it == dataChannelMap.end()) if (!dataChannel)
return; return -1;
it->second->onMessage( if (cb)
[dc, messageCallback](const binary &b) { dataChannel->onError(
messageCallback(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc)); [dc, cb](const string &error) { cb(error.c_str(), getUserPointer(dc)); });
else
dataChannel->onError(nullptr);
return 0;
}
int rtcSetMessageCallback(int dc, messageCallbackFunc cb) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (cb)
dataChannel->onMessage(
[dc, cb](const binary &b) {
cb(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
}, },
[dc, messageCallback](const string &s) { [dc, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(dc)); });
messageCallback(s.c_str(), -1, getUserPointer(dc)); else
}); dataChannel->onMessage(nullptr);
return 0;
} }
int rtcSendMessage(int dc, const char *data, int size) { int rtcSendMessage(int dc, const char *data, int size) {
auto it = dataChannelMap.find(dc); auto dataChannel = getDataChannel(dc);
if (it == dataChannelMap.end()) if (!dataChannel)
return 0; return -1;
if (size >= 0) { if (size >= 0) {
auto b = reinterpret_cast<const byte *>(data); auto b = reinterpret_cast<const byte *>(data);
it->second->send(b, size); CATCH(dataChannel->send(b, size));
return size; return size;
} else { } else {
string s(data); string s(data);
it->second->send(s); CATCH(dataChannel->send(s));
return s.size(); return s.size();
} }
} }
void rtcSetUserPointer(int i, void *ptr) { int rtcGetBufferedAmount(int dc) {
if (ptr) auto dataChannel = getDataChannel(dc);
userPointerMap.insert(std::make_pair(i, ptr)); if (!dataChannel)
else return -1;
userPointerMap.erase(i);
CATCH(return int(dataChannel->bufferedAmount()));
} }
int rtcSetBufferedAmountLowThreshold(int dc, int amount) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
CATCH(dataChannel->setBufferedAmountLowThreshold(size_t(amount)));
return 0;
}
int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (cb)
dataChannel->onBufferedAmountLow([dc, cb]() { cb(getUserPointer(dc)); });
else
dataChannel->onBufferedAmountLow(nullptr);
return 0;
}
int rtcGetAvailableAmount(int dc) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
CATCH(return int(dataChannel->availableAmount()));
}
int rtcSetAvailableCallback(int dc, availableCallbackFunc cb) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (cb)
dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); });
else
dataChannel->onOpen(nullptr);
return 0;
}
int rtcReceiveMessage(int dc, char *buffer, int *size) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (!size)
return -1;
CATCH({
auto message = dataChannel->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);
});
}
void rtcCleanup() { rtc::Cleanup(); }

View File

@ -21,9 +21,27 @@
#include <chrono> #include <chrono>
#include <exception> #include <exception>
#include <iostream> #include <iostream>
#include <thread>
#include <vector> #include <vector>
#include <arpa/inet.h> #ifdef USE_JUICE
#ifndef __APPLE__
// libjuice enables Linux path MTU discovery or sets the DF flag
#define USE_PMTUD 1
#else
// Setting the DF flag is not available on Mac OS
#define USE_PMTUD 0
#endif
#else
#ifdef __linux__
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE)
// It should be safe to enable discovery for SCTP.
#define USE_PMTUD 1
#else
// Otherwise assume fragmentation
#define USE_PMTUD 0
#endif
#endif
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace std::chrono; using namespace std::chrono;
@ -32,12 +50,7 @@ using std::shared_ptr;
namespace rtc { namespace rtc {
std::mutex SctpTransport::GlobalMutex; void SctpTransport::Init() {
int SctpTransport::InstancesCount = 0;
void SctpTransport::GlobalInit() {
std::lock_guard lock(GlobalMutex);
if (InstancesCount++ == 0) {
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);
usrsctp_sysctl_set_sctp_init_rtx_max_default(5); usrsctp_sysctl_set_sctp_init_rtx_max_default(5);
@ -49,13 +62,10 @@ void SctpTransport::GlobalInit() {
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
} }
}
void SctpTransport::GlobalCleanup() { void SctpTransport::Cleanup() {
std::lock_guard lock(GlobalMutex); while (usrsctp_finish() != 0)
if (--InstancesCount == 0) { std::this_thread::sleep_for(100ms);
usrsctp_finish();
}
} }
SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
@ -67,7 +77,6 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
onRecv(recvCallback); onRecv(recvCallback);
PLOG_DEBUG << "Initializing SCTP transport"; PLOG_DEBUG << "Initializing SCTP transport";
GlobalInit();
usrsctp_register_address(this); usrsctp_register_address(this);
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback, mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
@ -117,12 +126,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
std::to_string(errno)); std::to_string(errno));
struct sctp_paddrparams spp = {}; struct sctp_paddrparams spp = {};
#ifdef __linux__ #if USE_PMTUD
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE). // Enabled SCTP path MTU discovery
// It should be safe to enable discovery for SCTP.
spp.spp_flags = SPP_PMTUD_ENABLE; spp.spp_flags = SPP_PMTUD_ENABLE;
#else #else
// Otherwise, fall back to a safe MTU value. // Fall back to a safe MTU value.
spp.spp_flags = SPP_PMTUD_DISABLE; spp.spp_flags = SPP_PMTUD_DISABLE;
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261 spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
// See https://tools.ietf.org/html/rfc8261#section-5 // See https://tools.ietf.org/html/rfc8261#section-5
@ -140,6 +148,14 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
throw std::runtime_error("Could not set socket option SCTP_INITMSG, errno=" + throw std::runtime_error("Could not set socket option SCTP_INITMSG, errno=" +
std::to_string(errno)); std::to_string(errno));
// Prevent fragmented interleave of messages (i.e. level 0), see RFC 6458 8.1.20.
// Unless the user has set the fragmentation interleave level to 0, notifications
// may also be interleaved with partially delivered messages.
int level = 0;
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &level, sizeof(level)))
throw std::runtime_error("Could not disable SCTP fragmented interleave, errno=" +
std::to_string(errno));
// 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
@ -151,32 +167,36 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
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));
registerIncoming();
connect(); connect();
} }
SctpTransport::~SctpTransport() { SctpTransport::~SctpTransport() {
stop(); stop();
if (mSock)
usrsctp_close(mSock); usrsctp_close(mSock);
usrsctp_deregister_address(this);
GlobalCleanup(); usrsctp_deregister_address(this);
} }
SctpTransport::State SctpTransport::state() const { return mState; } SctpTransport::State SctpTransport::state() const { return mState; }
void SctpTransport::stop() { bool SctpTransport::stop() {
Transport::stop(); if (!Transport::stop())
onRecv(nullptr); return false;
if (!mShutdown.exchange(true)) {
mSendQueue.stop(); mSendQueue.stop();
flush(); safeFlush();
shutdown(); shutdown();
} onRecv(nullptr);
return true;
} }
void SctpTransport::connect() { void SctpTransport::connect() {
if (!mSock)
return;
PLOG_DEBUG << "SCTP connect"; PLOG_DEBUG << "SCTP connect";
changeState(State::Connecting); changeState(State::Connecting);
@ -200,12 +220,19 @@ void SctpTransport::connect() {
} }
void SctpTransport::shutdown() { void SctpTransport::shutdown() {
if (!mSock)
return;
PLOG_DEBUG << "SCTP shutdown"; PLOG_DEBUG << "SCTP shutdown";
if (usrsctp_shutdown(mSock, SHUT_RDWR)) { if (usrsctp_shutdown(mSock, SHUT_RDWR) != 0 && errno != ENOTCONN) {
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
usrsctp_close(mSock);
mSock = nullptr;
PLOG_INFO << "SCTP disconnected"; PLOG_INFO << "SCTP disconnected";
changeState(State::Disconnected); changeState(State::Disconnected);
mWrittenCondition.notify_all(); mWrittenCondition.notify_all();
@ -227,31 +254,15 @@ bool SctpTransport::send(message_ptr message) {
return false; return false;
} }
void SctpTransport::close(unsigned int stream) {
send(make_message(0, Message::Reset, uint16_t(stream)));
}
void SctpTransport::flush() { void SctpTransport::flush() {
std::lock_guard lock(mSendMutex); std::lock_guard lock(mSendMutex);
trySendQueue(); trySendQueue();
} }
void SctpTransport::reset(unsigned int stream) {
PLOG_DEBUG << "SCTP resetting stream " << stream;
std::unique_lock lock(mWriteMutex);
mWritten = false;
using srs_t = struct sctp_reset_streams;
const size_t len = sizeof(srs_t) + sizeof(uint16_t);
byte buffer[len] = {};
srs_t &srs = *reinterpret_cast<srs_t *>(buffer);
srs.srs_flags = SCTP_STREAM_RESET_OUTGOING;
srs.srs_number_streams = 1;
srs.srs_stream_list[0] = uint16_t(stream);
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &srs, len) == 0) {
mWrittenCondition.wait_for(lock, 1000ms,
[&]() { return mWritten || mState != State::Connected; });
} else {
PLOG_WARNING << "SCTP reset stream " << stream << " failed, errno=" << errno;
}
}
void SctpTransport::incoming(message_ptr message) { 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
@ -261,13 +272,15 @@ void SctpTransport::incoming(message_ptr message) {
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || mState != State::Connected; }); mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || mState != State::Connected; });
} }
if (message) { if (!message) {
usrsctp_conninput(this, message->data(), message->size(), 0);
} else {
PLOG_INFO << "SCTP disconnected"; PLOG_INFO << "SCTP disconnected";
changeState(State::Disconnected); changeState(State::Disconnected);
recv(nullptr); recv(nullptr);
return;
} }
PLOG_VERBOSE << "Incoming size=" << message->size();
usrsctp_conninput(this, message->data(), message->size(), 0);
} }
void SctpTransport::changeState(State state) { void SctpTransport::changeState(State state) {
@ -289,16 +302,9 @@ bool SctpTransport::trySendQueue() {
bool SctpTransport::trySendMessage(message_ptr message) { bool SctpTransport::trySendMessage(message_ptr message) {
// Requires mSendMutex to be locked // Requires mSendMutex to be locked
if (mState != State::Connected) if (!mSock || mState != State::Connected)
return false; return false;
PLOG_VERBOSE << "SCTP try send size=" << message->size();
// TODO: Implement SCTP ndata specification draft when supported everywhere
// See https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-08
const Reliability reliability = message->reliability ? *message->reliability : Reliability();
uint32_t ppid; uint32_t ppid;
switch (message->type) { switch (message->type) {
case Message::String: case Message::String:
@ -307,11 +313,24 @@ bool SctpTransport::trySendMessage(message_ptr message) {
case Message::Binary: case Message::Binary:
ppid = !message->empty() ? PPID_BINARY : PPID_BINARY_EMPTY; ppid = !message->empty() ? PPID_BINARY : PPID_BINARY_EMPTY;
break; break;
default: case Message::Control:
ppid = PPID_CONTROL; ppid = PPID_CONTROL;
break; break;
case Message::Reset:
sendReset(message->stream);
return true;
default:
// Ignore
return true;
} }
PLOG_VERBOSE << "SCTP try send size=" << message->size();
// TODO: Implement SCTP ndata specification draft when supported everywhere
// See https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-08
const Reliability reliability = message->reliability ? *message->reliability : Reliability();
struct sctp_sendv_spa spa = {}; struct sctp_sendv_spa spa = {};
// set sndinfo // set sndinfo
@ -352,8 +371,10 @@ bool SctpTransport::trySendMessage(message_ptr message) {
if (ret >= 0) { if (ret >= 0) {
PLOG_VERBOSE << "SCTP sent size=" << message->size(); PLOG_VERBOSE << "SCTP sent size=" << message->size();
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
mBytesSent += message->size();
return true; return true;
} else if (errno == EWOULDBLOCK && errno == EAGAIN) { } else if (errno == EWOULDBLOCK || errno == EAGAIN) {
PLOG_VERBOSE << "SCTP sending not possible"; PLOG_VERBOSE << "SCTP sending not possible";
return false; return false;
} else { } else {
@ -365,25 +386,69 @@ bool SctpTransport::trySendMessage(message_ptr message) {
void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) { void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
// Requires mSendMutex to be locked // Requires mSendMutex to be locked
auto it = mBufferedAmount.insert(std::make_pair(streamId, 0)).first; auto it = mBufferedAmount.insert(std::make_pair(streamId, 0)).first;
size_t amount = it->second; size_t amount = size_t(std::max(long(it->second) + delta, long(0)));
amount = size_t(std::max(long(amount) + delta, long(0)));
if (amount == 0) if (amount == 0)
mBufferedAmount.erase(it); mBufferedAmount.erase(it);
else
it->second = amount;
mBufferedAmountCallback(streamId, amount); mBufferedAmountCallback(streamId, amount);
} }
void SctpTransport::sendReset(uint16_t streamId) {
// Requires mSendMutex to be locked
if (!mSock || state() != State::Connected)
return;
PLOG_DEBUG << "SCTP resetting stream " << streamId;
using srs_t = struct sctp_reset_streams;
const size_t len = sizeof(srs_t) + sizeof(uint16_t);
byte buffer[len] = {};
srs_t &srs = *reinterpret_cast<srs_t *>(buffer);
srs.srs_flags = SCTP_STREAM_RESET_OUTGOING;
srs.srs_number_streams = 1;
srs.srs_stream_list[0] = streamId;
mWritten = false;
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &srs, len) == 0) {
std::unique_lock lock(mWriteMutex); // locking before setsockopt might deadlock usrsctp...
mWrittenCondition.wait_for(lock, 1000ms,
[&]() { return mWritten || mState != State::Connected; });
} else if (errno == EINVAL) {
PLOG_VERBOSE << "SCTP stream " << streamId << " already reset";
} else {
PLOG_WARNING << "SCTP reset stream " << streamId << " failed, errno=" << errno;
}
}
bool SctpTransport::safeFlush() {
try {
flush();
return true;
} catch (const std::exception &e) {
PLOG_ERROR << "SCTP flush: " << e.what();
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, const byte *data,
size_t len, struct sctp_rcvinfo info, int flags) { size_t len, struct sctp_rcvinfo info, int flags) {
try { try {
PLOG_VERBOSE << "Handle recv, len=" << len;
if (!len) if (!len)
return -1; return -1;
// This is valid because SCTP_FRAGMENT_INTERLEAVE is set to level 0
// so partial messages and notifications may not be interleaved.
if (flags & MSG_EOR) { if (flags & MSG_EOR) {
if (!mPartialRecv.empty()) { if (!mPartialRecv.empty()) {
mPartialRecv.insert(mPartialRecv.end(), data, data + len); mPartialRecv.insert(mPartialRecv.end(), data, data + len);
data = mPartialRecv.data(); data = mPartialRecv.data();
len = mPartialRecv.size(); len = mPartialRecv.size();
} }
// Message is complete, process it // Message/Notification is complete, process it
if (flags & MSG_NOTIFICATION) if (flags & MSG_NOTIFICATION)
processNotification(reinterpret_cast<const union sctp_notification *>(data), len); processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
else else
@ -391,7 +456,7 @@ int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, co
mPartialRecv.clear(); mPartialRecv.clear();
} else { } else {
// Message is not complete // Message/Notification is not complete
mPartialRecv.insert(mPartialRecv.end(), data, data + len); mPartialRecv.insert(mPartialRecv.end(), data, data + len);
} }
} catch (const std::exception &e) { } catch (const std::exception &e) {
@ -402,24 +467,21 @@ int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, co
} }
int SctpTransport::handleSend(size_t free) { int SctpTransport::handleSend(size_t free) {
try { PLOG_VERBOSE << "Handle send, free=" << free;
std::lock_guard lock(mSendMutex); return safeFlush() ? 0 : -1;
trySendQueue();
} catch (const std::exception &e) {
PLOG_ERROR << "SCTP send: " << e.what();
return -1;
}
return 0; // success
} }
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 {
PLOG_VERBOSE << "Handle write, len=" << len;
std::unique_lock lock(mWriteMutex); 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();
} catch (const std::exception &e) { } catch (const std::exception &e) {
PLOG_ERROR << "SCTP write: " << e.what(); PLOG_ERROR << "SCTP write: " << e.what();
return -1; return -1;
@ -428,6 +490,8 @@ int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_
} }
void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, PayloadId ppid) { void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, PayloadId ppid) {
PLOG_VERBOSE << "Process data, len=" << len;
// The usage of the PPIDs "WebRTC String Partial" and "WebRTC Binary Partial" is deprecated. // The usage of the PPIDs "WebRTC String Partial" and "WebRTC Binary Partial" is deprecated.
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.6 // See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.6
// We handle them at reception for compatibility reasons but should never send them. // We handle them at reception for compatibility reasons but should never send them.
@ -442,9 +506,11 @@ void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, Payl
case PPID_STRING: case PPID_STRING:
if (mPartialStringData.empty()) { if (mPartialStringData.empty()) {
mBytesReceived += len;
recv(make_message(data, data + len, Message::String, sid)); recv(make_message(data, data + len, Message::String, sid));
} else { } else {
mPartialStringData.insert(mPartialStringData.end(), data, data + len); mPartialStringData.insert(mPartialStringData.end(), data, data + len);
mBytesReceived += mPartialStringData.size();
recv(make_message(mPartialStringData.begin(), mPartialStringData.end(), Message::String, recv(make_message(mPartialStringData.begin(), mPartialStringData.end(), Message::String,
sid)); sid));
mPartialStringData.clear(); mPartialStringData.clear();
@ -464,9 +530,11 @@ void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, Payl
case PPID_BINARY: case PPID_BINARY:
if (mPartialBinaryData.empty()) { if (mPartialBinaryData.empty()) {
mBytesReceived += len;
recv(make_message(data, data + len, Message::Binary, sid)); recv(make_message(data, data + len, Message::Binary, sid));
} else { } else {
mPartialBinaryData.insert(mPartialBinaryData.end(), data, data + len); mPartialBinaryData.insert(mPartialBinaryData.end(), data, data + len);
mBytesReceived += mPartialStringData.size();
recv(make_message(mPartialBinaryData.begin(), mPartialBinaryData.end(), Message::Binary, recv(make_message(mPartialBinaryData.begin(), mPartialBinaryData.end(), Message::Binary,
sid)); sid));
mPartialBinaryData.clear(); mPartialBinaryData.clear();
@ -488,10 +556,15 @@ void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, Payl
} }
void SctpTransport::processNotification(const union sctp_notification *notify, size_t len) { void SctpTransport::processNotification(const union sctp_notification *notify, size_t len) {
if (len != size_t(notify->sn_header.sn_length)) if (len != size_t(notify->sn_header.sn_length)) {
PLOG_WARNING << "Invalid notification length";
return; return;
}
switch (notify->sn_header.sn_type) { auto type = notify->sn_header.sn_type;
PLOG_VERBOSE << "Process notification, type=" << type;
switch (type) {
case SCTP_ASSOC_CHANGE: { case SCTP_ASSOC_CHANGE: {
const struct sctp_assoc_change &assoc_change = notify->sn_assoc_change; const struct sctp_assoc_change &assoc_change = notify->sn_assoc_change;
if (assoc_change.sac_state == SCTP_COMM_UP) { if (assoc_change.sac_state == SCTP_COMM_UP) {
@ -507,13 +580,16 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
} }
mWrittenCondition.notify_all(); mWrittenCondition.notify_all();
} }
break;
} }
case SCTP_SENDER_DRY_EVENT: { case SCTP_SENDER_DRY_EVENT: {
// It not should be necessary since the send callback should have been called already, // It not should be necessary since the send callback should have been called already,
// but to be sure, let's try to send now. // but to be sure, let's try to send now.
std::lock_guard lock(mSendMutex); safeFlush();
trySendQueue(); break;
} }
case SCTP_STREAM_RESET_EVENT: { case SCTP_STREAM_RESET_EVENT: {
const struct sctp_stream_reset_event &reset_event = notify->sn_strreset_event; const struct sctp_stream_reset_event &reset_event = notify->sn_strreset_event;
const int count = (reset_event.strreset_length - sizeof(reset_event)) / sizeof(uint16_t); const int count = (reset_event.strreset_length - sizeof(reset_event)) / sizeof(uint16_t);
@ -522,7 +598,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];
reset(streamId); close(streamId);
} }
} }
if (flags & SCTP_STREAM_RESET_INCOMING_SSN) { if (flags & SCTP_STREAM_RESET_INCOMING_SSN) {
@ -542,6 +618,28 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
} }
} }
void SctpTransport::clearStats() {
mBytesReceived = 0;
mBytesSent = 0;
}
size_t SctpTransport::bytesSent() { return mBytesSent; }
size_t SctpTransport::bytesReceived() { return mBytesReceived; }
std::optional<milliseconds> SctpTransport::rtt() {
if (!mSock || state() != State::Connected)
return nullopt;
struct sctp_status status = {};
socklen_t len = sizeof(status);
if (usrsctp_getsockopt(mSock, IPPROTO_SCTP, SCTP_STATUS, &status, &len)) {
PLOG_WARNING << "Could not read SCTP_STATUS";
return nullopt;
}
return milliseconds(status.sstat_primary.spinfo_srtt);
}
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) { size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) {
int ret = static_cast<SctpTransport *>(ptr)->handleRecv( int ret = static_cast<SctpTransport *>(ptr)->handleRecv(

View File

@ -29,15 +29,15 @@
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <sys/socket.h>
#include <sys/types.h>
#include "usrsctp.h" #include "usrsctp.h"
namespace rtc { namespace rtc {
class SctpTransport : public Transport { class SctpTransport : public Transport {
public: public:
static void Init();
static void Cleanup();
enum class State { Disconnected, Connecting, Connected, Failed }; enum class State { Disconnected, Connecting, Connected, Failed };
using amount_callback = std::function<void(uint16_t streamId, size_t amount)>; using amount_callback = std::function<void(uint16_t streamId, size_t amount)>;
@ -49,10 +49,16 @@ public:
State state() const; State state() const;
void 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 flush(); void flush();
void reset(unsigned int stream);
// Stats
void clearStats();
size_t bytesSent();
size_t bytesReceived();
std::optional<std::chrono::milliseconds> rtt();
private: private:
// Order seems wrong but these are the actual values // Order seems wrong but these are the actual values
@ -75,6 +81,8 @@ private:
bool trySendQueue(); bool trySendQueue();
bool trySendMessage(message_ptr message); bool trySendMessage(message_ptr message);
void updateBufferedAmount(uint16_t streamId, long delta); void updateBufferedAmount(uint16_t streamId, long delta);
void sendReset(uint16_t streamId);
bool safeFlush();
int handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data, size_t len, int handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data, size_t len,
struct sctp_rcvinfo recv_info, int flags); struct sctp_rcvinfo recv_info, int flags);
@ -92,28 +100,23 @@ private:
std::map<uint16_t, size_t> mBufferedAmount; std::map<uint16_t, size_t> mBufferedAmount;
amount_callback mBufferedAmountCallback; amount_callback mBufferedAmountCallback;
std::recursive_mutex mWriteMutex; std::mutex mWriteMutex;
std::condition_variable_any mWrittenCondition; std::condition_variable mWrittenCondition;
bool mWritten = false; std::atomic<bool> mWritten = false; // written outside lock
bool mWrittenOnce = false; bool mWrittenOnce = false;
std::atomic<bool> mShutdown = false;
state_callback mStateChangeCallback; state_callback mStateChangeCallback;
std::atomic<State> mState; std::atomic<State> mState;
// Stats
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
binary mPartialRecv, mPartialStringData, mPartialBinaryData; binary mPartialRecv, mPartialStringData, mPartialBinaryData;
static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len, static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
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);
void GlobalInit();
void GlobalCleanup();
static std::mutex GlobalMutex;
static int InstancesCount;
}; };
} // namespace rtc } // namespace rtc

View File

@ -32,25 +32,30 @@ using namespace std::placeholders;
class Transport { class Transport {
public: public:
Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(std::move(lower)) { Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(std::move(lower)) {}
virtual ~Transport() {
stop();
if (mLower)
mLower->onRecv(nullptr); // doing it on stop could cause a deadlock
}
virtual bool stop() {
return !mShutdown.exchange(true);
}
void registerIncoming() {
if (mLower) if (mLower)
mLower->onRecv(std::bind(&Transport::incoming, this, _1)); mLower->onRecv(std::bind(&Transport::incoming, this, _1));
} }
virtual ~Transport() { stop(); }
virtual void stop() {
if (mLower)
mLower->onRecv(nullptr);
}
virtual bool send(message_ptr message) = 0;
void onRecv(message_callback callback) { mRecvCallback = std::move(callback); } void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }
virtual bool send(message_ptr message) { return outgoing(message); }
protected: protected:
void recv(message_ptr message) { mRecvCallback(message); } void recv(message_ptr message) { mRecvCallback(message); }
virtual void incoming(message_ptr message) = 0; virtual void incoming(message_ptr message) { recv(message); }
virtual bool outgoing(message_ptr message) { virtual bool outgoing(message_ptr message) {
if (mLower) if (mLower)
return mLower->send(message); return mLower->send(message);
@ -61,6 +66,7 @@ protected:
private: private:
std::shared_ptr<Transport> mLower; std::shared_ptr<Transport> mLower;
synchronized_callback<message_ptr> mRecvCallback; synchronized_callback<message_ptr> mRecvCallback;
std::atomic<bool> mShutdown = false;
}; };
} // namespace rtc } // namespace rtc

220
test/capi.cpp Normal file
View File

@ -0,0 +1,220 @@
/**
* 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 <rtc/rtc.h>
#include <cstdbool>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
typedef struct {
rtcState state;
rtcGatheringState gatheringState;
int pc;
int dc;
bool connected;
} Peer;
Peer *peer1 = NULL;
Peer *peer2 = NULL;
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
Peer *peer = (Peer *)ptr;
printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
Peer *other = peer == peer1 ? peer2 : peer1;
rtcSetRemoteDescription(other->pc, sdp, type);
}
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
Peer *peer = (Peer *)ptr;
printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
Peer *other = peer == peer1 ? peer2 : peer1;
rtcAddRemoteCandidate(other->pc, cand, mid);
}
static void stateChangeCallback(rtcState state, void *ptr) {
Peer *peer = (Peer *)ptr;
peer->state = state;
printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
}
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
Peer *peer = (Peer *)ptr;
peer->gatheringState = state;
printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
}
static void openCallback(void *ptr) {
Peer *peer = (Peer *)ptr;
peer->connected = true;
printf("DataChannel %d: Open\n", peer == peer1 ? 1 : 2);
const char *message = peer == peer1 ? "Hello from 1" : "Hello from 2";
rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
}
static void closedCallback(void *ptr) {
Peer *peer = (Peer *)ptr;
peer->connected = false;
}
static void messageCallback(const char *message, int size, void *ptr) {
Peer *peer = (Peer *)ptr;
if (size < 0) { // negative size indicates a null-terminated string
printf("Message %d: %s\n", peer == peer1 ? 1 : 2, message);
} else {
printf("Message %d: [binary of size %d]\n", peer == peer1 ? 1 : 2, size);
}
}
static void dataChannelCallback(int dc, void *ptr) {
Peer *peer = (Peer *)ptr;
peer->dc = dc;
peer->connected = true;
rtcSetClosedCallback(dc, closedCallback);
rtcSetMessageCallback(dc, messageCallback);
char buffer[256];
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
printf("DataChannel %d: Received with label \"%s\"\n", peer == peer1 ? 1 : 2, buffer);
const char *message = peer == peer1 ? "Hello from 1" : "Hello from 2";
rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
}
static Peer *createPeer(const rtcConfiguration *config) {
Peer *peer = (Peer *)malloc(sizeof(Peer));
if (!peer)
return nullptr;
memset(peer, 0, sizeof(Peer));
// Create peer connection
peer->pc = rtcCreatePeerConnection(config);
rtcSetUserPointer(peer->pc, peer);
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
return peer;
}
static void deletePeer(Peer *peer) {
if (peer) {
if (peer->dc)
rtcDeleteDataChannel(peer->dc);
if (peer->pc)
rtcDeletePeerConnection(peer->pc);
free(peer);
}
}
int test_capi_main() {
int attempts;
rtcInitLogger(RTC_LOG_DEBUG);
// Create peer 1
rtcConfiguration config1;
memset(&config1, 0, sizeof(config1));
// STUN server example
// const char *iceServers[1] = {"stun:stun.l.google.com:19302"};
// config1.iceServers = iceServers;
// config1.iceServersCount = 1;
peer1 = createPeer(&config1);
if (!peer1)
goto error;
// Create peer 2
rtcConfiguration config2;
memset(&config2, 0, sizeof(config2));
// STUN server example
// config2.iceServers = iceServers;
// config2.iceServersCount = 1;
// Port range example
config2.portRangeBegin = 5000;
config2.portRangeEnd = 6000;
peer2 = createPeer(&config2);
if (!peer2)
goto error;
// Peer 1: Create data channel
peer1->dc = rtcCreateDataChannel(peer1->pc, "test");
rtcSetOpenCallback(peer1->dc, openCallback);
rtcSetClosedCallback(peer1->dc, closedCallback);
rtcSetMessageCallback(peer1->dc, messageCallback);
attempts = 10;
while (!peer2->connected && !peer1->connected && attempts--)
sleep(1);
if (peer1->state != RTC_CONNECTED || peer2->state != RTC_CONNECTED) {
fprintf(stderr, "PeerConnection is not connected\n");
goto error;
}
if (!peer1->connected || !peer2->connected) {
fprintf(stderr, "DataChannel is not connected\n");
goto error;
}
char buffer[256];
if (rtcGetLocalAddress(peer1->pc, buffer, 256) >= 0)
printf("Local address 1: %s\n", buffer);
if (rtcGetRemoteAddress(peer1->pc, buffer, 256) >= 0)
printf("Remote address 1: %s\n", buffer);
if (rtcGetLocalAddress(peer2->pc, buffer, 256) >= 0)
printf("Local address 2: %s\n", buffer);
if (rtcGetRemoteAddress(peer2->pc, buffer, 256) >= 0)
printf("Remote address 2: %s\n", buffer);
deletePeer(peer1);
sleep(1);
deletePeer(peer2);
sleep(1);
// You may call rtcCleanup() when finished to free static resources
rtcCleanup();
sleep(1);
printf("Success\n");
return 0;
error:
deletePeer(peer1);
deletePeer(peer2);
return -1;
}
#include <stdexcept>
void test_capi() {
if (test_capi_main())
throw std::runtime_error("Connection failed");
}

148
test/connectivity.cpp Normal file
View File

@ -0,0 +1,148 @@
/**
* 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 <chrono>
#include <iostream>
#include <memory>
#include <thread>
using namespace rtc;
using namespace std;
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
void test_connectivity() {
InitLogger(LogLevel::Debug);
Configuration config1;
// STUN server example
// config1.iceServers.emplace_back("stun:stun.l.google.com:19302");
auto pc1 = std::make_shared<PeerConnection>(config1);
Configuration config2;
// STUN server example
// config2.iceServers.emplace_back("stun:stun.l.google.com:19302");
// Port range example
config2.portRangeBegin = 5000;
config2.portRangeEnd = 6000;
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;
});
shared_ptr<DataChannel> dc2;
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
dc2 = dc;
dc2->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) {
cout << "Message 2: " << get<string>(message) << endl;
}
});
dc2->send("Hello from 2");
});
auto dc1 = pc1->createDataChannel("test");
dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() {
auto dc1 = wdc1.lock();
if (!dc1)
return;
cout << "DataChannel 1: Open" << endl;
dc1->send("Hello from 1");
});
dc1->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) {
cout << "Message 1: " << get<string>(message) << endl;
}
});
int attempts = 10;
while ((!dc2 || !dc2->isOpen() || !dc1->isOpen()) && attempts--)
this_thread::sleep_for(1s);
if (pc1->state() != PeerConnection::State::Connected &&
pc2->state() != PeerConnection::State::Connected)
throw runtime_error("PeerConnection is not connected");
if (!dc1->isOpen() || !dc2->isOpen())
throw runtime_error("DataChannel is not open");
if (auto addr = pc1->localAddress())
cout << "Local address 1: " << *addr << endl;
if (auto addr = pc1->remoteAddress())
cout << "Remote address 1: " << *addr << endl;
if (auto addr = pc2->localAddress())
cout << "Local address 2: " << *addr << endl;
if (auto addr = pc2->remoteAddress())
cout << "Remote address 2: " << *addr << endl;
// Delay close of peer 2 to check closing works properly
pc1->close();
this_thread::sleep_for(1s);
pc2->close();
this_thread::sleep_for(1s);
// You may call rtc::Cleanup() when finished to free static resources
rtc::Cleanup();
this_thread::sleep_for(1s);
cout << "Success" << endl;
}

View File

@ -16,120 +16,29 @@
* 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 "rtc/rtc.hpp"
#include <chrono>
#include <iostream> #include <iostream>
#include <memory>
#include <thread>
using namespace rtc;
using namespace std; using namespace std;
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; } void test_connectivity();
void test_capi();
int main(int argc, char **argv) { int main(int argc, char **argv) {
InitLogger(LogLevel::Warning); try {
std::cout << "*** Running connectivity test..." << std::endl;
Configuration config; test_connectivity();
// config.iceServers.emplace_back("stun:stun.l.google.com:19302"); std::cout << "*** Finished connectivity test" << std::endl;
// config.enableIceTcp = true; } catch (const exception &e) {
std::cerr << "Connectivity test failed: " << e.what() << endl;
// TURN server example return -1;
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnUdp);
// config.iceServers.push_back(turnServer);
auto pc1 = std::make_shared<PeerConnection>(config);
auto pc2 = std::make_shared<PeerConnection>(config);
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;
});
shared_ptr<DataChannel> dc2;
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
cout << "Got a DataChannel with label: " << dc->label() << endl;
dc2 = dc;
dc2->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) {
cout << "Received 2: " << get<string>(message) << endl;
} }
}); try {
dc2->send("Hello from 2"); std::cout << "*** Running C API test..." << std::endl;
}); test_capi();
std::cout << "*** Finished C API test" << std::endl;
auto dc1 = pc1->createDataChannel("test"); } catch (const exception &e) {
dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() { std::cerr << "C API test failed: " << e.what() << endl;
auto dc1 = wdc1.lock(); return -1;
if (!dc1)
return;
cout << "DataChannel open: " << dc1->label() << endl;
dc1->send("Hello from 1");
});
dc1->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) {
cout << "Received 1: " << get<string>(message) << endl;
} }
});
this_thread::sleep_for(3s);
if (auto addr = pc1->localAddress())
cout << "Local address 1: " << *addr << endl;
if (auto addr = pc1->remoteAddress())
cout << "Remote address 1: " << *addr << endl;
if (auto addr = pc2->localAddress())
cout << "Local address 2: " << *addr << endl;
if (auto addr = pc2->remoteAddress())
cout << "Remote address 2: " << *addr << endl;
if (dc1->isOpen() && dc2->isOpen()) {
pc1->close();
pc2->close();
cout << "Success" << endl;
return 0; return 0;
} else {
cout << "Failure" << endl;
return 1;
}
} }

View File

@ -21,6 +21,7 @@
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <thread>
using namespace rtc; using namespace rtc;
using namespace std; using namespace std;
@ -32,23 +33,18 @@ int main(int argc, char **argv) {
Configuration config; Configuration config;
// config.iceServers.emplace_back("stun.l.google.com:19302"); // config.iceServers.emplace_back("stun.l.google.com:19302");
// config.enableIceTcp = true;
// TURN Server example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnUdp);
// config.iceServers.push_back(turnServer);
auto pc = std::make_shared<PeerConnection>(config); auto pc = std::make_shared<PeerConnection>(config);
pc->onLocalDescription([](const Description &sdp) { pc->onLocalDescription([](const Description &description) {
std::string s(sdp); cout << "Local Description (Paste this to the other peer):" << endl;
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94)); cout << string(description) << endl;
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
}); });
pc->onLocalCandidate([](const Candidate &candidate) { pc->onLocalCandidate([](const Candidate &candidate) {
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl; cout << "Local Candidate (Paste this to the other peer after the local description):"
<< endl;
cout << string(candidate) << endl << endl;
}); });
pc->onStateChange( pc->onStateChange(
@ -66,7 +62,7 @@ int main(int argc, char **argv) {
dc->onMessage([](const variant<binary, string> &message) { dc->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) { if (holds_alternative<string>(message)) {
cout << "[ Received: " << get<string>(message) << " ]" << endl; cout << "[Received message: " << get<string>(message) << "]" << endl;
} }
}); });
}); });
@ -74,68 +70,87 @@ int main(int argc, char **argv) {
bool exit = false; bool exit = false;
while (!exit) { while (!exit) {
cout << endl cout << endl
<< "**********************************************************************************"
"*****"
<< endl << endl
<< "*************************************************************************" << endl
<< "* 0: Exit /" << "* 0: Exit /"
<< " 1: Enter Description /" << " 1: Enter remote description /"
<< " 2: Enter Candidate /" << " 2: Enter remote candidate /"
<< " 3: Send Message *" << endl << " 3: Send message /"
<< " 4: Print Connection Info *" << endl
<< "[Command]: "; << "[Command]: ";
int command; int command = -1;
std::string sdp, candidate, message;
const char *a;
std::unique_ptr<Candidate> candidatePtr;
std::unique_ptr<Description> descPtr;
cin >> command; cin >> command;
cin.ignore();
switch (command) { switch (command) {
case 0: case 0: {
exit = true; exit = true;
break; break;
}
case 1: case 1: {
// Parse Description // Parse Description
cout << "[SDP]: "; cout << "[Description]: ";
sdp = ""; string sdp, line;
while (sdp.length() == 0) while (getline(cin, line) && !line.empty()) {
getline(cin, sdp); sdp += line;
sdp += "\r\n";
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n'); }
descPtr = std::make_unique<Description>(sdp, Description::Type::Offer, std::cout << sdp;
Description::Role::Passive); pc->setRemoteDescription(sdp);
pc->setRemoteDescription(*descPtr);
break; break;
}
case 2: case 2: {
// Parse Candidate // Parse Candidate
cout << "[Candidate]: "; cout << "[Candidate]: ";
candidate = ""; string candidate;
while (candidate.length() == 0)
getline(cin, candidate); getline(cin, candidate);
pc->addRemoteCandidate(candidate);
candidatePtr = std::make_unique<Candidate>(candidate);
pc->addRemoteCandidate(*candidatePtr);
break; break;
}
case 3: case 3: {
// Send Message // Send Message
if (!dc || !dc->isOpen()) { if (!dc || !dc->isOpen()) {
cout << "** Channel is not Open ** "; cout << "** Channel is not Open ** ";
break; break;
} }
cout << "[Message]: "; cout << "[Message]: ";
message = ""; string message;
while (message.length() == 0)
getline(cin, message); getline(cin, message);
dc->send(message); dc->send(message);
break; break;
}
default: case 4: {
// Connection Info
if (!dc || !dc->isOpen()) {
cout << "** Channel is not Open ** ";
break;
}
CandidateInfo local, remote;
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
if (pc->getSelectedCandidatePair(&local, &remote)) {
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
<< local.transportType << endl;
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
<< " " << remote.transportType << endl;
cout << "Bytes Sent:" << pc->bytesSent()
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
if (rtt.has_value())
cout << rtt.value().count();
else
cout << "null";
cout << " ms";
} else
cout << "Could not get Candidate Pair Info" << endl;
break;
}
default: {
cout << "** Invalid Command ** "; cout << "** Invalid Command ** ";
break; break;
} }
} }
}
if (dc) if (dc)
dc->close(); dc->close();

View File

@ -21,6 +21,7 @@
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <thread>
using namespace rtc; using namespace rtc;
using namespace std; using namespace std;
@ -32,23 +33,18 @@ int main(int argc, char **argv) {
Configuration config; Configuration config;
// config.iceServers.emplace_back("stun.l.google.com:19302"); // config.iceServers.emplace_back("stun.l.google.com:19302");
// config.enableIceTcp = true;
// TURN server example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnUdp);
// config.iceServers.push_back(turnServer);
auto pc = std::make_shared<PeerConnection>(config); auto pc = std::make_shared<PeerConnection>(config);
pc->onLocalDescription([](const Description &sdp) { pc->onLocalDescription([](const Description &description) {
std::string s(sdp); cout << "Local Description (Paste this to the other peer):" << endl;
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94)); cout << string(description) << endl;
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
}); });
pc->onLocalCandidate([](const Candidate &candidate) { pc->onLocalCandidate([](const Candidate &candidate) {
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl; cout << "Local Candidate (Paste this to the other peer after the local description):"
<< endl;
cout << string(candidate) << endl << endl;
}); });
pc->onStateChange( pc->onStateChange(
@ -58,7 +54,8 @@ int main(int argc, char **argv) {
cout << "[Gathering State: " << state << "]" << endl; cout << "[Gathering State: " << state << "]" << endl;
}); });
auto dc = pc->createDataChannel("test"); auto dc = pc->createDataChannel("test"); // this is the offerer, so create a data channel
dc->onOpen([&]() { cout << "[DataChannel open: " << dc->label() << "]" << endl; }); dc->onOpen([&]() { cout << "[DataChannel open: " << dc->label() << "]" << endl; });
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; }); dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
@ -69,70 +66,91 @@ int main(int argc, char **argv) {
} }
}); });
this_thread::sleep_for(1s);
bool exit = false; bool exit = false;
while (!exit) { while (!exit) {
cout << endl cout << endl
<< "**********************************************************************************"
"*****"
<< endl << endl
<< "*************************************************************************" << endl
<< "* 0: Exit /" << "* 0: Exit /"
<< " 1: Enter Description /" << " 1: Enter remote description /"
<< " 2: Enter Candidate /" << " 2: Enter remote candidate /"
<< " 3: Send Message *" << endl << " 3: Send message /"
<< " 4: Print Connection Info *" << endl
<< "[Command]: "; << "[Command]: ";
int command; int command = -1;
std::string sdp, candidate, message;
const char *a;
std::unique_ptr<Candidate> candidatePtr;
std::unique_ptr<Description> descPtr;
cin >> command; cin >> command;
cin.ignore();
switch (command) { switch (command) {
case 0: case 0: {
exit = true; exit = true;
break; break;
}
case 1: case 1: {
// Parse Description // Parse Description
cout << "[SDP]: "; cout << "[Description]: ";
sdp = ""; string sdp, line;
while (sdp.length() == 0) while (getline(cin, line) && !line.empty()) {
getline(cin, sdp); sdp += line;
sdp += "\r\n";
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n'); }
descPtr = std::make_unique<Description>(sdp); pc->setRemoteDescription(sdp);
pc->setRemoteDescription(*descPtr);
break; break;
}
case 2: case 2: {
// Parse Candidate // Parse Candidate
cout << "[Candidate]: "; cout << "[Candidate]: ";
candidate = ""; string candidate;
while (candidate.length() == 0)
getline(cin, candidate); getline(cin, candidate);
pc->addRemoteCandidate(candidate);
candidatePtr = std::make_unique<Candidate>(candidate);
pc->addRemoteCandidate(*candidatePtr);
break; break;
}
case 3: case 3: {
// Send Message // Send Message
if (!dc->isOpen()) { if (!dc->isOpen()) {
cout << "** Channel is not Open ** "; cout << "** Channel is not Open ** ";
break; break;
} }
cout << "[Message]: "; cout << "[Message]: ";
message = ""; string message;
while (message.length() == 0)
getline(cin, message); getline(cin, message);
dc->send(message); dc->send(message);
break; break;
}
default: case 4: {
// Connection Info
if (!dc || !dc->isOpen()) {
cout << "** Channel is not Open ** ";
break;
}
CandidateInfo local, remote;
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
if (pc->getSelectedCandidatePair(&local, &remote)) {
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
<< local.transportType << endl;
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
<< " " << remote.transportType << endl;
cout << "Bytes Sent:" << pc->bytesSent()
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
if (rtt.has_value())
cout << rtt.value().count();
else
cout << "null";
cout << " ms";
} else
cout << "Could not get Candidate Pair Info" << endl;
break;
}
default: {
cout << "** Invalid Command ** "; cout << "** Invalid Command ** ";
break; break;
} }
} }
}
if (dc) if (dc)
dc->close(); dc->close();

View File

@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.5.1)
project(offerer C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "-Wall -g -O2")
add_executable(offerer offerer.c)
target_link_libraries(offerer datachannel)
add_executable(answerer answerer.c)
target_link_libraries(answerer datachannel)

View File

@ -0,0 +1,326 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
* Copyright (c) 2020 Stevedan Ogochukwu Omodolor
*
* 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.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for sleep
#include <ctype.h>
typedef struct {
rtcState state;
rtcGatheringState gatheringState;
int pc;
int dc;
bool connected;
} Peer;
Peer *peer = NULL;
static void dataChannelCallback(int dc, void *ptr);
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
static void candidateCallback(const char *cand, const char *mid, void *ptr);
static void stateChangeCallback(rtcState state, void *ptr);
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
static void closedCallback(void *ptr);
static void messageCallback(const char *message, int size, void *ptr);
static void deletePeer(Peer *peer);
int all_space(const char *str);
char* state_print(rtcState state);
char* rtcGatheringState_print(rtcState state);
int main(int argc, char **argv) {
rtcInitLogger(RTC_LOG_DEBUG);
// Create peer
rtcConfiguration config;
memset(&config, 0, sizeof(config));
Peer *peer = (Peer *)malloc(sizeof(Peer));
if (!peer) {
printf("Error allocating memory for peer\n");
deletePeer(peer);
}
memset(peer, 0, sizeof(Peer));
printf("Peer created\n");
// Create peer connection
peer->pc = rtcCreatePeerConnection(&config);
rtcSetUserPointer(peer->pc, peer);
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
rtcSetUserPointer(peer->dc, NULL);
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
bool exit = false;
while (!exit) {
printf("\n");
printf("***************************************************************************************\n");
// << endl
printf("* 0: Exit /"
" 1: Enter remote description /"
" 2: Enter remote candidate /"
" 3: Send message /"
" 4: Print Connection Info *\n"
"[Command]: ");
int command = -1;
int c;
// int check_scan
if (scanf("%d", &command)) {
}else {
break;
}
while ((c = getchar()) != '\n' && c != EOF) { }
fflush(stdin);
switch (command) {
case 0: {
exit = true;
break;
}
case 1: {
// Parse Description
printf("[Description]: ");
char *line = NULL;
size_t len = 0;
size_t read = 0;
char *sdp = (char*) malloc(sizeof(char));
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
strcat(sdp, line);
}
printf("%s\n",sdp);
rtcSetRemoteDescription(peer->pc, sdp, "offer");
free(sdp);
free(line);
break;
}
case 2: {
// Parse Candidate
printf("[Candidate]: ");
char* candidate = NULL;
size_t candidate_size = 0;
if(getline(&candidate, &candidate_size, stdin)) {
rtcAddRemoteCandidate(peer->pc, candidate, "0");
free(candidate);
}else {
printf("Error reading line\n");
break;
}
break;
}
case 3: {
// Send Message
if(!peer->connected) {
printf("** Channel is not Open **");
break;
}
printf("[Message]: ");
char* message = NULL;
size_t message_size = 0;
if(getline(&message, &message_size, stdin)) {
rtcSendMessage(peer->dc, message, -1);
free(message);
}else {
printf("Error reading line\n");
break;
}
break;
}
case 4: {
// Connection Info
if(!peer->connected) {
printf("** Channel is not Open **");
break;
}
char buffer[256];
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
printf("Local address 1: %s\n", buffer);
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
printf("Remote address 1: %s\n", buffer);
else
printf("Could not get Candidate Pair Info\n");
break;
}
default: {
printf("** Invalid Command **");
break;
}
}
}
deletePeer(peer);
return 0;
}
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
// Peer *peer = (Peer *)ptr;
printf("Description %s:\n%s\n", "answerer", sdp);
}
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
// Peer *peer = (Peer *)ptr;
printf("Candidate %s: %s\n", "answerer", cand);
}
static void stateChangeCallback(rtcState state, void *ptr) {
Peer *peer = (Peer *)ptr;
peer->state = state;
printf("State %s: %s\n", "answerer", state_print(state));
}
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
Peer *peer = (Peer *)ptr;
peer->gatheringState = state;
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
}
static void closedCallback(void *ptr) {
Peer *peer = (Peer *)ptr;
peer->connected = false;
}
static void messageCallback(const char *message, int size, void *ptr) {
// Peer *peer = (Peer *)ptr;
if (size < 0) { // negative size indicates a null-terminated string
printf("Message %s: %s\n", "answerer", message);
} else {
printf("Message %s: [binary of size %d]\n", "answerer", size);
}
}
static void deletePeer(Peer *peer) {
if (peer) {
if (peer->dc)
rtcDeleteDataChannel(peer->dc);
if (peer->pc)
rtcDeletePeerConnection(peer->pc);
free(peer);
}
}
static void dataChannelCallback(int dc, void *ptr) {
Peer *peer = (Peer *)ptr;
peer->dc = dc;
peer->connected = true;
rtcSetClosedCallback(dc, closedCallback);
rtcSetMessageCallback(dc, messageCallback);
char buffer[256];
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
}
int all_space(const char *str) {
while (*str) {
if (!isspace(*str++)) {
return 0;
}
}
return 1;
}
char* state_print(rtcState state) {
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,334 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
* Copyright (c) 2020 Stevedan Ogochukwu Omodolor
*
* 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.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for sleep
#include <ctype.h>
char* state_print(rtcState state);
char* rtcGatheringState_print(rtcState state);
typedef struct {
rtcState state;
rtcGatheringState gatheringState;
int pc;
int dc;
bool connected;
} Peer;
Peer *peer = NULL;
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
static void candidateCallback(const char *cand, const char *mid, void *ptr);
static void stateChangeCallback(rtcState state, void *ptr);
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
static void openCallback(void *ptr);
static void closedCallback(void *ptr);
static void messageCallback(const char *message, int size, void *ptr);
static void deletePeer(Peer *peer);
int all_space(const char *str);
int main(int argc, char **argv){
rtcInitLogger(RTC_LOG_DEBUG);
// Create peer
rtcConfiguration config;
memset(&config, 0, sizeof(config));
Peer *peer = (Peer *)malloc(sizeof(Peer));
if (!peer) {
printf("Error allocating memory for peer\n");
deletePeer(peer);
}
memset(peer, 0, sizeof(Peer));
printf("Peer created\n");
// Create peer connection
peer->pc = rtcCreatePeerConnection(&config);
rtcSetUserPointer(peer->pc, peer);
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
// Since this is the offere, we will create a datachannel
peer->dc = rtcCreateDataChannel(peer->pc, "test");
rtcSetOpenCallback(peer->dc, openCallback);
rtcSetClosedCallback(peer->dc, closedCallback);
rtcSetMessageCallback(peer->dc, messageCallback);
sleep(1);
bool exit = false;
while (!exit) {
printf("\n");
printf("***************************************************************************************\n");
// << endl
printf("* 0: Exit /"
" 1: Enter remote description /"
" 2: Enter remote candidate /"
" 3: Send message /"
" 4: Print Connection Info *\n"
"[Command]: ");
int command = -1;
int c;
if (scanf("%d", &command)) {
}else {
break;
}
while ((c = getchar()) != '\n' && c != EOF) { }
fflush(stdin);
switch (command) {
case 0: {
exit = true;
break;
}
case 1: {
// Parse Description
printf("[Description]: ");
char *line = NULL;
size_t len = 0;
size_t read = 0;
char *sdp = (char*) malloc(sizeof(char));
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
strcat(sdp, line);
}
printf("%s\n",sdp);
rtcSetRemoteDescription(peer->pc, sdp, "answer");
free(sdp);
free(line);
break;
}
case 2: {
// Parse Candidate
printf("[Candidate]: ");
char* candidate = NULL;
size_t candidate_size = 0;
if(getline(&candidate, &candidate_size, stdin)) {
rtcAddRemoteCandidate(peer->pc, candidate, "0");
free(candidate);
}else {
printf("Error reading line\n");
break;
}
break;
}
case 3: {
// Send Message
if(!peer->connected) {
printf("** Channel is not Open **");
break;
}
printf("[Message]: ");
char* message = NULL;
size_t message_size = 0;
if(getline(&message, &message_size, stdin)) {
rtcSendMessage(peer->dc, message, -1);
free(message);
}else {
printf("Error reading line\n");
break;
}
break;
}
case 4: {
// Connection Info
if(!peer->connected) {
printf("** Channel is not Open **");
break;
}
char buffer[256];
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
printf("Local address 1: %s\n", buffer);
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
printf("Remote address 1: %s\n", buffer);
else
printf("Could not get Candidate Pair Info\n");
break;
}
default: {
printf("** Invalid Command **");
break;
}
}
}
deletePeer(peer);
return 0;
}
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
// Peer *peer = (Peer *)ptr;
printf("Description %s:\n%s\n", "offerer", sdp);
}
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
// Peer *peer = (Peer *)ptr;
printf("Candidate %s: %s\n", "offerer", cand);
}
static void stateChangeCallback(rtcState state, void *ptr) {
Peer *peer = (Peer *)ptr;
peer->state = state;
printf("State %s: %s\n", "offerer", state_print(state));
}
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
Peer *peer = (Peer *)ptr;
peer->gatheringState = state;
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
}
static void openCallback(void *ptr) {
Peer *peer = (Peer *)ptr;
peer->connected = true;
char buffer[256];
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
}
static void closedCallback(void *ptr) {
Peer *peer = (Peer *)ptr;
peer->connected = false;
}
static void messageCallback(const char *message, int size, void *ptr) {
// Peer *peer = (Peer *)ptr;
if (size < 0) { // negative size indicates a null-terminated string
printf("Message %s: %s\n", "offerer", message);
} else {
printf("Message %s: [binary of size %d]\n", "offerer", size);
}
}
static void deletePeer(Peer *peer) {
if (peer) {
if (peer->dc)
rtcDeleteDataChannel(peer->dc);
if (peer->pc)
rtcDeletePeerConnection(peer->pc);
free(peer);
}
}
int all_space(const char *str) {
while (*str) {
if (!isspace(*str++)) {
return 0;
}
}
return 1;
}
char* state_print(rtcState state) {
char *str = NULL;
switch (state) {
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;
}