Compare commits

...

94 Commits

Author SHA1 Message Date
679ecfc066 Bumped version to 0.10.5 2021-01-21 14:40:21 +01:00
4e3ea69073 Join thread pool at exit to prevent static destruction order issue 2021-01-21 12:08:57 +01:00
b79c886480 Force BUILD_SHARED_LIBS to OFF to build usrsctp as static 2021-01-20 20:27:01 +01:00
08da691911 Added RTC defines to usage requirement in Jamfile 2021-01-20 20:27:01 +01:00
754568506a Fixed implicit conversion of stream from unsigned int to uint16_t 2021-01-20 20:27:01 +01:00
7ac351d1b9 Fixed local variable shadowing 2021-01-20 20:27:01 +01:00
6d5cecbca1 Fixed usrsctp build in Jamfile 2021-01-20 20:27:01 +01:00
abec5fc219 Bumped version to 0.10.4 2020-11-29 19:15:54 +01:00
684b7ba925 Merge pull request #280 from paullouisageneau/default-data-mid
Change default data mid
2020-11-29 17:33:20 +01:00
d8515b6362 Some cleanup 2020-11-29 17:24:20 +01:00
62da885028 Changed default data mid to a number 2020-11-29 17:13:55 +01:00
ff2e83bbdc Call rtcAddRemoteCandidate() with NULL mid 2020-11-29 17:06:19 +01:00
8f9e8e718e Uncluttered client example to keep it simple 2020-11-29 17:02:01 +01:00
c6bee7b0d4 Added console logging in web example 2020-11-29 17:00:43 +01:00
4d93303be8 Updated libjuice to v0.6.2 2020-11-29 16:26:31 +01:00
1620ddfb03 Merge pull request #279 from paullouisageneau/fix-candidates-mid
Fix mid on local candidates
2020-11-29 16:25:50 +01:00
452b742adc Fixed mid on local candidates 2020-11-29 16:03:57 +01:00
244c834992 Cleanup and reformatting 2020-11-28 17:03:51 +01:00
ffe202a6a2 Merge pull request #278 from hhgyu/add-support-protocols
support Subprotocols
2020-11-28 13:18:06 +01:00
ea87e5ae09 Exposed send(data, size) on Channel 2020-11-27 21:20:24 +01:00
4259b4e968 fixed build failed for windows 2020-11-27 12:22:22 +09:00
6aff5dc5bc fixed build failed for macos 2020-11-27 12:11:16 +09:00
99bae7f830 support WebSocket Protocol RFC6455 2020-11-27 11:15:37 +09:00
7598d992dc Merge pull request #274 from stazio/newdesc
Exposed Additional rtc::Description::Media calls
2020-11-25 22:24:07 +01:00
6380038584 Merge pull request #277 from paullouisageneau/fix-websocket-case
Fix WebSocket Upgrade header check
2020-11-25 22:23:18 +01:00
6144bca0f7 Changed update header check to be case-insensitive 2020-11-25 20:46:02 +01:00
6ec129f8f8 Re-Formatting to use spaces 2020-11-23 20:37:29 -05:00
be394b7185 Cleaned up addVideoCodec 2020-11-23 20:33:26 -05:00
f008b5b447 Merge remote-tracking branch 'paulgit/master' into newdesc 2020-11-23 20:22:03 -05:00
5482912e18 Merge pull request #273 from stazio/fix_rtp_protect
Fixed a typo during the protection of RTP packets
2020-11-23 22:59:29 +01:00
fcc4eaf78b Fixed a typo during the protection of RTP packets 2020-11-23 15:55:36 -05:00
cca0742973 Added some more description options 2020-11-23 15:54:01 -05:00
495b389e05 Fixed signaling state callback in C API test 2020-11-21 18:05:43 +01:00
e83494df09 Bumped version to 0.10.3 2020-11-21 17:28:24 +01:00
7bf87c6989 Updated libjuice to 0.6.1 2020-11-21 17:27:51 +01:00
cb591de15f Added DiffServ QoS in features 2020-11-21 16:06:09 +01:00
eb4540e319 Merge pull request #271 from paullouisageneau/fix-dscp-rtcp
Send RTCP with the same DSCP as RTP
2020-11-21 16:04:44 +01:00
5d34439cb7 Added warning when track queue is full 2020-11-21 15:55:51 +01:00
b19e9077af Reformatting 2020-11-21 15:52:58 +01:00
4ff010b20b Added maybe_unused 2020-11-21 15:51:49 +01:00
442e50825c Changed warnings for media support 2020-11-21 15:47:51 +01:00
9f2801b7b9 Set the same DSCP for RTP and RTCP 2020-11-21 15:46:54 +01:00
3b0bf3a152 Updated libjuice 2020-11-21 15:30:14 +01:00
fe4a9ec453 Merge pull request #269 from paullouisageneau/update-libsrtp
Update libsrtp
2020-11-21 13:17:45 +01:00
dc1d14adf1 Merge pull request #270 from paullouisageneau/fix-dscp
Fix DSCP for tracks
2020-11-21 13:17:31 +01:00
14918c16e8 Fixed track DSCP 2020-11-21 12:43:34 +01:00
a023acfa33 Updated libsrtp 2020-11-21 12:43:00 +01:00
f098019c1f Merge pull request #268 from paullouisageneau/dscp
Differentiated Services support
2020-11-21 00:56:05 +01:00
a67ca9da9b Implemented support for DSCP 2020-11-21 00:45:18 +01:00
613ebf2564 Merge pull request #267 from paullouisageneau/windows-examples-dynamic
Link examples against dynamic library on Windows
2020-11-20 00:46:36 +01:00
dc6427770c Made winsock library dependency public 2020-11-20 00:29:39 +01:00
81e0a05a1a Added missing exports 2020-11-20 00:29:31 +01:00
9ea613f05f Also link tests against the dynamic library on Windows 2020-11-20 00:13:56 +01:00
eb4a764648 Disable MSVC warning 4251 in header rather than in CMakeLists 2020-11-20 00:04:53 +01:00
baf8a3adce Made examples also link against the dynamic library on Windows 2020-11-19 23:39:49 +01:00
d9aec59352 Fixed include 2020-11-19 23:29:44 +01:00
3ff5801512 Merge pull request #264 from hanseuljun/cpp-export
Exporting C++ API for Windows
2020-11-19 23:02:34 +01:00
fcc2577e11 Merge pull request #266 from supercairos/fix/msvc-w4-build-error-shadow-class-member
fix build warning on windows when built with MSVC /W4.
2020-11-19 23:00:10 +01:00
b4865f26e4 Move RTC_CPP_EXPORT from log.hpp to include.hpp, remove RTC_CPP_EXPORT from /src classes, and add RTC_CPP_EXPORT to Description. 2020-11-19 08:09:00 -08:00
fc6d5afdd9 fix build warning on windows when built with MSVC /W4.
Some arguments where conflicting with data member names.
2020-11-19 14:10:52 +01:00
7a49c0b88b Updated libjuice 2020-11-19 09:41:29 +01:00
679c0ccd2e Add more dllexports, leave RTC_CPP_EXPORT only in log.hpp, and ignore C4251 warning. 2020-11-18 14:55:17 -08:00
ee3bc9694b Bumped version to 0.10.2 2020-11-18 23:39:06 +01:00
0c0ba77de5 Merge pull request #265 from paullouisageneau/optimize-sctp-recv
Schedule SCTP recv task only if necessary
2020-11-18 23:38:49 +01:00
8729e0d2aa Scheduled SCTP recv task only if there is no pending task 2020-11-18 23:26:47 +01:00
12098e7c41 Start exporting c++ API for windows. 2020-11-17 22:20:54 -08:00
90eb610bfe Merge pull request #263 from paullouisageneau/release-rdesc-lock
Prevent holding multiple locks
2020-11-17 23:02:15 +01:00
08ddfa1276 Release remote description lock before passing candidate to transport 2020-11-17 22:41:42 +01:00
87df64a002 Merge pull request #262 from paullouisageneau/fix-duplicate-candidates
Prevent duplicate candidates
2020-11-17 20:40:15 +01:00
5af414d0df Cosmetic fixes in Description 2020-11-17 20:23:59 +01:00
2443c72350 Refactored trimming with util functions 2020-11-17 20:10:47 +01:00
f033e4ab8f Prevent whitspaces at the end of candidates as they confuse libnice 2020-11-17 19:57:51 +01:00
1a6dcdce6f Reordered Candidate getters 2020-11-17 19:28:13 +01:00
100039eba8 Enforce candidates uniqueness in description 2020-11-17 19:23:29 +01:00
e2005c789a Refactored candidate storage and split parsing and resolution 2020-11-17 19:21:48 +01:00
819566b4c1 Merge pull request #261 from paullouisageneau/fix-remote-unordered-flag
Fix remote unordered flag
2020-11-17 00:15:35 +01:00
82caab8906 Added tests for remote protocol and reliability in C API 2020-11-16 23:59:59 +01:00
802516b2db Fixed remote DataChannel unordered flag 2020-11-16 23:59:59 +01:00
0fcafad9c7 Bumped version to 0.10.1 2020-11-16 01:00:49 +01:00
aab876d346 Removed SCTP transport receiving flag 2020-11-16 01:00:49 +01:00
11ec8f7247 Made the logging of an RTP packet be verbose 2020-11-16 01:00:49 +01:00
1597c9ae6f Exposed a function to log an RTP packet 2020-11-16 01:00:49 +01:00
b093c4c3d5 Merge pull request #255 from paullouisageneau/fix-renegociation-role
Fix roles during renegociation
2020-11-16 00:02:35 +01:00
447624322c Removed useless role from ICE transport constructor 2020-11-15 23:50:41 +01:00
422713cbdc Added safety check on remote description role 2020-11-15 23:41:52 +01:00
d3d4187021 Fixed roles when receiving actpass during a renegociation 2020-11-15 23:40:16 +01:00
f2dd46e589 Merge pull request #254 from paullouisageneau/usrsctp-no-callback
Change usrsctp callbacks to upcall
2020-11-15 23:37:28 +01:00
5b5debf260 Use upcall and recv instead of message callback for usrsctp 2020-11-15 23:23:40 +01:00
86c3f914fb Merge pull request #252 from paullouisageneau/update-usrsctp
Update usrsctp and enhance threading
2020-11-15 18:31:09 +01:00
6a1fff13c1 Fixed SCTP processor limit 2020-11-15 18:23:00 +01:00
91a854aa5b Refactored processor enqueueing 2020-11-15 18:15:45 +01:00
1181fdc599 Introduced SCTP transport processor 2020-11-15 16:09:29 +01:00
fe3d92cebf Removed usrsctp-static from CMakeLists 2020-11-15 14:44:22 +01:00
c06d77bd8e Updated usrsctp 2020-11-15 14:38:50 +01:00
52 changed files with 852 additions and 659 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.7) cmake_minimum_required(VERSION 3.7)
project(libdatachannel project(libdatachannel
VERSION 0.10.0 VERSION 0.10.5
LANGUAGES CXX) LANGUAGES CXX)
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library") set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
@ -33,10 +33,11 @@ endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
set(BUILD_SHARED_LIBS OFF) # to force usrsctp to be built static
if(WIN32) if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN) add_definitions(-DWIN32_LEAN_AND_MEAN)
if (MSVC) if(MSVC)
add_definitions(-DNOMINMAX) add_definitions(-DNOMINMAX)
add_definitions(-D_CRT_SECURE_NO_WARNINGS) add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING) add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
@ -115,17 +116,15 @@ set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
add_subdirectory(deps/plog) add_subdirectory(deps/plog)
option(sctp_build_programs 0) option(sctp_build_programs 0)
option(sctp_build_shared_lib 0)
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL) add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
if (MSYS OR MINGW) if (MSYS OR MINGW)
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>) target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
target_compile_definitions(usrsctp-static PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
endif() endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation) target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
endif() endif()
add_library(Usrsctp::Usrsctp ALIAS usrsctp) add_library(Usrsctp::Usrsctp ALIAS usrsctp)
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
if (NO_WEBSOCKET) if (NO_WEBSOCKET)
add_library(datachannel SHARED add_library(datachannel SHARED
@ -156,17 +155,17 @@ target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includ
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc) target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(datachannel PUBLIC Threads::Threads plog::plog) target_link_libraries(datachannel PUBLIC Threads::Threads plog::plog)
target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic) target_link_libraries(datachannel PRIVATE Usrsctp::Usrsctp)
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc) target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog) target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic) target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp)
if(WIN32) if(WIN32)
target_link_libraries(datachannel PRIVATE ws2_32) # winsock2 target_link_libraries(datachannel PUBLIC ws2_32) # winsock2
target_link_libraries(datachannel-static PRIVATE ws2_32) # winsock2 target_link_libraries(datachannel-static PUBLIC ws2_32) # winsock2
endif() endif()
if(NO_MEDIA) if(NO_MEDIA)
@ -274,11 +273,7 @@ if(NOT NO_TESTS)
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests) set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
endif() endif()
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
if(WIN32) target_link_libraries(datachannel-tests datachannel)
target_link_libraries(datachannel-tests datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-tests datachannel)
endif()
# Benchmark # Benchmark
add_executable(datachannel-benchmark test/benchmark.cpp) add_executable(datachannel-benchmark test/benchmark.cpp)
@ -290,11 +285,7 @@ if(NOT NO_TESTS)
endif() endif()
target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1) target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
if(WIN32) target_link_libraries(datachannel-benchmark datachannel)
target_link_libraries(datachannel-benchmark datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-benchmark datachannel)
endif()
endif() endif()
# Examples # Examples

View File

@ -15,9 +15,9 @@ lib libdatachannel
: # requirements : # requirements
<cxxstd>17 <cxxstd>17
<include>./include/rtc <include>./include/rtc
<define>USE_NICE=0
<define>RTC_ENABLE_MEDIA=0 <define>RTC_ENABLE_MEDIA=0
<define>RTC_ENABLE_WEBSOCKET=0 <define>RTC_ENABLE_WEBSOCKET=0
<define>USE_NICE=0
<toolset>msvc:<define>WIN32_LEAN_AND_MEAN <toolset>msvc:<define>WIN32_LEAN_AND_MEAN
<toolset>msvc:<define>NOMINMAX <toolset>msvc:<define>NOMINMAX
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS <toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS
@ -32,6 +32,8 @@ lib libdatachannel
<link>static <link>static
: # usage requirements : # usage requirements
<include>./include <include>./include
<define>RTC_ENABLE_MEDIA=0
<define>RTC_ENABLE_WEBSOCKET=0
<library>/libdatachannel//plog <library>/libdatachannel//plog
<toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable" <toolset>gcc:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
<toolset>clang:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable" <toolset>clang:<cxxflags>"-pthread -Wno-pedantic -Wno-unused-parameter -Wno-unused-variable"
@ -94,7 +96,7 @@ rule make_libusrsctp ( targets * : sources * : properties * )
} }
actions make_libusrsctp actions make_libusrsctp
{ {
(cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC -Wno-unknown-warning-option -Wno-format-truncation" .. && make -j2 usrsctp-static) (cd $(CWD)/deps/usrsctp && mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(VARIANT) -DCMAKE_C_FLAGS="-fPIC -Wno-unknown-warning-option -Wno-format-truncation" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 .. && make -j2 usrsctp)
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<) cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/libusrsctp.a $(<)
} }
rule make_libusrsctp_msvc ( targets * : sources * : properties * ) rule make_libusrsctp_msvc ( targets * : sources * : properties * )
@ -109,7 +111,7 @@ actions make_libusrsctp_msvc
cd $(CWD)/deps/usrsctp cd $(CWD)/deps/usrsctp
mkdir $(BUILD_DIR) mkdir $(BUILD_DIR)
cd $(BUILD_DIR) cd $(BUILD_DIR)
cmake -G "Visual Studio 16 2019" .. cmake -G "Visual Studio 16 2019" -Dsctp_build_shared_lib=0 -Dsctp_build_programs=0 ..
msbuild usrsctplib.sln /property:Configuration=$(VARIANT) msbuild usrsctplib.sln /property:Configuration=$(VARIANT)
cd %OLDD% cd %OLDD%
cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<) cp $(CWD)/deps/usrsctp/$(BUILD_DIR)/usrsctplib/Release/usrsctp.lib $(<)

View File

