Compare commits

...

330 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
be6470d8bc Version 0.4.2 2020-02-06 11:42:03 +01:00
8a92c97058 Updated libjuice to v0.2.2 2020-02-06 11:36:01 +01:00
93da605230 Changed usrsctp submodule origin to sctplab/usrsctp 2020-01-29 11:24:11 +01:00
ff0f409d80 Added instructions to build with Makefile 2020-01-22 11:12:46 +01:00
a483e8135b Correct URL for libnice 2020-01-22 10:55:17 +01:00
36e4fdce1e Fixed and updated usrsctp then removed the usrsctp cmake hack 2020-01-22 10:54:21 +01:00
ea636d1f29 Changed version to 0.4.0 2020-01-21 15:39:30 +01:00
472d480978 Updated libjuice to v0.2.0 2020-01-21 15:31:22 +01:00
486fc373b2 Added details about dependencies 2020-01-21 15:30:27 +01:00
2a6c10269e Use nettle for libjuice only if using GnuTLS 2020-01-21 13:51:02 +01:00
ed68ba5402 Merge pull request #34 from paullouisageneau/libjuice
Libjuice as alternative to Libnice
2020-01-21 11:48:15 +00:00
5f91c0c1e3 Updated libjuice 2020-01-21 12:37:34 +01:00
d4c618ae38 Fixed OpenSSL MTU handling with a custom BIO for writing 2020-01-21 10:56:11 +01:00
3f52aa3d56 Fixed local and remote address getters with libjuice 2020-01-20 18:25:42 +01:00
c16ff99d83 Updated libjuice 2020-01-20 17:58:26 +01:00
e14b53f348 Include src headers from tests 2020-01-19 13:45:54 +01:00
ec8847cbf8 Updated libjuice 2020-01-19 11:54:00 +01:00
6c4e8f0d46 Fixed STUN server for libjuice 2020-01-18 17:31:29 +01:00
e2a5d5e1fe Updated libjuice and added logging for STUN server 2020-01-18 17:16:35 +01:00
fa8fda25c8 Updated Jamfile 2020-01-18 16:19:02 +01:00
47c29f0ec1 Set libjuice logging properly 2020-01-18 16:08:31 +01:00
a92438e94d Generated SDP description now uses CRLF instead of LF 2020-01-18 15:37:30 +01:00
0729ab28fd Replaced usrsctp-static by its alias Usrsctp::UsrsctpStatic 2020-01-18 12:50:14 +01:00
fa64b67006 Removed extern C around includes 2020-01-18 12:37:13 +01:00
b4d99158c6 Updated Readme 2020-01-18 00:33:06 +01:00
0ded19992c Integrated libjuice for ICE transport 2020-01-18 00:28:10 +01:00
2dece6afff Updated Makefile and CMakeLists for libjuice 2020-01-18 00:28:04 +01:00
0b554f988e Added submodule libjuice 2020-01-17 11:41:16 +01:00
15d29fc038 Merge pull request #33 from paullouisageneau/rfc8445
Updated to RFC 8445 for ICE
2020-01-14 08:27:16 +00:00
bb2c6c157d Updated to RFC 8445 for ICE 2020-01-14 09:26:05 +01:00
fd0f237a59 Merge pull request #32 from murat-dogan/master
Create stream before turn server addition
2019-12-26 09:05:57 +00:00
f09006f3ef Create stream before turn server addition 2019-12-26 09:48:55 +03:00
06369f1f14 Merge pull request #31 from paullouisageneau/turn-ipv6
TURN server URL and IPv6 support
2019-12-25 15:55:11 +00:00
61354b7101 Added comment 2019-12-25 16:55:01 +01:00
f34791b450 Removed useless include 2019-12-25 16:55:01 +01:00
c3f2f6bc63 Fixed TURN-TLS fallback port 2019-12-25 16:55:01 +01:00
a683b76a21 Extended STUN server URL parsing to TURN server URLs 2019-12-25 16:54:59 +01:00
e11de119be Added TURN server IPv6 support 2019-12-24 12:28:30 +01:00
2129e3cfb9 Cleanup datachannel closing test 2019-12-21 18:56:33 +01:00
58c8bad453 Reset ICE timeout id on timeout 2019-12-21 17:58:03 +01:00
1566c0ef21 Updated compilation with Makefile and Jamfile 2019-12-21 11:44:50 +01:00
03399e4b55 Fixed misplaced SCTP reading shutdown 2019-12-21 11:33:19 +01:00
0066b3aef0 Merge pull request #29 from paullouisageneau/ice-timeout
Fix connection shutdown
2019-12-20 23:01:10 +00:00
75f23f202f Removed trailing space 2019-12-20 23:58:41 +01:00
23e1a75248 Fixed DataChannel and SCTP shutdown 2019-12-20 23:52:38 +01:00
f930cfbe44 Reduced SCTP retransmission count and max RTO 2019-12-20 16:15:54 +01:00
72a0e2fe07 Merge pull request #28 from paullouisageneau/ice-timeout
Add trickle ICE timeout
2019-12-20 12:55:50 +00:00
f90ffbcf86 Added trickle ICE timeout + added some logging 2019-12-20 13:51:00 +01:00
a6992c765d Merge pull request #23 from murat-dogan/p2p-tests
P2P tests
2019-12-20 10:54:59 +00:00
1602498eab Merge pull request #27 from murat-dogan/master
Add Thread as a required package
2019-12-20 10:39:18 +00:00
e4ace4a750 Add Thread as an REQUIRED package 2019-12-20 12:00:30 +03:00
b5a13d2d66 Rename peers 2019-12-20 09:48:23 +03:00
aeb777aa49 Merge pull request #24 from paullouisageneau/integrate-plog
Integrate plog
2019-12-19 17:45:04 +00:00
7a552bb0fa Replaced console output with logging 2019-12-19 18:43:48 +01:00
402a4df4a0 Added InitLogger function and renamed enums 2019-12-19 15:58:48 +01:00
34ef87e271 Add executables to cmake 2019-12-19 15:44:36 +03:00
522319ac5d P2P Tests 2019-12-19 15:34:08 +03:00
1ac00ce396 Move logger init task to a function 2019-12-18 14:53:53 +03:00
92f08948d3 Integrate plog 2019-12-18 10:33:35 +03:00
9749f8d63e Merge pull request #21 from paullouisageneau/deps
Separate directory for dependencies
2019-12-17 20:42:34 +00:00
8626a07824 Updated Jamfile 2019-12-17 21:41:06 +01:00
37ca38999c Updated CMakeLists 2019-12-17 21:39:26 +01:00
18eeac3c0c Updated Makefile 2019-12-17 21:33:01 +01:00
c7de492b4b Moved usrsctp to deps 2019-12-17 21:31:52 +01:00
901700177b Merge pull request #17 from murat-dogan/master
Fix logic error: If we pass Relay Type then it is a TURN Server
2019-12-17 18:05:54 +00:00
88348732d9 Merge pull request #19 from paullouisageneau/fix-late-candidates
Fix late candidates triggering DTLS init twice
2019-12-17 15:04:14 +00:00
281eea2cec Fixed late candidates triggering DTLS init twice 2019-12-17 16:00:43 +01:00
28f923b1ce Fix Logic Error: If we are asking Relay Type than it is a TURN Server 2019-12-17 16:24:19 +03:00
48bdb6a1c9 Merge pull request #16 from murat-dogan/master
Option for enabling ICE TCP Candidates
2019-12-17 08:11:18 +00:00
278ac22766 Option for enabling ICE TCP Candidates 2019-12-17 07:49:26 +03:00
4fb14244db Merge pull request #14 from murat-dogan/master
TURN Server Support
2019-12-16 20:46:52 +00:00
432be41b9a Turn server protocol & Consistency Changes 2019-12-16 21:12:59 +03:00
78c80992bc Merge pull request #15 from paullouisageneau/revised-sync
Add close method, revise synchronization and states
2019-12-16 11:22:45 +00:00
8b64f8a406 Added PeerConnection::close() calls to test 2019-12-16 12:21:25 +01:00
1d7d1358be Added PeerConnection::close() and revised state machine 2019-12-16 12:15:59 +01:00
5fc6a1c8ad TURN Server Support 2019-12-16 13:04:32 +03:00
e5a19f85ed Revised synchronization 2019-12-16 10:45:00 +01:00
5a8725dac1 Merge pull request #13 from paullouisageneau/stop-method
Stop transports before destruction
2019-12-15 20:19:35 +00:00
cd66a3f987 Added stop method on transports to stop them before destroying them 2019-12-15 20:41:45 +01:00
fc4091a9fc Merge pull request #11 from murat-dogan/master
Fix List TRANSFORM command & address-of-packed-member option
2019-12-15 19:12:41 +00:00
7a49a0cfd8 Fix List TRANSFORM command & address-of-packed-member option 2019-12-15 20:49:57 +03:00
de5aff68e6 Fixed transport synchronization on destruction 2019-12-15 16:35:58 +01:00
5416b66116 Merge pull request #10 from paullouisageneau/big-messages
Large messages support
2019-12-14 22:11:29 +00:00
8b94f22aca Cleanup and added some comments 2019-12-14 22:41:55 +01:00
1ab81731e3 Changed buffer amount low behavior to prevent deadlock situations 2019-12-14 21:13:51 +01:00
5213f12f1a Proper handling of SCTP EOR flag at reception 2019-12-14 17:23:31 +01:00
c5e25bbdbc Implemented max message size negociation 2019-12-14 17:23:31 +01:00
58eea3fcf6 Cleanup and destruction fixes 2019-12-14 17:23:31 +01:00
59517cb0da Fixed transition to disconnected state 2019-12-14 17:23:31 +01:00
e77586fc81 Added support for deprecated PPIDs String Partial and Binary Partial 2019-12-14 17:23:23 +01:00
cafc674689 Changed SCTP to non-blocking to spare a thread and fix blocking on close 2019-12-14 12:52:11 +01:00
96f08cb3c8 Fixed DataChannel recv queue limit 2019-12-12 19:42:32 +01:00
3220bf8ae1 Fixed formatting 2019-12-12 17:31:43 +01:00
7236c06880 Merge pull request #9 from paullouisageneau/send-buffer
Enhanced DataChannel API
2019-12-12 16:26:12 +00:00
89ff113688 Changed sent callback to more generic bufferAmountLow 2019-12-12 17:18:49 +01:00
aa55aa76df Added sendBuffer() methods to DataChannel 2019-12-12 11:07:31 +01:00
2c0955fe57 Some performance tuning for SCTP transport 2019-12-11 23:16:51 +01:00
d7e417ce3f Use structured binding instead of tie 2019-12-11 19:57:04 +01:00
1df2fa559c Added rvalue push to Queue 2019-12-11 16:28:09 +01:00
2d5d2f0486 Merge pull request #8 from paullouisageneau/openssl
OpenSSL as alternative to GnuTLS
2019-12-10 15:20:14 +00:00
e75ae36ba8 Added OpenSSL as an alternative to GnuTLS 2019-12-10 16:18:03 +01:00
585450d13e Better libs decalration in Makefile 2019-12-08 23:06:31 +01:00
bc0666be05 Added Jamfile for easier integration 2019-12-08 23:04:26 +01:00
b14518238a Fixed reorder warning 2019-12-08 23:03:57 +01:00
04df12b581 Merge pull request #7 from paullouisageneau/back-pressure
Back-pressure
2019-12-06 23:04:03 +00:00
08931de03b Added proper destructor for synchronized_callback 2019-12-06 16:59:43 +01:00
e6da6a185f Make DataChannel keep a strong reference on PeerConnection 2019-12-06 16:21:55 +01:00
87bf676428 Fixed SctpTransport::process() 2019-12-06 11:12:06 +01:00
4029a9bb4a Do not receive messages in onMessage is not set 2019-12-04 12:16:39 +01:00
a1df562785 Added available and availableSize getters on DataChannel 2019-12-04 12:00:40 +01:00
5d57b4e214 Used synchronized callbacks for PeerConnection 2019-12-03 12:05:19 +01:00
abdf61e841 Added callback wrapper in Channel 2019-12-03 11:55:54 +01:00
d9bfcbd6be Added sent callback on DataChannel 2019-12-03 11:17:56 +01:00
e55d4e906b Handle state changes atomically 2019-12-02 22:18:29 +01:00
6363361e0c Send thread might now change SCTP state to disconnected 2019-12-02 21:28:37 +01:00
21f43611b6 Some cleanup to thread handling 2019-12-01 22:00:31 +01:00
b20f3b30c0 Remaned runConnect() to runConnectAndSendLoop() 2019-12-01 21:50:44 +01:00
fcb1a2571d Consider DTLS premature termination as remote closing 2019-12-01 21:46:50 +01:00
900c482146 Implemented reading back-pressure and callbacks synchronization 2019-12-01 16:03:50 +01:00
d27ed8aab0 Don't wait for resolve threads on destruction 2019-11-28 09:47:20 +01:00
84219d381d Implemented async sending in SCTP transport 2019-11-27 13:26:02 +01:00
75735fb8d8 Enforced setup:actpass on offers 2019-11-24 21:06:50 +01:00
d2b0d1e07f Added bundle line 2019-11-24 20:14:54 +01:00
6f09bc7a17 Added local and remote address accessors 2019-11-23 19:16:10 +01:00
ac6cae8fc4 Merge pull request #4 from paullouisageneau/dtls-on-connected
Do not wait for ICE Ready
2019-11-22 21:40:59 +01:00
000bef45f6 Renamed ICE transport Ready state to Completed for consistency with web API 2019-11-22 21:32:08 +01:00
c2bba83254 Start DTLS transport on ICE state Connected instead of Ready 2019-11-22 21:30:02 +01:00
5839e9d3db Enabled SCTP MTU discovery 2019-11-22 16:48:52 +01:00
2ff361ab29 Fixed DTLS transport send logic to also fail on non-fatal errors 2019-11-22 16:05:27 +01:00
4f6bdc5135 Merge pull request #3 from aaronalbers/aa_lifetime_fixes_
Fixed lifetime issues
2019-11-22 15:59:11 +01:00
65e584107c Fixed lifetime issues
- Channels are not longer immortal objects
- Fixed (or mitigated) crashes on cleanup
2019-11-21 09:03:15 -07:00
cd0f17e36d Implemented DTLS timeout handling 2019-11-19 15:45:34 +01:00
71bdc94804 Removed full-mode ICE option 2019-11-19 15:45:18 +01:00
648644895c Set DTLS timeouts 2019-11-19 14:14:11 +01:00
2e59e44a83 Fixed ICE controlling role and reduced STUN timeout for gathering 2019-11-19 14:07:35 +01:00
3afc127750 Reset end of candidates status when extracted from description 2019-11-19 12:34:40 +01:00
44cdbab8dc Changed remote description logic to resolve candidates asyncronously 2019-11-19 12:31:15 +01:00
f083815569 Prevent unresolved candidates from going through libnice 2019-11-17 14:19:31 +01:00
6f0bcbb1e6 Fixed nice_agent_parse_remote_sdp() check breaking non-trickle case 2019-11-17 14:00:39 +01:00
ae46162649 Fixed typo in gatheringState() 2019-11-14 19:29:09 +01:00
64ed232d1b Merge pull request #2 from aaronalbers/aa_fix_clang_build_
Fix clang build
2019-11-09 12:41:55 +01:00
0e2c992d1c Fix clang build
- Fix the build for the clang compiler since it doesn't support -Wno-error=format-truncation.
- Added missing `#include <string>` that caused `implicit instantiation of undefined
      template`
2019-11-08 15:53:27 -07:00
a10d47499b Excluded target datachannel-static from all 2019-10-18 20:55:48 +02:00
3528432c5c Removed useless usrsctp defines 2019-10-18 20:47:37 +02:00
dd3012ac35 Added tests to CMakeLists 2019-10-18 20:41:33 +02:00
640144e01d Fixed usrsctp compilation and include directory 2019-10-18 20:26:48 +02:00
dc7a59503a Fixed usrsctp cleanup not triggered 2019-10-09 08:56:11 +02:00
c5f7502397 Fixed compilation of usrsctp with -Wno-error=format-truncation 2019-10-07 21:08:59 +02:00
c8f2b79015 Updated Readme for CMake 2019-10-05 16:12:08 +02:00
defc230dba Added CMake support 2019-10-05 16:08:24 +02:00
81308b2095 Added links for implemented IETF drafts and RFCs 2019-10-01 09:20:47 +02:00
5842be3442 Fixed datachannel open state not toggled on incoming channels 2019-09-30 20:15:44 +02:00
bca4d89f93 Added bidirectional message exchange in test 2019-09-30 19:48:41 +02:00
4ae3d51f96 Updated Readme with gathering state callback 2019-09-23 22:39:51 +02:00
cd47c31f3f Added separate PeerConnection gathering state 2019-09-23 22:31:54 +02:00
7e5e503835 Refactored operator<< for PeerConnection::State 2019-09-22 23:01:33 +02:00
ff7f45d88b Updated Readme with new state callback 2019-09-22 22:56:56 +02:00
86179f0691 Added state with corresponding callback, and removed optional candidates 2019-09-22 22:50:04 +02:00
6c7896dca3 Added catch guards to nice callbacks for safety 2019-09-13 21:13:25 +02:00
68b0ab73b5 Properly handle unexpected DTLS termination 2019-09-12 23:23:44 +02:00
c50b055779 Updated Readme 2019-09-10 23:45:47 +02:00
64e23341ad Added STUN server to test 2019-09-10 23:45:31 +02:00
686b468d7b Fixed double space in generated srflx candidates 2019-09-10 23:44:54 +02:00
56 changed files with 5999 additions and 948 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

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
build/
*.d *.d
*.o *.o
*.a *.a

8
.gitmodules vendored
View File

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

185
CMakeLists.txt Normal file
View File

@ -0,0 +1,185 @@
cmake_minimum_required (VERSION 3.7)
project (libdatachannel
DESCRIPTION "WebRTC DataChannels Library"
VERSION 0.5.1
LANGUAGES CXX)
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
option(USE_JUICE "Use libjuice instead of libnice" OFF)
if(USE_GNUTLS)
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
else()
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
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
${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/channel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/configuration.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/datachannel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.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/rtc.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
${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
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/offerer.cpp
)
set(TESTS_ANSWERER_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp
)
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED)
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::UsrsctpStatic ALIAS usrsctp-static)
add_library(datachannel SHARED ${LIBDATACHANNEL_SOURCES})
set_target_properties(datachannel PROPERTIES
VERSION ${PROJECT_VERSION}
CXX_STANDARD 17)
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}/src)
target_link_libraries(datachannel PUBLIC Threads::Threads)
target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic)
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
set_target_properties(datachannel-static PROPERTIES
VERSION ${PROJECT_VERSION}
CXX_STANDARD 17)
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}/src)
target_link_libraries(datachannel-static PUBLIC Threads::Threads)
target_link_libraries(datachannel-static PRIVATE 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)
find_package(GnuTLS REQUIRED)
if(NOT TARGET GnuTLS::GnuTLS)
add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED)
set_target_properties(GnuTLS::GnuTLS PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${GNUTLS_INCLUDE_DIRS}"
INTERFACE_COMPILE_DEFINITIONS "${GNUTLS_DEFINITIONS}"
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
endif()
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
target_link_libraries(datachannel PRIVATE GnuTLS::GnuTLS)
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
target_link_libraries(datachannel-static PRIVATE GnuTLS::GnuTLS)
else()
find_package(OpenSSL REQUIRED)
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=0)
target_link_libraries(datachannel PRIVATE OpenSSL::SSL)
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=0)
target_link_libraries(datachannel-static PRIVATE OpenSSL::SSL)
endif()
if (USE_JUICE)
add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
target_compile_definitions(datachannel PRIVATE USE_JUICE=1)
target_link_libraries(datachannel PRIVATE LibJuice::LibJuiceStatic)
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=1)
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
else()
find_package(LibNice REQUIRED)
target_compile_definitions(datachannel PRIVATE USE_JUICE=0)
target_link_libraries(datachannel PRIVATE LibNice::LibNice)
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=0)
target_link_libraries(datachannel-static PRIVATE LibNice::LibNice)
endif()
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
install(TARGETS datachannel LIBRARY DESTINATION lib)
install(FILES ${LIBDATACHANNEL_HEADERS} DESTINATION include/rtc)
# Main Test
add_executable(datachannel-tests ${TESTS_SOURCES})
set_target_properties(datachannel-tests PROPERTIES
VERSION ${PROJECT_VERSION}
CXX_STANDARD 17)
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(datachannel-tests datachannel)
# P2P Test: offerer
add_executable(datachannel-offerer ${TESTS_OFFERER_SOURCES})
set_target_properties(datachannel-offerer PROPERTIES
VERSION ${PROJECT_VERSION}
CXX_STANDARD 17)
set_target_properties(datachannel-offerer PROPERTIES OUTPUT_NAME offerer)
target_link_libraries(datachannel-offerer datachannel)
# P2P Test: answerer
add_executable(datachannel-answerer ${TESTS_ANSWERER_SOURCES})
set_target_properties(datachannel-answerer PROPERTIES
VERSION ${PROJECT_VERSION}
CXX_STANDARD 17)
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME answerer)
target_link_libraries(datachannel-answerer datachannel)

84
Jamfile Normal file
View File

@ -0,0 +1,84 @@
import feature : feature ;
project libdatachannel ;
path-constant CWD : . ;
lib libdatachannel
: # sources
[ glob ./src/*.cpp ]
: # requirements
<cxxstd>17
<include>./include/rtc
<define>USE_JUICE=1
<library>/libdatachannel//usrsctp
<library>/libdatachannel//juice
<library>/libdatachannel//plog
: # default build
<link>static
: # usage requirements
<include>./include
<library>/libdatachannel//plog
<cxxflags>-pthread
;
feature crypto : openssl gnutls : composite propagated ;
feature.compose <crypto>openssl
: <define>USE_GNUTLS=0 ;
feature.compose <crypto>gnutls
: <define>USE_GNUTLS=1 ;
alias plog
: # no sources
: # no build requirements
: # no default build
: # usage requirements
<include>./deps/plog/include
;
alias usrsctp
: # no sources
: # no build requirements
: # no default build
: # usage requirements
<include>./deps/usrsctp/usrsctplib
<library>libusrsctp.a
;
alias juice
: # no sources
: # no build requirements
: # no default build
: # usage requirements
<include>./deps/libjuice/include
<library>libjuice.a
;
make libusrsctp.a : : @make_libusrsctp ;
actions make_libusrsctp
{
(cd $(CWD)/deps/usrsctp && \
./bootstrap && \
./configure --enable-static --disable-debug CFLAGS="-fPIC -Wno-address-of-packed-member" && \
make)
cp $(CWD)/deps/usrsctp/usrsctplib/.libs/libusrsctp.a $(<)
}
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
{
(cd $(CWD)/deps/libjuice && make $(MAKEOPTS))
cp $(CWD)/deps/libjuice/libjuice.a $(<)
}

View File

@ -4,36 +4,67 @@ NAME=libdatachannel
CXX=$(CROSS)g++ CXX=$(CROSS)g++
AR=$(CROSS)ar AR=$(CROSS)ar
RM=rm -f RM=rm -f
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
CXXFLAGS=-std=c++17 CXXFLAGS=-std=c++17
CPPFLAGS=-O2 -pthread -fPIC -Wall
LDFLAGS=-pthread LDFLAGS=-pthread
LDLIBS= -lgnutls $(shell pkg-config --libs glib-2.0 gobject-2.0 nice) LIBS=
INCLUDES=-Iinclude/rtc -I$(USRSCTP_DIR)/usrsctplib $(shell pkg-config --cflags glib-2.0 gobject-2.0 nice) LOCALLIBS=libusrsctp.a
USRSCTP_DIR=deps/usrsctp
JUICE_DIR=deps/libjuice
PLOG_DIR=deps/plog
USRSCTP_DIR:=usrsctp INCLUDES=-Iinclude/rtc -I$(PLOG_DIR)/include -I$(USRSCTP_DIR)/usrsctplib
USRSCTP_DEFINES:=-DINET -DINET6 LDLIBS=
USE_GNUTLS ?= 0
ifneq ($(USE_GNUTLS), 0)
CPPFLAGS+=-DUSE_GNUTLS=1
LIBS+=gnutls
else
CPPFLAGS+=-DUSE_GNUTLS=0
LIBS+=openssl
endif
USE_JUICE ?= 0
ifneq ($(USE_JUICE), 0)
CPPFLAGS+=-DUSE_JUICE=1
INCLUDES+=-I$(JUICE_DIR)/include
LOCALLIBS+=libjuice.a
ifneq ($(USE_GNUTLS), 0)
LIBS+=nettle
endif
else
CPPFLAGS+=-DUSE_JUICE=0
LIBS+=glib-2.0 gobject-2.0 nice
endif
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
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
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) $(USRSCTP_DEFINES) -MMD -MP -o $@ -c $< $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) -MMD -MP -o $@ -c $<
test/%.o: test/%.cpp test/%.o: test/%.cpp
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -Iinclude -MMD -MP -o $@ -c $< $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) -Iinclude -Isrc -MMD -MP -o $@ -c $<
-include $(subst .cpp,.d,$(SRCS)) -include $(subst .cpp,.d,$(SRCS))
$(NAME).a: $(OBJS) $(NAME).a: $(OBJS)
$(AR) crf $@ $(OBJS) $(AR) crf $@ $(OBJS)
$(NAME).so: libusrsctp.a $(OBJS) $(NAME).so: $(LOCALLIBS) $(OBJS)
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS) libusrsctp.a $(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
tests: $(NAME).a test/main.o tests: $(NAME).a $(TEST_OBJS)
$(CXX) $(LDFLAGS) -o $@ test/main.o $(LDLIBS) $(NAME).a libusrsctp.a $(CXX) $(LDFLAGS) -o $@ $(TEST_OBJS) $(NAME).a $(LDLIBS)
clean: clean:
-$(RM) include/rtc/*.d *.d -$(RM) include/rtc/*.d *.d
@ -44,16 +75,26 @@ dist-clean: clean
-$(RM) $(NAME).a -$(RM) $(NAME).a
-$(RM) $(NAME).so -$(RM) $(NAME).so
-$(RM) libusrsctp.a -$(RM) libusrsctp.a
-$(RM) libjuice.a
-$(RM) tests -$(RM) tests
-$(RM) include/*~ -$(RM) include/*~
-$(RM) src/*~ -$(RM) src/*~
-$(RM) test/*~ -$(RM) test/*~
-cd $(USRSCTP_DIR) && make clean -cd $(USRSCTP_DIR) && make clean
-cd $(JUICE_DIR) && make 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 .
libjuice.a:
ifneq ($(USE_GNUTLS), 0)
cd $(JUICE_DIR) && make USE_NETTLE=1
else
cd $(JUICE_DIR) && make USE_NETTLE=0
endif
cp $(JUICE_DIR)/libjuice.a .

View File

@ -1,33 +1,49 @@
# 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.
The connectivity can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice). The security layer can be provided through [GnuTLS](https://www.gnutls.org/) or [OpenSSL](https://www.openssl.org/).
Licensed under LGPLv2, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE). Licensed under LGPLv2, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE).
## Compatibility ## Compatibility
This implementation has been tested to be compatible with Firefox and Chromium. It supports Multicast DNS candidates resolution provided the operating system also supports it. The library aims at fully implementing WebRTC SCTP DataChannels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13)) over DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261)) with ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)). It has been tested to be compatible with Firefox and Chromium. It supports IPv6 and Multicast DNS candidates resolution ([draft-ietf-rtcweb-mdns-ice-candidates-03](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03)) provided the operating system also supports it.
## Dependencies ## Dependencies
- libnice: https://github.com/libnice/libnice - GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/
- GnuTLS: https://www.gnutls.org/
Optional:
- libnice: https://nice.freedesktop.org/ (substituable with libjuice)
Submodules: Submodules:
- usrsctp: https://github.com/sctplab/usrsctp - usrsctp: https://github.com/sctplab/usrsctp
- libjuice: https://github.com/paullouisageneau/libjuice
## Building ## Building
### Building with CMake (preferred)
```bash ```bash
git submodule update --init --recursive $ git submodule update --init --recursive
make $ mkdir build
$ cd build
$ cmake -DUSE_JUICE=1 -DUSE_GNUTLS=1 ..
$ make
```
### Building directly with Make
```bash
$ git submodule update --init --recursive
$ make USE_JUICE=1 USE_GNUTLS=1
``` ```
## Example ## Example
In the following example, notes the callbacks are called in another thread. In the following example, note the callbacks are called in another thread.
### Signal a PeerConnection ### Signal a PeerConnection
@ -37,7 +53,7 @@ In the following example, notes the callbacks are called in another thread.
```cpp ```cpp
rtc::Configuration config; rtc::Configuration config;
config.iceServers.emplace_back("stunserver.org:3478"); config.iceServers.emplace_back("mystunserver.org:3478");
auto pc = make_shared<rtc::PeerConnection>(config); auto pc = make_shared<rtc::PeerConnection>(config);
@ -46,12 +62,9 @@ pc->onLocalDescription([](const rtc::Description &sdp) {
MY_SEND_DESCRIPTION_TO_REMOTE(string(sdp)); MY_SEND_DESCRIPTION_TO_REMOTE(string(sdp));
}); });
pc->onLocalCandidate([](const optional<rtc::Candidate> &candidate) { pc->onLocalCandidate([](const rtc::Candidate &candidate) {
if (candidate) { // Send the candidate to the remote peer
MY_SEND_CANDIDATE_TO_REMOTE(candidate->candidate(), candidate->mid()); MY_SEND_CANDIDATE_TO_REMOTE(candidate.candidate(), candidate.mid());
} else {
// Gathering finished
}
}); });
MY_ON_RECV_DESCRIPTION_FROM_REMOTE([pc](string sdp) { MY_ON_RECV_DESCRIPTION_FROM_REMOTE([pc](string sdp) {
@ -63,6 +76,19 @@ MY_ON_RECV_CANDIDATE_FROM_REMOTE([pc](string candidate, string mid) {
}); });
``` ```
### Observe the PeerConnection state
```cpp
pc->onStateChange([](PeerConnection::State state) {
cout << "State: " << state << endl;
});
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "Gathering state: " << state << endl;
});
```
### Create a DataChannel ### Create a DataChannel
```cpp ```cpp
@ -81,12 +107,14 @@ dc->onMessage([](const variant<binary, string> &message) {
```cpp ```cpp
shared_ptr<rtc::DataChannel> dc; shared_ptr<rtc::DataChannel> dc;
pc->onDataChannel([&dc](const shared_ptr<rtc::DataChannel> &incoming) { pc->onDataChannel([&dc](shared_ptr<rtc::DataChannel> incoming) {
dc = incoming; dc = incoming;
dc->send("Hello world!"); dc->send("Hello world!");
}); });
``` ```
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

@ -0,0 +1,123 @@
# - Try to find Glib and its components (gio, gobject etc)
# Once done, this will define
#
# GLIB_FOUND - system has Glib
# GLIB_INCLUDE_DIRS - the Glib include directories
# GLIB_LIBRARIES - link these to use Glib
#
# Optionally, the COMPONENTS keyword can be passed to find_package()
# and Glib components can be looked for. Currently, the following
# components can be used, and they define the following variables if
# found:
#
# gio: GLIB_GIO_LIBRARIES
# gobject: GLIB_GOBJECT_LIBRARIES
# gmodule: GLIB_GMODULE_LIBRARIES
# gthread: GLIB_GTHREAD_LIBRARIES
#
# Note that the respective _INCLUDE_DIR variables are not set, since
# all headers are in the same directory as GLIB_INCLUDE_DIRS.
#
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
find_package(PkgConfig)
pkg_check_modules(PC_GLIB QUIET glib-2.0)
find_library(GLIB_LIBRARIES
NAMES glib-2.0
HINTS ${PC_GLIB_LIBDIR}
${PC_GLIB_LIBRARY_DIRS}
)
# Files in glib's main include path may include glibconfig.h, which,
# for some odd reason, is normally in $LIBDIR/glib-2.0/include.
get_filename_component(_GLIB_LIBRARY_DIR ${GLIB_LIBRARIES} PATH)
find_path(GLIBCONFIG_INCLUDE_DIR
NAMES glibconfig.h
HINTS ${PC_LIBDIR} ${PC_LIBRARY_DIRS} ${_GLIB_LIBRARY_DIR}
${PC_GLIB_INCLUDEDIR} ${PC_GLIB_INCLUDE_DIRS}
PATH_SUFFIXES glib-2.0/include
)
find_path(GLIB_INCLUDE_DIR
NAMES glib.h
HINTS ${PC_GLIB_INCLUDEDIR}
${PC_GLIB_INCLUDE_DIRS}
PATH_SUFFIXES glib-2.0
)
set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR} ${GLIBCONFIG_INCLUDE_DIR})
# Version detection
if (EXISTS "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h")
file(READ "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h" GLIBCONFIG_H_CONTENTS)
string(REGEX MATCH "#define GLIB_MAJOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}")
set(GLIB_VERSION_MAJOR "${CMAKE_MATCH_1}")
string(REGEX MATCH "#define GLIB_MINOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}")
set(GLIB_VERSION_MINOR "${CMAKE_MATCH_1}")
string(REGEX MATCH "#define GLIB_MICRO_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}")
set(GLIB_VERSION_MICRO "${CMAKE_MATCH_1}")
set(GLIB_VERSION "${GLIB_VERSION_MAJOR}.${GLIB_VERSION_MINOR}.${GLIB_VERSION_MICRO}")
endif ()
# Additional Glib components. We only look for libraries, as not all of them
# have corresponding headers and all headers are installed alongside the main
# glib ones.
foreach (_component ${GLIB_FIND_COMPONENTS})
if (${_component} STREQUAL "gio")
find_library(GLIB_GIO_LIBRARIES NAMES gio-2.0 HINTS ${_GLIB_LIBRARY_DIR})
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GIO_LIBRARIES)
elseif (${_component} STREQUAL "gobject")
find_library(GLIB_GOBJECT_LIBRARIES NAMES gobject-2.0 HINTS ${_GLIB_LIBRARY_DIR})
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GOBJECT_LIBRARIES)
elseif (${_component} STREQUAL "gmodule")
find_library(GLIB_GMODULE_LIBRARIES NAMES gmodule-2.0 HINTS ${_GLIB_LIBRARY_DIR})
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GMODULE_LIBRARIES)
elseif (${_component} STREQUAL "gthread")
find_library(GLIB_GTHREAD_LIBRARIES NAMES gthread-2.0 HINTS ${_GLIB_LIBRARY_DIR})
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GTHREAD_LIBRARIES)
elseif (${_component} STREQUAL "gio-unix")
# gio-unix is compiled as part of the gio library, but the include paths
# are separate from the shared glib ones. Since this is currently only used
# by WebKitGTK we don't go to extraordinary measures beyond pkg-config.
pkg_check_modules(GIO_UNIX QUIET gio-unix-2.0)
endif ()
endforeach ()
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GLIB REQUIRED_VARS GLIB_INCLUDE_DIRS GLIB_LIBRARIES ${ADDITIONAL_REQUIRED_VARS}
VERSION_VAR GLIB_VERSION)
mark_as_advanced(
GLIBCONFIG_INCLUDE_DIR
GLIB_GIO_LIBRARIES
GLIB_GIO_UNIX_LIBRARIES
GLIB_GMODULE_LIBRARIES
GLIB_GOBJECT_LIBRARIES
GLIB_GTHREAD_LIBRARIES
GLIB_INCLUDE_DIR
GLIB_INCLUDE_DIRS
GLIB_LIBRARIES
)

View File

@ -0,0 +1,35 @@
if (NOT TARGET LibNice::LibNice)
find_package(PkgConfig)
pkg_check_modules(PC_LIBNICE nice)
set(LIBNICE_DEFINITIONS ${PC_LIBNICE_CFLAGS_OTHER})
find_path(LIBNICE_INCLUDE_DIR nice/agent.h
HINTS ${PC_LIBNICE_INCLUDEDIR} ${PC_LIBNICE_INCLUDE_DIRS}
PATH_SUFFICES libnice)
find_library(LIBNICE_LIBRARY NAMES nice libnice
HINTS ${PC_LIBNICE_LIBDIR} ${PC_LIBNICE_LIBRARY_DIRS})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibNice DEFAULT_MSG
LIBNICE_LIBRARY LIBNICE_INCLUDE_DIR)
mark_as_advanced(LIBNICE_INCLUDE_DIR LIBNICE_LIBRARY)
set(LIBNICE_LIBRARIES ${LIBNICE_LIBRARY})
set(LIBNICE_INCLUDE_DIRS ${LIBNICE_INCLUDE_DIR})
find_package(GLIB REQUIRED COMPONENTS gio gobject gmodule gthread)
list(APPEND LIBNICE_INCLUDE_DIRS ${GLIB_INCLUDE_DIRS})
list(APPEND LIBNICE_LIBRARIES ${GLIB_GOBJECT_LIBRARIES} ${GLIB_LIBRARIES})
if (LIBNICE_FOUND)
add_library(LibNice::LibNice UNKNOWN IMPORTED)
set_target_properties(LibNice::LibNice PROPERTIES
IMPORTED_LOCATION "${LIBNICE_LIBRARY}"
INTERFACE_COMPILE_DEFINITIONS "_REENTRANT"
INTERFACE_INCLUDE_DIRECTORIES "${LIBNICE_INCLUDE_DIRS}"
INTERFACE_LINK_LIBRARIES "${LIBNICE_LIBRARIES}"
IMPORTED_LINK_INTERFACE_LANGUAGES "C")
endif ()
endif ()

1
deps/libjuice vendored Submodule

Submodule deps/libjuice added at 6f6faa5783

1
deps/plog vendored Submodule

Submodule deps/plog added at 2931644689

1
deps/usrsctp vendored Submodule

Submodule deps/usrsctp added at aa10d60bc2

View File

@ -25,10 +25,23 @@
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 = "");
enum class ResolveMode { Simple, Lookup };
bool resolve(ResolveMode mode = ResolveMode::Simple);
bool isResolved() const;
string candidate() const; string candidate() const;
string mid() const; string mid() const;
operator string() const; operator string() const;
@ -36,11 +49,14 @@ public:
private: private:
string mCandidate; string mCandidate;
string mMid; string mMid;
bool mIsResolved;
}; };
} // 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

@ -21,6 +21,7 @@
#include "include.hpp" #include "include.hpp"
#include <atomic>
#include <functional> #include <functional>
#include <variant> #include <variant>
@ -28,32 +29,52 @@ namespace rtc {
class Channel { class Channel {
public: public:
virtual void close(void) = 0; virtual void close() = 0;
virtual void send(const std::variant<binary, string> &data) = 0; virtual bool send(const std::variant<binary, string> &data) = 0; // returns false if buffered
virtual bool isOpen(void) const = 0; virtual bool isOpen() const = 0;
virtual bool isClosed(void) const = 0; virtual bool isClosed() const = 0;
virtual size_t maxMessageSize() const; // max message size in a call to send
virtual size_t bufferedAmount() const; // total size buffered to send
void onOpen(std::function<void()> callback); void onOpen(std::function<void()> callback);
void onClosed(std::function<void()> callback); void onClosed(std::function<void()> callback);
void onError(std::function<void(const string &error)> callback); void onError(std::function<void(const string &error)> callback);
void onMessage(std::function<void(const std::variant<binary, string> &data)> callback); void onMessage(std::function<void(const std::variant<binary, string> &data)> callback);
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 onBufferedAmountLow(std::function<void()> callback);
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(void); virtual void triggerOpen();
virtual void triggerClosed(void); virtual void triggerClosed();
virtual void triggerError(const string &error); virtual void triggerError(const string &error);
virtual void triggerMessage(const std::variant<binary, string> &data); virtual void triggerAvailable(size_t count);
virtual void triggerBufferedAmount(size_t amount);
void resetCallbacks();
private: private:
std::function<void()> mOpenCallback; synchronized_callback<> mOpenCallback;
std::function<void()> mClosedCallback; synchronized_callback<> mClosedCallback;
std::function<void(const string &)> mErrorCallback; synchronized_callback<const string &> mErrorCallback;
std::function<void(const std::variant<binary, string> &)> mMessageCallback; synchronized_callback<const std::variant<binary, string> &> mMessageCallback;
synchronized_callback<> mAvailableCallback;
synchronized_callback<> mBufferedAmountLowCallback;
std::atomic<size_t> mBufferedAmount = 0;
std::atomic<size_t> mBufferedAmountLowThreshold = 0;
}; };
} // namespace rtc } // namespace rtc
#endif // RTC_CHANNEL_H #endif // RTC_CHANNEL_H

View File

@ -27,16 +27,47 @@
namespace rtc { namespace rtc {
struct IceServer { struct IceServer {
IceServer(const string &host_); enum class Type { Stun, Turn };
IceServer(const string &hostname_, uint16_t port_); enum class RelayType { TurnUdp, TurnTcp, TurnTls };
IceServer(const string &hostname_, const string &service_);
// Any type
IceServer(const string &url);
// STUN
IceServer(string hostname_, uint16_t port_);
IceServer(string hostname_, string service_);
// TURN
IceServer(string hostname_, uint16_t port, string username_, string password_,
RelayType relayType_ = RelayType::TurnUdp);
IceServer(string hostname_, string service_, string username_, string password_,
RelayType relayType_ = RelayType::TurnUdp);
string hostname; string hostname;
string service; string service;
Type type;
string username;
string password;
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;
uint16_t portRangeBegin = 1024; uint16_t portRangeBegin = 1024;
uint16_t portRangeEnd = 65535; uint16_t portRangeEnd = 65535;
}; };

View File

@ -22,10 +22,13 @@
#include "channel.hpp" #include "channel.hpp"
#include "include.hpp" #include "include.hpp"
#include "message.hpp" #include "message.hpp"
#include "queue.hpp"
#include "reliability.hpp" #include "reliability.hpp"
#include <atomic>
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include <type_traits>
#include <variant> #include <variant>
namespace rtc { namespace rtc {
@ -33,41 +36,85 @@ namespace rtc {
class SctpTransport; class SctpTransport;
class PeerConnection; class PeerConnection;
class DataChannel : public Channel { class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel {
public: public:
DataChannel(unsigned int stream_, string label_, string protocol_, Reliability reliability_); DataChannel(std::weak_ptr<PeerConnection> pc, unsigned int stream, string label,
DataChannel(unsigned int stream, std::shared_ptr<SctpTransport> sctpTransport); string protocol, Reliability reliability);
DataChannel(std::weak_ptr<PeerConnection> pc, std::weak_ptr<SctpTransport> transport,
unsigned int stream);
~DataChannel(); ~DataChannel();
void close(void);
void send(const std::variant<binary, string> &data);
void send(const byte *data, size_t size);
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;
bool isOpen(void) const; void close(void) override;
bool isClosed(void) const; 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 open(std::shared_ptr<SctpTransport> sctpTransport); void remoteClose();
void open(std::shared_ptr<SctpTransport> transport);
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 unsigned int mStream; const std::weak_ptr<PeerConnection> mPeerConnection;
std::shared_ptr<SctpTransport> mSctpTransport; std::weak_ptr<SctpTransport> mSctpTransport;
unsigned int mStream;
string mLabel; string mLabel;
string mProtocol; string mProtocol;
std::shared_ptr<Reliability> mReliability; std::shared_ptr<Reliability> mReliability;
bool mIsOpen = false; std::atomic<bool> mIsOpen = false;
bool mIsClosed = false; std::atomic<bool> mIsClosed = false;
Queue<message_ptr> mRecvQueue;
std::atomic<size_t> mRecvAmount = 0;
friend class PeerConnection; friend class PeerConnection;
}; };
template <typename Buffer> std::pair<const byte *, size_t> to_bytes(const Buffer &buf) {
using T = typename std::remove_pointer<decltype(buf.data())>::type;
using E = typename std::conditional<std::is_void<T>::value, byte, T>::type;
return std::make_pair(static_cast<const byte *>(static_cast<const void *>(buf.data())),
buf.size() * sizeof(E));
}
template <typename Buffer> bool DataChannel::sendBuffer(const Buffer &buf) {
auto [bytes, size] = to_bytes(buf);
auto message = std::make_shared<Message>(size);
std::copy(bytes, bytes + size, message->data());
return outgoing(message);
}
template <typename Iterator> bool DataChannel::sendBuffer(Iterator first, Iterator last) {
size_t size = 0;
for (Iterator it = first; it != last; ++it)
size += it->size();
auto message = std::make_shared<Message>(size);
auto pos = message->begin();
for (Iterator it = first; it != last; ++it) {
auto [bytes, size] = to_bytes(*it);
pos = std::copy(bytes, bytes + size, pos);
}
return outgoing(message);
}
} // namespace rtc } // namespace rtc
#endif #endif

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;
@ -44,13 +45,22 @@ public:
string mid() const; string mid() const;
std::optional<string> fingerprint() const; std::optional<string> fingerprint() const;
std::optional<uint16_t> sctpPort() const; std::optional<uint16_t> sctpPort() const;
std::optional<size_t> maxMessageSize() 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 addCandidate(std::optional<Candidate> candidate); void setMaxMessageSize(size_t size);
void addCandidate(Candidate candidate);
void endCandidates();
std::vector<Candidate> extractCandidates();
operator string() const; operator string() const;
string generateSdp(const string &eol) const;
private: private:
Type mType; Type mType;
Role mRole; Role mRole;
@ -59,6 +69,7 @@ private:
string mIceUfrag, mIcePwd; string mIceUfrag, mIcePwd;
std::optional<string> mFingerprint; std::optional<string> mFingerprint;
std::optional<uint16_t> mSctpPort; std::optional<uint16_t> mSctpPort;
std::optional<size_t> mMaxMessageSize;
std::vector<Candidate> mCandidates; std::vector<Candidate> mCandidates;
bool mTrickle; bool mTrickle;

View File

@ -19,9 +19,20 @@
#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 <memory> #include <memory>
#include <mutex>
#include <optional> #include <optional>
#include <string>
#include <vector> #include <vector>
namespace rtc { namespace rtc {
@ -32,6 +43,7 @@ using binary = std::vector<byte>;
using std::nullopt; using std::nullopt;
using std::size_t;
using std::uint16_t; using std::uint16_t;
using std::uint32_t; using std::uint32_t;
using std::uint64_t; using std::uint64_t;
@ -41,9 +53,40 @@ 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
const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
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...>;
template <typename... P> class synchronized_callback {
public:
synchronized_callback() = default;
synchronized_callback(std::function<void(P...)> func) { *this = std::move(func); };
~synchronized_callback() { *this = nullptr; }
synchronized_callback &operator=(std::function<void(P...)> func) {
std::lock_guard lock(mutex);
callback = std::move(func);
return *this;
}
void operator()(P... args) const {
std::lock_guard lock(mutex);
if (callback)
callback(args...);
}
operator bool() const {
std::lock_guard lock(mutex);
return callback ? true : false;
}
private:
std::function<void(P...)> callback;
mutable std::recursive_mutex mutex;
};
} }
#endif #endif

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,26 +28,44 @@
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, Type type_ = Binary) : binary(size), type(type_) {}
template <typename Iterator> template <typename Iterator>
Message(Iterator begin_, Iterator end_, Type type_ = Binary, unsigned int stream_ = 0, Message(Iterator begin_, Iterator end_, Type type_ = Binary)
std::shared_ptr<Reliability> reliability_ = nullptr) : binary(begin_, end_), type(type_) {}
: binary(begin_, end_), type(type_), stream(stream_), reliability(reliability_) {}
Type type; Type type;
unsigned int stream; unsigned int stream = 0;
std::shared_ptr<Reliability> reliability; std::shared_ptr<Reliability> reliability;
}; };
using message_ptr = std::shared_ptr<const Message>; using message_ptr = std::shared_ptr<const Message>;
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 {
return m->type == Message::Binary || m->type == Message::String ? m->size() : 0;
};
template <typename Iterator> template <typename Iterator>
message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Message::Binary, message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Message::Binary,
unsigned int stream = 0, unsigned int stream = 0,
std::shared_ptr<Reliability> reliability = nullptr) { std::shared_ptr<Reliability> reliability = nullptr) {
return std::make_shared<Message>(begin, end, type, stream, reliability); auto message = std::make_shared<Message>(begin, end, type);
message->stream = stream;
message->reliability = reliability;
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

View File

@ -20,15 +20,22 @@
#define RTC_PEER_CONNECTION_H #define RTC_PEER_CONNECTION_H
#include "candidate.hpp" #include "candidate.hpp"
#include "configuration.hpp"
#include "datachannel.hpp" #include "datachannel.hpp"
#include "description.hpp" #include "description.hpp"
#include "configuration.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 <atomic> #include <atomic>
#include <functional> #include <functional>
#include <future>
#include <list>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <unordered_map> #include <unordered_map>
namespace rtc { namespace rtc {
@ -38,16 +45,39 @@ class IceTransport;
class DtlsTransport; class DtlsTransport;
class SctpTransport; class SctpTransport;
class PeerConnection { 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> {
public: public:
enum class State : int {
New = RTC_NEW,
Connecting = RTC_CONNECTING,
Connected = RTC_CONNECTED,
Disconnected = RTC_DISCONNECTED,
Failed = RTC_FAILED,
Closed = RTC_CLOSED
};
enum class GatheringState : int {
New = RTC_GATHERING_NEW,
InProgress = RTC_GATHERING_INPROGRESS,
Complete = RTC_GATHERING_COMPLETE
};
PeerConnection(void); PeerConnection(void);
PeerConnection(const Configuration &config); PeerConnection(const Configuration &config);
~PeerConnection(); ~PeerConnection();
const Configuration *config() const; void close();
const Configuration *config() const;
State state() const;
GatheringState gatheringState() const;
std::optional<Description> localDescription() const; std::optional<Description> localDescription() const;
std::optional<Description> remoteDescription() const; std::optional<Description> remoteDescription() const;
std::optional<string> localAddress() const;
std::optional<string> remoteAddress() const;
void setRemoteDescription(Description description); void setRemoteDescription(Description description);
void addRemoteCandidate(Candidate candidate); void addRemoteCandidate(Candidate candidate);
@ -57,38 +87,74 @@ public:
void onDataChannel(std::function<void(std::shared_ptr<DataChannel> dataChannel)> callback); void onDataChannel(std::function<void(std::shared_ptr<DataChannel> dataChannel)> callback);
void onLocalDescription(std::function<void(const Description &description)> callback); void onLocalDescription(std::function<void(const Description &description)> callback);
void onLocalCandidate(std::function<void(const std::optional<Candidate> &candidate)> callback); void onLocalCandidate(std::function<void(const Candidate &candidate)> callback);
void onStateChange(std::function<void(State 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:
void initIceTransport(Description::Role role); init_token mInitToken = Init::Token();
void initDtlsTransport();
void initSctpTransport();
std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
std::shared_ptr<DtlsTransport> initDtlsTransport();
std::shared_ptr<SctpTransport> initSctpTransport();
void closeTransports();
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 openDataChannels(void); 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 openDataChannels();
void closeDataChannels();
void remoteCloseDataChannels();
void processLocalDescription(Description description); void processLocalDescription(Description description);
void processLocalCandidate(std::optional<Candidate> candidate); void processLocalCandidate(Candidate candidate);
void triggerDataChannel(std::shared_ptr<DataChannel> dataChannel); void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
bool changeState(State 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; std::optional<Description> mLocalDescription, mRemoteDescription;
std::optional<Description> mRemoteDescription; mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
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::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels; std::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels;
std::shared_mutex mDataChannelsMutex;
std::function<void(std::shared_ptr<DataChannel> dataChannel)> mDataChannelCallback; std::atomic<State> mState;
std::function<void(const Description &description)> mLocalDescriptionCallback; std::atomic<GatheringState> mGatheringState;
std::function<void(const std::optional<Candidate> &candidate)> mLocalCandidateCallback;
synchronized_callback<std::shared_ptr<DataChannel>> mDataChannelCallback;
synchronized_callback<const Description &> mLocalDescriptionCallback;
synchronized_callback<const Candidate &> mLocalCandidateCallback;
synchronized_callback<State> mStateChangeCallback;
synchronized_callback<GatheringState> mGatheringStateChangeCallback;
}; };
} // namespace rtc } // namespace rtc
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state);
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state);
#endif #endif

134
include/rtc/queue.hpp Normal file
View File

@ -0,0 +1,134 @@
/**
* 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_QUEUE_H
#define RTC_QUEUE_H
#include "include.hpp"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <queue>
namespace rtc {
template <typename T> class Queue {
public:
using amount_function = std::function<size_t(const T &element)>;
Queue(size_t limit = 0, amount_function func = nullptr);
~Queue();
void stop();
bool empty() const;
size_t size() const; // elements
size_t amount() const; // amount
void push(T element);
std::optional<T> pop();
std::optional<T> peek();
bool wait(const std::optional<std::chrono::milliseconds> &duration = nullopt);
private:
const size_t mLimit;
size_t mAmount;
std::queue<T> mQueue;
std::condition_variable mPopCondition, mPushCondition;
amount_function mAmountFunction;
bool mStopping = false;
mutable std::mutex mMutex;
};
template <typename T>
Queue<T>::Queue(size_t limit, amount_function func) : mLimit(limit), mAmount(0) {
mAmountFunction = func ? func : [](const T &element) -> size_t { return 1; };
}
template <typename T> Queue<T>::~Queue() { stop(); }
template <typename T> void Queue<T>::stop() {
std::lock_guard lock(mMutex);
mStopping = true;
mPopCondition.notify_all();
mPushCondition.notify_all();
}
template <typename T> bool Queue<T>::empty() const {
std::lock_guard lock(mMutex);
return mQueue.empty();
}
template <typename T> size_t Queue<T>::size() const {
std::lock_guard lock(mMutex);
return mQueue.size();
}
template <typename T> size_t Queue<T>::amount() const {
std::lock_guard lock(mMutex);
return mAmount;
}
template <typename T> void Queue<T>::push(T element) {
std::unique_lock lock(mMutex);
mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
if (!mStopping) {
mAmount += mAmountFunction(element);
mQueue.emplace(std::move(element));
mPopCondition.notify_one();
}
}
template <typename T> std::optional<T> Queue<T>::pop() {
std::unique_lock lock(mMutex);
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
if (!mQueue.empty()) {
mAmount -= mAmountFunction(mQueue.front());
std::optional<T> element{std::move(mQueue.front())};
mQueue.pop();
return element;
} else {
return nullopt;
}
}
template <typename T> std::optional<T> Queue<T>::peek() {
std::unique_lock lock(mMutex);
if (!mQueue.empty()) {
return std::optional<T>{mQueue.front()};
} else {
return nullopt;
}
}
template <typename T>
bool Queue<T>::wait(const std::optional<std::chrono::milliseconds> &duration) {
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; });
return !mStopping;
}
} // namespace rtc
#endif

View File

@ -16,30 +16,109 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#ifndef RTC_C_API
#define RTC_C_API
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
// libdatachannel rtc C API #include <stdint.h>
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount);
void rtcDeletePeerConnection(int pc); // libdatachannel C API
int rtcCreateDataChannel(int pc, const char *label);
void rtcDeleteDataChannel(int dc); typedef enum {
void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *)); RTC_NEW = 0,
void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *, RTC_CONNECTING = 1,
void *)); RTC_CONNECTED = 2,
void rtcSetLocalCandidateCallback(int pc, RTC_DISCONNECTED = 3,
void (*candidateCallback)(const char *, const char *, void *)); RTC_FAILED = 4,
void rtcSetRemoteDescription(int pc, const char *sdp, const char *type); RTC_CLOSED = 5
void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid); } rtcState;
int rtcGetDataChannelLabel(int dc, char *data, int size);
void rtcSetOpenCallback(int dc, void (*openCallback)(void *)); typedef enum {
void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *)); RTC_GATHERING_NEW = 0,
void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *)); RTC_GATHERING_INPROGRESS = 1,
int rtcSendMessage(int dc, const char *data, int size); RTC_GATHERING_COMPLETE = 2
} rtcGatheringState;
// Don't change, it must match plog severity
typedef enum {
RTC_LOG_NONE = 0,
RTC_LOG_FATAL = 1,
RTC_LOG_ERROR = 2,
RTC_LOG_WARNING = 3,
RTC_LOG_INFO = 4,
RTC_LOG_DEBUG = 5,
RTC_LOG_VERBOSE = 6
} rtcLogLevel;
typedef struct {
const char **iceServers;
int iceServersCount;
uint16_t portRangeBegin;
uint16_t portRangeEnd;
} rtcConfiguration;
typedef void (*dataChannelCallbackFunc)(int dc, void *ptr);
typedef void (*descriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
typedef void (*candidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
typedef void (*stateChangeCallbackFunc)(rtcState state, void *ptr);
typedef void (*gatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
typedef void (*openCallbackFunc)(void *ptr);
typedef void (*closedCallbackFunc)(void *ptr);
typedef void (*errorCallbackFunc)(const char *error, void *ptr);
typedef void (*messageCallbackFunc)(const char *message, int size, void *ptr);
typedef void (*bufferedAmountLowCallbackFunc)(void *ptr);
typedef void (*availableCallbackFunc)(void *ptr);
// Log
void rtcInitLogger(rtcLogLevel level);
// User pointer
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
#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;
@ -40,7 +46,7 @@ inline bool hasprefix(const string &str, const string &prefix) {
namespace rtc { namespace rtc {
Candidate::Candidate(string candidate, string mid) { Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
const std::array prefixes{"a=", "candidate:"}; const std::array prefixes{"a=", "candidate:"};
for (string prefix : prefixes) for (string prefix : prefixes)
if (hasprefix(candidate, prefix)) if (hasprefix(candidate, prefix))
@ -48,13 +54,25 @@ Candidate::Candidate(string candidate, string mid) {
mCandidate = std::move(candidate); mCandidate = std::move(candidate);
mMid = std::move(mid); mMid = std::move(mid);
}
// See RFC 5245 for format bool Candidate::resolve(ResolveMode mode) {
std::stringstream ss(mCandidate); if (mIsResolved)
return true;
PLOG_VERBOSE << "Resolving candidate (mode="
<< (mode == ResolveMode::Simple ? "simple" : "lookup")
<< "): " << mCandidate;
// See RFC 8445 for format
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 = {};
@ -64,6 +82,15 @@ Candidate::Candidate(string candidate, string mid) {
hints.ai_socktype = SOCK_DGRAM; hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP; hints.ai_protocol = IPPROTO_UDP;
} }
if (transport == "TCP" || transport == "tcp") {
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
}
if (mode == ResolveMode::Simple)
hints.ai_flags |= AI_NUMERICHOST;
struct addrinfo *result = nullptr; struct addrinfo *result = nullptr;
if (getaddrinfo(node.c_str(), service.c_str(), &hints, &result) == 0) { if (getaddrinfo(node.c_str(), service.c_str(), &hints, &result) == 0) {
for (auto p = result; p; p = p->ai_next) for (auto p = result; p; p = p->ai_next)
@ -74,24 +101,26 @@ Candidate::Candidate(string candidate, string mid) {
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 << sp << left; mCandidate = oss.str();
mCandidate = ss.str(); PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
break; return mIsResolved = true;
} }
} }
} }
freeaddrinfo(result); freeaddrinfo(result);
} }
return false;
} }
bool Candidate::isResolved() const { return mIsResolved; }
string Candidate::candidate() const { return "candidate:" + mCandidate; } string Candidate::candidate() const { return "candidate:" + mCandidate; }
string Candidate::mid() const { return mMid; } string Candidate::mid() const { return mMid; }
@ -108,3 +137,32 @@ 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

@ -25,10 +25,13 @@
#include <sstream> #include <sstream>
#include <unordered_map> #include <unordered_map>
#include <gnutls/crypto.h>
using std::shared_ptr; using std::shared_ptr;
using std::string; using std::string;
using std::unique_ptr;
#if USE_GNUTLS
#include <gnutls/crypto.h>
namespace { namespace {
@ -117,10 +120,10 @@ Certificate::Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey)
"Unable to set certificate and key pair in credentials"); "Unable to set certificate and key pair in credentials");
} }
string Certificate::fingerprint() const { return mFingerprint; }
gnutls_certificate_credentials_t Certificate::credentials() const { return *mCredentials; } gnutls_certificate_credentials_t Certificate::credentials() const { return *mCredentials; }
string Certificate::fingerprint() const { return mFingerprint; }
string make_fingerprint(gnutls_x509_crt_t crt) { string make_fingerprint(gnutls_x509_crt_t crt) {
const size_t size = 32; const size_t size = 32;
unsigned char buffer[size]; unsigned char buffer[size];
@ -138,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<std::mutex> 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);
@ -171,9 +169,156 @@ 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
#else
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
namespace rtc {
Certificate::Certificate(string crt_pem, string key_pem) {
BIO *bio;
bio = BIO_new(BIO_s_mem());
BIO_write(bio, crt_pem.data(), crt_pem.size());
mX509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, 0, 0), X509_free);
BIO_free(bio);
if (!mX509)
throw std::invalid_argument("Unable to import certificate PEM");
bio = BIO_new(BIO_s_mem());
BIO_write(bio, key_pem.data(), key_pem.size());
mPKey = shared_ptr<EVP_PKEY>(PEM_read_bio_PrivateKey(bio, nullptr, 0, 0), EVP_PKEY_free);
BIO_free(bio);
if (!mPKey)
throw std::invalid_argument("Unable to import PEM key PEM");
mFingerprint = make_fingerprint(mX509.get());
}
Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey) :
mX509(std::move(x509)), mPKey(std::move(pkey))
{
mFingerprint = make_fingerprint(mX509.get());
}
string Certificate::fingerprint() const { return mFingerprint; }
std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const { return {mX509.get(), mPKey.get()}; }
string make_fingerprint(X509 *x509) {
const size_t size = 32;
unsigned char buffer[size];
unsigned int len = size;
if (!X509_digest(x509, EVP_sha256(), buffer, &len))
throw std::runtime_error("X509 fingerprint error");
std::ostringstream oss;
oss << std::hex << std::uppercase << std::setfill('0');
for (size_t i = 0; i < len; ++i) {
if (i)
oss << std::setw(1) << ':';
oss << std::setw(2) << unsigned(buffer[i]);
}
return oss.str();
}
namespace {
certificate_ptr make_certificate_impl(string commonName) {
shared_ptr<X509> x509(X509_new(), X509_free);
shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
unique_ptr<RSA, decltype(&RSA_free)> rsa(RSA_new(), RSA_free);
unique_ptr<BIGNUM, decltype(&BN_free)> exponent(BN_new(), BN_free);
unique_ptr<BIGNUM, decltype(&BN_free)> serial_number(BN_new(), BN_free);
unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
throw std::runtime_error("Unable allocate structures for certificate generation");
const int bits = 4096;
const unsigned int e = 65537; // 2^16 + 1
if (!pkey || !rsa || !exponent || !BN_set_word(exponent.get(), e) ||
!RSA_generate_key_ex(rsa.get(), bits, exponent.get(), NULL) ||
!EVP_PKEY_assign_RSA(pkey.get(), rsa.release())) // the key will be freed when pkey is freed
throw std::runtime_error("Unable to generate key pair");
const size_t serialSize = 16;
auto *commonNameBytes =
reinterpret_cast<unsigned char *>(const_cast<char *>(commonName.c_str()));
if (!X509_gmtime_adj(X509_get_notBefore(x509.get()), 3600 * -1) ||
!X509_gmtime_adj(X509_get_notAfter(x509.get()), 3600 * 24 * 365) ||
!X509_set_version(x509.get(), 1) || !X509_set_pubkey(x509.get(), pkey.get()) ||
!BN_pseudo_rand(serial_number.get(), serialSize, 0, 0) ||
!BN_to_ASN1_INTEGER(serial_number.get(), X509_get_serialNumber(x509.get())) ||
!X509_NAME_add_entry_by_NID(name.get(), NID_commonName, MBSTRING_UTF8, commonNameBytes, -1,
-1, 0) ||
!X509_set_subject_name(x509.get(), name.get()) ||
!X509_set_issuer_name(x509.get(), name.get()))
throw std::runtime_error("Unable to set certificate properties");
if (!X509_sign(x509.get(), pkey.get(), EVP_sha256()))
throw std::runtime_error("Unable to auto-sign certificate");
return std::make_shared<Certificate>(x509, pkey);
}
} // namespace
} // namespace rtc
#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 } // namespace rtc

View File

@ -21,25 +21,54 @@
#include "include.hpp" #include "include.hpp"
#include <future>
#include <tuple>
#if USE_GNUTLS
#include <gnutls/x509.h> #include <gnutls/x509.h>
#else
#include <openssl/x509.h>
#endif
namespace rtc { namespace rtc {
class Certificate { class Certificate {
public: public:
Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey);
Certificate(string crt_pem, string key_pem); Certificate(string crt_pem, string key_pem);
string fingerprint() const; #if USE_GNUTLS
Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey);
gnutls_certificate_credentials_t credentials() const; gnutls_certificate_credentials_t credentials() const;
#else
Certificate(std::shared_ptr<X509> x509, std::shared_ptr<EVP_PKEY> pkey);
std::tuple<X509 *, EVP_PKEY *> credentials() const;
#endif
string fingerprint() const;
private: private:
#if USE_GNUTLS
std::shared_ptr<gnutls_certificate_credentials_t> mCredentials; std::shared_ptr<gnutls_certificate_credentials_t> mCredentials;
#else
std::shared_ptr<X509> mX509;
std::shared_ptr<EVP_PKEY> mPKey;
#endif
string mFingerprint; string mFingerprint;
}; };
#if USE_GNUTLS
string make_fingerprint(gnutls_x509_crt_t crt); string make_fingerprint(gnutls_x509_crt_t crt);
std::shared_ptr<Certificate> make_certificate(const string &commonName); #else
string make_fingerprint(X509 *x509);
#endif
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

@ -20,9 +20,19 @@
namespace rtc { namespace rtc {
void Channel::onOpen(std::function<void()> callback) { mOpenCallback = callback; } size_t Channel::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
void Channel::onClosed(std::function<void()> callback) { mClosedCallback = callback; } size_t Channel::bufferedAmount() const { return mBufferedAmount; }
size_t Channel::availableAmount() const { return 0; }
void Channel::onOpen(std::function<void()> callback) {
mOpenCallback = callback;
}
void Channel::onClosed(std::function<void()> callback) {
mClosedCallback = callback;
}
void Channel::onError(std::function<void(const string &error)> callback) { void Channel::onError(std::function<void(const string &error)> callback) {
mErrorCallback = callback; mErrorCallback = callback;
@ -30,6 +40,10 @@ void Channel::onError(std::function<void(const string &error)> callback) {
void Channel::onMessage(std::function<void(const std::variant<binary, string> &data)> callback) { void Channel::onMessage(std::function<void(const std::variant<binary, string> &data)> callback) {
mMessageCallback = callback; mMessageCallback = callback;
// Pass pending messages
while (auto message = receive())
mMessageCallback(*message);
} }
void Channel::onMessage(std::function<void(const binary &data)> binaryCallback, void Channel::onMessage(std::function<void(const binary &data)> binaryCallback,
@ -39,24 +53,48 @@ void Channel::onMessage(std::function<void(const binary &data)> binaryCallback,
}); });
} }
void Channel::triggerOpen(void) { void Channel::onBufferedAmountLow(std::function<void()> callback) {
if (mOpenCallback) mBufferedAmountLowCallback = callback;
mOpenCallback();
} }
void Channel::triggerClosed(void) { void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; }
if (mClosedCallback)
mClosedCallback(); void Channel::onAvailable(std::function<void()> callback) {
mAvailableCallback = callback;
} }
void Channel::triggerError(const string &error) { void Channel::triggerOpen() { mOpenCallback(); }
if (mErrorCallback)
mErrorCallback(error); void Channel::triggerClosed() { mClosedCallback(); }
void Channel::triggerError(const string &error) { mErrorCallback(error); }
void Channel::triggerAvailable(size_t count) {
if (count == 1)
mAvailableCallback();
while (mMessageCallback && count--) {
auto message = receive();
if (!message)
break;
mMessageCallback(*message);
}
} }
void Channel::triggerMessage(const std::variant<binary, string> &data) { void Channel::triggerBufferedAmount(size_t amount) {
if (mMessageCallback) size_t previous = mBufferedAmount.exchange(amount);
mMessageCallback(data); size_t threshold = mBufferedAmountLowThreshold.load();
if (previous > threshold && amount <= threshold)
mBufferedAmountLowCallback();
}
void Channel::resetCallbacks() {
mOpenCallback = nullptr;
mClosedCallback = nullptr;
mErrorCallback = nullptr;
mMessageCallback = nullptr;
mAvailableCallback = nullptr;
mBufferedAmountLowCallback = nullptr;
} }
} // namespace rtc } // namespace rtc

View File

@ -18,22 +18,74 @@
#include "configuration.hpp" #include "configuration.hpp"
#include <regex>
namespace rtc { namespace rtc {
using std::to_string; IceServer::IceServer(const string &url) {
// Modified regex from RFC 3986, see https://tools.ietf.org/html/rfc3986#appendix-B
static const char *rs =
R"(^(([^:.@/?#]+):)?(/{0,2}((([^:@]*)(:([^@]*))?)@)?(([^:/?#]*)(:([^/?#]*))?))?([^?#]*)(\?([^#]*))?(#(.*))?)";
static const std::regex r(rs, std::regex::extended);
IceServer::IceServer(const string &host) { std::smatch m;
if(size_t pos = host.rfind(':'); pos != string::npos) { if (!std::regex_match(url, m, r) || m[10].length() == 0)
hostname = host.substr(0, pos); throw std::invalid_argument("Invalid ICE server URL: " + url);
service = host.substr(pos + 1);
} else { std::vector<std::optional<string>> opt(m.size());
hostname = host; std::transform(m.begin(), m.end(), opt.begin(), [](const auto &sm) {
service = "3478"; // STUN UDP port return sm.length() > 0 ? std::make_optional(string(sm)) : nullopt;
});
string scheme = opt[2].value_or("stun");
relayType = RelayType::TurnUdp;
if (scheme == "stun" || scheme == "STUN")
type = Type::Stun;
else if (scheme == "turn" || scheme == "TURN")
type = Type::Turn;
else if (scheme == "turns" || scheme == "TURNS") {
type = Type::Turn;
relayType = RelayType::TurnTls;
} else
throw std::invalid_argument("Unknown ICE server protocol: " + scheme);
if (auto &query = opt[15]) {
if (query->find("transport=udp") != string::npos)
relayType = RelayType::TurnUdp;
if (query->find("transport=tcp") != string::npos)
relayType = RelayType::TurnTcp;
if (query->find("transport=tls") != string::npos)
relayType = RelayType::TurnTls;
} }
username = opt[6].value_or("");
password = opt[8].value_or("");
hostname = opt[10].value();
service = opt[12].value_or(relayType == RelayType::TurnTls ? "5349" : "3478");
while (!hostname.empty() && hostname.front() == '[')
hostname.erase(hostname.begin());
while (!hostname.empty() && hostname.back() == ']')
hostname.pop_back();
} }
IceServer::IceServer(const string &hostname_, uint16_t port_) : IceServer(hostname_, to_string(port_)) {} IceServer::IceServer(string hostname_, uint16_t port_)
: IceServer(std::move(hostname_), std::to_string(port_)) {}
IceServer::IceServer(const string &hostname_, const string &service_) : hostname(hostname_), service(service_) {} IceServer::IceServer(string hostname_, string service_)
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Stun) {}
IceServer::IceServer(string hostname_, uint16_t port_, string username_, string password_,
RelayType relayType_)
: IceServer(hostname_, std::to_string(port_), std::move(username_), std::move(password_),
relayType_) {}
IceServer::IceServer(string hostname_, string service_, string username_, string password_,
RelayType relayType_)
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Turn),
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

@ -17,12 +17,20 @@
*/ */
#include "datachannel.hpp" #include "datachannel.hpp"
#include "include.hpp"
#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
@ -57,48 +65,23 @@ struct CloseMessage {
}; };
#pragma pack(pop) #pragma pack(pop)
DataChannel::DataChannel(unsigned int stream, string label, string protocol, const size_t RECV_QUEUE_LIMIT = 1024 * 1024; // 1 MiB
Reliability reliability)
: mStream(stream), mLabel(std::move(label)), mProtocol(std::move(protocol)),
mReliability(std::make_shared<Reliability>(std::move(reliability))) {}
DataChannel::DataChannel(unsigned int stream, shared_ptr<SctpTransport> sctpTransport) DataChannel::DataChannel(weak_ptr<PeerConnection> pc, unsigned int stream, string label,
: mStream(stream), mSctpTransport(sctpTransport), string protocol, Reliability reliability)
mReliability(std::make_shared<Reliability>()) {} : mPeerConnection(pc), mStream(stream), mLabel(std::move(label)),
mProtocol(std::move(protocol)),
mReliability(std::make_shared<Reliability>(std::move(reliability))),
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
DataChannel::~DataChannel() { close(); } DataChannel::DataChannel(weak_ptr<PeerConnection> pc, weak_ptr<SctpTransport> transport,
unsigned int stream)
: mPeerConnection(pc), mSctpTransport(transport), mStream(stream),
mReliability(std::make_shared<Reliability>()),
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
void DataChannel::close() { DataChannel::~DataChannel() {
mIsOpen = false; close();
if (!mIsClosed) {
mIsClosed = true;
if (mSctpTransport)
mSctpTransport->reset(mStream);
}
}
void DataChannel::send(const std::variant<binary, string> &data) {
if (mIsClosed || !mSctpTransport)
return;
std::visit(
[this](const auto &d) {
using T = std::decay_t<decltype(d)>;
constexpr auto type = std::is_same_v<T, string> ? Message::String : Message::Binary;
auto *b = reinterpret_cast<const byte *>(d.data());
// Before the ACK has been received on a DataChannel, all messages must be sent ordered
auto reliability = mIsOpen ? mReliability : nullptr;
mSctpTransport->send(make_message(b, b + d.size(), type, mStream, reliability));
},
data);
}
void DataChannel::send(const byte *data, size_t size) {
if (mIsClosed || !mSctpTransport)
return;
auto reliability = mIsOpen ? mReliability : nullptr;
mSctpTransport->send(make_message(data, data + size, Message::Binary, mStream, reliability));
} }
unsigned int DataChannel::stream() const { return mStream; } unsigned int DataChannel::stream() const { return mStream; }
@ -109,12 +92,81 @@ string DataChannel::protocol() const { return mProtocol; }
Reliability DataChannel::reliability() const { return *mReliability; } Reliability DataChannel::reliability() const { return *mReliability; }
void DataChannel::close() {
mIsClosed = true;
if (mIsOpen.exchange(false))
if (auto transport = mSctpTransport.lock())
transport->close(mStream);
mSctpTransport.reset();
resetCallbacks();
}
void DataChannel::remoteClose() {
if (!mIsClosed.exchange(true))
triggerClosed();
mIsOpen = false;
mSctpTransport.reset();
}
bool DataChannel::send(const std::variant<binary, string> &data) {
return std::visit(
[&](const auto &d) {
using T = std::decay_t<decltype(d)>;
constexpr auto type = std::is_same_v<T, string> ? Message::String : Message::Binary;
auto *b = reinterpret_cast<const byte *>(d.data());
return outgoing(std::make_shared<Message>(b, b + d.size(), type));
},
data);
}
bool DataChannel::send(const byte *data, size_t size) {
return outgoing(std::make_shared<Message>(data, data + size, Message::Binary));
}
std::optional<std::variant<binary, string>> DataChannel::receive() {
while (!mRecvQueue.empty()) {
auto message = *mRecvQueue.pop();
switch (message->type) {
case Message::Control: {
auto raw = reinterpret_cast<const uint8_t *>(message->data());
if (raw[0] == MESSAGE_CLOSE)
remoteClose();
break;
}
case Message::String:
return std::make_optional(
string(reinterpret_cast<const char *>(message->data()), message->size()));
case Message::Binary:
return std::make_optional(std::move(*message));
default:
// Ignore
break;
}
}
return nullopt;
}
bool DataChannel::isOpen(void) const { return mIsOpen; } bool DataChannel::isOpen(void) const { return mIsOpen; }
bool DataChannel::isClosed(void) const { return mIsClosed; } bool DataChannel::isClosed(void) const { return mIsClosed; }
void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) { size_t DataChannel::maxMessageSize() const {
mSctpTransport = sctpTransport; size_t max = DEFAULT_MAX_MESSAGE_SIZE;
if (auto pc = mPeerConnection.lock())
if (auto description = pc->remoteDescription())
if (auto maxMessageSize = description->maxMessageSize())
return *maxMessageSize > 0 ? *maxMessageSize : LOCAL_MAX_MESSAGE_SIZE;
return std::min(max, LOCAL_MAX_MESSAGE_SIZE);
}
size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
void DataChannel::open(shared_ptr<SctpTransport> transport) {
mSctpTransport = transport;
uint8_t channelType = static_cast<uint8_t>(mReliability->type); uint8_t channelType = static_cast<uint8_t>(mReliability->type);
if (mReliability->unordered) if (mReliability->unordered)
@ -141,7 +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) {
if (mIsClosed)
throw std::runtime_error("DataChannel is closed");
if (message->size() > maxMessageSize())
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
message->reliability = mIsOpen ? mReliability : nullptr;
message->stream = mStream;
return transport->send(message);
} }
void DataChannel::incoming(message_ptr message) { void DataChannel::incoming(message_ptr message) {
@ -153,16 +222,14 @@ void DataChannel::incoming(message_ptr message) {
processOpenMessage(message); processOpenMessage(message);
break; break;
case MESSAGE_ACK: case MESSAGE_ACK:
if (!mIsOpen) { if (!mIsOpen.exchange(true)) {
mIsOpen = true;
triggerOpen(); triggerOpen();
} }
break; break;
case MESSAGE_CLOSE: case MESSAGE_CLOSE:
if (mIsOpen) { // The close message will be processed in-order in receive()
close(); mRecvQueue.push(message);
triggerClosed(); triggerAvailable(mRecvQueue.size());
}
break; break;
default: default:
// Ignore // Ignore
@ -170,18 +237,22 @@ void DataChannel::incoming(message_ptr message) {
} }
break; break;
} }
case Message::String: { case Message::String:
triggerMessage(string(reinterpret_cast<const char *>(message->data()), message->size())); case Message::Binary:
mRecvQueue.push(message);
triggerAvailable(mRecvQueue.size());
break; break;
} default:
case Message::Binary: { // Ignore
triggerMessage(*message);
break; break;
} }
}
} }
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");
@ -218,8 +289,9 @@ 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;
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);
@ -81,8 +82,10 @@ Description::Description(const string &sdp, Type type, Role role)
mIcePwd = line.substr(line.find(':') + 1); mIcePwd = line.substr(line.find(':') + 1);
} else if (hasprefix(line, "a=sctp-port")) { } else if (hasprefix(line, "a=sctp-port")) {
mSctpPort = uint16_t(std::stoul(line.substr(line.find(':') + 1))); mSctpPort = uint16_t(std::stoul(line.substr(line.find(':') + 1)));
} else if (hasprefix(line, "a=max-message-size")) {
mMaxMessageSize = size_t(std::stoul(line.substr(line.find(':') + 1)));
} else if (hasprefix(line, "a=candidate")) { } else if (hasprefix(line, "a=candidate")) {
mCandidates.emplace_back(Candidate(line.substr(2), mMid)); addCandidate(Candidate(line.substr(2), mMid));
} else if (hasprefix(line, "a=end-of-candidates")) { } else if (hasprefix(line, "a=end-of-candidates")) {
mTrickle = false; mTrickle = false;
} }
@ -103,48 +106,72 @@ std::optional<string> Description::fingerprint() const { return mFingerprint; }
std::optional<uint16_t> Description::sctpPort() const { return mSctpPort; } std::optional<uint16_t> Description::sctpPort() const { return mSctpPort; }
std::optional<size_t> Description::maxMessageSize() const { return mMaxMessageSize; }
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));
} }
void Description::setSctpPort(uint16_t port) { mSctpPort.emplace(port); } void Description::setSctpPort(uint16_t port) { mSctpPort.emplace(port); }
void Description::addCandidate(std::optional<Candidate> candidate) { void Description::setMaxMessageSize(size_t size) { mMaxMessageSize.emplace(size); }
if (candidate)
mCandidates.emplace_back(std::move(*candidate)); void Description::addCandidate(Candidate candidate) {
else mCandidates.emplace_back(std::move(candidate));
mTrickle = false;
} }
Description::operator string() const { void Description::endCandidates() { mTrickle = false; }
std::vector<Candidate> Description::extractCandidates() {
std::vector<Candidate> result;
std::swap(mCandidates, result);
mTrickle = true;
return result;
}
Description::operator string() const { return generateSdp("\r\n"); }
string Description::generateSdp(const string &eol) const {
if (!mFingerprint) if (!mFingerprint)
throw std::logic_error("Fingerprint must be set to generate a SDP"); throw std::logic_error("Fingerprint must be set to generate an SDP string");
std::ostringstream sdp; std::ostringstream sdp;
sdp << "v=0\n"; sdp << "v=0" << eol;
sdp << "o=- " << mSessionId << " 0 IN IP4 0.0.0.0\n"; sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
sdp << "s=-\n"; sdp << "s=-" << eol;
sdp << "t=0 0\n"; sdp << "t=0 0" << eol;
sdp << "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\n"; sdp << "a=group:BUNDLE 0" << eol;
sdp << "c=IN IP4 0.0.0.0\n"; sdp << "m=application 9 UDP/DTLS/SCTP webrtc-datachannel" << eol;
sdp << "a=ice-ufrag:" << mIceUfrag << "\n"; sdp << "c=IN IP4 0.0.0.0" << eol;
sdp << "a=ice-pwd:" << mIcePwd << "\n"; sdp << "a=ice-ufrag:" << mIceUfrag << eol;
sdp << "a=ice-pwd:" << mIcePwd << eol;
if (mTrickle) if (mTrickle)
sdp << "a=ice-options:trickle\n"; sdp << "a=ice-options:trickle" << eol;
sdp << "a=mid:" << mMid << "\n"; sdp << "a=mid:" << mMid << eol;
sdp << "a=setup:" << roleToString(mRole) << "\n"; sdp << "a=setup:" << roleToString(mRole) << eol;
sdp << "a=dtls-id:1\n"; sdp << "a=dtls-id:1" << eol;
if (mFingerprint) if (mFingerprint)
sdp << "a=fingerprint:sha-256 " << *mFingerprint << "\n"; sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
if (mSctpPort) if (mSctpPort)
sdp << "a=sctp-port:" << *mSctpPort << "\n"; sdp << "a=sctp-port:" << *mSctpPort << eol;
if (mMaxMessageSize)
sdp << "a=max-message-size:" << *mMaxMessageSize << eol;
for (const auto &candidate : mCandidates) { for (const auto &candidate : mCandidates) {
sdp << string(candidate) << "\n"; sdp << string(candidate) << eol;
} }
if (!mTrickle) if (!mTrickle)
sdp << "a=end-of-candidates\n"; sdp << "a=end-of-candidates" << eol;
return sdp.str(); return sdp.str();
} }