@ -27,8 +27,9 @@ Features:
- Full IPv6 support - Full IPv6 support
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21)) - Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
- JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26)) - JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26))
- SRTP and SRTCP key derivation from DTLS ([RFC5764](https://tools.ietf.org/html/rfc5764))
- Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04)) - Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
- SRTP and SRTCP key derivation from DTLS ([RFC5764](https://tools.ietf.org/html/rfc5764))
- Differentiated Services QoS ([draft-ietf-tsvwg-rtcweb-qos-18](https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18))
- TURN relaying ([RFC5766](https://tools.ietf.org/html/rfc5766)) with [libnice](https://github.com/libnice/libnice) as ICE backend - TURN relaying ([RFC5766](https://tools.ietf.org/html/rfc5766)) with [libnice](https://github.com/libnice/libnice) as ICE backend
Note only SDP BUNDLE mode is supported for media multiplexing ([draft-ietf-mmusic-sdp-bundle-negotiation-54](https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54)). The behavior is equivalent to the JSEP bundle-only policy: the library always negociates one unique network component, where SRTP media streams are multiplexed with SRTCP control packets ([RFC5761](https://tools.ietf.org/html/rfc5761)) and SCTP/DTLS data traffic ([RFC5764](https://tools.ietf.org/html/rfc5764)). Note only SDP BUNDLE mode is supported for media multiplexing ([draft-ietf-mmusic-sdp-bundle-negotiation-54](https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54)). The behavior is equivalent to the JSEP bundle-only policy: the library always negociates one unique network component, where SRTP media streams are multiplexed with SRTCP control packets ([RFC5761](https://tools.ietf.org/html/rfc5761)) and SCTP/DTLS data traffic ([RFC5764](https://tools.ietf.org/html/rfc5764)).

2
deps/libjuice vendored

2
deps/libsrtp vendored

2
deps/usrsctp vendored

View File

@ -9,14 +9,9 @@ target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
else() else()
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h) add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
endif() endif()
set_target_properties(datachannel-client PROPERTIES set_target_properties(datachannel-client PROPERTIES
CXX_STANDARD 17 CXX_STANDARD 17
OUTPUT_NAME client) OUTPUT_NAME client)
if(WIN32)
target_link_libraries(datachannel-client datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-client datachannel)
endif()
target_link_libraries(datachannel-client datachannel nlohmann_json) target_link_libraries(datachannel-client datachannel nlohmann_json)

View File

@ -23,17 +23,18 @@
#include "rtc/rtc.hpp" #include "rtc/rtc.hpp"
#include "parse_cl.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <algorithm> #include <algorithm>
#include <future>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <random> #include <random>
#include <thread>
#include <future>
#include <stdexcept> #include <stdexcept>
#include <thread>
#include <unordered_map> #include <unordered_map>
#include "parse_cl.h"
using namespace rtc; using namespace rtc;
using namespace std; using namespace std;
@ -47,25 +48,23 @@ unordered_map<string, shared_ptr<PeerConnection>> peerConnectionMap;
unordered_map<string, shared_ptr<DataChannel>> dataChannelMap; unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
string localId; string localId;
bool echoDataChannelMessages = false;
shared_ptr<PeerConnection> createPeerConnection(const Configuration &config, shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
weak_ptr<WebSocket> wws, string id); weak_ptr<WebSocket> wws, string id);
void printReceived(bool echoed, string id, string type, size_t length);
string randomId(size_t length); string randomId(size_t length);
int main(int argc, char **argv) try { int main(int argc, char **argv) try {
auto params = std::make_unique<Cmdline>(argc, argv); auto params = std::make_unique<Cmdline>(argc, argv);
rtc::InitLogger(LogLevel::Debug); rtc::InitLogger(LogLevel::Info);
Configuration config; Configuration config;
string stunServer = ""; string stunServer = "";
if (params->noStun()) { if (params->noStun()) {
cout << "No STUN server is configured. Only local hosts and public IP addresses supported." << endl; cout << "No STUN server is configured. Only local hosts and public IP addresses supported."
<< endl;
} else { } else {
if (params->stunServer().substr(0,5).compare("stun:") != 0) { if (params->stunServer().substr(0, 5).compare("stun:") != 0) {
stunServer = "stun:"; stunServer = "stun:";
} }
stunServer += params->stunServer() + ":" + to_string(params->stunPort()); stunServer += params->stunServer() + ":" + to_string(params->stunPort());
@ -76,27 +75,23 @@ int main(int argc, char **argv) try {
localId = randomId(4); localId = randomId(4);
cout << "The local ID is: " << localId << endl; cout << "The local ID is: " << localId << endl;
echoDataChannelMessages = params->echoDataChannelMessages();
cout << "Received data channel messages will be "
<< (echoDataChannelMessages ? "echoed back to sender" : "printed to stdout") << endl;
auto ws = make_shared<WebSocket>(); auto ws = make_shared<WebSocket>();
std::promise<void> wsPromise; std::promise<void> wsPromise;
auto wsFuture = wsPromise.get_future(); auto wsFuture = wsPromise.get_future();
ws->onOpen([&wsPromise]() { ws->onOpen([&wsPromise]() {
cout << "WebSocket connected, signaling ready" << endl; cout << "WebSocket connected, signaling ready" << endl;
wsPromise.set_value(); wsPromise.set_value();
}); });
ws->onError([&wsPromise](string s) { ws->onError([&wsPromise](string s) {
cout << "WebSocket error" << endl; cout << "WebSocket error" << endl;
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s))); wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
}); });
ws->onClosed([]() { cout << "WebSocket closed" << endl; }); ws->onClosed([]() { cout << "WebSocket closed" << endl; });
ws->onMessage([&](variant<binary, string> data) { ws->onMessage([&](variant<binary, string> data) {
if (!holds_alternative<string>(data)) if (!holds_alternative<string>(data))
return; return;
@ -134,14 +129,14 @@ int main(int argc, char **argv) try {
}); });
string wsPrefix = ""; string wsPrefix = "";
if (params->webSocketServer().substr(0,5).compare("ws://") != 0) { if (params->webSocketServer().substr(0, 5).compare("ws://") != 0) {
wsPrefix = "ws://"; wsPrefix = "ws://";
} }
const string url = wsPrefix + params->webSocketServer() + ":" + const string url = wsPrefix + params->webSocketServer() + ":" +
to_string(params->webSocketPort()) + "/" + localId; to_string(params->webSocketPort()) + "/" + localId;
cout << "Url is " << url << endl; cout << "Url is " << url << endl;
ws->open(url); ws->open(url);
cout << "Waiting for signaling to be connected..." << endl; cout << "Waiting for signaling to be connected..." << endl;
wsFuture.get(); wsFuture.get();
@ -171,20 +166,12 @@ int main(int argc, char **argv) try {
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; }); dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) { dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
static bool firstMessage = true; if (holds_alternative<string>(data))
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) { cout << "Message from " << id << " received: " << get<string>(data) << endl;
cout << "Message from " << id << " received: " << get<string>(message) << endl; else
firstMessage = false; cout << "Binary message from " << id
} else if (echoDataChannelMessages) { << " received, size=" << get<binary>(data).size() << endl;
bool echoed = false;
if (auto dc = wdc.lock()) {
dc->send(message);
echoed = true;
}
printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
get<string>(message).length());
}
}); });
dataChannelMap.emplace(id, dc); dataChannelMap.emplace(id, dc);
@ -237,20 +224,12 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; }); dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) { dc->onMessage([id, wdc = make_weak_ptr(dc)](variant<binary, string> data) {
static bool firstMessage = true; if (holds_alternative<string>(data))
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) { cout << "Message from " << id << " received: " << get<string>(data) << endl;
cout << "Message from " << id << " received: " << get<string>(message) << endl; else
firstMessage = false; cout << "Binary message from " << id
} else if (echoDataChannelMessages) { << " received, size=" << get<binary>(data).size() << endl;
bool echoed = false;
if (auto dc = wdc.lock()) {
dc->send(message);
echoed = true;
}
printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
get<string>(message).length());
}
}); });
dc->send("Hello from " + localId); dc->send("Hello from " + localId);
@ -262,20 +241,6 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
return pc; return pc;
}; };
// Helper function to print received pings
void printReceived(bool echoed, string id, string type, size_t length) {
static long count = 0;
static long freq = 100;
if (!(++count%freq)) {
cout << "Received " << count << " pings in total from " << id << ", most recent of type "
<< type << " and " << (echoed ? "" : "un") << "successfully echoed most recent ping of size "
<< length << " back to " << id << endl;
if (count >= (freq * 10) && freq < 1000000) {
freq *= 10;
}
}
}
// Helper function to generate a random ID // Helper function to generate a random ID
string randomId(size_t length) { string randomId(size_t length) {
static const string characters( static const string characters(

View File

@ -43,47 +43,39 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
static struct option long_options[] = static struct option long_options[] =
{ {
{"echo", no_argument, NULL, 'e'},
{"noStun", no_argument, NULL, 'n'}, {"noStun", no_argument, NULL, 'n'},
{"stunServer", required_argument, NULL, 's'}, {"stunServer", required_argument, NULL, 's'},
{"stunPort", required_argument, NULL, 't'}, {"stunPort", required_argument, NULL, 't'},
{"webSocketServer", required_argument, NULL, 'w'}, {"webSocketServer", required_argument, NULL, 'w'},
{"webSocketPort", required_argument, NULL, 'x'}, {"webSocketPort", required_argument, NULL, 'x'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0} {NULL, 0, NULL, 0}
}; };
_program_name += argv[0]; _program_name += argv[0];
/* default values */ /* default values */
_e = false;
_n = false; _n = false;
_s = "stun.l.google.com"; _s = "stun.l.google.com";
_t = 19302; _t = 19302;
_w = "localhost"; _w = "localhost";
_x = 8000; _x = 8000;
_h = false; _h = false;
_v = false;
optind = 0; optind = 0;
while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1) while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1)
{ {
switch (c) switch (c)
{ {
case 'e': case 'n':
_e = true;
break;
case 'n':
_n = true; _n = true;
break; break;
case 's': case 's':
_s = optarg; _s = optarg;
break; break;
case 't': case 't':
_t = atoi (optarg); _t = atoi (optarg);
if (_t < 0) if (_t < 0)
{ {
@ -99,11 +91,11 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
} }
break; break;
case 'w': case 'w':
_w = optarg; _w = optarg;
break; break;
case 'x': case 'x':
_x = atoi (optarg); _x = atoi (optarg);
if (_x < 0) if (_x < 0)
{ {
@ -119,16 +111,11 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
} }
break; break;
case 'h': case 'h':
_h = true; _h = true;
this->usage (EXIT_SUCCESS); this->usage (EXIT_SUCCESS);
break; break;
case 'v':
_v = true;
this->version (EXIT_SUCCESS);
break;
default: default:
this->usage (EXIT_FAILURE); this->usage (EXIT_FAILURE);
@ -155,8 +142,6 @@ void Cmdline::usage (int status)
std::cout << "\ std::cout << "\
usage: " << _program_name << " [ -enstwxhv ] \n\ usage: " << _program_name << " [ -enstwxhv ] \n\
libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\ libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\
[ -e ] [ --echo ] (type=FLAG)\n\
Echo data channel messages back to sender rather than putting to stdout.\n\
[ -n ] [ --noStun ] (type=FLAG)\n\ [ -n ] [ --noStun ] (type=FLAG)\n\
Do NOT use a stun server (overrides -s and -t).\n\ Do NOT use a stun server (overrides -s and -t).\n\
[ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\ [ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\
@ -168,15 +153,8 @@ libdatachannel client implementing WebRTC Data Channels with WebSocket signaling
[ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\ [ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\
Web socket server port.\n\ Web socket server port.\n\
[ -h ] [ --help ] (type=FLAG)\n\ [ -h ] [ --help ] (type=FLAG)\n\
Display this help and exit.\n\ Display this help and exit.\n";
[ -v ] [ --version ] (type=FLAG)\n\
Output version information and exit.\n";
} }
exit (status); exit (status);
} }
void Cmdline::version (int status)
{
std::cout << _program_name << " v0.5\n";
exit (status);
}

View File

@ -34,14 +34,12 @@ class Cmdline
{ {
private: private:
/* parameters */ /* parameters */
bool _e;
bool _n; bool _n;
std::string _s; std::string _s;
int _t; int _t;
std::string _w; std::string _w;
int _x; int _x;
bool _h; bool _h;
bool _v;
/* other stuff to keep track of */ /* other stuff to keep track of */
std::string _program_name; std::string _program_name;
@ -55,20 +53,15 @@ public:
/* usage function */ /* usage function */
void usage (int status); void usage (int status);
/* version function */
void version (int status);
/* return next (non-option) parameter */ /* return next (non-option) parameter */
int next_param () { return _optind; } int next_param () { return _optind; }
bool echoDataChannelMessages () const { return _e; }
bool noStun () const { return _n; } bool noStun () const { return _n; }
std::string stunServer () const { return _s; } std::string stunServer () const { return _s; }
int stunPort () const { return _t; } int stunPort () const { return _t; }
std::string webSocketServer () const { return _w; } std::string webSocketServer () const { return _w; }
int webSocketPort () const { return _x; } int webSocketPort () const { return _x; }
bool h () const { return _h; } bool h () const { return _h; }
bool v () const { return _v; }
}; };
#endif #endif

View File

@ -137,7 +137,7 @@ int main(int argc, char **argv) {
size_t candidate_size = 0; size_t candidate_size = 0;
if (getline(&candidate, &candidate_size, stdin)) { if (getline(&candidate, &candidate_size, stdin)) {
rtcAddRemoteCandidate(peer->pc, candidate, "0"); rtcAddRemoteCandidate(peer->pc, candidate, NULL);
free(candidate); free(candidate);
} else { } else {

View File

@ -140,7 +140,7 @@ int main(int argc, char **argv) {
char *candidate = NULL; char *candidate = NULL;
size_t candidate_size = 0; size_t candidate_size = 0;
if (getline(&candidate, &candidate_size, stdin)) { if (getline(&candidate, &candidate_size, stdin)) {
rtcAddRemoteCandidate(peer->pc, candidate, "0"); rtcAddRemoteCandidate(peer->pc, candidate, NULL);
free(candidate); free(candidate);
} else { } else {

View File

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

View File

@ -4,11 +4,5 @@ add_executable(datachannel-media main.cpp)
set_target_properties(datachannel-media PROPERTIES set_target_properties(datachannel-media PROPERTIES
CXX_STANDARD 17 CXX_STANDARD 17
OUTPUT_NAME media) OUTPUT_NAME media)
target_link_libraries(datachannel-media datachannel nlohmann_json)
if(WIN32)
target_link_libraries(datachannel-media datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-media datachannel)
endif()
target_link_libraries(datachannel-media datachannel nlohmann_json)

View File

@ -4,12 +4,5 @@ add_executable(datachannel-sfu-media main.cpp)
set_target_properties(datachannel-sfu-media PROPERTIES set_target_properties(datachannel-sfu-media PROPERTIES
CXX_STANDARD 17 CXX_STANDARD 17
OUTPUT_NAME sfu-media) OUTPUT_NAME sfu-media)
if(WIN32)
target_link_libraries(datachannel-sfu-media datachannel-static) # DLL exports only the C API
else()
target_link_libraries(datachannel-sfu-media datachannel)
endif()
target_link_libraries(datachannel-sfu-media datachannel nlohmann_json) target_link_libraries(datachannel-sfu-media datachannel nlohmann_json)

View File

@ -59,6 +59,7 @@ function openSignaling(url) {
ws.onmessage = (e) => { ws.onmessage = (e) => {
if(typeof(e.data) != 'string') return; if(typeof(e.data) != 'string') return;
const message = JSON.parse(e.data); const message = JSON.parse(e.data);
console.log(message);
const { id, type } = message; const { id, type } = message;
let pc = peerConnectionMap[id]; let pc = peerConnectionMap[id];

View File

@ -25,7 +25,7 @@
namespace rtc { namespace rtc {
class Candidate { class RTC_CPP_EXPORT Candidate {
public: public:
enum class Family { Unresolved, Ipv4, Ipv6 }; enum class Family { Unresolved, Ipv4, Ipv6 };
enum class Type { Unknown, Host, ServerReflexive, PeerReflexive, Relayed }; enum class Type { Unknown, Host, ServerReflexive, PeerReflexive, Relayed };
@ -40,35 +40,44 @@ public:
enum class ResolveMode { Simple, Lookup }; enum class ResolveMode { Simple, Lookup };
bool resolve(ResolveMode mode = ResolveMode::Simple); bool resolve(ResolveMode mode = ResolveMode::Simple);
Type type() const;
TransportType transportType() const;
uint32_t priority() const;
string candidate() const; string candidate() const;
string mid() const; string mid() const;
operator string() const; operator string() const;
bool operator==(const Candidate &other) const;
bool operator!=(const Candidate &other) const;
bool isResolved() const; bool isResolved() const;
Family family() const; Family family() const;
Type type() const;
TransportType transportType() const;
std::optional<string> address() const; std::optional<string> address() const;
std::optional<uint16_t> port() const; std::optional<uint16_t> port() const;
std::optional<uint32_t> priority() const;
private: private:
string mCandidate; void parse(string candidate);
string mFoundation;
uint32_t mComponent, mPriority;
string mTypeString, mTransportString;
Type mType;
TransportType mTransportType;
string mNode, mService;
string mTail;
std::optional<string> mMid; std::optional<string> mMid;
// Extracted on resolution // Extracted on resolution
Family mFamily; Family mFamily;
Type mType;
TransportType mTransportType;
string mAddress; string mAddress;
uint16_t mPort; uint16_t mPort;
uint32_t mPriority;
}; };
} // namespace rtc } // namespace rtc
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate); RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type); RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type);
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType); RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType);
#endif #endif

View File

@ -28,13 +28,14 @@
namespace rtc { namespace rtc {
class Channel { class RTC_CPP_EXPORT Channel {
public: public:
Channel() = default; Channel() = default;
virtual ~Channel() = default; virtual ~Channel() = default;
virtual void close() = 0; virtual void close() = 0;
virtual bool send(message_variant data) = 0; // returns false if buffered virtual bool send(message_variant data) = 0; // returns false if buffered
virtual bool send(const byte *data, size_t size) = 0;
virtual bool isOpen() const = 0; virtual bool isOpen() const = 0;
virtual bool isClosed() const = 0; virtual bool isClosed() const = 0;

View File

@ -26,7 +26,7 @@
namespace rtc { namespace rtc {
struct IceServer { struct RTC_CPP_EXPORT IceServer {
enum class Type { Stun, Turn }; enum class Type { Stun, Turn };
enum class RelayType { TurnUdp, TurnTcp, TurnTls }; enum class RelayType { TurnUdp, TurnTcp, TurnTls };
@ -51,7 +51,7 @@ struct IceServer {
RelayType relayType; RelayType relayType;
}; };
struct ProxyServer { struct RTC_CPP_EXPORT ProxyServer {
enum class Type { None = 0, Socks5, Http, Last = Http }; enum class Type { None = 0, Socks5, Http, Last = Http };
ProxyServer(Type type_, string ip_, uint16_t port_, string username_ = "", ProxyServer(Type type_, string ip_, uint16_t port_, string username_ = "",
@ -64,7 +64,7 @@ struct ProxyServer {
string password; string password;
}; };
struct Configuration { struct RTC_CPP_EXPORT Configuration {
std::vector<IceServer> iceServers; std::vector<IceServer> iceServers;
std::optional<ProxyServer> proxyServer; std::optional<ProxyServer> proxyServer;
bool enableIceTcp = false; bool enableIceTcp = false;

View File

@ -36,7 +36,7 @@ namespace rtc {
class SctpTransport; class SctpTransport;
class PeerConnection; class PeerConnection;
class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel { class RTC_CPP_EXPORT DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel {
public: public:
DataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream, string label, string protocol, DataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream, string label, string protocol,
Reliability reliability); Reliability reliability);
@ -50,7 +50,7 @@ public:
void close(void) override; void close(void) override;
bool send(message_variant data) override; bool send(message_variant data) override;
bool send(const byte *data, size_t size); bool send(const byte *data, size_t size) override;
template <typename Buffer> bool sendBuffer(const Buffer &buf); template <typename Buffer> bool sendBuffer(const Buffer &buf);
template <typename Iterator> bool sendBuffer(Iterator first, Iterator last); template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
@ -87,7 +87,7 @@ private:
friend class PeerConnection; friend class PeerConnection;
}; };
class NegociatedDataChannel final : public DataChannel { class RTC_CPP_EXPORT NegociatedDataChannel final : public DataChannel {
public: public:
NegociatedDataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream, string label, NegociatedDataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream, string label,
string protocol, Reliability reliability); string protocol, Reliability reliability);

View File

@ -32,7 +32,16 @@
namespace rtc { namespace rtc {
class Description { const string DEFAULT_AUDIO_PROFILE =
"minptime=10;maxaveragebitrate=96000;stereo=1;sprop-stereo=1;useinbandfec=1";
// Use Constrained Baseline profile Level 4.2 (necessary for Firefox)
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#Supported_video_codecs
// TODO: Should be 42E0 but 42C0 appears to be more compatible. Investigate this.
const string DEFAULT_VIDEO_PROFILE =
"profile-level-id=42e01f;packetization-mode=1;level-asymmetry-allowed=1";
class RTC_CPP_EXPORT Description {
public: public:
enum class Type { Unspec, Offer, Answer, Pranswer, Rollback }; enum class Type { Unspec, Offer, Answer, Pranswer, Rollback };
enum class Role { ActPass, Passive, Active }; enum class Role { ActPass, Passive, Active };
@ -53,6 +62,7 @@ public:
void hintType(Type type); void hintType(Type type);
void setFingerprint(string fingerprint); void setFingerprint(string fingerprint);
bool hasCandidate(const Candidate &candidate) const;
void addCandidate(Candidate candidate); void addCandidate(Candidate candidate);
void addCandidates(std::vector<Candidate> candidates); void addCandidates(std::vector<Candidate> candidates);
void endCandidates(); void endCandidates();
@ -62,7 +72,7 @@ public:
string generateSdp(string_view eol) const; string generateSdp(string_view eol) const;
string generateApplicationSdp(string_view eol) const; string generateApplicationSdp(string_view eol) const;
class Entry { class RTC_CPP_EXPORT Entry {
public: public:
virtual ~Entry() = default; virtual ~Entry() = default;
@ -94,7 +104,7 @@ public:
Direction mDirection; Direction mDirection;
}; };
struct Application : public Entry { struct RTC_CPP_EXPORT Application : public Entry {
public: public:
Application(string mid = "data"); Application(string mid = "data");
virtual ~Application() = default; virtual ~Application() = default;
@ -119,7 +129,7 @@ public:
}; };
// Media (non-data) // Media (non-data)
class Media : public Entry { class RTC_CPP_EXPORT Media : public Entry {
public: public:
Media(const string &sdp); Media(const string &sdp);
Media(const string &mline, string mid, Direction dir = Direction::SendOnly); Media(const string &mline, string mid, Direction dir = Direction::SendOnly);
@ -130,9 +140,10 @@ public:
void removeFormat(const string &fmt); void removeFormat(const string &fmt);
void addSSRC(uint32_t ssrc, std::string name); void addSSRC(uint32_t ssrc, std::optional<string> name,
void addSSRC(uint32_t ssrc); std::optional<string> msid = nullopt);
void replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, string name); void replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, std::optional<string> name,
std::optional<string> msid = nullopt);
bool hasSSRC(uint32_t ssrc); bool hasSSRC(uint32_t ssrc);
std::vector<uint32_t> getSSRCs(); std::vector<uint32_t> getSSRCs();
@ -141,6 +152,9 @@ public:
bool hasPayloadType(int payloadType) const; bool hasPayloadType(int payloadType) const;
void addRTXCodec(unsigned int payloadType, unsigned int originalPayloadType,
unsigned int clockRate);
virtual void parseSdpLine(string_view line) override; virtual void parseSdpLine(string_view line) override;
struct RTPMap { struct RTPMap {
@ -149,7 +163,7 @@ public:
void removeFB(const string &string); void removeFB(const string &string);
void addFB(const string &string); void addFB(const string &string);
void addAttribute(std::string attr) { fmtps.emplace_back(attr); } void addAttribute(string attr) { fmtps.emplace_back(std::move(attr)); }
int pt; int pt;
string format; string format;
@ -180,21 +194,27 @@ public:
public: public:
void addRTPMap(const RTPMap &map); void addRTPMap(const RTPMap &map);
void removeSSRC(uint32_t oldSSRC);
}; };
class Audio : public Media { class RTC_CPP_EXPORT Audio : public Media {
public: public:
Audio(string mid = "audio", Direction dir = Direction::SendOnly); Audio(string mid = "audio", Direction dir = Direction::SendOnly);
void addAudioCodec(int payloadType, const string &codec); void addAudioCodec(int payloadType, string codec,
std::optional<string> profile = DEFAULT_AUDIO_PROFILE);
void addOpusCodec(int payloadType); void addOpusCodec(int payloadType);
}; };
class Video : public Media { class RTC_CPP_EXPORT Video : public Media {
public: public:
Video(string mid = "video", Direction dir = Direction::SendOnly); Video(string mid = "video", Direction dir = Direction::SendOnly);
void addVideoCodec(int payloadType, const string &codec); void addVideoCodec(int payloadType, string codec,
std::optional<string> profile = DEFAULT_VIDEO_PROFILE);
void addH264Codec(int payloadType); void addH264Codec(int payloadType);
void addVP8Codec(int payloadType); void addVP8Codec(int payloadType);
void addVP9Codec(int payloadType); void addVP9Codec(int payloadType);
@ -244,8 +264,8 @@ private:
} // namespace rtc } // namespace rtc
std::ostream &operator<<(std::ostream &out, const rtc::Description &description); RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const rtc::Description &description);
std::ostream &operator<<(std::ostream &out, rtc::Description::Type type); RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::Description::Type type);
std::ostream &operator<<(std::ostream &out, rtc::Description::Role role); RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::Description::Role role);
#endif #endif

View File

@ -28,9 +28,15 @@
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
#define RTC_CPP_EXPORT __declspec(dllexport)
#ifndef _WIN32_WINNT #ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0602 // Windows 8 #define _WIN32_WINNT 0x0602 // Windows 8
#endif #endif
#ifdef _MSC_VER
#pragma warning(disable:4251) // disable "X needs to have dll-interface..."
#endif
#else
#define RTC_CPP_EXPORT
#endif #endif
#include "log.hpp" #include "log.hpp"
@ -102,12 +108,12 @@ private:
std::function<void()> function; std::function<void()> function;
}; };
template <typename... P> class synchronized_callback { template <typename... Args> class synchronized_callback {
public: public:
synchronized_callback() = default; synchronized_callback() = default;
synchronized_callback(synchronized_callback &&cb) { *this = std::move(cb); } synchronized_callback(synchronized_callback &&cb) { *this = std::move(cb); }
synchronized_callback(const synchronized_callback &cb) { *this = cb; } synchronized_callback(const synchronized_callback &cb) { *this = cb; }
synchronized_callback(std::function<void(P...)> func) { *this = std::move(func); } synchronized_callback(std::function<void(Args...)> func) { *this = std::move(func); }
~synchronized_callback() { *this = nullptr; } ~synchronized_callback() { *this = nullptr; }
synchronized_callback &operator=(synchronized_callback &&cb) { synchronized_callback &operator=(synchronized_callback &&cb) {
@ -123,16 +129,16 @@ public:
return *this; return *this;
} }
synchronized_callback &operator=(std::function<void(P...)> func) { synchronized_callback &operator=(std::function<void(Args...)> func) {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
callback = std::move(func); callback = std::move(func);
return *this; return *this;
} }
void operator()(P... args) const { void operator()(Args... args) const {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
if (callback) if (callback)
callback(args...); callback(std::move(args)...);
} }
operator bool() const { operator bool() const {
@ -140,8 +146,12 @@ public:
return callback ? true : false; return callback ? true : false;
} }
std::function<void(Args...)> wrap() const {
return [this](Args... args) { (*this)(std::move(args)...); };
}
private: private:
std::function<void(P...)> callback; std::function<void(Args...)> callback;
mutable std::recursive_mutex mutex; mutable std::recursive_mutex mutex;
}; };
} // namespace rtc } // namespace rtc

View File

@ -27,7 +27,7 @@ namespace rtc {
using init_token = std::shared_ptr<void>; using init_token = std::shared_ptr<void>;
class Init { class RTC_CPP_EXPORT Init {
public: public:
static init_token Token(); static init_token Token();
static void Preload(); static void Preload();

View File

@ -35,6 +35,8 @@
#pragma warning(pop) #pragma warning(pop)
#endif #endif
#include "include.hpp"
namespace rtc { namespace rtc {
enum class LogLevel { // Don't change, it must match plog severity enum class LogLevel { // Don't change, it must match plog severity
@ -47,8 +49,8 @@ enum class LogLevel { // Don't change, it must match plog severity
Verbose = 6 Verbose = 6
}; };
void InitLogger(LogLevel level); RTC_CPP_EXPORT void InitLogger(LogLevel level);
void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr); RTC_CPP_EXPORT void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr);
} // namespace rtc } // namespace rtc
#endif #endif

View File

@ -29,7 +29,7 @@
namespace rtc { namespace rtc {
struct Message : binary { struct RTC_CPP_EXPORT Message : binary {
enum Type { Binary, String, Control, Reset }; enum Type { Binary, String, Control, Reset };
Message(const Message &message) = default; Message(const Message &message) = default;
@ -42,7 +42,8 @@ struct Message : binary {
Message(binary &&data, Type type_ = Binary) : binary(std::move(data)), type(type_) {} Message(binary &&data, Type type_ = Binary) : binary(std::move(data)), type(type_) {}
Type type; Type type;
unsigned int stream = 0; unsigned int stream = 0; // Stream id (SCTP stream or SSRC)
unsigned int dscp = 0; // Differentiated Services Code Point
std::shared_ptr<Reliability> reliability; std::shared_ptr<Reliability> reliability;
}; };

View File

@ -50,14 +50,14 @@ class SctpTransport;
using certificate_ptr = std::shared_ptr<Certificate>; using certificate_ptr = std::shared_ptr<Certificate>;
using future_certificate_ptr = std::shared_future<certificate_ptr>; using future_certificate_ptr = std::shared_future<certificate_ptr>;
struct DataChannelInit { struct RTC_CPP_EXPORT DataChannelInit {
Reliability reliability = {}; Reliability reliability = {};
bool negotiated = false; bool negotiated = false;
std::optional<uint16_t> id = nullopt; std::optional<uint16_t> id = nullopt;
string protocol = ""; string protocol = "";
}; };
class PeerConnection final : public std::enable_shared_from_this<PeerConnection> { class RTC_CPP_EXPORT PeerConnection final : public std::enable_shared_from_this<PeerConnection> {
public: public:
enum class State : int { enum class State : int {
New = RTC_NEW, New = RTC_NEW,
@ -129,7 +129,7 @@ public:
void onTrack(std::function<void(std::shared_ptr<Track> track)> callback); void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
private: private:
std::shared_ptr<IceTransport> initIceTransport(Description::Role role); std::shared_ptr<IceTransport> initIceTransport();
std::shared_ptr<DtlsTransport> initDtlsTransport(); std::shared_ptr<DtlsTransport> initDtlsTransport();
std::shared_ptr<SctpTransport> initSctpTransport(); std::shared_ptr<SctpTransport> initSctpTransport();
void closeTransports(); void closeTransports();
@ -157,6 +157,8 @@ private:
void processLocalCandidate(Candidate candidate); void processLocalCandidate(Candidate candidate);
void processRemoteDescription(Description description); void processRemoteDescription(Description description);
void processRemoteCandidate(Candidate candidate); void processRemoteCandidate(Candidate candidate);
string localBundleMid() const;
void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel); void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
void triggerTrack(std::shared_ptr<Track> track); void triggerTrack(std::shared_ptr<Track> track);
bool changeState(State state); bool changeState(State state);
@ -203,8 +205,8 @@ private:
} // namespace rtc } // namespace rtc
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state); RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state);
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state); RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state);
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::SignalingState state); RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::SignalingState state);
#endif #endif

View File

@ -29,7 +29,7 @@
namespace rtc { namespace rtc {
class RtcpHandler { class RTC_CPP_EXPORT RtcpHandler {
protected: protected:
/** /**
* Use this callback when trying to send custom data (such as RTCP) to the client. * Use this callback when trying to send custom data (such as RTCP) to the client.
@ -64,7 +64,7 @@ public:
class Track; class Track;
// An RtcpSession can be plugged into a Track to handle the whole RTCP session // An RtcpSession can be plugged into a Track to handle the whole RTCP session
class RtcpReceivingSession : public RtcpHandler { class RTC_CPP_EXPORT RtcpReceivingSession : public RtcpHandler {
public: public:
rtc::message_ptr incoming(rtc::message_ptr ptr) override; rtc::message_ptr incoming(rtc::message_ptr ptr) override;
rtc::message_ptr outgoing(rtc::message_ptr ptr) override; rtc::message_ptr outgoing(rtc::message_ptr ptr) override;

View File

@ -20,7 +20,7 @@
#ifndef RTC_RTP_HPP #ifndef RTC_RTP_HPP
#define RTC_RTP_HPP #define RTC_RTP_HPP
#include <rtc/log.hpp> #include "log.hpp"
#include <cmath> #include <cmath>
@ -57,6 +57,7 @@ public:
inline uint8_t version() const { return _first >> 6; } inline uint8_t version() const { return _first >> 6; }
inline bool padding() const { return (_first >> 5) & 0x01; } inline bool padding() const { return (_first >> 5) & 0x01; }
inline bool extension() const { return (_first >> 4) & 0x01; }
inline uint8_t csrcCount() const { return _first & 0x0F; } inline uint8_t csrcCount() const { return _first & 0x0F; }
inline uint8_t marker() const { return _payloadType & 0b10000000; } inline uint8_t marker() const { return _payloadType & 0b10000000; }
inline uint8_t payloadType() const { return _payloadType & 0b01111111; } inline uint8_t payloadType() const { return _payloadType & 0b01111111; }
@ -65,18 +66,32 @@ public:
inline uint32_t ssrc() const { return ntohl(_ssrc); } inline uint32_t ssrc() const { return ntohl(_ssrc); }
inline size_t getSize() const { inline size_t getSize() const {
return ((char *)&csrc) - ((char *)this) + sizeof(SSRC) * csrcCount(); return reinterpret_cast<const char *>(&csrc) - reinterpret_cast<const char *>(this) +
sizeof(SSRC) * csrcCount();
} }
char *getBody() const { return ((char *)&csrc) + sizeof(SSRC) * csrcCount(); } [[nodiscard]] char *getBody() {
return reinterpret_cast<char *>(&csrc) + sizeof(SSRC) * csrcCount();
}
[[nodiscard]] const char *getBody() const {
return reinterpret_cast<const char *>(&csrc) + sizeof(SSRC) * csrcCount();
}
inline void setSeqNumber(uint16_t newSeqNo) { _seqNumber = htons(newSeqNo); } inline void setSeqNumber(uint16_t newSeqNo) { _seqNumber = htons(newSeqNo); }
inline void setPayloadType(uint8_t newPayloadType) { inline void setPayloadType(uint8_t newPayloadType) {
_payloadType = (_payloadType & 0b10000000u) | (0b01111111u & newPayloadType); _payloadType = (_payloadType & 0b10000000u) | (0b01111111u & newPayloadType);
} }
inline void setSsrc(uint32_t ssrc) { _ssrc = htonl(ssrc); } inline void setSsrc(uint32_t in_ssrc) { _ssrc = htonl(in_ssrc); }
void setTimestamp(uint32_t i) { _timestamp = htonl(i); } void setTimestamp(uint32_t i) { _timestamp = htonl(i); }
void log() {
PLOG_VERBOSE << "RTP V: " << (int)version() << " P: " << (padding() ? "P" : " ")
<< " X: " << (extension() ? "X" : " ") << " CC: " << (int)csrcCount()
<< " M: " << (marker() ? "M" : " ") << " PT: " << (int)payloadType()
<< " SEQNO: " << seqNumber() << " TS: " << timestamp();
}
}; };
struct RTCP_ReportBlock { struct RTCP_ReportBlock {
@ -91,13 +106,13 @@ private:
uint32_t _delaySinceLastReport; uint32_t _delaySinceLastReport;
public: public:
inline void preparePacket(SSRC ssrc, [[maybe_unused]] unsigned int packetsLost, inline void preparePacket(SSRC in_ssrc, [[maybe_unused]] unsigned int packetsLost,
[[maybe_unused]] unsigned int totalPackets, uint16_t highestSeqNo, [[maybe_unused]] unsigned int totalPackets, uint16_t highestSeqNo,
uint16_t seqNoCycles, uint32_t jitter, uint64_t lastSR_NTP, uint16_t seqNoCycles, uint32_t jitter, uint64_t lastSR_NTP,
uint64_t lastSR_DELAY) { uint64_t lastSR_DELAY) {
setSeqNo(highestSeqNo, seqNoCycles); setSeqNo(highestSeqNo, seqNoCycles);
setJitter(jitter); setJitter(jitter);
setSSRC(ssrc); setSSRC(in_ssrc);
// Middle 32 bits of NTP Timestamp // Middle 32 bits of NTP Timestamp
// this->lastReport = lastSR_NTP >> 16u; // this->lastReport = lastSR_NTP >> 16u;
@ -105,22 +120,23 @@ public:
setDelaySinceSR(uint32_t(lastSR_DELAY)); setDelaySinceSR(uint32_t(lastSR_DELAY));
// The delay, expressed in units of 1/65536 seconds // The delay, expressed in units of 1/65536 seconds
// this->delaySinceLastReport = lastSR_DELAY; // this->delaySinceLastReport = lastSR_DELAY;
} }
inline void setSSRC(SSRC ssrc) { this->ssrc = htonl(ssrc); } inline void setSSRC(SSRC in_ssrc) { this->ssrc = htonl(in_ssrc); }
inline SSRC getSSRC() const { return ntohl(ssrc); } [[nodiscard]] inline SSRC getSSRC() const { return ntohl(ssrc); }
inline void setPacketsLost([[maybe_unused]] unsigned int packetsLost, inline void setPacketsLost([[maybe_unused]] unsigned int packetsLost,
[[maybe_unused]] unsigned int totalPackets) { [[maybe_unused]] unsigned int totalPackets) {
// TODO Implement loss percentages. // TODO Implement loss percentages.
_fractionLostAndPacketsLost = 0; _fractionLostAndPacketsLost = 0;
} }
inline unsigned int getLossPercentage() const {
[[nodiscard]] inline unsigned int getLossPercentage() const {
// TODO Implement loss percentages. // TODO Implement loss percentages.
return 0; return 0;
} }
inline unsigned int getPacketLostCount() const { [[nodiscard]] inline unsigned int getPacketLostCount() const {
// TODO Implement total packets lost. // TODO Implement total packets lost.
return 0; return 0;
} }
@ -137,13 +153,13 @@ public:
inline void setJitter(uint32_t jitter) { _jitter = htonl(jitter); } inline void setJitter(uint32_t jitter) { _jitter = htonl(jitter); }
inline void setNTPOfSR(uint64_t ntp) { _lastReport = htonll(ntp >> 16u); } inline void setNTPOfSR(uint64_t ntp) { _lastReport = htonll(ntp >> 16u); }
inline uint32_t getNTPOfSR() const { return ntohl(_lastReport) << 16u; } [[nodiscard]] inline uint32_t getNTPOfSR() const { return ntohl(_lastReport) << 16u; }
inline void setDelaySinceSR(uint32_t sr) { inline void setDelaySinceSR(uint32_t sr) {
// The delay, expressed in units of 1/65536 seconds // The delay, expressed in units of 1/65536 seconds
_delaySinceLastReport = htonl(sr); _delaySinceLastReport = htonl(sr);
} }
inline uint32_t getDelaySinceSR() const { return ntohl(_delaySinceLastReport); } [[nodiscard]] inline uint32_t getDelaySinceSR() const { return ntohl(_delaySinceLastReport); }
inline void log() const { inline void log() const {
PLOG_VERBOSE << "RTCP report block: " PLOG_VERBOSE << "RTCP report block: "
@ -187,9 +203,9 @@ public:
inline void log() const { inline void log() const {
PLOG_VERBOSE << "RTCP header: " PLOG_VERBOSE << "RTCP header: "
<< "version=" << unsigned(version()) << ", padding=" << padding() << "version=" << unsigned(version()) << ", padding=" << padding()
<< ", reportCount=" << unsigned(reportCount()) << ", reportCount=" << unsigned(reportCount())
<< ", payloadType=" << unsigned(payloadType()) << ", length=" << length(); << ", payloadType=" << unsigned(payloadType()) << ", length=" << length();
} }
}; };
@ -234,8 +250,10 @@ public:
this->_senderSSRC = htonl(senderSSRC); this->_senderSSRC = htonl(senderSSRC);
} }
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; } [[nodiscard]] inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; } [[nodiscard]] inline const RTCP_ReportBlock *getReportBlock(int num) const {
return &_reportBlocks + num;
}
[[nodiscard]] inline size_t getSize() const { [[nodiscard]] inline size_t getSize() const {
// "length" in packet is one less than the number of 32 bit words in the packet. // "length" in packet is one less than the number of 32 bit words in the packet.
@ -272,8 +290,10 @@ private:
RTCP_ReportBlock _reportBlocks; RTCP_ReportBlock _reportBlocks;
public: public:
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; } [[nodiscard]] inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; } [[nodiscard]] inline const RTCP_ReportBlock *getReportBlock(int num) const {
return &_reportBlocks + num;
}
inline SSRC senderSSRC() const { return ntohl(_senderSSRC); } inline SSRC senderSSRC() const { return ntohl(_senderSSRC); }
inline void setSenderSSRC(SSRC ssrc) { this->_senderSSRC = htonl(ssrc); } inline void setSenderSSRC(SSRC ssrc) { this->_senderSSRC = htonl(ssrc); }
@ -325,7 +345,7 @@ struct RTCP_REMB {
return sizeof(uint32_t) * (1 + header.header.length()); return sizeof(uint32_t) * (1 + header.header.length());
} }
void preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int bitrate) { void preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int in_bitrate) {
// Report Count becomes the format here. // Report Count becomes the format here.
header.header.prepareHeader(206, 15, 0); header.header.prepareHeader(206, 15, 0);
@ -340,21 +360,21 @@ struct RTCP_REMB {
id[2] = 'M'; id[2] = 'M';
id[3] = 'B'; id[3] = 'B';
setBitrate(numSSRC, bitrate); setBitrate(numSSRC, in_bitrate);
} }
void setBitrate(unsigned int numSSRC, unsigned int bitrate) { void setBitrate(unsigned int numSSRC, unsigned int in_bitrate) {
unsigned int exp = 0; unsigned int exp = 0;
while (bitrate > pow(2, 18) - 1) { while (in_bitrate > pow(2, 18) - 1) {
exp++; exp++;
bitrate /= 2; in_bitrate /= 2;
} }
// "length" in packet is one less than the number of 32 bit words in the packet. // "length" in packet is one less than the number of 32 bit words in the packet.
header.header.setLength( header.header.setLength(
uint16_t((offsetof(RTCP_REMB, ssrc) / sizeof(uint32_t)) - 1 + numSSRC)); uint16_t((offsetof(RTCP_REMB, ssrc) / sizeof(uint32_t)) - 1 + numSSRC));
this->bitrate = htonl((numSSRC << (32u - 8u)) | (exp << (32u - 8u - 6u)) | bitrate); this->bitrate = htonl((numSSRC << (32u - 8u)) | (exp << (32u - 8u - 6u)) | in_bitrate);
} }
void setSsrc(int iterator, SSRC newSssrc) { ssrc[iterator] = htonl(newSssrc); } void setSsrc(int iterator, SSRC newSssrc) { ssrc[iterator] = htonl(newSssrc); }
@ -416,7 +436,7 @@ public:
public: public:
void preparePacket(SSRC ssrc, unsigned int discreteSeqNoCount) { void preparePacket(SSRC ssrc, unsigned int discreteSeqNoCount) {
header.header.prepareHeader(205, 1, 2 + discreteSeqNoCount); header.header.prepareHeader(205, 1, 2 + uint16_t(discreteSeqNoCount));
header.setMediaSourceSSRC(ssrc); header.setMediaSourceSSRC(ssrc);
header.setPacketSenderSSRC(ssrc); header.setPacketSenderSSRC(ssrc);
} }
@ -469,11 +489,15 @@ public:
return ntohs(*(uint16_t *)(header.getBody())); return ntohs(*(uint16_t *)(header.getBody()));
} }
char *getBody() { return header.getBody() + sizeof(uint16_t); } [[nodiscard]] char *getBody() { return header.getBody() + sizeof(uint16_t); }
size_t getBodySize(size_t totalSize) { return totalSize - ((char *)getBody() - (char *)this); } [[nodiscard]] const char *getBody() const { return header.getBody() + sizeof(uint16_t); }
RTP &getHeader() { return header; } [[nodiscard]] size_t getBodySize(size_t totalSize) {
return totalSize - (getBody() - reinterpret_cast<char *>(this));
}
[[nodiscard]] RTP &getHeader() { return header; }
size_t normalizePacket(size_t totalSize, SSRC originalSSRC, uint8_t originalPayloadType) { size_t normalizePacket(size_t totalSize, SSRC originalSSRC, uint8_t originalPayloadType) {
header.setSeqNumber(getOriginalSeqNo()); header.setSeqNumber(getOriginalSeqNo());

View File

@ -35,7 +35,7 @@ namespace rtc {
class DtlsSrtpTransport; class DtlsSrtpTransport;
#endif #endif
class Track final : public std::enable_shared_from_this<Track>, public Channel { class RTC_CPP_EXPORT Track final : public std::enable_shared_from_this<Track>, public Channel {
public: public:
Track(Description::Media description); Track(Description::Media description);
~Track() = default; ~Track() = default;
@ -47,7 +47,7 @@ public:
void close(void) override; void close(void) override;
bool send(message_variant data) override; bool send(message_variant data) override;
bool send(const byte *data, size_t size); bool send(const byte *data, size_t size) override;
bool isOpen(void) const override; bool isOpen(void) const override;
bool isClosed(void) const override; bool isClosed(void) const override;
@ -70,8 +70,8 @@ private:
std::weak_ptr<DtlsSrtpTransport> mDtlsSrtpTransport; std::weak_ptr<DtlsSrtpTransport> mDtlsSrtpTransport;
#endif #endif
bool outgoing(message_ptr message);
void incoming(message_ptr message); void incoming(message_ptr message);
bool outgoing(message_ptr message);
Description::Media mMediaDescription; Description::Media mMediaDescription;
std::atomic<bool> mIsClosed = false; std::atomic<bool> mIsClosed = false;

View File

@ -38,7 +38,7 @@ class TcpTransport;
class TlsTransport; class TlsTransport;
class WsTransport; class WsTransport;
class WebSocket final : public Channel, public std::enable_shared_from_this<WebSocket> { class RTC_CPP_EXPORT WebSocket final : public Channel, public std::enable_shared_from_this<WebSocket> {
public: public:
enum class State : int { enum class State : int {
Connecting = 0, Connecting = 0,
@ -49,6 +49,7 @@ public:
struct Configuration { struct Configuration {
bool disableTlsVerification = false; // if true, don't verify the TLS certificate bool disableTlsVerification = false; // if true, don't verify the TLS certificate
std::vector<string> protocols;
}; };
WebSocket(std::optional<Configuration> config = nullopt); WebSocket(std::optional<Configuration> config = nullopt);
@ -59,6 +60,7 @@ public:
void open(const string &url); void open(const string &url);
void close() override; void close() override;
bool send(const message_variant data) override; bool send(const message_variant data) override;
bool send(const byte *data, size_t size) override;
bool isOpen() const override; bool isOpen() const override;
bool isClosed() const override; bool isClosed() const override;

View File

@ -20,6 +20,7 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cctype>
#include <sstream> #include <sstream>
#include <unordered_map> #include <unordered_map>
@ -39,39 +40,44 @@ using std::string;
namespace { namespace {
inline bool hasprefix(const string &str, const string &prefix) { inline bool match_prefix(const string &str, const string &prefix) {
return str.size() >= prefix.size() && return str.size() >= prefix.size() &&
std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end(); std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
} }
inline void trim_begin(string &str) {
str.erase(str.begin(),
std::find_if(str.begin(), str.end(), [](char c) { return !std::isspace(c); }));
}
inline void trim_end(string &str) {
str.erase(
std::find_if(str.rbegin(), str.rend(), [](char c) { return !std::isspace(c); }).base(),
str.end());
}
} // namespace } // namespace
namespace rtc { namespace rtc {
Candidate::Candidate() Candidate::Candidate()
: mFamily(Family::Unresolved), mType(Type::Unknown), mTransportType(TransportType::Unknown), : mFoundation("none"), mComponent(0), mPriority(0), mTypeString("unknown"),
mPort(0), mPriority(0) {} mTransportString("unknown"), mType(Type::Unknown), mTransportType(TransportType::Unknown),
mNode("0.0.0.0"), mService("9"), mFamily(Family::Unresolved), mPort(0) {}
Candidate::Candidate(string candidate) : Candidate() { Candidate::Candidate(string candidate) : Candidate() {
const std::array prefixes{"a=", "candidate:"}; if (!candidate.empty())
for (const string &prefix : prefixes) parse(std::move(candidate));
if (hasprefix(candidate, prefix))
candidate.erase(0, prefix.size());
mCandidate = std::move(candidate);
} }
Candidate::Candidate(string candidate, string mid) : Candidate(std::move(candidate)) { Candidate::Candidate(string candidate, string mid) : Candidate() {
if (!candidate.empty())
parse(std::move(candidate));
if (!mid.empty()) if (!mid.empty())
mMid.emplace(std::move(mid)); mMid.emplace(std::move(mid));
} }
void Candidate::hintMid(string mid) { void Candidate::parse(string candidate) {
if (!mMid)
mMid.emplace(std::move(mid));
}
bool Candidate::resolve(ResolveMode mode) {
using TypeMap_t = std::unordered_map<string, Type>; using TypeMap_t = std::unordered_map<string, Type>;
using TcpTypeMap_t = std::unordered_map<string, TransportType>; using TcpTypeMap_t = std::unordered_map<string, TransportType>;
@ -84,99 +90,122 @@ bool Candidate::resolve(ResolveMode mode) {
{"passive", TransportType::TcpPassive}, {"passive", TransportType::TcpPassive},
{"so", TransportType::TcpSo}}; {"so", TransportType::TcpSo}};
if (mFamily != Family::Unresolved) const std::array prefixes{"a=", "candidate:"};
return true; for (const string &prefix : prefixes)
if (match_prefix(candidate, prefix))
candidate.erase(0, prefix.size());
if (mCandidate.empty()) PLOG_VERBOSE << "Parsing candidate: " << candidate;
throw std::logic_error("Candidate is empty");
PLOG_VERBOSE << "Resolving candidate (mode="
<< (mode == ResolveMode::Simple ? "simple" : "lookup") << "): " << mCandidate;
// See RFC 8445 for format // See RFC 8445 for format
std::istringstream iss(mCandidate); std::istringstream iss(candidate);
int component{0}, priority{0}; string transport, typ_, type;
string foundation, transport, node, service, typ_, type; if (!(iss >> mFoundation >> mComponent >> mTransportString >> mPriority &&
if (iss >> foundation >> component >> transport >> priority && iss >> mNode >> mService >> typ_ >> mTypeString && typ_ == "typ"))
iss >> node >> service >> typ_ >> type && typ_ == "typ") { throw std::invalid_argument("Invalid candidate format");
string left; std::getline(iss, mTail);
std::getline(iss, left); trim_begin(mTail);
trim_end(mTail);
if (auto it = TypeMap.find(type); it != TypeMap.end()) if (auto it = TypeMap.find(type); it != TypeMap.end())
mType = it->second; mType = it->second;
else else
mType = Type::Unknown; mType = Type::Unknown;
if (transport == "UDP" || transport == "udp") { if (transport == "UDP" || transport == "udp") {
mTransportType = TransportType::Udp; mTransportType = TransportType::Udp;
} else if (transport == "TCP" || transport == "tcp") { } else if (transport == "TCP" || transport == "tcp") {
std::istringstream iss(left); // Peek tail to find TCP type
string tcptype_, tcptype; std::istringstream tiss(mTail);
if (iss >> tcptype_ >> tcptype && tcptype_ == "tcptype") { string tcptype_, tcptype;
if (auto it = TcpTypeMap.find(tcptype); it != TcpTypeMap.end()) if (tiss >> tcptype_ >> tcptype && tcptype_ == "tcptype") {
mTransportType = it->second; if (auto it = TcpTypeMap.find(tcptype); it != TcpTypeMap.end())
else mTransportType = it->second;
mTransportType = TransportType::TcpUnknown; else
} else {
mTransportType = TransportType::TcpUnknown; mTransportType = TransportType::TcpUnknown;
}
} else { } else {
mTransportType = TransportType::Unknown; mTransportType = TransportType::TcpUnknown;
} }
} else {
mTransportType = TransportType::Unknown;
}
}
// Try to resolve the node void Candidate::hintMid(string mid) {
struct addrinfo hints = {}; if (!mMid)
hints.ai_family = AF_UNSPEC; mMid.emplace(std::move(mid));
hints.ai_flags = AI_ADDRCONFIG; }
if (mTransportType == TransportType::Udp) {
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
} else if (mTransportType != TransportType::Unknown) {
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
}
if (mode == ResolveMode::Simple) bool Candidate::resolve(ResolveMode mode) {
hints.ai_flags |= AI_NUMERICHOST; PLOG_VERBOSE << "Resolving candidate (mode="
<< (mode == ResolveMode::Simple ? "simple" : "lookup") << "): " << mNode << ' '
<< mService;
struct addrinfo *result = nullptr; // Try to resolve the node and service
if (getaddrinfo(node.c_str(), service.c_str(), &hints, &result) == 0) { struct addrinfo hints = {};
for (auto p = result; p; p = p->ai_next) { hints.ai_family = AF_UNSPEC;
if (p->ai_family == AF_INET || p->ai_family == AF_INET6) { hints.ai_flags = AI_ADDRCONFIG;
// Rewrite the candidate if (mTransportType == TransportType::Udp) {
char nodebuffer[MAX_NUMERICNODE_LEN]; hints.ai_socktype = SOCK_DGRAM;
char servbuffer[MAX_NUMERICSERV_LEN]; hints.ai_protocol = IPPROTO_UDP;
if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer, } else if (mTransportType != TransportType::Unknown) {
MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN, hints.ai_socktype = SOCK_STREAM;
NI_NUMERICHOST | NI_NUMERICSERV) == 0) { hints.ai_protocol = IPPROTO_TCP;
}
mAddress = nodebuffer; if (mode == ResolveMode::Simple)
mPort = uint16_t(std::stoul(servbuffer)); hints.ai_flags |= AI_NUMERICHOST;
mFamily = p->ai_family == AF_INET6 ? Family::Ipv6 : Family::Ipv4;
const char sp{' '}; struct addrinfo *result = nullptr;
std::ostringstream oss; if (getaddrinfo(mNode.c_str(), mService.c_str(), &hints, &result) == 0) {
oss << foundation << sp << component << sp << transport << sp << priority; for (auto p = result; p; p = p->ai_next) {
oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type; if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
oss << left; char nodebuffer[MAX_NUMERICNODE_LEN];
mCandidate = oss.str(); char servbuffer[MAX_NUMERICSERV_LEN];
if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
PLOG_VERBOSE << "Resolved candidate: " << mCandidate; mAddress = nodebuffer;
break; mPort = uint16_t(std::stoul(servbuffer));
} mFamily = p->ai_family == AF_INET6 ? Family::Ipv6 : Family::Ipv4;
PLOG_VERBOSE << "Resolved candidate: " << mAddress << ' ' << mPort;
break;
} }
} }
freeaddrinfo(result);
} }
freeaddrinfo(result);
} }
return mFamily != Family::Unresolved; return mFamily != Family::Unresolved;
} }
string Candidate::candidate() const { return "candidate:" + mCandidate; } Candidate::Type Candidate::type() const { return mType; }
Candidate::TransportType Candidate::transportType() const { return mTransportType; }
uint32_t Candidate::priority() const { return mPriority; }
string Candidate::candidate() const {
const char sp{' '};
std::ostringstream oss;
oss << "candidate:";
oss << mFoundation << sp << mComponent << sp << mTransportString << sp << mPriority << sp;
if (isResolved())
oss << mAddress << sp << mPort;
else
oss << mNode << sp << mService;
oss << sp << "typ" << sp << mTypeString;
if (!mTail.empty())
oss << sp << mTail;
return oss.str();
}
string Candidate::mid() const { return mMid.value_or("0"); } string Candidate::mid() const { return mMid.value_or("0"); }
@ -186,14 +215,18 @@ Candidate::operator string() const {
return line.str(); return line.str();
} }
bool Candidate::operator==(const Candidate &other) const {
return mFoundation == other.mFoundation;
}
bool Candidate::operator!=(const Candidate &other) const {
return mFoundation != other.mFoundation;
}
bool Candidate::isResolved() const { return mFamily != Family::Unresolved; } bool Candidate::isResolved() const { return mFamily != Family::Unresolved; }
Candidate::Family Candidate::family() const { return mFamily; } Candidate::Family Candidate::family() const { return mFamily; }
Candidate::Type Candidate::type() const { return mType; }
Candidate::TransportType Candidate::transportType() const { return mTransportType; }
std::optional<string> Candidate::address() const { std::optional<string> Candidate::address() const {
return isResolved() ? std::make_optional(mAddress) : nullopt; return isResolved() ? std::make_optional(mAddress) : nullopt;
} }
@ -202,10 +235,6 @@ std::optional<uint16_t> Candidate::port() const {
return isResolved() ? std::make_optional(mPort) : nullopt; return isResolved() ? std::make_optional(mPort) : nullopt;
} }
std::optional<uint32_t> Candidate::priority() const {
return isResolved() ? std::make_optional(mPriority) : nullopt;
}
} // namespace rtc } // namespace rtc
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) { std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
@ -217,11 +246,11 @@ std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type) {
case rtc::Candidate::Type::Host: case rtc::Candidate::Type::Host:
return out << "host"; return out << "host";
case rtc::Candidate::Type::PeerReflexive: case rtc::Candidate::Type::PeerReflexive:
return out << "peer_reflexive"; return out << "prflx";
case rtc::Candidate::Type::ServerReflexive: case rtc::Candidate::Type::ServerReflexive:
return out << "server_reflexive"; return out << "srflx";
case rtc::Candidate::Type::Relayed: case rtc::Candidate::Type::Relayed:
return out << "relayed"; return out << "relay";
default: default:
return out << "unknown"; return out << "unknown";
} }

View File

@ -306,7 +306,7 @@ void NegociatedDataChannel::processOpenMessage(message_ptr message) {
mLabel.assign(end, open.labelLength); mLabel.assign(end, open.labelLength);
mProtocol.assign(end + open.labelLength, open.protocolLength); mProtocol.assign(end + open.labelLength, open.protocolLength);
mReliability->unordered = (open.reliabilityParameter & 0x80) != 0; mReliability->unordered = (open.channelType & 0x80) != 0;
switch (open.channelType & 0x7F) { switch (open.channelType & 0x7F) {
case CHANNEL_PARTIAL_RELIABLE_REXMIT: case CHANNEL_PARTIAL_RELIABLE_REXMIT:
mReliability->type = Reliability::Type::Rexmit; mReliability->type = Reliability::Type::Rexmit;

View File

@ -29,13 +29,13 @@
#include <unordered_map> #include <unordered_map>
using std::shared_ptr; using std::shared_ptr;
using std::size_t;
using std::string;
using std::string_view;
using std::chrono::system_clock; using std::chrono::system_clock;
namespace { namespace {
using std::string;
using std::string_view;
inline bool match_prefix(string_view str, string_view prefix) { inline bool match_prefix(string_view str, string_view prefix) {
return str.size() >= prefix.size() && return str.size() >= prefix.size() &&
std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end(); std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
@ -106,7 +106,7 @@ Description::Description(const string &sdp, Type type, Role role)
mFingerprint->begin(), mFingerprint->begin(),
[](char c) { return char(std::toupper(c)); }); [](char c) { return char(std::toupper(c)); });
} else { } else {
PLOG_WARNING << "Unknown SDP fingerprint type: " << value; PLOG_WARNING << "Unknown SDP fingerprint format: " << value;
} }
} else if (key == "ice-ufrag") { } else if (key == "ice-ufrag") {
mIceUfrag = value; mIceUfrag = value;
@ -171,16 +171,27 @@ void Description::setFingerprint(string fingerprint) {
mFingerprint.emplace(std::move(fingerprint)); mFingerprint.emplace(std::move(fingerprint));
} }
bool Description::hasCandidate(const Candidate &candidate) const {
for (const Candidate &other : mCandidates)
if (candidate == other)
return true;
return false;
}
void Description::addCandidate(Candidate candidate) { void Description::addCandidate(Candidate candidate) {
candidate.hintMid(bundleMid()); candidate.hintMid(bundleMid());
for (const Candidate &other : mCandidates)
if (candidate == other)
return;
mCandidates.emplace_back(std::move(candidate)); mCandidates.emplace_back(std::move(candidate));
} }
void Description::addCandidates(std::vector<Candidate> candidates) { void Description::addCandidates(std::vector<Candidate> candidates) {
for (Candidate candidate : candidates) { for (Candidate candidate : candidates)
candidate.hintMid(bundleMid()); addCandidate(std::move(candidate));
mCandidates.emplace_back(std::move(candidate));
}
} }
void Description::endCandidates() { mEnded = true; } void Description::endCandidates() { mEnded = true; }
@ -472,8 +483,7 @@ string Description::Entry::generateSdpLines(string_view eol) const {
} }
for (const auto &attr : mAttributes) { for (const auto &attr : mAttributes) {
if (attr.find("extmap") == std::string::npos && if (attr.find("extmap") == string::npos && attr.find("rtcp-rsize") == string::npos)
attr.find("rtcp-rsize") == std::string::npos)
sdp << "a=" << attr << eol; sdp << "a=" << attr << eol;
} }
@ -508,12 +518,21 @@ Description::Entry::removeAttribute(std::vector<string>::iterator it) {
return mAttributes.erase(it); return mAttributes.erase(it);
} }
void Description::Media::addSSRC(uint32_t ssrc, std::string name) { void Description::Media::addSSRC(uint32_t ssrc, std::optional<string> name,
mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + name); std::optional<string> msid) {
if (name)
mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + *name);
else
mAttributes.emplace_back("ssrc:" + std::to_string(ssrc));
if (msid)
mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " msid:" + *msid + " " + *msid);
mSsrcs.emplace_back(ssrc); mSsrcs.emplace_back(ssrc);
} }
void Description::Media::replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, std::string name) { void Description::Media::replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, std::optional<string> name,
std::optional<string> msid) {
auto it = mAttributes.begin(); auto it = mAttributes.begin();
while (it != mAttributes.end()) { while (it != mAttributes.end()) {
if (it->find("ssrc:" + std::to_string(oldSSRC)) == 0) { if (it->find("ssrc:" + std::to_string(oldSSRC)) == 0) {
@ -521,11 +540,17 @@ void Description::Media::replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, std::strin
} else } else
it++; it++;
} }
mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + name); addSSRC(ssrc, std::move(name), std::move(msid));
} }
void Description::Media::addSSRC(uint32_t ssrc) { void Description::Media::removeSSRC(uint32_t oldSSRC) {
mAttributes.emplace_back("ssrc:" + std::to_string(ssrc)); auto it = mAttributes.begin();
while (it != mAttributes.end()) {
if (it->find("ssrc:" + std::to_string(oldSSRC)) == 0) {
it = mAttributes.erase(it);
} else
it++;
}
} }
bool Description::Media::hasSSRC(uint32_t ssrc) { bool Description::Media::hasSSRC(uint32_t ssrc) {
@ -678,32 +703,17 @@ void Description::Media::removeFormat(const string &fmt) {
} }
} }
void Description::Video::addVideoCodec(int payloadType, const string &codec) { void Description::Video::addVideoCodec(int payloadType, string codec,
std::optional<string> profile) {
RTPMap map(std::to_string(payloadType) + ' ' + codec + "/90000"); RTPMap map(std::to_string(payloadType) + ' ' + codec + "/90000");
map.addFB("nack"); map.addFB("nack");
map.addFB("nack pli"); map.addFB("nack pli");
// map.addFB("nack fir"); // map.addFB("ccm fir");
map.addFB("goog-remb"); map.addFB("goog-remb");
if (codec == "H264") { if (profile)
// Use Constrained Baseline profile Level 4.2 (necessary for Firefox) map.fmtps.emplace_back(*profile);
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#Supported_video_codecs
// TODO: Should be 42E0 but 42C0 appears to be more compatible. Investigate this.
map.fmtps.emplace_back(
"profile-level-id=4de01f;packetization-mode=1;level-asymmetry-allowed=1");
// Because certain Android devices don't like me, let us just negotiate some random
{
RTPMap map(std::to_string(payloadType + 1) + ' ' + codec + "/90000");
map.addFB("nack");
map.addFB("nack pli");
// map.addFB("nack fir");
map.addFB("goog-remb");
addRTPMap(map);
}
}
addRTPMap(map); addRTPMap(map);
// // RTX Packets
/* TODO /* TODO
* TIL that Firefox does not properly support the negotiation of RTX! It works, but doesn't * TIL that Firefox does not properly support the negotiation of RTX! It works, but doesn't
* negotiate the SSRC so we have no idea what SSRC is RTX going to be. Three solutions: One) we * negotiate the SSRC so we have no idea what SSRC is RTX going to be. Three solutions: One) we
@ -711,24 +721,34 @@ void Description::Video::addVideoCodec(int payloadType, const string &codec) {
* rebuild the original packet before we send it distribute it to each track. Three) we complain * rebuild the original packet before we send it distribute it to each track. Three) we complain
* to mozilla. This one probably won't do much. * to mozilla. This one probably won't do much.
*/ */
// RTPMap rtx(std::to_string(payloadType+1) + " rtx/90000"); // RTX Packets
// // TODO rtx-time is how long can a request be stashed for before needing to resend it. // RTPMap rtx(std::to_string(payloadType+1) + " rtx/90000");
// Needs to be parameterized rtx.addAttribute("apt=" + std::to_string(payloadType) + // // TODO rtx-time is how long can a request be stashed for before needing to resend it.
// ";rtx-time=3000"); addRTPMap(rtx); // Needs to be parameterized rtx.addAttribute("apt=" + std::to_string(payloadType) +
// ";rtx-time=3000"); addRTPMap(rtx);
} }
void Description::Audio::addAudioCodec(int payloadType, const string &codec) { void Description::Audio::addAudioCodec(int payloadType, string codec,
std::optional<string> profile) {
// TODO This 48000/2 should be parameterized // TODO This 48000/2 should be parameterized
RTPMap map(std::to_string(payloadType) + ' ' + codec + "/48000/2"); RTPMap map(std::to_string(payloadType) + ' ' + codec + "/48000/2");
map.fmtps.emplace_back("maxaveragebitrate=96000; stereo=1; sprop-stereo=1; useinbandfec=1"); if (profile)
map.fmtps.emplace_back(*profile);
addRTPMap(map);
}
void Description::Media::addRTXCodec(unsigned int payloadType, unsigned int originalPayloadType,
unsigned int clockRate) {
RTPMap map(std::to_string(payloadType) + " RTX/" + std::to_string(clockRate));
map.fmtps.emplace_back("apt=" + std::to_string(originalPayloadType));
addRTPMap(map); addRTPMap(map);
} }
void Description::Video::addH264Codec(int pt) { addVideoCodec(pt, "H264"); } void Description::Video::addH264Codec(int pt) { addVideoCodec(pt, "H264"); }
void Description::Video::addVP8Codec(int payloadType) { addVideoCodec(payloadType, "VP8"); } void Description::Video::addVP8Codec(int payloadType) { addVideoCodec(payloadType, "VP8", nullopt); }
void Description::Video::addVP9Codec(int payloadType) { addVideoCodec(payloadType, "VP9"); } void Description::Video::addVP9Codec(int payloadType) { addVideoCodec(payloadType, "VP9", nullopt); }
void Description::Media::setBitrate(int bitrate) { mBas = bitrate; } void Description::Media::setBitrate(int bitrate) { mBas = bitrate; }
@ -797,7 +817,7 @@ void Description::Media::parseSdpLine(string_view line) {
} else if (key == "rtcp-mux") { } else if (key == "rtcp-mux") {
// always added // always added
} else if (key == "ssrc") { } else if (key == "ssrc") {
mSsrcs.emplace_back(std::stoul((std::string)value)); mSsrcs.emplace_back(std::stoul(string(value)));
} else { } else {
Entry::parseSdpLine(line); Entry::parseSdpLine(line);
} }
@ -817,7 +837,7 @@ std::vector<uint32_t> Description::Media::getSSRCs() {
for (auto &val : mAttributes) { for (auto &val : mAttributes) {
PLOG_DEBUG << val; PLOG_DEBUG << val;
if (val.find("ssrc:") == 0) { if (val.find("ssrc:") == 0) {
vec.emplace_back(std::stoul((std::string)val.substr(5, val.find(" ")))); vec.emplace_back(std::stoul(string(val.substr(5, val.find(" ")))));
} }
} }
return vec; return vec;
@ -841,7 +861,7 @@ Description::Media::RTPMap::RTPMap(string_view mline) { setMLine(mline); }
void Description::Media::RTPMap::removeFB(const string &str) { void Description::Media::RTPMap::removeFB(const string &str) {
auto it = rtcpFbs.begin(); auto it = rtcpFbs.begin();
while (it != rtcpFbs.end()) { while (it != rtcpFbs.end()) {
if (it->find(str) != std::string::npos) { if (it->find(str) != string::npos) {
it = rtcpFbs.erase(it); it = rtcpFbs.erase(it);
} else } else
it++; it++;

View File

@ -110,7 +110,7 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
if (err == srtp_err_status_replay_fail) if (err == srtp_err_status_replay_fail)
throw std::runtime_error("SRTCP packet is a replay"); throw std::runtime_error("SRTCP packet is a replay");
else if (err == srtp_err_status_no_ctx) { else if (err == srtp_err_status_no_ctx) {
auto ssrc = ((RTCP_SR *)message->data())->senderSSRC(); auto ssrc = reinterpret_cast<RTCP_SR *>(message->data())->senderSSRC();
PLOG_INFO << "Adding SSRC to SRTCP: " << ssrc; PLOG_INFO << "Adding SSRC to SRTCP: " << ssrc;
addSSRC(ssrc); addSSRC(ssrc);
if ((err = srtp_protect_rtcp(mSrtpOut, message->data(), &size))) if ((err = srtp_protect_rtcp(mSrtpOut, message->data(), &size)))
@ -127,11 +127,11 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
if (err == srtp_err_status_replay_fail) if (err == srtp_err_status_replay_fail)
throw std::runtime_error("Outgoing SRTP packet is a replay"); throw std::runtime_error("Outgoing SRTP packet is a replay");
else if (err == srtp_err_status_no_ctx) { else if (err == srtp_err_status_no_ctx) {
auto ssrc = ((RTP *)message->data())->ssrc(); auto ssrc = reinterpret_cast<RTP *>(message->data())->ssrc();
PLOG_INFO << "Adding SSRC to RTP: " << ssrc; PLOG_INFO << "Adding SSRC to RTP: " << ssrc;
addSSRC(ssrc); addSSRC(ssrc);
if ((err = srtp_protect_rtcp(mSrtpOut, message->data(), &size))) if ((err = srtp_protect(mSrtpOut, message->data(), &size)))
throw std::runtime_error("SRTCP protect error, status=" + throw std::runtime_error("SRTP protect error, status=" +
to_string(static_cast<int>(err))); to_string(static_cast<int>(err)));
} else } else
throw std::runtime_error("SRTP protect error, status=" + throw std::runtime_error("SRTP protect error, status=" +
@ -141,7 +141,14 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
} }
message->resize(size); message->resize(size);
return outgoing(message);
if (message->dscp == 0) { // Track might override the value
// Set recommended medium-priority DSCP value
// See https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18
message->dscp = 36; // AF42: Assured Forwarding class 4, medium drop probability
}
return Transport::outgoing(message); // bypass DTLS DSCP marking
} }
void DtlsSrtpTransport::incoming(message_ptr message) { void DtlsSrtpTransport::incoming(message_ptr message) {
@ -189,7 +196,7 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
else if (err == srtp_err_status_auth_fail) else if (err == srtp_err_status_auth_fail)
PLOG_WARNING << "Incoming SRTCP packet failed authentication check"; PLOG_WARNING << "Incoming SRTCP packet failed authentication check";
else if (err == srtp_err_status_no_ctx) { else if (err == srtp_err_status_no_ctx) {
auto ssrc = ((RTCP_SR *)message->data())->senderSSRC(); auto ssrc = reinterpret_cast<RTCP_SR *>(message->data())->senderSSRC();
PLOG_INFO << "Adding SSRC to RTCP: " << ssrc; PLOG_INFO << "Adding SSRC to RTCP: " << ssrc;
addSSRC(ssrc); addSSRC(ssrc);
if ((err = srtp_unprotect_rtcp(mSrtpIn, message->data(), &size))) if ((err = srtp_unprotect_rtcp(mSrtpIn, message->data(), &size)))
@ -203,8 +210,7 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
} }
PLOG_VERBOSE << "Unprotected SRTCP packet, size=" << size; PLOG_VERBOSE << "Unprotected SRTCP packet, size=" << size;
message->type = Message::Type::Control; message->type = Message::Type::Control;
auto rtp = (RTCP_SR *)message->data(); message->stream = reinterpret_cast<RTCP_SR *>(message->data())->senderSSRC();
message->stream = rtp->senderSSRC();
} else { } else {
PLOG_VERBOSE << "Incoming SRTP packet, size=" << size; PLOG_VERBOSE << "Incoming SRTP packet, size=" << size;
if (srtp_err_status_t err = srtp_unprotect(mSrtpIn, message->data(), &size)) { if (srtp_err_status_t err = srtp_unprotect(mSrtpIn, message->data(), &size)) {
@ -213,21 +219,20 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
else if (err == srtp_err_status_auth_fail) else if (err == srtp_err_status_auth_fail)
PLOG_WARNING << "Incoming SRTP packet failed authentication check"; PLOG_WARNING << "Incoming SRTP packet failed authentication check";
else if (err == srtp_err_status_no_ctx) { else if (err == srtp_err_status_no_ctx) {
auto ssrc = ((RTP *)message->data())->ssrc(); auto ssrc = reinterpret_cast<RTP *>(message->data())->ssrc();
PLOG_INFO << "Adding SSRC to RTP: " << ssrc; PLOG_INFO << "Adding SSRC to RTP: " << ssrc;
addSSRC(ssrc); addSSRC(ssrc);
if ((err = srtp_unprotect(mSrtpIn, message->data(), &size))) if ((err = srtp_unprotect(mSrtpIn, message->data(), &size)))
throw std::runtime_error("SRTCP unprotect error, status=" + throw std::runtime_error("SRTP unprotect error, status=" +
to_string(static_cast<int>(err))); to_string(static_cast<int>(err)));
} else } else
PLOG_WARNING << "SRTP unprotect error, status=" << err PLOG_WARNING << "SRTP unprotect error, status=" << err
<< " SSRC=" << ((RTP *)message->data())->ssrc(); << " SSRC=" << reinterpret_cast<RTP *>(message->data())->ssrc();
return; return;
} }
PLOG_VERBOSE << "Unprotected SRTP packet, size=" << size; PLOG_VERBOSE << "Unprotected SRTP packet, size=" << size;
message->type = Message::Type::Binary; message->type = Message::Type::Binary;
auto rtp = (RTP *)message->data(); message->stream = reinterpret_cast<RTP *>(message->data())->ssrc();
message->stream = rtp->ssrc();
} }
message->resize(size); message->resize(size);

View File

@ -53,7 +53,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr cer
verifier_callback verifierCallback, state_callback stateChangeCallback) verifier_callback verifierCallback, state_callback stateChangeCallback)
: Transport(lower, std::move(stateChangeCallback)), mCertificate(certificate), : Transport(lower, std::move(stateChangeCallback)), mCertificate(certificate),
mVerifierCallback(std::move(verifierCallback)), mVerifierCallback(std::move(verifierCallback)),
mIsClient(lower->role() == Description::Role::Active) { mIsClient(lower->role() == Description::Role::Active), mCurrentDscp(0) {
PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)"; PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)";
@ -122,6 +122,7 @@ bool DtlsTransport::send(message_ptr message) {
PLOG_VERBOSE << "Send size=" << message->size(); PLOG_VERBOSE << "Send size=" << message->size();
mCurrentDscp = message->dscp;
ssize_t ret; ssize_t ret;
do { do {
ret = gnutls_record_send(mSession, message->data(), message->size()); ret = gnutls_record_send(mSession, message->data(), message->size());
@ -143,6 +144,13 @@ void DtlsTransport::incoming(message_ptr message) {
mIncomingQueue.push(message); mIncomingQueue.push(message);
} }
bool DtlsTransport::outgoing(message_ptr message) {
if (message->dscp == 0)
message->dscp = mCurrentDscp;
return Transport::outgoing(std::move(message));
}
void DtlsTransport::postHandshake() { void DtlsTransport::postHandshake() {
// Dummy // Dummy
} }
@ -309,7 +317,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
verifier_callback verifierCallback, state_callback stateChangeCallback) verifier_callback verifierCallback, state_callback stateChangeCallback)
: Transport(lower, std::move(stateChangeCallback)), mCertificate(certificate), : Transport(lower, std::move(stateChangeCallback)), mCertificate(certificate),
mVerifierCallback(std::move(verifierCallback)), mVerifierCallback(std::move(verifierCallback)),
mIsClient(lower->role() == Description::Role::Active) { mIsClient(lower->role() == Description::Role::Active), mCurrentDscp(0) {
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)"; PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
try { try {
@ -405,6 +413,7 @@ bool DtlsTransport::send(message_ptr message) {
PLOG_VERBOSE << "Send size=" << message->size(); PLOG_VERBOSE << "Send size=" << message->size();
mCurrentDscp = message->dscp;
int ret = SSL_write(mSsl, message->data(), int(message->size())); int ret = SSL_write(mSsl, message->data(), int(message->size()));
return openssl::check(mSsl, ret); return openssl::check(mSsl, ret);
} }
@ -419,6 +428,13 @@ void DtlsTransport::incoming(message_ptr message) {
mIncomingQueue.push(message); mIncomingQueue.push(message);
} }
bool DtlsTransport::outgoing(message_ptr message) {
if (message->dscp == 0)
message->dscp = mCurrentDscp;
return Transport::outgoing(std::move(message));
}
void DtlsTransport::postHandshake() { void DtlsTransport::postHandshake() {
// Dummy // Dummy
} }

View File

@ -53,6 +53,7 @@ public:
protected: protected:
virtual void incoming(message_ptr message) override; virtual void incoming(message_ptr message) override;
virtual bool outgoing(message_ptr message) override;
virtual void postHandshake(); virtual void postHandshake();
void runRecvLoop(); void runRecvLoop();
@ -62,6 +63,7 @@ protected:
Queue<message_ptr> mIncomingQueue; Queue<message_ptr> mIncomingQueue;
std::thread mRecvThread; std::thread mRecvThread;
std::atomic<unsigned int> mCurrentDscp;
#if USE_GNUTLS #if USE_GNUTLS
gnutls_session_t mSession; gnutls_session_t mSession;

View File

@ -46,11 +46,12 @@ using std::chrono::system_clock;
namespace rtc { namespace rtc {
IceTransport::IceTransport(const Configuration &config, Description::Role role, IceTransport::IceTransport(const Configuration &config, candidate_callback candidateCallback,
candidate_callback candidateCallback, state_callback stateChangeCallback, state_callback stateChangeCallback,
gathering_state_callback gatheringStateChangeCallback) gathering_state_callback gatheringStateChangeCallback)
: Transport(nullptr, std::move(stateChangeCallback)), mRole(role), mMid("0"), : Transport(nullptr, std::move(stateChangeCallback)), mRole(Description::Role::ActPass),
mGatheringState(GatheringState::New), mCandidateCallback(std::move(candidateCallback)), mMid("0"), mGatheringState(GatheringState::New),
mCandidateCallback(std::move(candidateCallback)),
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)), mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
mAgent(nullptr, nullptr) { mAgent(nullptr, nullptr) {
@ -139,13 +140,19 @@ Description IceTransport::getLocalDescription(Description::Type type) const {
if (juice_get_local_description(mAgent.get(), sdp, JUICE_MAX_SDP_STRING_LEN) < 0) if (juice_get_local_description(mAgent.get(), sdp, JUICE_MAX_SDP_STRING_LEN) < 0)
throw std::runtime_error("Failed to generate local SDP"); throw std::runtime_error("Failed to generate local SDP");
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
// setup:actpass.
// See https://tools.ietf.org/html/rfc5763#section-5
return Description(string(sdp), type, return Description(string(sdp), type,
type == Description::Type::Offer ? Description::Role::ActPass : mRole); type == Description::Type::Offer ? Description::Role::ActPass : mRole);
} }
void IceTransport::setRemoteDescription(const Description &description) { void IceTransport::setRemoteDescription(const Description &description) {
mRole = description.role() == Description::Role::Active ? Description::Role::Passive if (mRole == Description::Role::ActPass)
: Description::Role::Active; mRole = description.role() == Description::Role::Active ? Description::Role::Passive
: Description::Role::Active;
if (mRole == description.role())
throw std::logic_error("Incompatible roles with remote description");
mMid = description.bundleMid(); mMid = description.bundleMid();
if (juice_set_remote_description(mAgent.get(), if (juice_set_remote_description(mAgent.get(),
@ -161,7 +168,9 @@ bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
return juice_add_remote_candidate(mAgent.get(), string(candidate).c_str()) >= 0; return juice_add_remote_candidate(mAgent.get(), string(candidate).c_str()) >= 0;
} }
void IceTransport::gatherLocalCandidates() { void IceTransport::gatherLocalCandidates(string mid) {
mMid = std::move(mid);
// Change state now as candidates calls can be synchronous // Change state now as candidates calls can be synchronous
changeGatheringState(GatheringState::InProgress); changeGatheringState(GatheringState::InProgress);
@ -215,8 +224,10 @@ bool IceTransport::send(message_ptr message) {
} }
bool IceTransport::outgoing(message_ptr message) { bool IceTransport::outgoing(message_ptr message) {
return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()), // Explicit Congestion Notification takes the least-significant 2 bits of the DS field
message->size()) >= 0; int ds = int(message->dscp << 2);
return juice_send_diffserv(mAgent.get(), reinterpret_cast<const char *>(message->data()),
message->size(), ds) >= 0;
} }
void IceTransport::changeGatheringState(GatheringState state) { void IceTransport::changeGatheringState(GatheringState state) {
@ -316,13 +327,14 @@ void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
namespace rtc { namespace rtc {
IceTransport::IceTransport(const Configuration &config, Description::Role role, IceTransport::IceTransport(const Configuration &config, candidate_callback candidateCallback,
candidate_callback candidateCallback, state_callback stateChangeCallback, state_callback stateChangeCallback,
gathering_state_callback gatheringStateChangeCallback) gathering_state_callback gatheringStateChangeCallback)
: Transport(nullptr, std::move(stateChangeCallback)), mRole(role), mMid("0"), : Transport(nullptr, std::move(stateChangeCallback)), mRole(Description::Role::ActPass),
mGatheringState(GatheringState::New), mCandidateCallback(std::move(candidateCallback)), mMid("0"), mGatheringState(GatheringState::New),
mCandidateCallback(std::move(candidateCallback)),
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)), mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr) { mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr), mOutgoingDscp(0) {
PLOG_DEBUG << "Initializing ICE transport (libnice)"; PLOG_DEBUG << "Initializing ICE transport (libnice)";
@ -526,12 +538,21 @@ Description IceTransport::getLocalDescription(Description::Type type) const {
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);
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
// setup:actpass.
// See https://tools.ietf.org/html/rfc5763#section-5
return Description(string(sdp.get()), type,
type == Description::Type::Offer ? Description::Role::ActPass : mRole);
} }
void IceTransport::setRemoteDescription(const Description &description) { void IceTransport::setRemoteDescription(const Description &description) {
mRole = description.role() == Description::Role::Active ? Description::Role::Passive if (mRole == Description::Role::ActPass)
: Description::Role::Active; mRole = description.role() == Description::Role::Active ? Description::Role::Passive
: Description::Role::Active;
if (mRole == description.role())
throw std::logic_error("Incompatible roles with remote description");
mMid = description.bundleMid(); mMid = description.bundleMid();
mTrickleTimeout = !description.ended() ? 30s : 0s; mTrickleTimeout = !description.ended() ? 30s : 0s;
@ -547,12 +568,14 @@ bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
return false; 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 or whitespace, else libnice will reject it.
string sdp(candidate); string sdp(candidate);
NiceCandidate *cand = NiceCandidate *cand =
nice_agent_parse_remote_candidate_sdp(mNiceAgent.get(), mStreamId, sdp.c_str()); nice_agent_parse_remote_candidate_sdp(mNiceAgent.get(), mStreamId, sdp.c_str());
if (!cand) if (!cand) {
PLOG_WARNING << "Rejected ICE candidate: " << sdp;
return false; return false;
}
GSList *list = g_slist_append(nullptr, cand); GSList *list = g_slist_append(nullptr, cand);
int ret = nice_agent_set_remote_candidates(mNiceAgent.get(), mStreamId, 1, list); int ret = nice_agent_set_remote_candidates(mNiceAgent.get(), mStreamId, 1, list);
@ -561,7 +584,9 @@ bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
return ret > 0; return ret > 0;
} }
void IceTransport::gatherLocalCandidates() { void IceTransport::gatherLocalCandidates(string mid) {
mMid = std::move(mid);
// Change state now as candidates calls can be synchronous // Change state now as candidates calls can be synchronous
changeGatheringState(GatheringState::InProgress); changeGatheringState(GatheringState::InProgress);
@ -598,6 +623,13 @@ bool IceTransport::send(message_ptr message) {
} }
bool IceTransport::outgoing(message_ptr message) { bool IceTransport::outgoing(message_ptr message) {
std::lock_guard lock(mOutgoingMutex);
if (mOutgoingDscp != message->dscp) {
mOutgoingDscp = message->dscp;
// Explicit Congestion Notification takes the least-significant 2 bits of the DS field
int ds = int(message->dscp << 2);
nice_agent_set_stream_tos(mNiceAgent.get(), mStreamId, ds); // ToS is the legacy name for DS
}
return nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(), return nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(),
reinterpret_cast<const char *>(message->data())) >= 0; reinterpret_cast<const char *>(message->data())) >= 0;
} }

View File

@ -35,6 +35,7 @@
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <mutex>
namespace rtc { namespace rtc {
@ -45,8 +46,8 @@ public:
using candidate_callback = std::function<void(const Candidate &candidate)>; using candidate_callback = std::function<void(const Candidate &candidate)>;
using gathering_state_callback = std::function<void(GatheringState state)>; using gathering_state_callback = std::function<void(GatheringState state)>;
IceTransport(const Configuration &config, Description::Role role, IceTransport(const Configuration &config, candidate_callback candidateCallback,
candidate_callback candidateCallback, state_callback stateChangeCallback, state_callback stateChangeCallback,
gathering_state_callback gatheringStateChangeCallback); gathering_state_callback gatheringStateChangeCallback);
~IceTransport(); ~IceTransport();
@ -55,7 +56,7 @@ public:
Description getLocalDescription(Description::Type type) const; Description getLocalDescription(Description::Type type) const;
void setRemoteDescription(const Description &description); void setRemoteDescription(const Description &description);
bool addRemoteCandidate(const Candidate &candidate); bool addRemoteCandidate(const Candidate &candidate);
void gatherLocalCandidates(); void gatherLocalCandidates(string mid);
std::optional<string> getLocalAddress() const; std::optional<string> getLocalAddress() const;
std::optional<string> getRemoteAddress() const; std::optional<string> getRemoteAddress() const;
@ -99,6 +100,8 @@ private:
std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop; std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop;
std::thread mMainLoopThread; std::thread mMainLoopThread;
guint mTimeoutId = 0; guint mTimeoutId = 0;
std::mutex mOutgoingMutex;
unsigned int mOutgoingDscp;
static string AddressToString(const NiceAddress &addr); static string AddressToString(const NiceAddress &addr);

View File

@ -77,7 +77,7 @@ void PeerConnection::close() {
mNegotiationNeeded = false; mNegotiationNeeded = false;
// Close data channels asynchronously // Close data channels asynchronously
mProcessor->enqueue(std::bind(&PeerConnection::closeDataChannels, this)); mProcessor->enqueue(&PeerConnection::closeDataChannels, this);
closeTransports(); closeTransports();
} }
@ -187,21 +187,16 @@ void PeerConnection::setLocalDescription(Description::Type type) {
} }
} }
auto iceTransport = std::atomic_load(&mIceTransport); auto iceTransport = initIceTransport();
if (!iceTransport) {
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
// setup:actpass.
// See https://tools.ietf.org/html/rfc5763#section-5
iceTransport = initIceTransport(Description::Role::ActPass);
}
Description localDescription = iceTransport->getLocalDescription(type); Description local = iceTransport->getLocalDescription(type);
processLocalDescription(std::move(localDescription)); processLocalDescription(std::move(local));
changeSignalingState(newSignalingState); changeSignalingState(newSignalingState);
if (mGatheringState == GatheringState::New) if (mGatheringState == GatheringState::New) {
iceTransport->gatherLocalCandidates(); iceTransport->gatherLocalCandidates(localBundleMid());
}
} }
void PeerConnection::setRemoteDescription(Description description) { void PeerConnection::setRemoteDescription(Description description) {
@ -272,12 +267,6 @@ void PeerConnection::setRemoteDescription(Description description) {
// Candidates will be added at the end, extract them for now // Candidates will be added at the end, extract them for now
auto remoteCandidates = description.extractCandidates(); auto remoteCandidates = description.extractCandidates();
auto type = description.type(); auto type = description.type();
auto iceTransport = std::atomic_load(&mIceTransport);
if (!iceTransport)
iceTransport = initIceTransport(Description::Role::ActPass);
iceTransport->setRemoteDescription(description);
processRemoteDescription(std::move(description)); processRemoteDescription(std::move(description));
changeSignalingState(newSignalingState); changeSignalingState(newSignalingState);
@ -287,8 +276,9 @@ void PeerConnection::setRemoteDescription(Description description) {
setLocalDescription(Description::Type::Answer); setLocalDescription(Description::Type::Answer);
} else { } else {
// This is an answer // This is an answer
auto iceTransport = std::atomic_load(&mIceTransport);
auto sctpTransport = std::atomic_load(&mSctpTransport); auto sctpTransport = std::atomic_load(&mSctpTransport);
if (!sctpTransport && iceTransport->role() == Description::Role::Active) { if (!sctpTransport && iceTransport && iceTransport->role() == Description::Role::Active) {
// Since we assumed passive role during DataChannel creation, we need to shift the // Since we assumed passive role during DataChannel creation, we need to shift the
// stream numbers by one to shift them from odd to even. // stream numbers by one to shift them from odd to even.
std::unique_lock lock(mDataChannelsMutex); // we are going to swap the container std::unique_lock lock(mDataChannelsMutex); // we are going to swap the container
@ -380,7 +370,7 @@ void PeerConnection::onSignalingStateChange(std::function<void(SignalingState st
std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) { std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) {
#if !RTC_ENABLE_MEDIA #if !RTC_ENABLE_MEDIA
if (mTracks.empty()) { if (mTracks.empty()) {
PLOG_WARNING << "Tracks will be inative (not compiled with SRTP support)"; PLOG_WARNING << "Tracks will be inative (not compiled with media support)";
} }
#endif #endif
@ -405,14 +395,14 @@ void PeerConnection::onTrack(std::function<void(std::shared_ptr<Track>)> callbac
mTrackCallback = callback; mTrackCallback = callback;
} }
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) { shared_ptr<IceTransport> PeerConnection::initIceTransport() {
PLOG_VERBOSE << "Starting ICE transport"; PLOG_VERBOSE << "Starting ICE transport";
try { try {
if (auto transport = std::atomic_load(&mIceTransport)) if (auto transport = std::atomic_load(&mIceTransport))
return transport; return transport;
auto transport = std::make_shared<IceTransport>( auto transport = std::make_shared<IceTransport>(
mConfig, role, weak_bind(&PeerConnection::processLocalCandidate, this, _1), mConfig, weak_bind(&PeerConnection::processLocalCandidate, this, _1),
[this, weak_this = weak_from_this()](IceTransport::State state) { [this, weak_this = weak_from_this()](IceTransport::State state) {
auto shared_this = weak_this.lock(); auto shared_this = weak_this.lock();
if (!shared_this) if (!shared_this)
@ -490,7 +480,7 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
else else
changeState(State::Connected); changeState(State::Connected);
mProcessor->enqueue(std::bind(&PeerConnection::openTracks, this)); mProcessor->enqueue(&PeerConnection::openTracks, this);
break; break;
case DtlsTransport::State::Failed: case DtlsTransport::State::Failed:
changeState(State::Failed); changeState(State::Failed);
@ -514,7 +504,7 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
lower, certificate, verifierCallback, lower, certificate, verifierCallback,
std::bind(&PeerConnection::forwardMedia, this, _1), stateChangeCallback); std::bind(&PeerConnection::forwardMedia, this, _1), stateChangeCallback);
#else #else
PLOG_WARNING << "Ignoring media support (not compiled with SRTP support)"; PLOG_WARNING << "Ignoring media support (not compiled with media support)";
#endif #endif
} }
@ -561,16 +551,16 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
switch (state) { switch (state) {
case SctpTransport::State::Connected: case SctpTransport::State::Connected:
changeState(State::Connected); changeState(State::Connected);
mProcessor->enqueue(std::bind(&PeerConnection::openDataChannels, this)); mProcessor->enqueue(&PeerConnection::openDataChannels, this);
break; break;
case SctpTransport::State::Failed: case SctpTransport::State::Failed:
LOG_WARNING << "SCTP transport failed"; LOG_WARNING << "SCTP transport failed";
changeState(State::Failed); changeState(State::Failed);
mProcessor->enqueue(std::bind(&PeerConnection::remoteCloseDataChannels, this)); mProcessor->enqueue(&PeerConnection::remoteCloseDataChannels, this);
break; break;
case SctpTransport::State::Disconnected: case SctpTransport::State::Disconnected:
changeState(State::Disconnected); changeState(State::Disconnected);
mProcessor->enqueue(std::bind(&PeerConnection::remoteCloseDataChannels, this)); mProcessor->enqueue(&PeerConnection::remoteCloseDataChannels, this);
break; break;
default: default:
// Ignore // Ignore
@ -659,10 +649,10 @@ void PeerConnection::forwardMessage(message_ptr message) {
stream % 2 == remoteParity) { stream % 2 == remoteParity) {
channel = std::make_shared<NegociatedDataChannel>(shared_from_this(), sctpTransport, channel = std::make_shared<NegociatedDataChannel>(shared_from_this(), sctpTransport,
message->stream); stream);
channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this, channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
weak_ptr<DataChannel>{channel})); weak_ptr<DataChannel>{channel}));
mDataChannels.emplace(message->stream, channel); mDataChannels.emplace(stream, channel);
} else { } else {
// Invalid, close the DataChannel // Invalid, close the DataChannel
sctpTransport->closeStream(message->stream); sctpTransport->closeStream(message->stream);
@ -883,7 +873,7 @@ void PeerConnection::incomingTrack(Description::Media description) {
std::unique_lock lock(mTracksMutex); // we are going to emplace std::unique_lock lock(mTracksMutex); // we are going to emplace
#if !RTC_ENABLE_MEDIA #if !RTC_ENABLE_MEDIA
if (mTracks.empty()) { if (mTracks.empty()) {
PLOG_WARNING << "Tracks will be inative (not compiled with SRTP support)"; PLOG_WARNING << "Tracks will be inative (not compiled with media support)";
} }
#endif #endif
if (mTracks.find(description.mid()) == mTracks.end()) { if (mTracks.find(description.mid()) == mTracks.end()) {
@ -1024,7 +1014,10 @@ void PeerConnection::processLocalDescription(Description description) {
if (!description.hasApplication()) { if (!description.hasApplication()) {
std::shared_lock lock(mDataChannelsMutex); std::shared_lock lock(mDataChannelsMutex);
if (!mDataChannels.empty()) { if (!mDataChannels.empty()) {
Description::Application app("data"); unsigned int m = 0;
while (description.hasMid(std::to_string(m)))
++m;
Description::Application app(std::to_string(m));
app.setSctpPort(DEFAULT_SCTP_PORT); app.setSctpPort(DEFAULT_SCTP_PORT);
app.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE); app.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
@ -1069,19 +1062,17 @@ void PeerConnection::processLocalDescription(Description description) {
mCurrentLocalDescription.emplace(std::move(*mLocalDescription)); mCurrentLocalDescription.emplace(std::move(*mLocalDescription));
} }
mLocalDescription.emplace(std::move(description)); mLocalDescription.emplace(description);
mLocalDescription->addCandidates(std::move(existingCandidates)); mLocalDescription->addCandidates(std::move(existingCandidates));
} }
mProcessor->enqueue([this, description = *mLocalDescription]() { PLOG_VERBOSE << "Issuing local description: " << description;
PLOG_VERBOSE << "Issuing local description: " << description; mProcessor->enqueue(mLocalDescriptionCallback.wrap(), std::move(description));
mLocalDescriptionCallback(std::move(description));
});
// Reciprocated tracks might need to be open // Reciprocated tracks might need to be open
if (auto dtlsTransport = std::atomic_load(&mDtlsTransport); if (auto dtlsTransport = std::atomic_load(&mDtlsTransport);
dtlsTransport && dtlsTransport->state() == Transport::State::Connected) dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
mProcessor->enqueue(std::bind(&PeerConnection::openTracks, this)); mProcessor->enqueue(&PeerConnection::openTracks, this);
} }
void PeerConnection::processLocalCandidate(Candidate candidate) { void PeerConnection::processLocalCandidate(Candidate candidate) {
@ -1089,13 +1080,11 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
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");
candidate.resolve(Candidate::ResolveMode::Simple); // for proper SDP generation later candidate.resolve(Candidate::ResolveMode::Simple);
mLocalDescription->addCandidate(candidate); mLocalDescription->addCandidate(candidate);
mProcessor->enqueue([this, candidate = std::move(candidate)]() { PLOG_VERBOSE << "Issuing local candidate: " << candidate;
PLOG_VERBOSE << "Issuing local candidate: " << candidate; mProcessor->enqueue(mLocalCandidateCallback.wrap(), std::move(candidate));
mLocalCandidateCallback(std::move(candidate));
});
} }
void PeerConnection::processRemoteDescription(Description description) { void PeerConnection::processRemoteDescription(Description description) {
@ -1107,10 +1096,13 @@ void PeerConnection::processRemoteDescription(Description description) {
if (mRemoteDescription) if (mRemoteDescription)
existingCandidates = mRemoteDescription->extractCandidates(); existingCandidates = mRemoteDescription->extractCandidates();
mRemoteDescription.emplace(std::move(description)); mRemoteDescription.emplace(description);
mRemoteDescription->addCandidates(std::move(existingCandidates)); mRemoteDescription->addCandidates(std::move(existingCandidates));
} }
auto iceTransport = initIceTransport();
iceTransport->setRemoteDescription(std::move(description));
if (description.hasApplication()) { if (description.hasApplication()) {
auto dtlsTransport = std::atomic_load(&mDtlsTransport); auto dtlsTransport = std::atomic_load(&mDtlsTransport);
auto sctpTransport = std::atomic_load(&mSctpTransport); auto sctpTransport = std::atomic_load(&mSctpTransport);
@ -1121,28 +1113,45 @@ void PeerConnection::processRemoteDescription(Description description) {
} }
void PeerConnection::processRemoteCandidate(Candidate candidate) { void PeerConnection::processRemoteCandidate(Candidate candidate) {
std::lock_guard lock(mRemoteDescriptionMutex);
auto iceTransport = std::atomic_load(&mIceTransport); auto iceTransport = std::atomic_load(&mIceTransport);
if (!mRemoteDescription || !iceTransport) {
throw std::logic_error("Got a remote candidate without remote description"); // Set as remote candidate
std::lock_guard lock(mRemoteDescriptionMutex);
if (!mRemoteDescription)
throw std::logic_error("Got a remote candidate without remote description");
candidate.hintMid(mRemoteDescription->bundleMid()); if (!iceTransport)
throw std::logic_error("Got a remote candidate without ICE transport");
if (candidate.resolve(Candidate::ResolveMode::Simple)) { candidate.hintMid(mRemoteDescription->bundleMid());
iceTransport->addRemoteCandidate(candidate);
} else { if (mRemoteDescription->hasCandidate(candidate))
// OK, we might need a lookup, do it asynchronously return; // already in description, ignore
// We don't use the thread pool because we have no control on the timeout
weak_ptr<IceTransport> weakIceTransport{iceTransport}; candidate.resolve(Candidate::ResolveMode::Simple);
std::thread t([weakIceTransport, candidate]() mutable { mRemoteDescription->addCandidate(candidate);
if (candidate.resolve(Candidate::ResolveMode::Lookup))
if (auto iceTransport = weakIceTransport.lock())
iceTransport->addRemoteCandidate(candidate);
});
t.detach();
} }
mRemoteDescription->addCandidate(std::move(candidate)); if (candidate.isResolved()) {
iceTransport->addRemoteCandidate(std::move(candidate));
} else {
// We might need a lookup, do it asynchronously
// We don't use the thread pool because we have no control on the timeout
if ((iceTransport = std::atomic_load(&mIceTransport))) {
weak_ptr<IceTransport> weakIceTransport{iceTransport};
std::thread t([weakIceTransport, candidate = std::move(candidate)]() mutable {
if (candidate.resolve(Candidate::ResolveMode::Lookup))
if (auto iceTransport = weakIceTransport.lock())
iceTransport->addRemoteCandidate(std::move(candidate));
});
t.detach();
}
}
}
string PeerConnection::localBundleMid() const {
std::lock_guard lock(mLocalDescriptionMutex);
return mLocalDescription ? mLocalDescription->bundleMid() : "0";
} }
void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) { void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
@ -1150,12 +1159,11 @@ void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
if (!dataChannel) if (!dataChannel)
return; return;
mProcessor->enqueue( mProcessor->enqueue(mDataChannelCallback.wrap(), std::move(dataChannel));
[this, dataChannel = std::move(dataChannel)]() { mDataChannelCallback(dataChannel); });
} }
void PeerConnection::triggerTrack(std::shared_ptr<Track> track) { void PeerConnection::triggerTrack(std::shared_ptr<Track> track) {
mProcessor->enqueue([this, track = std::move(track)]() { mTrackCallback(track); }); mProcessor->enqueue(mTrackCallback.wrap(), std::move(track));
} }
bool PeerConnection::changeState(State state) { bool PeerConnection::changeState(State state) {
@ -1177,7 +1185,7 @@ bool PeerConnection::changeState(State state) {
// This is the last state change, so we may steal the callback // This is the last state change, so we may steal the callback
mProcessor->enqueue([cb = std::move(mStateChangeCallback)]() { cb(State::Closed); }); mProcessor->enqueue([cb = std::move(mStateChangeCallback)]() { cb(State::Closed); });
else else
mProcessor->enqueue([this, state]() { mStateChangeCallback(state); }); mProcessor->enqueue(mStateChangeCallback.wrap(), state);
return true; return true;
} }
@ -1189,7 +1197,7 @@ bool PeerConnection::changeGatheringState(GatheringState state) {
std::ostringstream s; std::ostringstream s;
s << state; s << state;
PLOG_INFO << "Changed gathering state to " << s.str(); PLOG_INFO << "Changed gathering state to " << s.str();
mProcessor->enqueue([this, state] { mGatheringStateChangeCallback(state); }); mProcessor->enqueue(mGatheringStateChangeCallback.wrap(), state);
return true; return true;
} }
@ -1200,7 +1208,7 @@ bool PeerConnection::changeSignalingState(SignalingState state) {
std::ostringstream s; std::ostringstream s;
s << state; s << state;
PLOG_INFO << "Changed signaling state to " << s.str(); PLOG_INFO << "Changed signaling state to " << s.str();
mProcessor->enqueue([this, state] { mSignalingStateChangeCallback(state); }); mProcessor->enqueue(mSignalingStateChangeCallback.wrap(), state);
return true; return true;
} }

View File

@ -20,6 +20,8 @@
namespace rtc { namespace rtc {
Processor::Processor(size_t limit) : mTasks(limit) {}
Processor::~Processor() { join(); } Processor::~Processor() { join(); }
void Processor::join() { void Processor::join() {
@ -29,15 +31,13 @@ void Processor::join() {
void Processor::schedule() { void Processor::schedule() {
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
if (mTasks.empty()) { if (auto next = mTasks.tryPop()) {
ThreadPool::Instance().enqueue(std::move(*next));
} else {
// No more tasks // No more tasks
mPending = false; mPending = false;
mCondition.notify_all(); mCondition.notify_all();
return;
} }
ThreadPool::Instance().enqueue(std::move(mTasks.front()));
mTasks.pop();
} }
} // namespace rtc } // namespace rtc

View File

@ -22,6 +22,7 @@
#include "include.hpp" #include "include.hpp"
#include "init.hpp" #include "init.hpp"
#include "threadpool.hpp" #include "threadpool.hpp"
#include "queue.hpp"
#include <condition_variable> #include <condition_variable>
#include <future> #include <future>
@ -34,7 +35,7 @@ namespace rtc {
// Processed tasks in order by delegating them to the thread pool // Processed tasks in order by delegating them to the thread pool
class Processor final { class Processor final {
public: public:
Processor() = default; Processor(size_t limit = 0);
~Processor(); ~Processor();
Processor(const Processor &) = delete; Processor(const Processor &) = delete;
@ -52,7 +53,7 @@ protected:
// Keep an init token // Keep an init token
const init_token mInitToken = Init::Token(); const init_token mInitToken = Init::Token();
std::queue<std::function<void()>> mTasks; Queue<std::function<void()>> mTasks;
bool mPending = false; // true iff a task is pending in the thread pool bool mPending = false; // true iff a task is pending in the thread pool
mutable std::mutex mMutex; mutable std::mutex mMutex;
@ -71,7 +72,7 @@ template <class F, class... Args> void Processor::enqueue(F &&f, Args &&... args
ThreadPool::Instance().enqueue(std::move(task)); ThreadPool::Instance().enqueue(std::move(task));
mPending = true; mPending = true;
} else { } else {
mTasks.emplace(std::move(task)); mTasks.push(std::move(task));
} }
} }

View File

@ -126,7 +126,7 @@ bool RtcpReceivingSession::requestKeyframe() {
void RtcpReceivingSession::pushPLI() { void RtcpReceivingSession::pushPLI() {
auto msg = rtc::make_message(rtc::RTCP_PLI::size(), rtc::Message::Type::Control); auto msg = rtc::make_message(rtc::RTCP_PLI::size(), rtc::Message::Type::Control);
auto *pli = (rtc::RTCP_PLI *)msg->data(); auto *pli = reinterpret_cast<rtc::RTCP_PLI *>(msg->data());
pli->preparePacket(mSsrc); pli->preparePacket(mSsrc);
send(msg); send(msg);
} }

View File

@ -88,7 +88,7 @@ void SctpTransport::Cleanup() {
SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
message_callback recvCallback, amount_callback bufferedAmountCallback, message_callback recvCallback, amount_callback bufferedAmountCallback,
state_callback stateChangeCallback) state_callback stateChangeCallback)
: Transport(lower, std::move(stateChangeCallback)), mPort(port), : Transport(lower, std::move(stateChangeCallback)), mPort(port), mPendingRecvCount(0),
mSendQueue(0, message_size_func), mBufferedAmountCallback(std::move(bufferedAmountCallback)) { mSendQueue(0, message_size_func), mBufferedAmountCallback(std::move(bufferedAmountCallback)) {
onRecv(recvCallback); onRecv(recvCallback);
@ -100,11 +100,12 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
Instances.insert(this); Instances.insert(this);
} }
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback, mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, nullptr, nullptr, 0, nullptr);
&SctpTransport::SendCallback, 0, this);
if (!mSock) if (!mSock)
throw std::runtime_error("Could not create SCTP socket, errno=" + std::to_string(errno)); throw std::runtime_error("Could not create SCTP socket, errno=" + std::to_string(errno));
usrsctp_set_upcall(mSock, &SctpTransport::UpcallCallback, this);
if (usrsctp_set_non_blocking(mSock, 1)) if (usrsctp_set_non_blocking(mSock, 1))
throw std::runtime_error("Unable to set non-blocking mode, errno=" + std::to_string(errno)); throw std::runtime_error("Unable to set non-blocking mode, errno=" + std::to_string(errno));
@ -122,6 +123,10 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(av))) if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(av)))
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));
int on = 1;
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RECVRCVINFO, &on, sizeof(on)))
throw std::runtime_error("Could set socket option SCTP_RECVRCVINFO, errno=" +
std::to_string(errno));
struct sctp_event se = {}; struct sctp_event se = {};
se.se_assoc_id = SCTP_ALL_ASSOC; se.se_assoc_id = SCTP_ALL_ASSOC;
@ -225,6 +230,7 @@ bool SctpTransport::stop() {
void SctpTransport::close() { void SctpTransport::close() {
if (mSock) { if (mSock) {
mProcessor.join();
usrsctp_close(mSock); usrsctp_close(mSock);
mSock = nullptr; mSock = nullptr;
} }
@ -319,6 +325,67 @@ void SctpTransport::incoming(message_ptr message) {
usrsctp_conninput(this, message->data(), message->size(), 0); usrsctp_conninput(this, message->data(), message->size(), 0);
} }
bool SctpTransport::outgoing(message_ptr message) {
// Set recommended medium-priority DSCP value
// See https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18
message->dscp = 10; // AF11: Assured Forwarding class 1, low drop probability
return Transport::outgoing(std::move(message));
}
void SctpTransport::doRecv() {
std::lock_guard lock(mRecvMutex);
--mPendingRecvCount;
try {
while (true) {
const size_t bufferSize = 65536;
byte buffer[bufferSize];
socklen_t fromlen = 0;
struct sctp_rcvinfo info = {};
socklen_t infolen = sizeof(info);
unsigned int infotype = 0;
int flags = 0;
ssize_t len = usrsctp_recvv(mSock, buffer, bufferSize, nullptr, &fromlen, &info,
&infolen, &infotype, &flags);
if (len < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == ECONNRESET)
break;
else
throw std::runtime_error("SCTP recv failed, errno=" + std::to_string(errno));
}
PLOG_VERBOSE << "SCTP recv, len=" << len;
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
// therefore partial notifications and messages need to be handled separately.
if (flags & MSG_NOTIFICATION) {
// SCTP event notification
mPartialNotification.insert(mPartialNotification.end(), buffer, buffer + len);
if (flags & MSG_EOR) {
// Notification is complete, process it
auto notification =
reinterpret_cast<union sctp_notification *>(mPartialNotification.data());
processNotification(notification, mPartialNotification.size());
mPartialNotification.clear();
}
} else {
// SCTP message
mPartialMessage.insert(mPartialMessage.end(), buffer, buffer + len);
if (flags & MSG_EOR) {
// Message is complete, process it
if (infotype != SCTP_RECVV_RCVINFO)
throw std::runtime_error("Missing SCTP recv info");
processData(std::move(mPartialMessage), info.rcv_sid,
PayloadId(ntohl(info.rcv_ppid)));
mPartialMessage.clear();
}
}
}
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
bool SctpTransport::trySendQueue() { bool SctpTransport::trySendQueue() {
// Requires mSendMutex to be locked // Requires mSendMutex to be locked
while (auto next = mSendQueue.peek()) { while (auto next = mSendQueue.peek()) {
@ -472,44 +539,21 @@ bool SctpTransport::safeFlush() {
} }
} }
int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*addr*/, void SctpTransport::handleUpcall() {
const byte *data, size_t len, struct sctp_rcvinfo info, int flags) { if (!mSock)
try { return;
PLOG_VERBOSE << "Handle recv, len=" << len;
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB, PLOG_VERBOSE << "Handle upcall";
// therefore partial notifications and messages need to be handled separately.
if (flags & MSG_NOTIFICATION) {
// SCTP event notification
mPartialNotification.insert(mPartialNotification.end(), data, data + len);
if (flags & MSG_EOR) {
// Notification is complete, process it
processNotification(
reinterpret_cast<const union sctp_notification *>(mPartialNotification.data()),
mPartialNotification.size());
mPartialNotification.clear();
}
} else {
// SCTP message
mPartialMessage.insert(mPartialMessage.end(), data, data + len);
if (flags & MSG_EOR) {
// Message is complete, process it
processData(std::move(mPartialMessage), info.rcv_sid,
PayloadId(ntohl(info.rcv_ppid)));
mPartialMessage.clear();
}
}
} catch (const std::exception &e) { int events = usrsctp_get_events(mSock);
PLOG_ERROR << "SCTP recv: " << e.what();
return -1; if (events & SCTP_EVENT_READ && mPendingRecvCount == 0) {
++mPendingRecvCount;
mProcessor.enqueue(&SctpTransport::doRecv, this);
} }
return 0; // success
}
int SctpTransport::handleSend(size_t free) { if (events & SCTP_EVENT_WRITE)
PLOG_VERBOSE << "Handle send, free=" << free; mProcessor.enqueue(&SctpTransport::safeFlush, this);
return safeFlush() ? 0 : -1;
} }
int SctpTransport::handleWrite(byte *data, size_t len, uint8_t /*tos*/, uint8_t /*set_df*/) { int SctpTransport::handleWrite(byte *data, size_t len, uint8_t /*tos*/, uint8_t /*set_df*/) {
@ -517,6 +561,7 @@ int SctpTransport::handleWrite(byte *data, size_t len, uint8_t /*tos*/, uint8_t
std::unique_lock lock(mWriteMutex); std::unique_lock lock(mWriteMutex);
PLOG_VERBOSE << "Handle write, len=" << len; PLOG_VERBOSE << "Handle write, len=" << len;
auto message = make_message(data, data + len);
if (!outgoing(make_message(data, data + len))) if (!outgoing(make_message(data, data + len)))
return -1; return -1;
@ -699,31 +744,14 @@ std::optional<milliseconds> SctpTransport::rtt() {
return milliseconds(status.sstat_primary.spinfo_srtt); return milliseconds(status.sstat_primary.spinfo_srtt);
} }
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, void SctpTransport::UpcallCallback(struct socket *, void *arg, int /* flags */) {
size_t len, struct sctp_rcvinfo recv_info, int flags, auto *transport = static_cast<SctpTransport *>(arg);
void *ulp_info) {
auto *transport = static_cast<SctpTransport *>(ulp_info);
std::shared_lock lock(InstancesMutex);
if (Instances.find(transport) == Instances.end()) {
free(data);
return -1;
}
int ret =
transport->handleRecv(sock, addr, static_cast<const byte *>(data), len, recv_info, flags);
free(data);
return ret;
}
int SctpTransport::SendCallback(struct socket *, uint32_t sb_free, void *ulp_info) {
auto *transport = static_cast<SctpTransport *>(ulp_info);
std::shared_lock lock(InstancesMutex); std::shared_lock lock(InstancesMutex);
if (Instances.find(transport) == Instances.end()) if (Instances.find(transport) == Instances.end())
return -1; return;
return transport->handleSend(size_t(sb_free)); transport->handleUpcall();
} }
int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) { int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) {

View File

@ -21,8 +21,10 @@
#include "include.hpp" #include "include.hpp"
#include "peerconnection.hpp" #include "peerconnection.hpp"
#include "processor.hpp"
#include "queue.hpp" #include "queue.hpp"
#include "transport.hpp" #include "transport.hpp"
#include "processor.hpp"
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
@ -35,7 +37,7 @@
namespace rtc { namespace rtc {
class SctpTransport : public Transport { class SctpTransport final : public Transport {
public: public:
static void Init(); static void Init();
static void Cleanup(); static void Cleanup();
@ -75,16 +77,16 @@ private:
void shutdown(); void shutdown();
void close(); void close();
void incoming(message_ptr message) override; void incoming(message_ptr message) override;
bool outgoing(message_ptr message) override;
void doRecv();
bool trySendQueue(); bool trySendQueue();
bool trySendMessage(message_ptr message); bool trySendMessage(message_ptr message);
void updateBufferedAmount(uint16_t streamId, long delta); void updateBufferedAmount(uint16_t streamId, long delta);
void sendReset(uint16_t streamId); void sendReset(uint16_t streamId);
bool safeFlush(); bool safeFlush();
int handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data, size_t len, void handleUpcall();
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); int handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df);
void processData(binary &&data, uint16_t streamId, PayloadId ppid); void processData(binary &&data, uint16_t streamId, PayloadId ppid);
@ -93,7 +95,9 @@ private:
const uint16_t mPort; const uint16_t mPort;
struct socket *mSock; struct socket *mSock;
std::mutex mSendMutex; Processor mProcessor;
std::atomic<int> mPendingRecvCount;
std::mutex mRecvMutex, mSendMutex;
Queue<message_ptr> mSendQueue; Queue<message_ptr> mSendQueue;
std::map<uint16_t, size_t> mBufferedAmount; std::map<uint16_t, size_t> mBufferedAmount;
amount_callback mBufferedAmountCallback; amount_callback mBufferedAmountCallback;
@ -109,9 +113,7 @@ private:
// Stats // Stats
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0; std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len, static void UpcallCallback(struct socket *sock, void *arg, int flags);
struct sctp_rcvinfo recv_info, int flags, void *ulp_info);
static int SendCallback(struct socket *sock, uint32_t sb_free, void *ulp_info);
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df); static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
static std::unordered_set<SctpTransport *> Instances; static std::unordered_set<SctpTransport *> Instances;

View File

@ -18,15 +18,26 @@
#include "threadpool.hpp" #include "threadpool.hpp"
#include <cstdlib>
namespace {
void joinThreadPoolInstance() {
rtc::ThreadPool::Instance().join();
}
}
namespace rtc { namespace rtc {
ThreadPool &ThreadPool::Instance() { ThreadPool &ThreadPool::Instance() {
// Init handles joining on cleanup
static ThreadPool *instance = new ThreadPool; static ThreadPool *instance = new ThreadPool;
return *instance; return *instance;
} }
ThreadPool::~ThreadPool() { join(); } ThreadPool::ThreadPool() {
std::atexit(joinThreadPoolInstance);
}
ThreadPool::~ThreadPool() {}
int ThreadPool::count() const { int ThreadPool::count() const {
std::unique_lock lock(mWorkersMutex); std::unique_lock lock(mWorkersMutex);

View File

@ -56,7 +56,7 @@ public:
auto enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...>; auto enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...>;
protected: protected:
ThreadPool() = default; ThreadPool();
~ThreadPool(); ~ThreadPool();
std::function<void()> dequeue(); // returns null function if joining std::function<void()> dequeue(); // returns null function if joining

View File

@ -45,12 +45,30 @@ void Track::close() {
setRtcpHandler(nullptr); setRtcpHandler(nullptr);
} }
bool Track::send(message_variant data) { return outgoing(make_message(std::move(data))); } bool Track::send(message_variant data) {
if (mIsClosed)
throw std::runtime_error("Track is closed");
bool Track::send(const byte *data, size_t size) { auto direction = mMediaDescription.direction();
return outgoing(std::make_shared<Message>(data, data + size, Message::Binary)); if ((direction == Description::Direction::RecvOnly ||
direction == Description::Direction::Inactive)) {
PLOG_WARNING << "Track media direction does not allow transmission, dropping";
return false;
}
auto message = make_message(std::move(data));
if (mRtcpHandler) {
message = mRtcpHandler->outgoing(message);
if (!message)
return false;
}
return outgoing(std::move(message));
} }
bool Track::send(const byte *data, size_t size) { return send(binary(data, data + size)); }
std::optional<message_variant> Track::receive() { std::optional<message_variant> Track::receive() {
if (auto next = mRecvQueue.tryPop()) if (auto next = mRecvQueue.tryPop())
return to_variant(std::move(**next)); return to_variant(std::move(**next));
@ -88,50 +106,10 @@ void Track::open(shared_ptr<DtlsSrtpTransport> transport) {
} }
#endif #endif
bool Track::outgoing(message_ptr message) {
if (mRtcpHandler) {
message = mRtcpHandler->outgoing(message);
if (!message)
return false;
}
auto direction = mMediaDescription.direction();
if ((direction == Description::Direction::RecvOnly ||
direction == Description::Direction::Inactive) &&
message->type != Message::Control) {
PLOG_WARNING << "Track media direction does not allow transmission, dropping";
return false;
}
if (mIsClosed)
throw std::runtime_error("Track is closed");
if (message->size() > maxMessageSize())
throw std::runtime_error("Message size exceeds limit");
#if RTC_ENABLE_MEDIA
auto transport = mDtlsSrtpTransport.lock();
if (!transport)
throw std::runtime_error("Track transport is not open");
return transport->sendMedia(message);
#else
PLOG_WARNING << "Ignoring track send (not compiled with SRTP support)";
return false;
#endif
}
void Track::incoming(message_ptr message) { void Track::incoming(message_ptr message) {
if (!message) if (!message)
return; return;
if (mRtcpHandler) {
message = mRtcpHandler->incoming(message);
if (!message)
return;
}
auto direction = mMediaDescription.direction(); auto direction = mMediaDescription.direction();
if ((direction == Description::Direction::SendOnly || if ((direction == Description::Direction::SendOnly ||
direction == Description::Direction::Inactive) && direction == Description::Direction::Inactive) &&
@ -140,30 +118,46 @@ void Track::incoming(message_ptr message) {
return; return;
} }
if (mRtcpHandler) {
message = mRtcpHandler->incoming(message);
if (!message)
return;
}
// Tail drop if queue is full // Tail drop if queue is full
if (mRecvQueue.full()) if (mRecvQueue.full()) {
PLOG_WARNING << "Track incoming queue is full, dropping";
return; return;
}
mRecvQueue.push(message); mRecvQueue.push(message);
triggerAvailable(mRecvQueue.size()); triggerAvailable(mRecvQueue.size());
} }
bool Track::outgoing([[maybe_unused]] message_ptr message) {
#if RTC_ENABLE_MEDIA
auto transport = mDtlsSrtpTransport.lock();
if (!transport)
throw std::runtime_error("Track transport is not open");
// Set recommended medium-priority DSCP value
// See https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18
if (mMediaDescription.type() == "audio")
message->dscp = 46; // EF: Expedited Forwarding
else
message->dscp = 36; // AF42: Assured Forwarding class 4, medium drop probability
return transport->sendMedia(message);
#else
PLOG_WARNING << "Ignoring track send (not compiled with media support)";
return false;
#endif
}
void Track::setRtcpHandler(std::shared_ptr<RtcpHandler> handler) { void Track::setRtcpHandler(std::shared_ptr<RtcpHandler> handler) {
mRtcpHandler = std::move(handler); mRtcpHandler = std::move(handler);
if (mRtcpHandler) { if (mRtcpHandler)
mRtcpHandler->onOutgoing([&]([[maybe_unused]] const rtc::message_ptr &message) { mRtcpHandler->onOutgoing(std::bind(&Track::outgoing, this, std::placeholders::_1));
#if RTC_ENABLE_MEDIA
auto transport = mDtlsSrtpTransport.lock();
if (!transport)
throw std::runtime_error("Track transport is not open");
return transport->sendMedia(message);
#else
PLOG_WARNING << "Ignoring track send (not compiled with SRTP support)";
return false;
#endif
});
}
} }
bool Track::requestKeyframe() { bool Track::requestKeyframe() {

View File

@ -28,8 +28,6 @@
namespace rtc { namespace rtc {
using namespace std::placeholders;
class Transport { class Transport {
public: public:
enum class State { Disconnected, Connecting, Connected, Completed, Failed }; enum class State { Disconnected, Connecting, Connected, Completed, Failed };
@ -57,7 +55,7 @@ public:
void registerIncoming() { void registerIncoming() {
if (mLower) { if (mLower) {
PLOG_VERBOSE << "Registering incoming callback"; PLOG_VERBOSE << "Registering incoming callback";
mLower->onRecv(std::bind(&Transport::incoming, this, _1)); mLower->onRecv(std::bind(&Transport::incoming, this, std::placeholders::_1));
} }
} }

View File

@ -36,6 +36,7 @@
namespace rtc { namespace rtc {
using std::shared_ptr; using std::shared_ptr;
using namespace std::placeholders;
WebSocket::WebSocket(std::optional<Configuration> config) WebSocket::WebSocket(std::optional<Configuration> config)
: mConfig(config ? std::move(*config) : Configuration()), : mConfig(config ? std::move(*config) : Configuration()),
@ -117,6 +118,10 @@ void WebSocket::remoteClose() {
bool WebSocket::send(message_variant data) { return outgoing(make_message(std::move(data))); } bool WebSocket::send(message_variant data) { return outgoing(make_message(std::move(data))); }
bool WebSocket::send(const byte *data, size_t size) {
return outgoing(make_message(data, data + size));
}
bool WebSocket::isOpen() const { return mState == State::Open; } bool WebSocket::isOpen() const { return mState == State::Open; }
bool WebSocket::isClosed() const { return mState == State::Closed; } bool WebSocket::isClosed() const { return mState == State::Closed; }
@ -286,8 +291,14 @@ shared_ptr<WsTransport> WebSocket::initWsTransport() {
shared_ptr<Transport> lower = std::atomic_load(&mTlsTransport); shared_ptr<Transport> lower = std::atomic_load(&mTlsTransport);
if (!lower) if (!lower)
lower = std::atomic_load(&mTcpTransport); lower = std::atomic_load(&mTcpTransport);
WsTransport::Configuration wsConfig = {};
wsConfig.host = mHost;
wsConfig.path = mPath;
wsConfig.protocols = mConfig.protocols;
auto transport = std::make_shared<WsTransport>( auto transport = std::make_shared<WsTransport>(
lower, mHost, mPath, weak_bind(&WebSocket::incoming, this, _1), lower, wsConfig, weak_bind(&WebSocket::incoming, this, _1),
[this, weak_this = weak_from_this()](State state) { [this, weak_this = weak_from_this()](State state) {
auto shared_this = weak_this.lock(); auto shared_this = weak_this.lock();
if (!shared_this) if (!shared_this)

View File

@ -24,8 +24,10 @@
#if RTC_ENABLE_WEBSOCKET #if RTC_ENABLE_WEBSOCKET
#include <chrono> #include <chrono>
#include <iterator>
#include <list> #include <list>
#include <map> #include <map>
#include <numeric>
#include <random> #include <random>
#include <regex> #include <regex>
@ -52,17 +54,17 @@ using std::to_string;
using random_bytes_engine = using random_bytes_engine =
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned short>; std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned short>;
WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string path, WsTransport::WsTransport(std::shared_ptr<Transport> lower, Configuration config,
message_callback recvCallback, state_callback stateCallback) message_callback recvCallback, state_callback stateCallback)
: Transport(lower, std::move(stateCallback)), mHost(std::move(host)), mPath(std::move(path)) { : Transport(lower, std::move(stateCallback)), mConfig(std::move(config)) {
onRecv(recvCallback); onRecv(recvCallback);
PLOG_DEBUG << "Initializing WebSocket transport"; PLOG_DEBUG << "Initializing WebSocket transport";
if (mHost.empty()) if (mConfig.host.empty())
throw std::invalid_argument("WebSocket HTTP host cannot be empty"); throw std::invalid_argument("WebSocket HTTP host cannot be empty");
if (mPath.empty()) if (mConfig.path.empty())
throw std::invalid_argument("WebSocket HTTP path cannot be empty"); throw std::invalid_argument("WebSocket HTTP path cannot be empty");
} }
@ -153,7 +155,7 @@ void WsTransport::close() {
} }
bool WsTransport::sendHttpRequest() { bool WsTransport::sendHttpRequest() {
PLOG_DEBUG << "Sending WebSocket HTTP request for path " << mPath; PLOG_DEBUG << "Sending WebSocket HTTP request for path " << mConfig.path;
changeState(State::Connecting); changeState(State::Connecting);
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count()); auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
@ -163,18 +165,27 @@ bool WsTransport::sendHttpRequest() {
auto k = reinterpret_cast<uint8_t *>(key.data()); auto k = reinterpret_cast<uint8_t *>(key.data());
std::generate(k, k + key.size(), [&]() { return uint8_t(generator()); }); std::generate(k, k + key.size(), [&]() { return uint8_t(generator()); });
const string request = "GET " + mPath + string appendHeader = "";
if (mConfig.protocols.size() > 0) {
appendHeader +=
"Sec-WebSocket-Protocol: " +
std::accumulate(mConfig.protocols.begin(), mConfig.protocols.end(), string(),
[](const string &a, const string &b) -> string {
return a + (a.length() > 0 ? "," : "") + b;
}) +
"\r\n";
}
const string request = "GET " + mConfig.path +
" HTTP/1.1\r\n" " HTTP/1.1\r\n"
"Host: " + "Host: " +
mHost + mConfig.host +
"\r\n" "\r\n"
"Connection: Upgrade\r\n" "Connection: Upgrade\r\n"
"Upgrade: websocket\r\n" "Upgrade: websocket\r\n"
"Sec-WebSocket-Version: 13\r\n" "Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Key: " + "Sec-WebSocket-Key: " +
to_base64(key) + to_base64(key) + "\r\n" + std::move(appendHeader) + "\r\n";
"\r\n"
"\r\n";
auto data = reinterpret_cast<const byte *>(request.data()); auto data = reinterpret_cast<const byte *>(request.data());
auto size = request.size(); auto size = request.size();
@ -227,8 +238,14 @@ size_t WsTransport::readHttpResponse(const byte *buffer, size_t size) {
} }
auto h = headers.find("upgrade"); auto h = headers.find("upgrade");
if (h == headers.end() || h->second != "websocket") if (h == headers.end())
throw std::runtime_error("WebSocket update header missing or mismatching"); throw std::runtime_error("WebSocket update header missing");
string upgrade;
std::transform(h->second.begin(), h->second.end(), std::back_inserter(upgrade),
[](char c) { return std::tolower(c); });
if (upgrade != "websocket")
throw std::runtime_error("WebSocket update header mismatching: " + h->second);
h = headers.find("sec-websocket-accept"); h = headers.find("sec-websocket-accept");
if (h == headers.end()) if (h == headers.end())

View File

@ -31,7 +31,13 @@ class TlsTransport;
class WsTransport : public Transport { class WsTransport : public Transport {
public: public:
WsTransport(std::shared_ptr<Transport> lower, string host, string path, struct Configuration {
string host;
string path = "/";
std::vector<string> protocols;
};
WsTransport(std::shared_ptr<Transport> lower, Configuration config,
message_callback recvCallback, state_callback stateCallback); message_callback recvCallback, state_callback stateCallback);
~WsTransport(); ~WsTransport();
@ -68,12 +74,12 @@ private:
void recvFrame(const Frame &frame); void recvFrame(const Frame &frame);
bool sendFrame(const Frame &frame); bool sendFrame(const Frame &frame);
const string mHost; const Configuration mConfig;
const string mPath;
binary mBuffer; binary mBuffer;
binary mPartial; binary mPartial;
Opcode mPartialOpcode; Opcode mPartialOpcode;
}; };
} // namespace rtc } // namespace rtc

View File

@ -100,14 +100,48 @@ static void RTC_API messageCallback(int id, const char *message, int size, void
static void RTC_API dataChannelCallback(int pc, int dc, void *ptr) { static void RTC_API dataChannelCallback(int pc, int dc, void *ptr) {
Peer *peer = (Peer *)ptr; Peer *peer = (Peer *)ptr;
peer->dc = dc;
peer->connected = true; char label[256];
if (rtcGetDataChannelLabel(dc, label, 256) < 0) {
fprintf(stderr, "rtcGetDataChannelLabel failed\n");
return;
}
char protocol[256];
if (rtcGetDataChannelProtocol(dc, protocol, 256) < 0) {
fprintf(stderr, "rtcGetDataChannelProtocol failed\n");
return;
}
rtcReliability reliability;
if (rtcGetDataChannelReliability(dc, &reliability) < 0) {
fprintf(stderr, "rtcGetDataChannelReliability failed\n");
return;
}
printf("DataChannel %d: Received with label \"%s\" and protocol \"%s\"\n",
peer == peer1 ? 1 : 2, label, protocol);
if (strcmp(label, "test") != 0) {
fprintf(stderr, "Wrong DataChannel label\n");
return;
}
if (strcmp(protocol, "protocol") != 0) {
fprintf(stderr, "Wrong DataChannel protocol\n");
return;
}
if (reliability.unordered == false) {
fprintf(stderr, "Wrong DataChannel reliability\n");
return;
}
rtcSetClosedCallback(dc, closedCallback); rtcSetClosedCallback(dc, closedCallback);
rtcSetMessageCallback(dc, messageCallback); rtcSetMessageCallback(dc, messageCallback);
char buffer[256]; peer->dc = dc;
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0) peer->connected = true;
printf("DataChannel %d: Received with label \"%s\"\n", peer == peer1 ? 1 : 2, buffer);
const char *message = peer == peer1 ? "Hello from 1" : "Hello from 2"; const char *message = peer == peer1 ? "Hello from 1" : "Hello from 2";
rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
@ -127,6 +161,7 @@ static Peer *createPeer(const rtcConfiguration *config) {
rtcSetLocalCandidateCallback(peer->pc, candidateCallback); rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
rtcSetStateChangeCallback(peer->pc, stateChangeCallback); rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback); rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
rtcSetSignalingStateChangeCallback(peer->pc, signalingStateCallback);
return peer; return peer;
} }
@ -173,7 +208,12 @@ int test_capi_connectivity_main() {
goto error; goto error;
// Peer 1: Create data channel // Peer 1: Create data channel
peer1->dc = rtcCreateDataChannel(peer1->pc, "test"); rtcDataChannelInit init;
memset(&init, 0, sizeof(init));
init.protocol = "protocol";
init.reliability.unordered = true;
peer1->dc = rtcCreateDataChannelEx(peer1->pc, "test", &init);
rtcSetOpenCallback(peer1->dc, openCallback); rtcSetOpenCallback(peer1->dc, openCallback);
rtcSetClosedCallback(peer1->dc, closedCallback); rtcSetClosedCallback(peer1->dc, closedCallback);
rtcSetMessageCallback(peer1->dc, messageCallback); rtcSetMessageCallback(peer1->dc, messageCallback);
@ -187,12 +227,6 @@ int test_capi_connectivity_main() {
goto error; goto error;
} }
if (peer1->signalingState != RTC_SIGNALING_STABLE ||
peer2->signalingState != RTC_SIGNALING_STABLE) {
fprintf(stderr, "Signaling state is not stable\n");
goto error;
}
if (!peer1->connected || !peer2->connected) { if (!peer1->connected || !peer2->connected) {
fprintf(stderr, "DataChannel is not connected\n"); fprintf(stderr, "DataChannel is not connected\n");
goto error; goto error;