View File

@ -18,22 +18,34 @@
#include "dtlstransport.hpp" #include "dtlstransport.hpp"
#include "icetransport.hpp" #include "icetransport.hpp"
#include "message.hpp"
#include <cassert> #include <cassert>
#include <chrono>
#include <cstring> #include <cstring>
#include <exception> #include <exception>
#include <iostream>
#include <gnutls/dtls.h> using namespace std::chrono;
using std::shared_ptr; using std::shared_ptr;
using std::string; using std::string;
using std::unique_ptr;
using std::weak_ptr;
#if USE_GNUTLS
#include <gnutls/dtls.h>
namespace { namespace {
static bool check_gnutls(int ret, const string &message = "GnuTLS error") { static bool check_gnutls(int ret, const string &message = "GnuTLS error") {
if (ret < 0) { if (ret < 0) {
if (!gnutls_error_is_fatal(ret)) if (!gnutls_error_is_fatal(ret)) {
PLOG_INFO << gnutls_strerror(ret);
return false; return false;
}
PLOG_ERROR << message << ": " << gnutls_strerror(ret);
throw std::runtime_error(message + ": " + gnutls_strerror(ret)); throw std::runtime_error(message + ": " + gnutls_strerror(ret));
} }
return true; return true;
@ -43,74 +55,171 @@ static bool check_gnutls(int ret, const string &message = "GnuTLS error") {
namespace rtc { namespace rtc {
using std::shared_ptr; void DtlsTransport::Init() {
// Nothing to do
}
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate, void DtlsTransport::Cleanup() {
verifier_callback verifier, ready_callback ready) // Nothing to do
: Transport(lower), mCertificate(certificate), mVerifierCallback(std::move(verifier)), }
mReadyCallback(std::move(ready)) {
gnutls_certificate_set_verify_function(mCertificate->credentials(), CertificateCallback); DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
verifier_callback verifierCallback, state_callback stateChangeCallback)
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
mVerifierCallback(std::move(verifierCallback)),
mStateChangeCallback(std::move(stateChangeCallback)) {
PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)";
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));
const char *priorities = "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-ARCFOUR-128"; // RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
// See https://tools.ietf.org/html/rfc8261#section-5
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(
gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate->credentials()));
gnutls_dtls_set_timeouts(mSession,
1000, // 1s retransmission timeout recommended by RFC 6347
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);
gnutls_transport_set_push_function(mSession, WriteCallback); gnutls_transport_set_push_function(mSession, WriteCallback);
gnutls_transport_set_pull_function(mSession, ReadCallback); gnutls_transport_set_pull_function(mSession, ReadCallback);
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback); gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
check_gnutls(
gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate->credentials()));
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this); mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
registerIncoming();
} }
DtlsTransport::~DtlsTransport() { DtlsTransport::~DtlsTransport() {
mIncomingQueue.stop(); stop();
if (mRecvThread.joinable())
mRecvThread.join();
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
gnutls_deinit(mSession); gnutls_deinit(mSession);
} }
bool DtlsTransport::send(message_ptr message) { DtlsTransport::State DtlsTransport::state() const { return mState; }
while (true) {
ssize_t ret = gnutls_record_send(mSession, message->data(), message->size()); bool DtlsTransport::stop() {
if (check_gnutls(ret)) { if (!Transport::stop())
return ret > 0; return false;
}
} PLOG_DEBUG << "Stopping DTLS recv thread";
mIncomingQueue.stop();
mRecvThread.join();
return true;
} }
void DtlsTransport::incoming(message_ptr message) { mIncomingQueue.push(message); } bool DtlsTransport::send(message_ptr message) {
if (!message || mState != State::Connected)
return false;
PLOG_VERBOSE << "Send size=" << message->size();
ssize_t ret;
do {
ret = gnutls_record_send(mSession, message->data(), message->size());
} while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
if (ret == GNUTLS_E_LARGE_PACKET)
return false;
return check_gnutls(ret);
}
void DtlsTransport::incoming(message_ptr message) {
if (!message) {
mIncomingQueue.stop();
return;
}
PLOG_VERBOSE << "Incoming size=" << message->size();
mIncomingQueue.push(message);
}
void DtlsTransport::changeState(State state) {
if (mState.exchange(state) != state)
mStateChangeCallback(state);
}
void DtlsTransport::runRecvLoop() { void DtlsTransport::runRecvLoop() {
while (!check_gnutls(gnutls_handshake(mSession), "TLS handshake failed")) {} const size_t maxMtu = 4096;
mReadyCallback(); // Handshake loop
try {
changeState(State::Connecting);
gnutls_dtls_set_mtu(mSession, 1280 - 40 - 8); // min MTU over UDP/IPv6
const size_t bufferSize = 2048; int ret;
char buffer[bufferSize]; do {
ret = gnutls_handshake(mSession);
while (true) { if (ret == GNUTLS_E_LARGE_PACKET)
ssize_t ret = gnutls_record_recv(mSession, buffer, bufferSize); throw std::runtime_error("MTU is too low");
if (check_gnutls(ret)) {
if (ret == 0) { } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN ||
// Closed !check_gnutls(ret, "TLS handshake failed"));
// RFC 8261: DTLS MUST support sending messages larger than the current path MTU
// See https://tools.ietf.org/html/rfc8261#section-5
gnutls_dtls_set_mtu(mSession, maxMtu + 1);
} catch (const std::exception &e) {
PLOG_ERROR << "DTLS handshake: " << e.what();
changeState(State::Failed);
return;
}
// Receive loop
try {
PLOG_INFO << "DTLS handshake done";
changeState(State::Connected);
const size_t bufferSize = maxMtu;
char buffer[bufferSize];
while (true) {
ssize_t ret;
do {
ret = gnutls_record_recv(mSession, buffer, bufferSize);
} while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
// Consider premature termination as remote closing
if (ret == GNUTLS_E_PREMATURE_TERMINATION) {
PLOG_DEBUG << "DTLS connection terminated";
break; break;
} }
auto *b = reinterpret_cast<byte *>(buffer);
recv(make_message(b, b + ret)); if (check_gnutls(ret)) {
if (ret == 0) {
// Closed
PLOG_DEBUG << "DTLS connection cleanly closed";
break;
}
auto *b = reinterpret_cast<byte *>(buffer);
recv(make_message(b, b + ret));
}
} }
} catch (const std::exception &e) {
PLOG_ERROR << "DTLS recv: " << e.what();
} }
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
PLOG_INFO << "DTLS disconnected";
changeState(State::Disconnected);
recv(nullptr);
} }
int DtlsTransport::CertificateCallback(gnutls_session_t session) { int DtlsTransport::CertificateCallback(gnutls_session_t session) {
@ -120,7 +229,6 @@ int DtlsTransport::CertificateCallback(gnutls_session_t session) {
return GNUTLS_E_CERTIFICATE_ERROR; return GNUTLS_E_CERTIFICATE_ERROR;
} }
// Get peer's certificate
unsigned int count = 0; unsigned int count = 0;
const gnutls_datum_t *array = gnutls_certificate_get_peers(session, &count); const gnutls_datum_t *array = gnutls_certificate_get_peers(session, &count);
if (!array || count == 0) { if (!array || count == 0) {
@ -154,22 +262,353 @@ ssize_t DtlsTransport::WriteCallback(gnutls_transport_ptr_t ptr, const void *dat
ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen) { ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen) {
DtlsTransport *t = static_cast<DtlsTransport *>(ptr); DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
auto next = t->mIncomingQueue.pop(); if (auto next = t->mIncomingQueue.pop()) {
if (!next) { auto message = *next;
// Closed ssize_t len = std::min(maxlen, message->size());
std::memcpy(data, message->data(), len);
gnutls_transport_set_errno(t->mSession, 0); gnutls_transport_set_errno(t->mSession, 0);
return 0; return len;
} }
// Closed
auto message = *next;
ssize_t len = std::min(maxlen, message->size());
std::memcpy(data, message->data(), len);
gnutls_transport_set_errno(t->mSession, 0); gnutls_transport_set_errno(t->mSession, 0);
return len; return 0;
} }
int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) { int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
return 1; // So ReadCallback is called DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
t->mIncomingQueue.wait(ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms))
: nullopt);
return !t->mIncomingQueue.empty() ? 1 : 0;
} }
} // namespace rtc } // namespace rtc
#else // USE_GNUTLS==0
#include <openssl/bio.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
namespace {
const int BIO_EOF = -1;
string openssl_error_string(unsigned long err) {
const size_t bufferSize = 256;
char buffer[bufferSize];
ERR_error_string_n(err, buffer, bufferSize);
return string(buffer);
}
bool check_openssl(int success, const string &message = "OpenSSL error") {
if (success)
return true;
string str = openssl_error_string(ERR_get_error());
PLOG_ERROR << message << ": " << str;
throw std::runtime_error(message + ": " + str);
}
bool check_openssl_ret(SSL *ssl, int ret, const string &message = "OpenSSL error") {
if (ret == BIO_EOF)
return true;
unsigned long err = SSL_get_error(ssl, ret);
if (err == SSL_ERROR_NONE || err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
return true;
}
if (err == SSL_ERROR_ZERO_RETURN) {
PLOG_DEBUG << "DTLS connection cleanly closed";
return false;
}
string str = openssl_error_string(err);
PLOG_ERROR << str;
throw std::runtime_error(message + ": " + str);
}
} // namespace
namespace rtc {
BIO_METHOD *DtlsTransport::BioMethods = NULL;
int DtlsTransport::TransportExIndex = -1;
std::mutex DtlsTransport::GlobalMutex;
void DtlsTransport::Init() {
std::lock_guard lock(GlobalMutex);
if (!BioMethods) {
BioMethods = BIO_meth_new(BIO_TYPE_BIO, "DTLS writer");
if (!BioMethods)
throw std::runtime_error("Unable to BIO methods for DTLS writer");
BIO_meth_set_create(BioMethods, BioMethodNew);
BIO_meth_set_destroy(BioMethods, BioMethodFree);
BIO_meth_set_write(BioMethods, BioMethodWrite);
BIO_meth_set_ctrl(BioMethods, BioMethodCtrl);
}
if (TransportExIndex < 0) {
TransportExIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
}
}
void DtlsTransport::Cleanup() {
// Nothing to do
}
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
verifier_callback verifierCallback, state_callback stateChangeCallback)
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
mVerifierCallback(std::move(verifierCallback)),
mStateChangeCallback(std::move(stateChangeCallback)) {
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
if (!(mCtx = SSL_CTX_new(DTLS_method())))
throw std::runtime_error("Unable to create SSL context");
check_openssl(SSL_CTX_set_cipher_list(mCtx, "ALL:!LOW:!EXP:!RC4:!MD5:@STRENGTH"),
"Unable to set SSL priorities");
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
// See https://tools.ietf.org/html/rfc8261#section-5
SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_NO_QUERY_MTU);
SSL_CTX_set_min_proto_version(mCtx, DTLS1_VERSION);
SSL_CTX_set_read_ahead(mCtx, 1);
SSL_CTX_set_quiet_shutdown(mCtx, 1);
SSL_CTX_set_info_callback(mCtx, InfoCallback);
SSL_CTX_set_verify(mCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
CertificateCallback);
SSL_CTX_set_verify_depth(mCtx, 1);
auto [x509, pkey] = mCertificate->credentials();
SSL_CTX_use_certificate(mCtx, x509);
SSL_CTX_use_PrivateKey(mCtx, pkey);
check_openssl(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed");
if (!(mSsl = SSL_new(mCtx)))
throw std::runtime_error("Unable to create SSL instance");
SSL_set_ex_data(mSsl, TransportExIndex, this);
if (lower->role() == Description::Role::Active)
SSL_set_connect_state(mSsl);
else
SSL_set_accept_state(mSsl);
if (!(mInBio = BIO_new(BIO_s_mem())) || !(mOutBio = BIO_new(BioMethods)))
throw std::runtime_error("Unable to create BIO");
BIO_set_mem_eof_return(mInBio, BIO_EOF);
BIO_set_data(mOutBio, this);
SSL_set_bio(mSsl, mInBio, mOutBio);
auto ecdh = unique_ptr<EC_KEY, decltype(&EC_KEY_free)>(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), EC_KEY_free);
SSL_set_options(mSsl, SSL_OP_SINGLE_ECDH_USE);
SSL_set_tmp_ecdh(mSsl, ecdh.get());
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
registerIncoming();
}
DtlsTransport::~DtlsTransport() {
stop();
SSL_free(mSsl);
SSL_CTX_free(mCtx);
}
bool DtlsTransport::stop() {
if (!Transport::stop())
return false;
PLOG_DEBUG << "Stopping DTLS recv thread";
mIncomingQueue.stop();
mRecvThread.join();
SSL_shutdown(mSsl);
return true;
}
DtlsTransport::State DtlsTransport::state() const { return mState; }
bool DtlsTransport::send(message_ptr message) {
if (!message || mState != State::Connected)
return false;
PLOG_VERBOSE << "Send size=" << message->size();
int ret = SSL_write(mSsl, message->data(), message->size());
if (!check_openssl_ret(mSsl, ret))
return false;
return true;
}
void DtlsTransport::incoming(message_ptr message) {
if (!message) {
mIncomingQueue.stop();
return;
}
PLOG_VERBOSE << "Incoming size=" << message->size();
mIncomingQueue.push(message);
}
void DtlsTransport::changeState(State state) {
if (mState.exchange(state) != state)
mStateChangeCallback(state);
}
void DtlsTransport::runRecvLoop() {
const size_t maxMtu = 4096;
try {
changeState(State::Connecting);
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
// Initiate the handshake
int ret = SSL_do_handshake(mSsl);
check_openssl_ret(mSsl, ret, "Handshake failed");
const size_t bufferSize = maxMtu;
byte buffer[bufferSize];
while (true) {
// Process pending messages
while (!mIncomingQueue.empty()) {
auto message = *mIncomingQueue.pop();
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);
if (!check_openssl_ret(mSsl, ret))
break;
if (ret > 0)
recv(make_message(buffer, buffer + ret));
}
}
// No more messages pending, retransmit and rearm timeout if connecting
std::optional<milliseconds> duration;
if (mState == State::Connecting) {
// Warning: This function breaks the usual return value convention
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";
}
struct timeval timeout = {};
if (mState == State::Connecting && DTLSv1_get_timeout(mSsl, &timeout)) {
duration = milliseconds(timeout.tv_sec * 1000 + timeout.tv_usec / 1000);
// Also handle handshake timeout manually because OpenSSL actually doesn't...
// OpenSSL backs off exponentially in base 2 starting from the recommended 1s
// 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 (!mIncomingQueue.wait(duration))
break; // queue is stopped
}
} catch (const std::exception &e) {
PLOG_ERROR << "DTLS recv: " << e.what();
}
if (mState == State::Connected) {
PLOG_INFO << "DTLS disconnected";
changeState(State::Disconnected);
recv(nullptr);
} else {
PLOG_ERROR << "DTLS handshake failed";
changeState(State::Failed);
}
}
int DtlsTransport::CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx) {
SSL *ssl =
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
DtlsTransport *t =
static_cast<DtlsTransport *>(SSL_get_ex_data(ssl, DtlsTransport::TransportExIndex));
X509 *crt = X509_STORE_CTX_get_current_cert(ctx);
std::string fingerprint = make_fingerprint(crt);
return t->mVerifierCallback(fingerprint) ? 1 : 0;
}
void DtlsTransport::InfoCallback(const SSL *ssl, int where, int ret) {
DtlsTransport *t =
static_cast<DtlsTransport *>(SSL_get_ex_data(ssl, DtlsTransport::TransportExIndex));
if (where & SSL_CB_ALERT) {
if (ret != 256) { // Close Notify
PLOG_ERROR << "DTLS alert: " << SSL_alert_desc_string_long(ret);
}
t->mIncomingQueue.stop(); // Close the connection
}
}
int DtlsTransport::BioMethodNew(BIO *bio) {
BIO_set_init(bio, 1);
BIO_set_data(bio, NULL);
BIO_set_shutdown(bio, 0);
return 1;
}
int DtlsTransport::BioMethodFree(BIO *bio) {
if (!bio)
return 0;
BIO_set_data(bio, NULL);
return 1;
}
int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
if (inl <= 0)
return inl;
auto transport = reinterpret_cast<DtlsTransport *>(BIO_get_data(bio));
if (!transport)
return -1;
auto b = reinterpret_cast<const byte *>(in);
transport->outgoing(make_message(b, b + inl));
return inl; // can't fail
}
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) {
switch (cmd) {
case BIO_CTRL_FLUSH:
return 1;
case BIO_CTRL_DGRAM_QUERY_MTU:
return 0; // SSL_OP_NO_QUERY_MTU must be set
case BIO_CTRL_WPENDING:
case BIO_CTRL_PENDING:
return 0;
default:
break;
}
return 0;
}
} // namespace rtc
#endif

View File

@ -25,11 +25,17 @@
#include "queue.hpp" #include "queue.hpp"
#include "transport.hpp" #include "transport.hpp"
#include <atomic>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex>
#include <thread> #include <thread>
#if USE_GNUTLS
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#else
#include <openssl/ssl.h>
#endif
namespace rtc { namespace rtc {
@ -37,31 +43,61 @@ class IceTransport;
class DtlsTransport : public Transport { class DtlsTransport : public Transport {
public: public:
using verifier_callback = std::function<bool(const std::string &fingerprint)>; static void Init();
using ready_callback = std::function<void(void)>; static void Cleanup();
DtlsTransport(std::shared_ptr<IceTransport> lower, std::shared_ptr<Certificate> certificate, enum class State { Disconnected, Connecting, Connected, Failed };
verifier_callback verifier, ready_callback ready);
using verifier_callback = std::function<bool(const std::string &fingerprint)>;
using state_callback = std::function<void(State state)>;
DtlsTransport(std::shared_ptr<IceTransport> lower, certificate_ptr certificate,
verifier_callback verifierCallback, state_callback stateChangeCallback);
~DtlsTransport(); ~DtlsTransport();
bool send(message_ptr message); State state() const;
bool stop() override;
bool send(message_ptr message) override; // false if dropped
private: private:
void incoming(message_ptr message); void incoming(message_ptr message) override;
void changeState(State state);
void runRecvLoop(); void runRecvLoop();
const std::shared_ptr<Certificate> mCertificate; const certificate_ptr mCertificate;
gnutls_session_t mSession;
Queue<message_ptr> mIncomingQueue; Queue<message_ptr> mIncomingQueue;
std::atomic<State> mState;
std::thread mRecvThread; std::thread mRecvThread;
verifier_callback mVerifierCallback; verifier_callback mVerifierCallback;
ready_callback mReadyCallback; state_callback mStateChangeCallback;
#if USE_GNUTLS
gnutls_session_t mSession;
static int CertificateCallback(gnutls_session_t session); static int CertificateCallback(gnutls_session_t session);
static ssize_t WriteCallback(gnutls_transport_ptr_t ptr, const void *data, size_t len); static ssize_t WriteCallback(gnutls_transport_ptr_t ptr, const void *data, size_t len);
static ssize_t ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen); static ssize_t ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen);
static int TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms); static int TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms);
#else
SSL_CTX *mCtx;
SSL *mSsl;
BIO *mInBio, *mOutBio;
static BIO_METHOD *BioMethods;
static int TransportExIndex;
static std::mutex GlobalMutex;
static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx);
static void InfoCallback(const SSL *ssl, int where, int ret);
static int BioMethodNew(BIO *bio);
static int BioMethodFree(BIO *bio);
static int BioMethodWrite(BIO *bio, const char *in, int inl);
static long BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr);
#endif
}; };
} // namespace rtc } // namespace rtc

View File

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2019 Paul-Louis Ageneau * Copyright (c) 2019-2020 Paul-Louis Ageneau
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -17,36 +17,271 @@
*/ */
#include "icetransport.hpp" #include "icetransport.hpp"
#include "configuration.hpp"
#include "transport.hpp"
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <chrono>
#include <iostream> #include <iostream>
#include <random> #include <random>
#include <sstream> #include <sstream>
namespace rtc { #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 std::shared_ptr; using std::shared_ptr;
using std::weak_ptr; using std::weak_ptr;
IceTransport::IceTransport(const Configuration &config, Description::Role role, #if USE_JUICE
candidate_callback candidateCallback, ready_callback ready)
: mRole(role), mMid("0"), mState(State::Disconnected), mNiceAgent(nullptr, nullptr),
mMainLoop(nullptr, nullptr), mCandidateCallback(std::move(candidateCallback)),
mReadyCallback(ready) {
auto logLevelFlags = GLogLevelFlags(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION); namespace rtc {
g_log_set_handler(nullptr, logLevelFlags, LogCallback, this);
nice_debug_enable(false); IceTransport::IceTransport(const Configuration &config, Description::Role role,
candidate_callback candidateCallback, state_callback stateChangeCallback,
gathering_state_callback gatheringStateChangeCallback)
: mRole(role), mMid("0"), mState(State::Disconnected), mGatheringState(GatheringState::New),
mCandidateCallback(std::move(candidateCallback)),
mStateChangeCallback(std::move(stateChangeCallback)),
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
mAgent(nullptr, nullptr) {
PLOG_DEBUG << "Initializing ICE transport (libjuice)";
if (config.enableIceTcp) {
PLOG_WARNING << "ICE-TCP is not supported with libjuice";
}
juice_set_log_handler(IceTransport::LogCallback);
juice_set_log_level(JUICE_LOG_LEVEL_VERBOSE);
juice_config_t jconfig = {};
jconfig.cb_state_changed = IceTransport::StateChangeCallback;
jconfig.cb_candidate = IceTransport::CandidateCallback;
jconfig.cb_gathering_done = IceTransport::GatheringDoneCallback;
jconfig.cb_recv = IceTransport::RecvCallback;
jconfig.user_ptr = this;
// Randomize servers order
std::vector<IceServer> servers = config.iceServers;
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
// Pick a STUN server (TURN support is not implemented in libjuice yet)
for (auto &server : servers) {
if (!server.hostname.empty() && server.type == IceServer::Type::Stun) {
if (server.service.empty())
server.service = "3478"; // STUN UDP port
PLOG_DEBUG << "Using STUN server \"" << server.hostname << ":" << server.service
<< "\"";
mStunHostname = server.hostname;
mStunService = server.service;
jconfig.stun_server_host = mStunHostname.c_str();
jconfig.stun_server_port = std::stoul(mStunService);
}
}
// 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
mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy);
if (!mAgent)
throw std::runtime_error("Failed to create the ICE agent");
}
IceTransport::~IceTransport() { stop(); }
bool IceTransport::stop() {
return Transport::stop();
}
Description::Role IceTransport::role() const { return mRole; }
IceTransport::State IceTransport::state() const { return mState; }
Description IceTransport::getLocalDescription(Description::Type type) const {
char sdp[JUICE_MAX_SDP_STRING_LEN];
if (juice_get_local_description(mAgent.get(), sdp, JUICE_MAX_SDP_STRING_LEN) < 0)
throw std::runtime_error("Failed to generate local SDP");
return Description(string(sdp), type, mRole);
}
void IceTransport::setRemoteDescription(const Description &description) {
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
: Description::Role::Active;
mMid = description.mid();
if (juice_set_remote_description(mAgent.get(), string(description).c_str()) < 0)
throw std::runtime_error("Failed to parse remote SDP");
}
bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
// Don't try to pass unresolved candidates for more safety
if (!candidate.isResolved())
return false;
return juice_add_remote_candidate(mAgent.get(), string(candidate).c_str()) >= 0;
}
void IceTransport::gatherLocalCandidates() {
// Change state now as candidates calls can be synchronous
changeGatheringState(GatheringState::InProgress);
if (juice_gather_candidates(mAgent.get()) < 0) {
throw std::runtime_error("Failed to gather local ICE candidates");
}
}
std::optional<string> IceTransport::getLocalAddress() const {
char str[JUICE_MAX_ADDRESS_STRING_LEN];
if (juice_get_selected_addresses(mAgent.get(), str, JUICE_MAX_ADDRESS_STRING_LEN, NULL, 0) ==
0) {
return std::make_optional(string(str));
}
return nullopt;
}
std::optional<string> IceTransport::getRemoteAddress() const {
char str[JUICE_MAX_ADDRESS_STRING_LEN];
if (juice_get_selected_addresses(mAgent.get(), NULL, 0, str, JUICE_MAX_ADDRESS_STRING_LEN) ==
0) {
return std::make_optional(string(str));
}
return nullopt;
}
bool IceTransport::send(message_ptr message) {
if (!message || (mState != State::Connected && mState != State::Completed))
return false;
PLOG_VERBOSE << "Send size=" << message->size();
return outgoing(message);
}
bool IceTransport::outgoing(message_ptr message) {
return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()),
message->size()) >= 0;
}
void IceTransport::changeState(State state) {
if (mState.exchange(state) != state)
mStateChangeCallback(mState);
}
void IceTransport::changeGatheringState(GatheringState state) {
if (mGatheringState.exchange(state) != state)
mGatheringStateChangeCallback(mGatheringState);
}
void IceTransport::processStateChange(unsigned int state) {
changeState(static_cast<State>(state));
}
void IceTransport::processCandidate(const string &candidate) {
mCandidateCallback(Candidate(candidate, mMid));
}
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
void IceTransport::StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try {
iceTransport->processStateChange(static_cast<unsigned int>(state));
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
void IceTransport::CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try {
iceTransport->processCandidate(sdp);
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
void IceTransport::GatheringDoneCallback(juice_agent_t *agent, void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try {
iceTransport->processGatheringDone();
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
void IceTransport::RecvCallback(juice_agent_t *agent, const char *data, size_t size,
void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try {
PLOG_VERBOSE << "Incoming size=" << size;
auto b = reinterpret_cast<const byte *>(data);
iceTransport->incoming(make_message(b, b + size));
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
plog::Severity severity;
switch (level) {
case JUICE_LOG_LEVEL_FATAL:
severity = plog::fatal;
break;
case JUICE_LOG_LEVEL_ERROR:
severity = plog::error;
break;
case JUICE_LOG_LEVEL_WARN:
severity = plog::warning;
break;
case JUICE_LOG_LEVEL_INFO:
severity = plog::info;
break;
default:
severity = plog::verbose; // libjuice debug as verbose
break;
}
PLOG(severity) << "juice: " << message;
}
} // namespace rtc
#else // USE_JUICE == 0
namespace rtc {
IceTransport::IceTransport(const Configuration &config, Description::Role role,
candidate_callback candidateCallback, state_callback stateChangeCallback,
gathering_state_callback gatheringStateChangeCallback)
: mRole(role), mMid("0"), mState(State::Disconnected), mGatheringState(GatheringState::New),
mCandidateCallback(std::move(candidateCallback)),
mStateChangeCallback(std::move(stateChangeCallback)),
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr) {
PLOG_DEBUG << "Initializing ICE transport (libnice)";
g_log_set_handler("libnice", G_LOG_LEVEL_MASK, LogCallback, this);
IF_PLOG(plog::verbose) {
nice_debug_enable(false); // do not output STUN debug messages
}
mMainLoop = decltype(mMainLoop)(g_main_loop_new(nullptr, FALSE), g_main_loop_unref); mMainLoop = decltype(mMainLoop)(g_main_loop_new(nullptr, FALSE), g_main_loop_unref);
if (!mMainLoop) if (!mMainLoop)
std::runtime_error("Failed to create the main loop"); std::runtime_error("Failed to create the main loop");
// RFC 5245 was obsoleted by RFC 8445 but this should be OK.
mNiceAgent = decltype(mNiceAgent)( mNiceAgent = decltype(mNiceAgent)(
nice_agent_new(g_main_loop_get_context(mMainLoop.get()), NICE_COMPATIBILITY_RFC5245), nice_agent_new(g_main_loop_get_context(mMainLoop.get()), NICE_COMPATIBILITY_RFC5245),
g_object_unref); g_object_unref);
@ -55,19 +290,51 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
throw std::runtime_error("Failed to create the nice agent"); throw std::runtime_error("Failed to create the nice agent");
mMainLoopThread = std::thread(g_main_loop_run, mMainLoop.get()); mMainLoopThread = std::thread(g_main_loop_run, mMainLoop.get());
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", FALSE, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-udp", TRUE, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-tcp", FALSE, nullptr);
mStreamId = nice_agent_add_stream(mNiceAgent.get(), 1);
if (!mStreamId)
throw std::runtime_error("Failed to add a stream");
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", TRUE, nullptr); // decided later
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-udp", TRUE, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-tcp", config.enableIceTcp ? TRUE : FALSE,
nullptr);
// RFC 8445: Agents MUST NOT use an RTO value smaller than 500 ms.
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-initial-timeout", 500, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-max-retransmissions", 3, nullptr);
// RFC 8445: ICE agents SHOULD use a default Ta value, 50 ms, but MAY use another value based on
// the characteristics of the associated data.
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-pacing-timer", 25, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, 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
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();
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed)); std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
// Add one STUN server
bool success = false; bool success = false;
for (auto &server : servers) { for (auto &server : servers) {
if (server.hostname.empty()) if (server.hostname.empty())
continue; continue;
if (server.type != IceServer::Type::Stun)
continue;
if (server.service.empty()) if (server.service.empty())
server.service = "3478"; // STUN UDP port server.service = "3478"; // STUN UDP port
@ -87,6 +354,8 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
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_NUMERICNODE_LEN, servbuffer, MAX_NUMERICNODE_LEN,
NI_NUMERICHOST | NI_NUMERICSERV) == 0) { NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
PLOG_DEBUG << "Using STUN server \"" << server.hostname << ":" << server.service
<< "\"";
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server", nodebuffer, nullptr); g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server", nodebuffer, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server-port", g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server-port",
std::stoul(servbuffer), nullptr); std::stoul(servbuffer), nullptr);
@ -101,17 +370,63 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
break; break;
} }
// Add TURN servers
for (auto &server : servers) {
if (server.hostname.empty())
continue;
if (server.type != IceServer::Type::Turn)
continue;
if (server.service.empty())
server.service = server.relayType == IceServer::RelayType::TurnTls ? "5349" : "3478";
struct addrinfo hints = {};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype =
server.relayType == IceServer::RelayType::TurnUdp ? SOCK_DGRAM : SOCK_STREAM;
hints.ai_protocol =
server.relayType == IceServer::RelayType::TurnUdp ? IPPROTO_UDP : IPPROTO_TCP;
hints.ai_flags = AI_ADDRCONFIG;
struct addrinfo *result = nullptr;
if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0)
continue;
for (auto p = result; p; p = p->ai_next) {
if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
char nodebuffer[MAX_NUMERICNODE_LEN];
char servbuffer[MAX_NUMERICSERV_LEN];
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
servbuffer, MAX_NUMERICNODE_LEN,
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
NiceRelayType niceRelayType;
switch (server.relayType) {
case IceServer::RelayType::TurnTcp:
niceRelayType = NICE_RELAY_TYPE_TURN_TCP;
break;
case IceServer::RelayType::TurnTls:
niceRelayType = NICE_RELAY_TYPE_TURN_TLS;
break;
default:
niceRelayType = NICE_RELAY_TYPE_TURN_UDP;
break;
}
nice_agent_set_relay_info(mNiceAgent.get(), mStreamId, 1, nodebuffer,
std::stoul(servbuffer), server.username.c_str(),
server.password.c_str(), niceRelayType);
}
}
}
freeaddrinfo(result);
}
g_signal_connect(G_OBJECT(mNiceAgent.get()), "component-state-changed", g_signal_connect(G_OBJECT(mNiceAgent.get()), "component-state-changed",
G_CALLBACK(StateChangedCallback), this); G_CALLBACK(StateChangeCallback), this);
g_signal_connect(G_OBJECT(mNiceAgent.get()), "new-candidate-full", g_signal_connect(G_OBJECT(mNiceAgent.get()), "new-candidate-full",
G_CALLBACK(CandidateCallback), this); G_CALLBACK(CandidateCallback), this);
g_signal_connect(G_OBJECT(mNiceAgent.get()), "candidate-gathering-done", g_signal_connect(G_OBJECT(mNiceAgent.get()), "candidate-gathering-done",
G_CALLBACK(GatheringDoneCallback), this); G_CALLBACK(GatheringDoneCallback), this);
mStreamId = nice_agent_add_stream(mNiceAgent.get(), 1);
if (!mStreamId)
throw std::runtime_error("Failed to add a stream");
nice_agent_set_stream_name(mNiceAgent.get(), mStreamId, "application"); nice_agent_set_stream_name(mNiceAgent.get(), mStreamId, "application");
nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1, config.portRangeBegin, nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1, config.portRangeBegin,
config.portRangeEnd); config.portRangeEnd);
@ -120,10 +435,24 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
RecvCallback, this); RecvCallback, this);
} }
IceTransport::~IceTransport() { IceTransport::~IceTransport() { stop(); }
bool IceTransport::stop() {
if (mTimeoutId) {
g_source_remove(mTimeoutId);
mTimeoutId = 0;
}
if (!Transport::stop())
return false;
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());
if (mMainLoopThread.joinable()) mMainLoopThread.join();
mMainLoopThread.join(); return true;
} }
Description::Role IceTransport::role() const { return mRole; } Description::Role IceTransport::role() const { return mRole; }
@ -131,6 +460,11 @@ Description::Role IceTransport::role() const { return mRole; }
IceTransport::State IceTransport::state() const { return mState; } IceTransport::State IceTransport::state() const { return mState; }
Description IceTransport::getLocalDescription(Description::Type type) const { Description IceTransport::getLocalDescription(Description::Type type) const {
// RFC 8445: The initiating agent that started the ICE processing MUST take the controlling
// role, and the other MUST take the controlled role.
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode",
type == Description::Type::Offer ? TRUE : FALSE, nullptr);
std::unique_ptr<gchar[], void (*)(void *)> sdp(nice_agent_generate_local_sdp(mNiceAgent.get()), std::unique_ptr<gchar[], void (*)(void *)> sdp(nice_agent_generate_local_sdp(mNiceAgent.get()),
g_free); g_free);
return Description(string(sdp.get()), type, mRole); return Description(string(sdp.get()), type, mRole);
@ -140,17 +474,18 @@ 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();
mTrickleTimeout = description.trickleEnabled() ? 30s : 0s;
if (nice_agent_parse_remote_sdp(mNiceAgent.get(), string(description).c_str())) // Warning: libnice expects "\n" as end of line
if (nice_agent_parse_remote_sdp(mNiceAgent.get(), description.generateSdp("\n").c_str()) < 0)
throw std::runtime_error("Failed to parse remote SDP"); throw std::runtime_error("Failed to parse remote SDP");
} }
void IceTransport::gatherLocalCandidates() {
if (!nice_agent_gather_candidates(mNiceAgent.get(), mStreamId))
throw std::runtime_error("Failed to gather local ICE candidates");
}
bool IceTransport::addRemoteCandidate(const Candidate &candidate) { bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
// Don't try to pass unresolved candidates to libnice for more safety
if (!candidate.isResolved())
return false;
// Warning: the candidate string must start with "a=candidate:" and it must not end with a // Warning: the candidate string must start with "a=candidate:" and it must not end with a
// newline, else libnice will reject it. // newline, else libnice will reject it.
string sdp(candidate); string sdp(candidate);
@ -166,66 +501,218 @@ bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
return ret > 0; return ret > 0;
} }
void IceTransport::gatherLocalCandidates() {
// Change state now as candidates calls can be synchronous
changeGatheringState(GatheringState::InProgress);
if (!nice_agent_gather_candidates(mNiceAgent.get(), mStreamId)) {
throw std::runtime_error("Failed to gather local ICE candidates");
}
}
std::optional<string> IceTransport::getLocalAddress() const {
NiceCandidate *local = nullptr;
NiceCandidate *remote = nullptr;
if (nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote)) {
return std::make_optional(AddressToString(local->addr));
}
return nullopt;
}
std::optional<string> IceTransport::getRemoteAddress() const {
NiceCandidate *local = nullptr;
NiceCandidate *remote = nullptr;
if (nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote)) {
return std::make_optional(AddressToString(remote->addr));
}
return nullopt;
}
bool IceTransport::send(message_ptr message) { bool IceTransport::send(message_ptr message) {
if (!mStreamId) if (!message || (mState != State::Connected && mState != State::Completed))
return false; return false;
outgoing(message); PLOG_VERBOSE << "Send size=" << message->size();
return true; return outgoing(message);
} }
void IceTransport::incoming(message_ptr message) { recv(message); } bool IceTransport::outgoing(message_ptr message) {
return nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(),
void IceTransport::incoming(const byte *data, int size) { reinterpret_cast<const char *>(message->data())) >= 0;
incoming(make_message(data, data + size));
} }
void IceTransport::outgoing(message_ptr message) { void IceTransport::changeState(State state) {
nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(), if (mState.exchange(state) != state)
reinterpret_cast<const char *>(message->data())); mStateChangeCallback(mState);
}
void IceTransport::changeGatheringState(GatheringState state) {
if (mGatheringState.exchange(state) != state)
mGatheringStateChangeCallback(mGatheringState);
}
void IceTransport::processTimeout() {
PLOG_WARNING << "ICE timeout";
mTimeoutId = 0;
changeState(State::Failed);
} }
void IceTransport::processCandidate(const string &candidate) { void IceTransport::processCandidate(const string &candidate) {
mCandidateCallback(Candidate(candidate, mMid)); mCandidateCallback(Candidate(candidate, mMid));
} }
void IceTransport::processGatheringDone() { mCandidateCallback(nullopt); } void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
void IceTransport::changeState(uint32_t state) { void IceTransport::processStateChange(unsigned int state) {
mState = static_cast<State>(state); if (state == NICE_COMPONENT_STATE_FAILED && mTrickleTimeout.count() > 0) {
if (mState == State::Ready) { if (mTimeoutId)
mReadyCallback(); g_source_remove(mTimeoutId);
mTimeoutId = g_timeout_add(mTrickleTimeout.count() /* ms */, TimeoutCallback, this);
return;
} }
if (state == NICE_COMPONENT_STATE_CONNECTED && mTimeoutId) {
g_source_remove(mTimeoutId);
mTimeoutId = 0;
}
changeState(static_cast<State>(state));
}
string IceTransport::AddressToString(const NiceAddress &addr) {
char buffer[NICE_ADDRESS_STRING_LEN];
nice_address_to_string(&addr, buffer);
unsigned int port = nice_address_get_port(&addr);
std::ostringstream ss;
ss << buffer << ":" << port;
return ss.str();
} }
void IceTransport::CandidateCallback(NiceAgent *agent, NiceCandidate *candidate, void IceTransport::CandidateCallback(NiceAgent *agent, NiceCandidate *candidate,
gpointer userData) { gpointer userData) {
auto iceTransport = static_cast<rtc::IceTransport *>(userData); auto iceTransport = static_cast<rtc::IceTransport *>(userData);
gchar *cand = nice_agent_generate_local_candidate_sdp(agent, candidate); gchar *cand = nice_agent_generate_local_candidate_sdp(agent, candidate);
iceTransport->processCandidate(cand); try {
iceTransport->processCandidate(cand);
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
g_free(cand); g_free(cand);
} }
void IceTransport::GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData) { void IceTransport::GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData) {
auto iceTransport = static_cast<rtc::IceTransport *>(userData); auto iceTransport = static_cast<rtc::IceTransport *>(userData);
iceTransport->processGatheringDone(); try {
iceTransport->processGatheringDone();
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
} }
void IceTransport::StateChangedCallback(NiceAgent *agent, guint streamId, guint componentId, void IceTransport::StateChangeCallback(NiceAgent *agent, guint streamId, guint componentId,
guint state, gpointer userData) { guint state, gpointer userData) {
auto iceTransport = static_cast<rtc::IceTransport *>(userData); auto iceTransport = static_cast<rtc::IceTransport *>(userData);
iceTransport->changeState(state); try {
iceTransport->processStateChange(state);
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
} }
void IceTransport::RecvCallback(NiceAgent *agent, guint streamId, guint componentId, guint len, void IceTransport::RecvCallback(NiceAgent *agent, guint streamId, guint componentId, guint len,
gchar *buf, gpointer userData) { gchar *buf, gpointer userData) {
auto iceTransport = static_cast<rtc::IceTransport *>(userData); auto iceTransport = static_cast<rtc::IceTransport *>(userData);
iceTransport->incoming(reinterpret_cast<byte *>(buf), len); try {
PLOG_VERBOSE << "Incoming size=" << len;
auto b = reinterpret_cast<byte *>(buf);
iceTransport->incoming(make_message(b, b + len));
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
gboolean IceTransport::TimeoutCallback(gpointer userData) {
auto iceTransport = static_cast<rtc::IceTransport *>(userData);
try {
iceTransport->processTimeout();
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
return FALSE;
} }
void IceTransport::LogCallback(const gchar *logDomain, GLogLevelFlags logLevel, void IceTransport::LogCallback(const gchar *logDomain, GLogLevelFlags logLevel,
const gchar *message, gpointer userData) { const gchar *message, gpointer userData) {
std::cout << message << std::endl; plog::Severity severity;
unsigned int flags = logLevel & G_LOG_LEVEL_MASK;
if (flags & G_LOG_LEVEL_ERROR)
severity = plog::fatal;
else if (flags & G_LOG_LEVEL_CRITICAL)
severity = plog::error;
else if (flags & G_LOG_LEVEL_WARNING)
severity = plog::warning;
else if (flags & G_LOG_LEVEL_MESSAGE)
severity = plog::info;
else if (flags & G_LOG_LEVEL_INFO)
severity = plog::info;
else
severity = plog::verbose; // libnice debug as verbose
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

View File

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2019 Paul-Louis Ageneau * Copyright (c) 2019-2020 Paul-Louis Ageneau
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -26,72 +26,116 @@
#include "peerconnection.hpp" #include "peerconnection.hpp"
#include "transport.hpp" #include "transport.hpp"
extern "C" { #if USE_JUICE
#include <juice/juice.h>
#else
#include <nice/agent.h> #include <nice/agent.h>
} #endif
#include <atomic> #include <atomic>
#include <optional> #include <chrono>
#include <thread> #include <thread>
namespace rtc { namespace rtc {
class IceTransport : public Transport { class IceTransport : public Transport {
public: public:
enum class State : uint32_t { #if USE_JUICE
enum class State : unsigned int{
Disconnected = JUICE_STATE_DISCONNECTED,
Connecting = JUICE_STATE_CONNECTING,
Connected = JUICE_STATE_CONNECTED,
Completed = JUICE_STATE_COMPLETED,
Failed = JUICE_STATE_FAILED,
};
#else
enum class State : unsigned int {
Disconnected = NICE_COMPONENT_STATE_DISCONNECTED, Disconnected = NICE_COMPONENT_STATE_DISCONNECTED,
Gathering = NICE_COMPONENT_STATE_GATHERING,
Connecting = NICE_COMPONENT_STATE_CONNECTING, Connecting = NICE_COMPONENT_STATE_CONNECTING,
Connected = NICE_COMPONENT_STATE_CONNECTED, Connected = NICE_COMPONENT_STATE_CONNECTED,
Ready = NICE_COMPONENT_STATE_READY, Completed = NICE_COMPONENT_STATE_READY,
Failed = NICE_COMPONENT_STATE_FAILED Failed = NICE_COMPONENT_STATE_FAILED,
}; };
using candidate_callback = std::function<void(const std::optional<Candidate> &candidate)>; bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
using ready_callback = std::function<void(void)>; #endif
enum class GatheringState { New = 0, InProgress = 1, Complete = 2 };
using candidate_callback = std::function<void(const Candidate &candidate)>;
using state_callback = std::function<void(State state)>;
using gathering_state_callback = std::function<void(GatheringState state)>;
IceTransport(const Configuration &config, Description::Role role, IceTransport(const Configuration &config, Description::Role role,
candidate_callback candidateCallback, ready_callback ready); candidate_callback candidateCallback, state_callback stateChangeCallback,
gathering_state_callback gatheringStateChangeCallback);
~IceTransport(); ~IceTransport();
Description::Role role() const; Description::Role role() const;
State state() const; State state() const;
GatheringState gatheringState() const;
Description getLocalDescription(Description::Type type) const; Description getLocalDescription(Description::Type type) const;
void setRemoteDescription(const Description &description); void setRemoteDescription(const Description &description);
void gatherLocalCandidates();
bool addRemoteCandidate(const Candidate &candidate); bool addRemoteCandidate(const Candidate &candidate);
void gatherLocalCandidates();
bool send(message_ptr message); std::optional<string> getLocalAddress() const;
std::optional<string> getRemoteAddress() const;
bool stop() override;
bool send(message_ptr message) override; // false if dropped
private: private:
void incoming(message_ptr message); bool outgoing(message_ptr message) override;
void incoming(const byte *data, int size);
void outgoing(message_ptr message);
void changeState(uint32_t state); void changeState(State state);
void changeGatheringState(GatheringState state);
void processStateChange(unsigned int state);
void processCandidate(const string &candidate); void processCandidate(const string &candidate);
void processGatheringDone(); void processGatheringDone();
void processTimeout();
Description::Role mRole; Description::Role mRole;
string mMid; string mMid;
State mState; std::chrono::milliseconds mTrickleTimeout;
std::atomic<State> mState;
std::atomic<GatheringState> mGatheringState;
candidate_callback mCandidateCallback;
state_callback mStateChangeCallback;
gathering_state_callback mGatheringStateChangeCallback;
#if USE_JUICE
std::unique_ptr<juice_agent_t, void (*)(juice_agent_t *)> mAgent;
string mStunHostname;
string mStunService;
static void StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void GatheringDoneCallback(juice_agent_t *agent, void *user_ptr);
static void RecvCallback(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void LogCallback(juice_log_level_t level, const char *message);
#else
uint32_t mStreamId = 0; uint32_t mStreamId = 0;
std::unique_ptr<NiceAgent, void (*)(gpointer)> mNiceAgent; std::unique_ptr<NiceAgent, void (*)(gpointer)> mNiceAgent;
std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop; std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop;
std::thread mMainLoopThread; std::thread mMainLoopThread;
guint mTimeoutId = 0;
candidate_callback mCandidateCallback; static string AddressToString(const NiceAddress &addr);
ready_callback mReadyCallback;
static void CandidateCallback(NiceAgent *agent, NiceCandidate *candidate, gpointer userData); static void CandidateCallback(NiceAgent *agent, NiceCandidate *candidate, gpointer userData);
static void GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData); static void GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData);
static void StateChangedCallback(NiceAgent *agent, guint streamId, guint componentId, static void StateChangeCallback(NiceAgent *agent, guint streamId, guint componentId,
guint state, gpointer userData); guint state, gpointer userData);
static void RecvCallback(NiceAgent *agent, guint stream_id, guint component_id, guint len, static void RecvCallback(NiceAgent *agent, guint stream_id, guint component_id, guint len,
gchar *buf, gpointer userData); gchar *buf, 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
}; };
} // namespace rtc } // namespace rtc

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

@ -20,82 +20,168 @@
#include "certificate.hpp" #include "certificate.hpp"
#include "dtlstransport.hpp" #include "dtlstransport.hpp"
#include "icetransport.hpp" #include "icetransport.hpp"
#include "include.hpp"
#include "sctptransport.hpp" #include "sctptransport.hpp"
#include <iostream> #include <iostream>
#include <thread>
namespace rtc { namespace rtc {
using namespace std::placeholders; using namespace std::placeholders;
using std::function;
using std::shared_ptr; using std::shared_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")) {} : mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New),
mGatheringState(GatheringState::New) {}
PeerConnection::~PeerConnection() {} PeerConnection::~PeerConnection() { close(); }
void PeerConnection::close() {
closeDataChannels();
closeTransports();
}
const Configuration *PeerConnection::config() const { return &mConfig; } const Configuration *PeerConnection::config() const { return &mConfig; }
std::optional<Description> PeerConnection::localDescription() const { return mLocalDescription; } PeerConnection::State PeerConnection::state() const { return mState; }
std::optional<Description> PeerConnection::remoteDescription() const { return mRemoteDescription; } PeerConnection::GatheringState PeerConnection::gatheringState() const { return mGatheringState; }
std::optional<Description> PeerConnection::localDescription() const {
std::lock_guard lock(mLocalDescriptionMutex);
return mLocalDescription;
}
std::optional<Description> PeerConnection::remoteDescription() const {
std::lock_guard lock(mRemoteDescriptionMutex);
return mRemoteDescription;
}
void PeerConnection::setRemoteDescription(Description description) { void PeerConnection::setRemoteDescription(Description description) {
if (!mIceTransport) { description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
initIceTransport(Description::Role::ActPass); auto remoteCandidates = description.extractCandidates();
mIceTransport->setRemoteDescription(description);
processLocalDescription(mIceTransport->getLocalDescription(Description::Type::Answer)); std::lock_guard lock(mRemoteDescriptionMutex);
mIceTransport->gatherLocalCandidates(); mRemoteDescription.emplace(std::move(description));
auto iceTransport = std::atomic_load(&mIceTransport);
if (!iceTransport)
iceTransport = initIceTransport(Description::Role::ActPass);
iceTransport->setRemoteDescription(*mRemoteDescription);
if (mRemoteDescription->type() == Description::Type::Offer) {
// This is an offer and we are the answerer.
processLocalDescription(iceTransport->getLocalDescription(Description::Type::Answer));
iceTransport->gatherLocalCandidates();
} else { } else {
mIceTransport->setRemoteDescription(description); // This is an answer and we are the offerer.
auto sctpTransport = std::atomic_load(&mSctpTransport);
if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
// Since we assumed passive role during DataChannel creation, we need to shift the
// 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;
auto it = mDataChannels.begin();
while (it != mDataChannels.end()) {
auto channel = it->second.lock();
if (channel->stream() % 2 == 1)
channel->mStream -= 1;
newDataChannels.emplace(channel->stream(), channel);
++it;
}
std::swap(mDataChannels, newDataChannels);
}
} }
mRemoteDescription.emplace(std::move(description)); for (const auto &candidate : remoteCandidates)
addRemoteCandidate(candidate);
} }
void PeerConnection::addRemoteCandidate(Candidate candidate) { void PeerConnection::addRemoteCandidate(Candidate candidate) {
if (!mRemoteDescription || !mIceTransport) std::lock_guard lock(mRemoteDescriptionMutex);
auto iceTransport = std::atomic_load(&mIceTransport);
if (!mRemoteDescription || !iceTransport)
throw std::logic_error("Remote candidate set without remote description"); throw std::logic_error("Remote candidate set without remote description");
if (mIceTransport->addRemoteCandidate(candidate)) mRemoteDescription->addCandidate(candidate);
mRemoteDescription->addCandidate(std::make_optional(std::move(candidate)));
else if (candidate.resolve(Candidate::ResolveMode::Simple)) {
std::cerr << "Failed to add remote ICE candidate" << std::endl; iceTransport->addRemoteCandidate(candidate);
} else {
// OK, we might need a lookup, do it asynchronously
weak_ptr<IceTransport> weakIceTransport{iceTransport};
std::thread t([weakIceTransport, candidate]() mutable {
if (candidate.resolve(Candidate::ResolveMode::Lookup))
if (auto iceTransport = weakIceTransport.lock())
iceTransport->addRemoteCandidate(candidate);
});
t.detach();
}
}
std::optional<string> PeerConnection::localAddress() const {
auto iceTransport = std::atomic_load(&mIceTransport);
return iceTransport ? iceTransport->getLocalAddress() : nullopt;
}
std::optional<string> PeerConnection::remoteAddress() const {
auto iceTransport = std::atomic_load(&mIceTransport);
return iceTransport ? iceTransport->getRemoteAddress() : nullopt;
} }
shared_ptr<DataChannel> PeerConnection::createDataChannel(const string &label, shared_ptr<DataChannel> PeerConnection::createDataChannel(const string &label,
const string &protocol, const string &protocol,
const Reliability &reliability) { const Reliability &reliability) {
// The active side must use streams with even identifiers, whereas the passive side must use // RFC 5763: The answerer MUST use either a setup attribute value of setup:active or
// streams with odd identifiers. // setup:passive. [...] Thus, setup:active is RECOMMENDED.
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6 // See https://tools.ietf.org/html/rfc5763#section-5
auto role = mIceTransport ? mIceTransport->role() : Description::Role::Active; // Therefore, we assume passive role when we are the offerer.
unsigned int stream = (role == Description::Role::Active) ? 0 : 1; auto iceTransport = std::atomic_load(&mIceTransport);
while (mDataChannels.find(stream) != mDataChannels.end()) { auto role = iceTransport ? iceTransport->role() : Description::Role::Passive;
stream += 2;
if (stream >= 65535)
throw std::runtime_error("Too many DataChannels");
}
auto channel = std::make_shared<DataChannel>(stream, label, protocol, reliability); auto channel = emplaceDataChannel(role, label, protocol, reliability);
mDataChannels.insert(std::make_pair(stream, channel));
if (!mIceTransport) { if (!iceTransport) {
initIceTransport(Description::Role::Active); // RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
processLocalDescription(mIceTransport->getLocalDescription(Description::Type::Offer)); // setup:actpass.
mIceTransport->gatherLocalCandidates(); // See https://tools.ietf.org/html/rfc5763#section-5
} else if (mSctpTransport && mSctpTransport->isReady()) { iceTransport = initIceTransport(Description::Role::ActPass);
channel->open(mSctpTransport); processLocalDescription(iceTransport->getLocalDescription(Description::Type::Offer));
iceTransport->gatherLocalCandidates();
} else {
if (auto transport = std::atomic_load(&mSctpTransport))
if (transport->state() == SctpTransport::State::Connected)
channel->open(transport);
} }
return channel; return channel;
} }
void PeerConnection::onDataChannel( void PeerConnection::onDataChannel(
std::function<void(std::shared_ptr<DataChannel> dataChannel)> callback) { std::function<void(shared_ptr<DataChannel> dataChannel)> callback) {
mDataChannelCallback = callback; mDataChannelCallback = callback;
} }
@ -104,31 +190,208 @@ void PeerConnection::onLocalDescription(
mLocalDescriptionCallback = callback; mLocalDescriptionCallback = callback;
} }
void PeerConnection::onLocalCandidate( void PeerConnection::onLocalCandidate(std::function<void(const Candidate &candidate)> callback) {
std::function<void(const std::optional<Candidate> &candidate)> callback) {
mLocalCandidateCallback = callback; mLocalCandidateCallback = callback;
} }
void PeerConnection::initIceTransport(Description::Role role) { void PeerConnection::onStateChange(std::function<void(State state)> callback) {
mIceTransport = std::make_shared<IceTransport>( mStateChangeCallback = callback;
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
std::bind(&PeerConnection::initDtlsTransport, this));
} }
void PeerConnection::initDtlsTransport() { void PeerConnection::onGatheringStateChange(std::function<void(GatheringState state)> callback) {
mDtlsTransport = std::make_shared<DtlsTransport>( mGatheringStateChangeCallback = callback;
mIceTransport, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
std::bind(&PeerConnection::initSctpTransport, this));
} }
void PeerConnection::initSctpTransport() { shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
uint16_t sctpPort = mRemoteDescription->sctpPort().value_or(DEFAULT_SCTP_PORT); try {
mSctpTransport = std::make_shared<SctpTransport>( if (auto transport = std::atomic_load(&mIceTransport))
mDtlsTransport, sctpPort, std::bind(&PeerConnection::openDataChannels, this), return transport;
std::bind(&PeerConnection::forwardMessage, this, _1));
auto transport = std::make_shared<IceTransport>(
mConfig, role, weak_bind(&PeerConnection::processLocalCandidate, this, _1),
[this, weak_this = weak_from_this()](IceTransport::State state) {
auto shared_this = weak_this.lock();
if (!shared_this)
return;
switch (state) {
case IceTransport::State::Connecting:
changeState(State::Connecting);
break;
case IceTransport::State::Failed:
changeState(State::Failed);
break;
case IceTransport::State::Connected:
initDtlsTransport();
break;
case IceTransport::State::Disconnected:
changeState(State::Disconnected);
break;
default:
// Ignore
break;
}
},
[this, weak_this = weak_from_this()](IceTransport::GatheringState state) {
auto shared_this = weak_this.lock();
if (!shared_this)
return;
switch (state) {
case IceTransport::GatheringState::InProgress:
changeGatheringState(GatheringState::InProgress);
break;
case IceTransport::GatheringState::Complete:
endLocalCandidates();
changeGatheringState(GatheringState::Complete);
break;
default:
// Ignore
break;
}
});
std::atomic_store(&mIceTransport, transport);
if (mState == State::Closed) {
mIceTransport.reset();
transport->stop();
throw std::runtime_error("Connection is closed");
}
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() {
try {
if (auto transport = std::atomic_load(&mDtlsTransport))
return transport;
auto certificate = mCertificate.get();
auto lower = std::atomic_load(&mIceTransport);
auto transport = std::make_shared<DtlsTransport>(
lower, certificate, weak_bind_verifier(&PeerConnection::checkFingerprint, this, _1),
[this, weak_this = weak_from_this()](DtlsTransport::State state) {
auto shared_this = weak_this.lock();
if (!shared_this)
return;
switch (state) {
case DtlsTransport::State::Connected:
initSctpTransport();
break;
case DtlsTransport::State::Failed:
changeState(State::Failed);
break;
case DtlsTransport::State::Disconnected:
changeState(State::Disconnected);
break;
default:
// Ignore
break;
}
});
std::atomic_store(&mDtlsTransport, transport);
if (mState == State::Closed) {
mDtlsTransport.reset();
transport->stop();
throw std::runtime_error("Connection is closed");
}
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() {
try {
if (auto transport = std::atomic_load(&mSctpTransport))
return transport;
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
auto lower = std::atomic_load(&mDtlsTransport);
auto transport = std::make_shared<SctpTransport>(
lower, sctpPort, weak_bind(&PeerConnection::forwardMessage, this, _1),
weak_bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
[this, weak_this = weak_from_this()](SctpTransport::State state) {
auto shared_this = weak_this.lock();
if (!shared_this)
return;
switch (state) {
case SctpTransport::State::Connected:
changeState(State::Connected);
openDataChannels();
break;
case SctpTransport::State::Failed:
remoteCloseDataChannels();
changeState(State::Failed);
break;
case SctpTransport::State::Disconnected:
remoteCloseDataChannels();
changeState(State::Disconnected);
break;
default:
// Ignore
break;
}
});
std::atomic_store(&mSctpTransport, transport);
if (mState == State::Closed) {
mSctpTransport.reset();
transport->stop();
throw std::runtime_error("Connection is closed");
}
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() {
std::lock_guard lock(mLocalDescriptionMutex);
if (mLocalDescription)
mLocalDescription->endCandidates();
} }
bool PeerConnection::checkFingerprint(const std::string &fingerprint) const { bool PeerConnection::checkFingerprint(const std::string &fingerprint) const {
std::lock_guard lock(mRemoteDescriptionMutex);
if (auto expectedFingerprint = if (auto expectedFingerprint =
mRemoteDescription ? mRemoteDescription->fingerprint() : nullopt) { mRemoteDescription ? mRemoteDescription->fingerprint() : nullopt) {
return *expectedFingerprint == fingerprint; return *expectedFingerprint == fingerprint;
@ -137,29 +400,31 @@ bool PeerConnection::checkFingerprint(const std::string &fingerprint) const {
} }
void PeerConnection::forwardMessage(message_ptr message) { void PeerConnection::forwardMessage(message_ptr message) {
if (!mIceTransport || !mSctpTransport) if (!message) {
throw std::logic_error("Got a DataChannel message without transport"); remoteCloseDataChannels();
return;
shared_ptr<DataChannel> channel;
if (auto it = mDataChannels.find(message->stream); it != mDataChannels.end()) {
channel = it->second.lock();
if (!channel || channel->isClosed()) {
mDataChannels.erase(it);
channel = nullptr;
}
} }
auto channel = findDataChannel(message->stream);
auto iceTransport = std::atomic_load(&mIceTransport);
auto sctpTransport = std::atomic_load(&mSctpTransport);
if (!iceTransport || !sctpTransport)
return;
if (!channel) { if (!channel) {
const byte dataChannelOpenMessage{0x03}; const byte dataChannelOpenMessage{0x03};
unsigned int remoteParity = (mIceTransport->role() == Description::Role::Active) ? 1 : 0; unsigned int remoteParity = (iceTransport->role() == Description::Role::Active) ? 1 : 0;
if (message->type == Message::Control && *message->data() == dataChannelOpenMessage && if (message->type == Message::Control && *message->data() == dataChannelOpenMessage &&
message->stream % 2 == remoteParity) { message->stream % 2 == remoteParity) {
channel = std::make_shared<DataChannel>(message->stream, mSctpTransport); channel =
channel->onOpen(std::bind(&PeerConnection::triggerDataChannel, this, channel)); std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
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
mSctpTransport->reset(message->stream); sctpTransport->close(message->stream);
return; return;
} }
} }
@ -167,43 +432,232 @@ void PeerConnection::forwardMessage(message_ptr message) {
channel->incoming(message); channel->incoming(message);
} }
void PeerConnection::openDataChannels(void) { void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
auto it = mDataChannels.begin(); if (auto channel = findDataChannel(stream))
while (it != mDataChannels.end()) { channel->triggerBufferedAmount(amount);
auto channel = it->second.lock(); }
if (!channel || channel->isClosed()) {
it = mDataChannels.erase(it); shared_ptr<DataChannel> PeerConnection::emplaceDataChannel(Description::Role role,
continue; 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(
std::function<void(shared_ptr<DataChannel> channel)> func) {
// Iterate
{
std::shared_lock lock(mDataChannelsMutex); // read-only
auto it = mDataChannels.begin();
while (it != mDataChannels.end()) {
auto channel = it->second.lock();
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);
continue;
}
++it;
} }
channel->open(mSctpTransport);
++it;
} }
} }
void PeerConnection::processLocalDescription(Description description) { void PeerConnection::openDataChannels() {
auto remoteSctpPort = mRemoteDescription ? mRemoteDescription->sctpPort() : nullopt; if (auto transport = std::atomic_load(&mSctpTransport))
iterateDataChannels([&](shared_ptr<DataChannel> channel) { channel->open(transport); });
description.setFingerprint(mCertificate->fingerprint());
description.setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
mLocalDescription.emplace(std::move(description));
if (mLocalDescriptionCallback)
mLocalDescriptionCallback(*mLocalDescription);
} }
void PeerConnection::processLocalCandidate(std::optional<Candidate> candidate) { void PeerConnection::closeDataChannels() {
iterateDataChannels([&](shared_ptr<DataChannel> channel) { channel->close(); });
}
void PeerConnection::remoteCloseDataChannels() {
iterateDataChannels([&](shared_ptr<DataChannel> channel) { channel->remoteClose(); });
}
void PeerConnection::processLocalDescription(Description description) {
std::optional<uint16_t> remoteSctpPort;
if (auto remote = remoteDescription())
remoteSctpPort = remote->sctpPort();
auto certificate = mCertificate.get(); // wait for certificate if not ready
std::lock_guard lock(mLocalDescriptionMutex);
mLocalDescription.emplace(std::move(description));
mLocalDescription->setFingerprint(certificate->fingerprint());
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
mLocalDescriptionCallback(*mLocalDescription);
}
void PeerConnection::processLocalCandidate(Candidate candidate) {
std::lock_guard lock(mLocalDescriptionMutex);
if (!mLocalDescription) if (!mLocalDescription)
throw std::logic_error("Got a local candidate without local description"); throw std::logic_error("Got a local candidate without local description");
mLocalDescription->addCandidate(candidate); mLocalDescription->addCandidate(candidate);
if (mLocalCandidateCallback) mLocalCandidateCallback(candidate);
mLocalCandidateCallback(candidate);
} }
void PeerConnection::triggerDataChannel(std::shared_ptr<DataChannel> dataChannel) { void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
if (mDataChannelCallback) auto dataChannel = weakDataChannel.lock();
mDataChannelCallback(dataChannel); if (!dataChannel)
return;
mDataChannelCallback(dataChannel);
}
bool PeerConnection::changeState(State state) {
State current;
do {
current = mState.load();
if (current == state)
return true;
if (current == State::Closed)
return false;
} while (!mState.compare_exchange_weak(current, state));
mStateChangeCallback(state);
return true;
}
bool PeerConnection::changeGatheringState(GatheringState state) {
if (mGatheringState.exchange(state) != 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
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state) {
using State = rtc::PeerConnection::State;
std::string str;
switch (state) {
case State::New:
str = "new";
break;
case State::Connecting:
str = "connecting";
break;
case State::Connected:
str = "connected";
break;
case State::Disconnected:
str = "disconnected";
break;
case State::Failed:
str = "failed";
break;
case State::Closed:
str = "closed";
break;
default:
str = "unknown";
break;
}
return out << str;
}
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state) {
using GatheringState = rtc::PeerConnection::GatheringState;
std::string str;
switch (state) {
case GatheringState::New:
str = "new";
break;
case GatheringState::InProgress:
str = "in_progress";
break;
case GatheringState::Complete:
str = "complete";
break;
default:
str = "unknown";
break;
}
return out << str;
}

View File

@ -1,90 +0,0 @@
/**
* 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_QUEUE_H
#define RTC_QUEUE_H
#include "include.hpp"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <queue>
namespace rtc {
template <typename T> class Queue {
public:
Queue();
~Queue();
void stop();
void push(const T &element);
std::optional<T> pop();
bool empty() const;
private:
std::queue<T> mQueue;
std::condition_variable mCondition;
std::atomic<bool> mStopping;
mutable std::mutex mMutex;
};
template <typename T> Queue<T>::Queue() : mStopping(false) {}
template <typename T> Queue<T>::~Queue() { stop(); }
template <typename T> void Queue<T>::stop() {
std::lock_guard<std::mutex> lock(mMutex);
mStopping = true;
mCondition.notify_all();
}
template <typename T> void Queue<T>::push(const T &element) {
std::lock_guard<std::mutex> lock(mMutex);
if (mStopping)
return;
mQueue.push(element);
mCondition.notify_one();
}
template <typename T> std::optional<T> Queue<T>::pop() {
std::unique_lock<std::mutex> lock(mMutex);
while (mQueue.empty()) {
if (mStopping)
return nullopt;
mCondition.wait(lock);
}
std::optional<T> element = mQueue.front();
mQueue.pop();
return element;
}
template <typename T> bool Queue<T>::empty() const {
std::lock_guard<std::mutex> lock(mMutex);
return mQueue.empty();
}
} // namespace rtc
#endif

View File

@ -17,178 +17,438 @@
*/ */
#include "datachannel.hpp" #include "datachannel.hpp"
#include "include.hpp"
#include "peerconnection.hpp" #include "peerconnection.hpp"
#include <rtc.h> #include <rtc.h>
#include <exception>
#include <mutex>
#include <unordered_map> #include <unordered_map>
#include <utility>
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);
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount) {
Configuration config;
for (int i = 0; i < iceServersCount; ++i) {
config.iceServers.emplace_back(IceServer(string(iceServers[i])));
}
int pc = ++lastId;
peerConnectionMap.emplace(std::make_pair(pc, std::make_shared<PeerConnection>(config)));
return pc;
}
void rtcDeletePeerConnection(int pc) { peerConnectionMap.erase(pc); }
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;
dataChannelMap.emplace(std::make_pair(dc, dataChannel));
return dc;
}
void rtcDeleteDataChannel(int dc) { dataChannelMap.erase(dc); }
void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *)) {
auto it = peerConnectionMap.find(pc);
if (it == peerConnectionMap.end())
return;
it->second->onDataChannel([pc, dataChannelCallback](std::shared_ptr<DataChannel> dataChannel) {
int dc = ++lastId;
dataChannelMap.emplace(std::make_pair(dc, dataChannel));
dataChannelCallback(dc, getUserPointer(pc));
});
}
void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *,
void *)) {
auto it = peerConnectionMap.find(pc);
if (it == peerConnectionMap.end())
return;
it->second->onLocalDescription([pc, descriptionCallback](const Description &description) {
descriptionCallback(string(description).c_str(), description.typeString().c_str(),
getUserPointer(pc));
});
}
void rtcSetLocalCandidateCallback(int pc,
void (*candidateCallback)(const char *, const char *, void *)) {
auto it = peerConnectionMap.find(pc);
if (it == peerConnectionMap.end())
return;
it->second->onLocalCandidate(
[pc, candidateCallback](const std::optional<Candidate> &candidate) {
if (candidate) {
candidateCallback(string(*candidate).c_str(), candidate->mid().c_str(),
getUserPointer(pc));
} else {
candidateCallback(nullptr, nullptr, getUserPointer(pc));
}
});
}
void rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
auto it = peerConnectionMap.find(pc);
if (it == peerConnectionMap.end())
return;
it->second->setRemoteDescription(Description(string(sdp), type ? string(type) : ""));
}
void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid) {
auto it = peerConnectionMap.find(pc);
if (it == peerConnectionMap.end())
return;
it->second->addRemoteCandidate(Candidate(string(candidate), mid ? string(mid) : ""));
}
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
auto it = dataChannelMap.find(dc);
if (it == dataChannelMap.end())
return 0;
if (!size)
return 0;
string label = it->second->label();
size = std::min(size_t(size - 1), label.size());
std::copy(label.data(), label.data() + size, buffer);
buffer[size] = '\0';
return size + 1;
}
void rtcSetOpenCallback(int dc, void (*openCallback)(void *)) {
auto it = dataChannelMap.find(dc);
if (it == dataChannelMap.end())
return;
it->second->onOpen([dc, openCallback]() { openCallback(getUserPointer(dc)); });
}
void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *)) {
auto it = dataChannelMap.find(dc);
if (it == dataChannelMap.end())
return;
it->second->onError([dc, errorCallback](const string &error) {
errorCallback(error.c_str(), getUserPointer(dc));
});
}
void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *)) {
auto it = dataChannelMap.find(dc);
if (it == dataChannelMap.end())
return;
it->second->onMessage(
[dc, messageCallback](const binary &b) {
messageCallback(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
},
[dc, messageCallback](const string &s) {
messageCallback(s.c_str(), -1, getUserPointer(dc));
});
}
int rtcSendMessage(int dc, const char *data, int size) {
auto it = dataChannelMap.find(dc);
if (it == dataChannelMap.end())
return 0;
if (size >= 0) {
auto b = reinterpret_cast<const byte *>(data);
it->second->send(b, size);
return size;
} else {
string s(data);
it->second->send(s);
return s.size();
}
}
void rtcSetUserPointer(int i, void *ptr) {
if (ptr) if (ptr)
userPointerMap.insert(std::make_pair(i, ptr)); userPointerMap.insert(std::make_pair(i, ptr));
else else
userPointerMap.erase(i); userPointerMap.erase(i);
} }
shared_ptr<PeerConnection> getPeerConnection(int id) {
std::lock_guard lock(mutex);
auto it = peerConnectionMap.find(id);
return it != peerConnectionMap.end() ? it->second : nullptr;
}
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;
peerConnectionMap.emplace(std::make_pair(pc, ptr));
return pc;
}
int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
std::lock_guard lock(mutex);
int dc = ++lastId;
dataChannelMap.emplace(std::make_pair(dc, ptr));
return dc;
}
bool erasePeerConnection(int pc) {
std::lock_guard lock(mutex);
if (peerConnectionMap.erase(pc) == 0)
return false;
userPointerMap.erase(pc);
return true;
}
bool eraseDataChannel(int dc) {
std::lock_guard lock(mutex);
if (dataChannelMap.erase(dc) == 0)
return false;
userPointerMap.erase(dc);
return true;
}
} // namespace
void rtcInitLogger(rtcLogLevel level) { InitLogger(static_cast<LogLevel>(level)); }
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;
}
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
});
else
peerConnection->onLocalDescription(nullptr);
return 0;
}
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
});
else
peerConnection->onLocalCandidate(nullptr);
return 0;
}
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
cb(static_cast<rtcState>(state), getUserPointer(pc));
});
else
peerConnection->onStateChange(nullptr);
return 0;
}
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (cb)
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
});
else
peerConnection->onGatheringStateChange(nullptr);
return 0;
}
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
return 0;
}
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
CATCH(peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}))
return 0;
}
int rtcGetLocalAddress(int pc, char *buffer, int size) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (auto addr = peerConnection->localAddress()) {
size = std::min(size_t(size - 1), addr->size());
std::copy(addr->data(), addr->data() + size, buffer);
buffer[size] = '\0';
return size + 1;
}
return -1;
}
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
auto peerConnection = getPeerConnection(pc);
if (!peerConnection)
return -1;
if (auto addr = peerConnection->remoteAddress()) {
size = std::min(size_t(size - 1), addr->size());
std::copy(addr->data(), addr->data() + size, buffer);
buffer[size] = '\0';
return size + 1;
}
return -1;
}
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (!size)
return 0;
string label = dataChannel->label();
size = std::min(size_t(size - 1), label.size());
std::copy(label.data(), label.data() + size, buffer);
buffer[size] = '\0';
return size + 1;
}
int rtcSetOpenCallback(int dc, openCallbackFunc 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 rtcSetClosedCallback(int dc, closedCallbackFunc cb) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (cb)
dataChannel->onClosed([dc, cb]() { cb(getUserPointer(dc)); });
else
dataChannel->onClosed(nullptr);
return 0;
}
int rtcSetErrorCallback(int dc, errorCallbackFunc cb) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (cb)
dataChannel->onError(
[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, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(dc)); });
else
dataChannel->onMessage(nullptr);
return 0;
}
int rtcSendMessage(int dc, const char *data, int size) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
if (size >= 0) {
auto b = reinterpret_cast<const byte *>(data);
CATCH(dataChannel->send(b, size));
return size;
} else {
string s(data);
CATCH(dataChannel->send(s));
return s.size();
}
}
int rtcGetBufferedAmount(int dc) {
auto dataChannel = getDataChannel(dc);
if (!dataChannel)
return -1;
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,45 +21,73 @@
#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;
using std::shared_ptr; using std::shared_ptr;
namespace rtc { namespace rtc {
std::mutex SctpTransport::GlobalMutex; void SctpTransport::Init() {
int SctpTransport::InstancesCount = 0; usrsctp_init(0, &SctpTransport::WriteCallback, nullptr);
usrsctp_sysctl_set_sctp_ecn_enable(0);
void SctpTransport::GlobalInit() { usrsctp_sysctl_set_sctp_init_rtx_max_default(5);
std::unique_lock<std::mutex> lock(GlobalMutex); usrsctp_sysctl_set_sctp_path_rtx_max_default(5);
if (InstancesCount++ == 0) { usrsctp_sysctl_set_sctp_assoc_rtx_max_default(5); // single path
usrsctp_init(0, &SctpTransport::WriteCallback, nullptr); usrsctp_sysctl_set_sctp_rto_min_default(1 * 1000); // ms
usrsctp_sysctl_set_sctp_ecn_enable(0); usrsctp_sysctl_set_sctp_rto_max_default(10 * 1000); // ms
} usrsctp_sysctl_set_sctp_rto_initial_default(1 * 1000); // ms
usrsctp_sysctl_set_sctp_init_rto_max_default(10 * 1000); // ms
usrsctp_sysctl_set_sctp_heartbeat_interval_default(10 * 1000); // ms
} }
void SctpTransport::GlobalCleanup() { void SctpTransport::Cleanup() {
std::unique_lock<std::mutex> 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, ready_callback ready, SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
message_callback recv) message_callback recvCallback, amount_callback bufferedAmountCallback,
: Transport(lower), mReadyCallback(std::move(ready)), mPort(port) { state_callback stateChangeCallback)
: Transport(lower), mPort(port), mSendQueue(0, message_size_func),
mBufferedAmountCallback(std::move(bufferedAmountCallback)),
mStateChangeCallback(std::move(stateChangeCallback)), mState(State::Disconnected) {
onRecv(recvCallback);
onRecv(recv); PLOG_DEBUG << "Initializing SCTP transport";
GlobalInit();
usrsctp_register_address(this); usrsctp_register_address(this);
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::ReadCallback, mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
nullptr, 0, this); &SctpTransport::SendCallback, 0, this);
if (!mSock) if (!mSock)
throw std::runtime_error("Could not create usrsctp socket, errno=" + std::to_string(errno)); throw std::runtime_error("Could not create SCTP socket, errno=" + std::to_string(errno));
if (usrsctp_set_non_blocking(mSock, 1))
throw std::runtime_error("Unable to set non-blocking mode, errno=" + std::to_string(errno));
// SCTP must stop sending after the lower layer is shut down, so disable linger
struct linger sol = {}; struct linger sol = {};
sol.l_onoff = 1; sol.l_onoff = 1;
sol.l_linger = 0; sol.l_linger = 0;
@ -67,14 +95,6 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, re
throw std::runtime_error("Could not set socket option SO_LINGER, errno=" + throw std::runtime_error("Could not set socket option SO_LINGER, errno=" +
std::to_string(errno)); std::to_string(errno));
struct sctp_paddrparams spp = {};
spp.spp_flags = SPP_PMTUD_DISABLE;
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
// See https://tools.ietf.org/html/rfc8261#section-5
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &spp, sizeof(spp)))
throw std::runtime_error("Could not set socket option SCTP_PEER_ADDR_PARAMS, errno=" +
std::to_string(errno));
struct sctp_assoc_value av = {}; struct sctp_assoc_value av = {};
av.assoc_id = SCTP_ALL_ASSOC; av.assoc_id = SCTP_ALL_ASSOC;
av.assoc_value = 1; av.assoc_value = 1;
@ -82,17 +102,41 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, re
throw std::runtime_error("Could not set socket option SCTP_ENABLE_STREAM_RESET, errno=" + throw std::runtime_error("Could not set socket option SCTP_ENABLE_STREAM_RESET, errno=" +
std::to_string(errno)); std::to_string(errno));
uint32_t nodelay = 1; struct sctp_event se = {};
se.se_assoc_id = SCTP_ALL_ASSOC;
se.se_on = 1;
se.se_type = SCTP_ASSOC_CHANGE;
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_EVENT, &se, sizeof(se)))
throw std::runtime_error("Could not subscribe to event SCTP_ASSOC_CHANGE, errno=" +
std::to_string(errno));
se.se_type = SCTP_SENDER_DRY_EVENT;
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_EVENT, &se, sizeof(se)))
throw std::runtime_error("Could not subscribe to event SCTP_SENDER_DRY_EVENT, errno=" +
std::to_string(errno));
se.se_type = SCTP_STREAM_RESET_EVENT;
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_EVENT, &se, sizeof(se)))
throw std::runtime_error("Could not subscribe to event SCTP_STREAM_RESET_EVENT, errno=" +
std::to_string(errno));
// The sender SHOULD disable the Nagle algorithm (see RFC1122) to minimize the latency.
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.6
int nodelay = 1;
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay))) if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay)))
throw std::runtime_error("Could not set socket option SCTP_NODELAY, errno=" + throw std::runtime_error("Could not set socket option SCTP_NODELAY, errno=" +
std::to_string(errno)); std::to_string(errno));
struct sctp_event se = {}; struct sctp_paddrparams spp = {};
se.se_assoc_id = SCTP_ALL_ASSOC; #if USE_PMTUD
se.se_on = 1; // Enabled SCTP path MTU discovery
se.se_type = SCTP_STREAM_RESET_EVENT; spp.spp_flags = SPP_PMTUD_ENABLE;
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_EVENT, &se, sizeof(se))) #else
throw std::runtime_error("Could not set socket option SCTP_EVENT, errno=" + // Fall back to a safe MTU value.
spp.spp_flags = SPP_PMTUD_DISABLE;
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
// See https://tools.ietf.org/html/rfc8261#section-5
#endif
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &spp, sizeof(spp)))
throw std::runtime_error("Could not set socket option SCTP_PEER_ADDR_PARAMS, errno=" +
std::to_string(errno)); std::to_string(errno));
// The IETF draft recommends the number of streams negotiated during SCTP association to be // The IETF draft recommends the number of streams negotiated during SCTP association to be
@ -104,6 +148,58 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, re
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
// realistic RTTs, therefore we increase it to 1MiB for better performance.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685
int bufSize = 1024 * 1024;
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &bufSize, sizeof(bufSize)))
throw std::runtime_error("Could not set SCTP recv buffer size, errno=" +
std::to_string(errno));
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_SNDBUF, &bufSize, sizeof(bufSize)))
throw std::runtime_error("Could not set SCTP send buffer size, errno=" +
std::to_string(errno));
registerIncoming();
connect();
}
SctpTransport::~SctpTransport() {
stop();
if (mSock)
usrsctp_close(mSock);
usrsctp_deregister_address(this);
}
SctpTransport::State SctpTransport::state() const { return mState; }
bool SctpTransport::stop() {
if (!Transport::stop())
return false;
mSendQueue.stop();
safeFlush();
shutdown();
onRecv(nullptr);
return true;
}
void SctpTransport::connect() {
if (!mSock)
return;
PLOG_DEBUG << "SCTP connect";
changeState(State::Connecting);
struct sockaddr_conn sconn = {}; struct sockaddr_conn sconn = {};
sconn.sconn_family = AF_CONN; sconn.sconn_family = AF_CONN;
sconn.sconn_port = htons(mPort); sconn.sconn_port = htons(mPort);
@ -115,29 +211,99 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, re
if (usrsctp_bind(mSock, reinterpret_cast<struct sockaddr *>(&sconn), sizeof(sconn))) if (usrsctp_bind(mSock, reinterpret_cast<struct sockaddr *>(&sconn), sizeof(sconn)))
throw std::runtime_error("Could not bind usrsctp socket, errno=" + std::to_string(errno)); throw std::runtime_error("Could not bind usrsctp socket, errno=" + std::to_string(errno));
mConnectThread = std::thread(&SctpTransport::runConnect, this); // According to the IETF draft, both endpoints must initiate the SCTP association, in a
// simultaneous-open manner, irrelevent to the SDP setup role.
// See https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-9.3
int ret = usrsctp_connect(mSock, reinterpret_cast<struct sockaddr *>(&sconn), sizeof(sconn));
if (ret && errno != EINPROGRESS)
throw std::runtime_error("Connection attempt failed, errno=" + std::to_string(errno));
} }
SctpTransport::~SctpTransport() { void SctpTransport::shutdown() {
mStopping = true; if (!mSock)
if (mConnectThread.joinable()) return;
mConnectThread.join();
if (mSock) { PLOG_DEBUG << "SCTP shutdown";
usrsctp_shutdown(mSock, SHUT_RDWR);
usrsctp_close(mSock); if (usrsctp_shutdown(mSock, SHUT_RDWR) != 0 && errno != ENOTCONN) {
PLOG_WARNING << "SCTP shutdown failed, errno=" << errno;
} }
usrsctp_deregister_address(this); // close() abort the connection when linger is disabled, call it now
GlobalCleanup(); usrsctp_close(mSock);
mSock = nullptr;
PLOG_INFO << "SCTP disconnected";
changeState(State::Disconnected);
mWrittenCondition.notify_all();
} }
bool SctpTransport::isReady() const { return mIsReady; }
bool SctpTransport::send(message_ptr message) { bool SctpTransport::send(message_ptr message) {
const Reliability reliability = message->reliability ? *message->reliability : Reliability(); std::lock_guard lock(mSendMutex);
if (!message)
return mSendQueue.empty();
struct sctp_sendv_spa spa = {}; PLOG_VERBOSE << "Send size=" << message->size();
// If nothing is pending, try to send directly
if (mSendQueue.empty() && trySendMessage(message))
return true;
mSendQueue.push(message);
updateBufferedAmount(message->stream, message_size_func(message));
return false;
}
void SctpTransport::close(unsigned int stream) {
send(make_message(0, Message::Reset, uint16_t(stream)));
}
void SctpTransport::flush() {
std::lock_guard lock(mSendMutex);
trySendQueue();
}
void SctpTransport::incoming(message_ptr message) {
// There could be a race condition here where we receive the remote INIT before the local one is
// sent, which would result in the connection being aborted. Therefore, we need to wait for data
// to be sent on our side (i.e. the local INIT) before proceeding.
{
std::unique_lock lock(mWriteMutex);
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || mState != State::Connected; });
}
if (!message) {
PLOG_INFO << "SCTP disconnected";
changeState(State::Disconnected);
recv(nullptr);
return;
}
PLOG_VERBOSE << "Incoming size=" << message->size();
usrsctp_conninput(this, message->data(), message->size(), 0);
}
void SctpTransport::changeState(State state) {
if (mState.exchange(state) != state)
mStateChangeCallback(state);
}
bool SctpTransport::trySendQueue() {
// Requires mSendMutex to be locked
while (auto next = mSendQueue.peek()) {
auto message = *next;
if (!trySendMessage(message))
return false;
mSendQueue.pop();
updateBufferedAmount(message->stream, -message_size_func(message));
}
return true;
}
bool SctpTransport::trySendMessage(message_ptr message) {
// Requires mSendMutex to be locked
if (!mSock || mState != State::Connected)
return false;
uint32_t ppid; uint32_t ppid;
switch (message->type) { switch (message->type) {
@ -147,23 +313,37 @@ bool SctpTransport::send(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 = {};
// set sndinfo // set sndinfo
spa.sendv_flags |= SCTP_SEND_SNDINFO_VALID; spa.sendv_flags |= SCTP_SEND_SNDINFO_VALID;
spa.sendv_sndinfo.snd_sid = uint16_t(message->stream); spa.sendv_sndinfo.snd_sid = uint16_t(message->stream);
spa.sendv_sndinfo.snd_ppid = htonl(ppid); spa.sendv_sndinfo.snd_ppid = htonl(ppid);
spa.sendv_sndinfo.snd_flags |= SCTP_EOR; spa.sendv_sndinfo.snd_flags |= SCTP_EOR; // implicit here
// set prinfo // set prinfo
spa.sendv_flags |= SCTP_SEND_PRINFO_VALID; spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
if (reliability.unordered) if (reliability.unordered)
spa.sendv_sndinfo.snd_flags |= SCTP_UNORDERED; spa.sendv_sndinfo.snd_flags |= SCTP_UNORDERED;
using std::chrono::milliseconds;
switch (reliability.type) { switch (reliability.type) {
case Reliability::TYPE_PARTIAL_RELIABLE_REXMIT: case Reliability::TYPE_PARTIAL_RELIABLE_REXMIT:
spa.sendv_flags |= SCTP_SEND_PRINFO_VALID; spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
@ -180,136 +360,251 @@ bool SctpTransport::send(message_ptr message) {
break; break;
} }
ssize_t ret;
if (!message->empty()) { if (!message->empty()) {
return usrsctp_sendv(mSock, message->data(), message->size(), nullptr, 0, &spa, sizeof(spa), ret = usrsctp_sendv(mSock, message->data(), message->size(), nullptr, 0, &spa, sizeof(spa),
SCTP_SENDV_SPA, 0) > 0; SCTP_SENDV_SPA, 0);
} else { } else {
const char zero = 0; const char zero = 0;
return usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0) > 0; ret = usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0);
}
if (ret >= 0) {
PLOG_VERBOSE << "SCTP sent size=" << message->size();
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
mBytesSent += message->size();
return true;
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
PLOG_VERBOSE << "SCTP sending not possible";
return false;
} else {
PLOG_ERROR << "SCTP sending failed, errno=" << errno;
throw std::runtime_error("Sending failed, errno=" + std::to_string(errno));
} }
} }
void SctpTransport::reset(unsigned int stream) { void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
// Requires mSendMutex to be locked
auto it = mBufferedAmount.insert(std::make_pair(streamId, 0)).first;
size_t amount = size_t(std::max(long(it->second) + delta, long(0)));
if (amount == 0)
mBufferedAmount.erase(it);
else
it->second = 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; using srs_t = struct sctp_reset_streams;
const size_t len = sizeof(srs_t) + sizeof(uint16_t); const size_t len = sizeof(srs_t) + sizeof(uint16_t);
byte buffer[len] = {}; byte buffer[len] = {};
srs_t &srs = *reinterpret_cast<srs_t *>(buffer); srs_t &srs = *reinterpret_cast<srs_t *>(buffer);
srs.srs_flags = SCTP_STREAM_RESET_OUTGOING; srs.srs_flags = SCTP_STREAM_RESET_OUTGOING;
srs.srs_number_streams = 1; srs.srs_number_streams = 1;
srs.srs_stream_list[0] = uint16_t(stream); srs.srs_stream_list[0] = streamId;
usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &srs, len);
}
void SctpTransport::incoming(message_ptr message) { mWritten = false;
// There could be a race condition here where we receive the remote INIT before the thread in if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &srs, len) == 0) {
// usrsctp_connect sends the local one, which would result in the connection being aborted. std::unique_lock lock(mWriteMutex); // locking before setsockopt might deadlock usrsctp...
// Therefore, we need to wait for data to be sent on our side (i.e. the local INIT) before mWrittenCondition.wait_for(lock, 1000ms,
// proceeding. [&]() { return mWritten || mState != State::Connected; });
if (!mConnectDataSent) { } else if (errno == EINVAL) {
std::unique_lock<std::mutex> lock(mConnectMutex); PLOG_VERBOSE << "SCTP stream " << streamId << " already reset";
mConnectCondition.wait(lock, [this] { return mConnectDataSent || mStopping; }); } else {
} PLOG_WARNING << "SCTP reset stream " << streamId << " failed, errno=" << errno;
if (!mStopping)
usrsctp_conninput(this, message->data(), message->size(), 0);
}
void SctpTransport::runConnect() {
struct sockaddr_conn sconn = {};
sconn.sconn_family = AF_CONN;
sconn.sconn_port = htons(mPort);
sconn.sconn_addr = this;
#ifdef HAVE_SCONN_LEN
sconn.sconn_len = sizeof(sconn);
#endif
// According to the IETF draft, both endpoints must initiate the SCTP association, in a
// simultaneous-open manner, irrelevent to the SDP setup role.
// See https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-9.3
if (usrsctp_connect(mSock, reinterpret_cast<struct sockaddr *>(&sconn), sizeof(sconn)) != 0) {
std::cerr << "SCTP connection failed, errno=" << errno << std::endl;
mStopping = true;
return;
}
if (!mStopping) {
mIsReady = true;
mReadyCallback();
} }
} }
int SctpTransport::handleWrite(void *data, size_t len, uint8_t tos, uint8_t set_df) { bool SctpTransport::safeFlush() {
byte *b = reinterpret_cast<byte *>(data); try {
outgoing(make_message(b, b + len)); flush();
return true;
if (!mConnectDataSent) { } catch (const std::exception &e) {
std::unique_lock<std::mutex> lock(mConnectMutex); PLOG_ERROR << "SCTP flush: " << e.what();
mConnectDataSent = true; return false;
mConnectCondition.notify_all(); }
}
int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data,
size_t len, struct sctp_rcvinfo info, int flags) {
try {
PLOG_VERBOSE << "Handle recv, len=" << len;
if (!len)
return -1;
// This is valid because SCTP_FRAGMENT_INTERLEAVE is set to level 0
// so partial messages and notifications may not be interleaved.
if (flags & MSG_EOR) {
if (!mPartialRecv.empty()) {
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
data = mPartialRecv.data();
len = mPartialRecv.size();
}
// Message/Notification is complete, process it
if (flags & MSG_NOTIFICATION)
processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
else
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
mPartialRecv.clear();
} else {
// Message/Notification is not complete
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
}
} catch (const std::exception &e) {
PLOG_ERROR << "SCTP recv: " << e.what();
return -1;
} }
return 0; // success return 0; // success
} }
int SctpTransport::process(struct socket *sock, union sctp_sockstore addr, void *data, size_t len, int SctpTransport::handleSend(size_t free) {
struct sctp_rcvinfo recv_info, int flags) { PLOG_VERBOSE << "Handle send, free=" << free;
if (flags & MSG_NOTIFICATION) { return safeFlush() ? 0 : -1;
processNotification((union sctp_notification *)data, len); }
} else {
processData((const byte *)data, len, recv_info.rcv_sid, int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df) {
PayloadId(htonl(recv_info.rcv_ppid))); try {
PLOG_VERBOSE << "Handle write, len=" << len;
std::unique_lock lock(mWriteMutex);
if (!outgoing(make_message(data, data + len)))
return -1;
mWritten = true;
mWrittenOnce = true;
mWrittenCondition.notify_all();
} catch (const std::exception &e) {
PLOG_ERROR << "SCTP write: " << e.what();
return -1;
} }
free(data); return 0; // success
return 0;
} }
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) {
Message::Type type; PLOG_VERBOSE << "Process data, len=" << len;
// 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
// We handle them at reception for compatibility reasons but should never send them.
switch (ppid) { switch (ppid) {
case PPID_STRING:
type = Message::String;
break;
case PPID_STRING_EMPTY:
type = Message::String;
len = 0;
break;
case PPID_BINARY:
type = Message::Binary;
break;
case PPID_BINARY_EMPTY:
type = Message::Binary;
len = 0;
break;
case PPID_CONTROL: case PPID_CONTROL:
type = Message::Control; recv(make_message(data, data + len, Message::Control, sid));
break; break;
case PPID_STRING_PARTIAL: // deprecated
mPartialStringData.insert(mPartialStringData.end(), data, data + len);
break;
case PPID_STRING:
if (mPartialStringData.empty()) {
mBytesReceived += len;
recv(make_message(data, data + len, Message::String, sid));
} else {
mPartialStringData.insert(mPartialStringData.end(), data, data + len);
mBytesReceived += mPartialStringData.size();
recv(make_message(mPartialStringData.begin(), mPartialStringData.end(), Message::String,
sid));
mPartialStringData.clear();
}
break;
case PPID_STRING_EMPTY:
// This only accounts for when the partial data is empty
recv(make_message(mPartialStringData.begin(), mPartialStringData.end(), Message::String,
sid));
mPartialStringData.clear();
break;
case PPID_BINARY_PARTIAL: // deprecated
mPartialBinaryData.insert(mPartialBinaryData.end(), data, data + len);
break;
case PPID_BINARY:
if (mPartialBinaryData.empty()) {
mBytesReceived += len;
recv(make_message(data, data + len, Message::Binary, sid));
} else {
mPartialBinaryData.insert(mPartialBinaryData.end(), data, data + len);
mBytesReceived += mPartialStringData.size();
recv(make_message(mPartialBinaryData.begin(), mPartialBinaryData.end(), Message::Binary,
sid));
mPartialBinaryData.clear();
}
break;
case PPID_BINARY_EMPTY:
// This only accounts for when the partial data is empty
recv(make_message(mPartialBinaryData.begin(), mPartialBinaryData.end(), Message::Binary,
sid));
mPartialBinaryData.clear();
break;
default: default:
// Unknown // Unknown
std::cerr << "Unknown PPID: " << uint32_t(ppid) << std::endl; PLOG_WARNING << "Unknown PPID: " << uint32_t(ppid);
return; return;
} }
recv(make_message(data, data + len, type, sid));
} }
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;
}
auto type = notify->sn_header.sn_type;
PLOG_VERBOSE << "Process notification, type=" << type;
switch (type) {
case SCTP_ASSOC_CHANGE: {
const struct sctp_assoc_change &assoc_change = notify->sn_assoc_change;
if (assoc_change.sac_state == SCTP_COMM_UP) {
PLOG_INFO << "SCTP connected";
changeState(State::Connected);
} else {
if (mState == State::Connecting) {
PLOG_ERROR << "SCTP connection failed";
changeState(State::Failed);
} else {
PLOG_INFO << "SCTP disconnected";
changeState(State::Disconnected);
}
mWrittenCondition.notify_all();
}
break;
}
case SCTP_SENDER_DRY_EVENT: {
// It not should be necessary since the send callback should have been called already,
// but to be sure, let's try to send now.
safeFlush();
break;
}
switch (notify->sn_header.sn_type) {
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);
const uint16_t flags = reset_event.strreset_flags;
if (reset_event->strreset_flags & SCTP_STREAM_RESET_INCOMING_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 (reset_event->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
const byte dataChannelCloseMessage{0x04}; const byte dataChannelCloseMessage{0x04};
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];
recv(make_message(&dataChannelCloseMessage, &dataChannelCloseMessage + 1, recv(make_message(&dataChannelCloseMessage, &dataChannelCloseMessage + 1,
Message::Control, streamId)); Message::Control, streamId));
} }
@ -322,16 +617,51 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
break; break;
} }
} }
int SctpTransport::WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos,
uint8_t set_df) { void SctpTransport::clearStats() {
return static_cast<SctpTransport *>(sctp_ptr)->handleWrite(data, len, tos, set_df); mBytesReceived = 0;
mBytesSent = 0;
} }
int SctpTransport::ReadCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t SctpTransport::bytesSent() { return mBytesSent; }
size_t len, struct sctp_rcvinfo recv_info, int flags,
void *user_data) { size_t SctpTransport::bytesReceived() { return mBytesReceived; }
return static_cast<SctpTransport *>(user_data)->process(sock, addr, data, len, recv_info,
flags); 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,
size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) {
int ret = static_cast<SctpTransport *>(ptr)->handleRecv(
sock, addr, static_cast<const byte *>(data), len, recv_info, flags);
free(data);
return ret;
}
int SctpTransport::SendCallback(struct socket *sock, uint32_t sb_free) {
struct sctp_paddrinfo paddrinfo = {};
socklen_t len = sizeof(paddrinfo);
if (usrsctp_getsockopt(sock, IPPROTO_SCTP, SCTP_GET_PEER_ADDR_INFO, &paddrinfo, &len))
return -1;
auto sconn = reinterpret_cast<struct sockaddr_conn *>(&paddrinfo.spinfo_address);
void *ptr = sconn->sconn_addr;
return static_cast<SctpTransport *>(ptr)->handleSend(size_t(sb_free));
}
int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) {
return static_cast<SctpTransport *>(ptr)->handleWrite(static_cast<byte *>(data), len, tos,
set_df);
} }
} // namespace rtc } // namespace rtc

View File

@ -21,74 +21,102 @@
#include "include.hpp" #include "include.hpp"
#include "peerconnection.hpp" #include "peerconnection.hpp"
#include "queue.hpp"
#include "transport.hpp" #include "transport.hpp"
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
#include <map>
#include <mutex> #include <mutex>
#include <thread>
#include <sys/socket.h> #include "usrsctp.h"
#include <sys/types.h>
#include <usrsctp.h>
namespace rtc { namespace rtc {
class SctpTransport : public Transport { class SctpTransport : public Transport {
public: public:
using ready_callback = std::function<void(void)>; static void Init();
static void Cleanup();
SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, ready_callback ready, enum class State { Disconnected, Connecting, Connected, Failed };
message_callback recv);
using amount_callback = std::function<void(uint16_t streamId, size_t amount)>;
using state_callback = std::function<void(State state)>;
SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, message_callback recvCallback,
amount_callback bufferedAmountCallback, state_callback stateChangeCallback);
~SctpTransport(); ~SctpTransport();
bool isReady() const; State state() const;
bool send(message_ptr message); bool stop() override;
void reset(unsigned int stream); bool send(message_ptr message) override; // false if buffered
void close(unsigned int stream);
void flush();
// 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
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-8
enum PayloadId : uint32_t { enum PayloadId : uint32_t {
PPID_CONTROL = 50, PPID_CONTROL = 50,
PPID_STRING = 51, PPID_STRING = 51,
PPID_BINARY_PARTIAL = 52,
PPID_BINARY = 53, PPID_BINARY = 53,
PPID_STRING_PARTIAL = 54,
PPID_STRING_EMPTY = 56, PPID_STRING_EMPTY = 56,
PPID_BINARY_EMPTY = 57 PPID_BINARY_EMPTY = 57
}; };
void incoming(message_ptr message); void connect();
void runConnect(); void shutdown();
void incoming(message_ptr message) override;
void changeState(State state);
int handleWrite(void *data, size_t len, uint8_t tos, uint8_t set_df); bool trySendQueue();
bool trySendMessage(message_ptr message);
void updateBufferedAmount(uint16_t streamId, long delta);
void sendReset(uint16_t streamId);
bool safeFlush();
int process(struct socket *sock, union sctp_sockstore addr, void *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);
int handleSend(size_t free);
int handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df);
void processData(const byte *data, size_t len, uint16_t streamId, PayloadId ppid); void processData(const byte *data, size_t len, uint16_t streamId, PayloadId ppid);
void processNotification(const union sctp_notification *notify, size_t len); void processNotification(const union sctp_notification *notify, size_t len);
ready_callback mReadyCallback; const uint16_t mPort;
struct socket *mSock; struct socket *mSock;
uint16_t mPort;
std::thread mConnectThread; std::mutex mSendMutex;
std::atomic<bool> mStopping = false; Queue<message_ptr> mSendQueue;
std::atomic<bool> mIsReady = false; std::map<uint16_t, size_t> mBufferedAmount;
amount_callback mBufferedAmountCallback;
std::mutex mConnectMutex; std::mutex mWriteMutex;
std::condition_variable mConnectCondition; std::condition_variable mWrittenCondition;
std::atomic<bool> mConnectDataSent = false; std::atomic<bool> mWritten = false; // written outside lock
bool mWrittenOnce = false;
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df); state_callback mStateChangeCallback;
static int ReadCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len, std::atomic<State> mState;
// Stats
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
binary mPartialRecv, mPartialStringData, mPartialBinaryData;
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);
void GlobalInit(); static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
void GlobalCleanup();
static std::mutex GlobalMutex;
static int InstancesCount;
}; };
} // namespace rtc } // namespace rtc

View File

@ -22,6 +22,7 @@
#include "include.hpp" #include "include.hpp"
#include "message.hpp" #include "message.hpp"
#include <atomic>
#include <functional> #include <functional>
#include <memory> #include <memory>
@ -31,36 +32,41 @@ using namespace std::placeholders;
class Transport { class Transport {
public: public:
Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(lower) { init(); } Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(std::move(lower)) {}
virtual ~Transport() {} virtual ~Transport() {
stop();
virtual bool send(message_ptr message) = 0; if (mLower)
void onRecv(message_callback callback) { mRecvCallback = std::move(callback); } mLower->onRecv(nullptr); // doing it on stop could cause a deadlock
protected:
void recv(message_ptr message) {
if (mRecvCallback)
mRecvCallback(message);
} }
virtual void incoming(message_ptr message) = 0; virtual bool stop() {
virtual void outgoing(message_ptr message) { getLower()->send(message); } return !mShutdown.exchange(true);
}
private: void registerIncoming() {
void init() {
if (mLower) if (mLower)
mLower->onRecv(std::bind(&Transport::incoming, this, _1)); mLower->onRecv(std::bind(&Transport::incoming, this, _1));
} }
std::shared_ptr<Transport> getLower() { void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }
virtual bool send(message_ptr message) { return outgoing(message); }
protected:
void recv(message_ptr message) { mRecvCallback(message); }
virtual void incoming(message_ptr message) { recv(message); }
virtual bool outgoing(message_ptr message) {
if (mLower) if (mLower)
return mLower; return mLower->send(message);
else else
throw std::logic_error("No lower transport to call"); return false;
} }
private:
std::shared_ptr<Transport> mLower; std::shared_ptr<Transport> mLower;
message_callback 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,61 +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;
void test_connectivity();
void test_capi();
int main(int argc, char **argv) { int main(int argc, char **argv) {
auto pc1 = std::make_shared<PeerConnection>(); try {
auto pc2 = std::make_shared<PeerConnection>(); std::cout << "*** Running connectivity test..." << std::endl;
test_connectivity();
pc1->onLocalDescription([pc2](const Description &sdp) { std::cout << "*** Finished connectivity test" << std::endl;
cout << "Description 1: " << sdp << endl; } catch (const exception &e) {
pc2->setRemoteDescription(sdp); std::cerr << "Connectivity test failed: " << e.what() << endl;
}); return -1;
}
pc1->onLocalCandidate([pc2](const optional<Candidate> &candidate) { try {
if (candidate) { std::cout << "*** Running C API test..." << std::endl;
cout << "Candidate 1: " << *candidate << endl; test_capi();
pc2->addRemoteCandidate(*candidate); std::cout << "*** Finished C API test" << std::endl;
} } catch (const exception &e) {
}); std::cerr << "C API test failed: " << e.what() << endl;
return -1;
pc2->onLocalDescription([pc1](const Description &sdp) { }
cout << "Description 2: " << sdp << endl; return 0;
pc1->setRemoteDescription(sdp);
});
pc2->onLocalCandidate([pc1](const optional<Candidate> &candidate) {
if (candidate) {
cout << "Candidate 2: " << *candidate << endl;
pc1->addRemoteCandidate(*candidate);
}
});
shared_ptr<DataChannel> dc2;
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
cout << "Got a DataChannel with label: " << dc->label() << endl;
dc2 = dc;
dc2->send("Hello world!");
});
auto dc1 = pc1->createDataChannel("test");
dc1->onOpen([dc1]() {
cout << "DataChannel open: " << dc1->label() << endl;
});
dc1->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) {
cout << "Received: " << get<string>(message) << endl;
}
});
this_thread::sleep_for(10s);
} }

9
test/p2p/README.md Normal file
View File

@ -0,0 +1,9 @@
* Execute ```offerer``` app in console
* Execute ```answerer``` app in another console
* Copy "Local Description" from ```offerer```
* Enter 1 to ```answerer```
* Paste copied description, press enter
* Redo same procedure for ```answerer```
* Redo same procedure for candidates
* Wait for "DataChannel open" message
* Send message from one peer to another

159
test/p2p/answerer.cpp Normal file
View File

@ -0,0 +1,159 @@
/**
* Copyright (c) 2019 Paul-Louis Ageneau, Murat Dogan
*
* 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; }
int main(int argc, char **argv) {
InitLogger(LogLevel::Warning);
Configuration config;
// config.iceServers.emplace_back("stun.l.google.com:19302");
auto pc = std::make_shared<PeerConnection>(config);
pc->onLocalDescription([](const Description &description) {
cout << "Local Description (Paste this to the other peer):" << endl;
cout << string(description) << endl;
});
pc->onLocalCandidate([](const Candidate &candidate) {
cout << "Local Candidate (Paste this to the other peer after the local description):"
<< endl;
cout << string(candidate) << endl << endl;
});
pc->onStateChange(
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "[Gathering State: " << state << "]" << endl;
});
shared_ptr<DataChannel> dc = nullptr;
pc->onDataChannel([&](shared_ptr<DataChannel> _dc) {
cout << "[Got a DataChannel with label: " << _dc->label() << "]" << endl;
dc = _dc;
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
dc->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) {
cout << "[Received message: " << get<string>(message) << "]" << endl;
}
});
});
bool exit = false;
while (!exit) {
cout << endl
<< "**********************************************************************************"
"*****"
<< endl
<< "* 0: Exit /"
<< " 1: Enter remote description /"
<< " 2: Enter remote candidate /"
<< " 3: Send message /"
<< " 4: Print Connection Info *" << endl
<< "[Command]: ";
int command = -1;
cin >> command;
cin.ignore();
switch (command) {
case 0: {
exit = true;
break;
}
case 1: {
// Parse Description
cout << "[Description]: ";
string sdp, line;
while (getline(cin, line) && !line.empty()) {
sdp += line;
sdp += "\r\n";
}
std::cout << sdp;
pc->setRemoteDescription(sdp);
break;
}
case 2: {
// Parse Candidate
cout << "[Candidate]: ";
string candidate;
getline(cin, candidate);
pc->addRemoteCandidate(candidate);
break;
}
case 3: {
// Send Message
if (!dc || !dc->isOpen()) {
cout << "** Channel is not Open ** ";
break;
}
cout << "[Message]: ";
string message;
getline(cin, message);
dc->send(message);
break;
}
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 ** ";
break;
}
}
}
if (dc)
dc->close();
if (pc)
pc->close();
}

159
test/p2p/offerer.cpp Normal file
View File

@ -0,0 +1,159 @@
/**
* Copyright (c) 2019 Paul-Louis Ageneau, Murat Dogan
*
* 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; }
int main(int argc, char **argv) {
InitLogger(LogLevel::Warning);
Configuration config;
// config.iceServers.emplace_back("stun.l.google.com:19302");
auto pc = std::make_shared<PeerConnection>(config);
pc->onLocalDescription([](const Description &description) {
cout << "Local Description (Paste this to the other peer):" << endl;
cout << string(description) << endl;
});
pc->onLocalCandidate([](const Candidate &candidate) {
cout << "Local Candidate (Paste this to the other peer after the local description):"
<< endl;
cout << string(candidate) << endl << endl;
});
pc->onStateChange(
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "[Gathering State: " << state << "]" << endl;
});
auto dc = pc->createDataChannel("test"); // this is the offerer, so create a data channel
dc->onOpen([&]() { cout << "[DataChannel open: " << dc->label() << "]" << endl; });
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
dc->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) {
cout << "[Received: " << get<string>(message) << "]" << endl;
}
});
this_thread::sleep_for(1s);
bool exit = false;
while (!exit) {
cout << endl
<< "**********************************************************************************"
"*****"
<< endl
<< "* 0: Exit /"
<< " 1: Enter remote description /"
<< " 2: Enter remote candidate /"
<< " 3: Send message /"
<< " 4: Print Connection Info *" << endl
<< "[Command]: ";
int command = -1;
cin >> command;
cin.ignore();
switch (command) {
case 0: {
exit = true;
break;
}
case 1: {
// Parse Description
cout << "[Description]: ";
string sdp, line;
while (getline(cin, line) && !line.empty()) {
sdp += line;
sdp += "\r\n";
}
pc->setRemoteDescription(sdp);
break;
}
case 2: {
// Parse Candidate
cout << "[Candidate]: ";
string candidate;
getline(cin, candidate);
pc->addRemoteCandidate(candidate);
break;
}
case 3: {
// Send Message
if (!dc->isOpen()) {
cout << "** Channel is not Open ** ";
break;
}
cout << "[Message]: ";
string message;
getline(cin, message);
dc->send(message);
break;
}
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 ** ";
break;
}
}
}
if (dc)
dc->close();
if (pc)
pc->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;
}

Submodule usrsctp deleted from 04d617c9c1