mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
150 Commits
Author | SHA1 | Date | |
---|---|---|---|
ff0f409d80 | |||
a483e8135b | |||
36e4fdce1e | |||
ea636d1f29 | |||
472d480978 | |||
486fc373b2 | |||
2a6c10269e | |||
ed68ba5402 | |||
5f91c0c1e3 | |||
d4c618ae38 | |||
3f52aa3d56 | |||
c16ff99d83 | |||
e14b53f348 | |||
ec8847cbf8 | |||
6c4e8f0d46 | |||
e2a5d5e1fe | |||
fa8fda25c8 | |||
47c29f0ec1 | |||
a92438e94d | |||
0729ab28fd | |||
fa64b67006 | |||
b4d99158c6 | |||
0ded19992c | |||
2dece6afff | |||
0b554f988e | |||
15d29fc038 | |||
bb2c6c157d | |||
fd0f237a59 | |||
f09006f3ef | |||
06369f1f14 | |||
61354b7101 | |||
f34791b450 | |||
c3f2f6bc63 | |||
a683b76a21 | |||
e11de119be | |||
2129e3cfb9 | |||
58c8bad453 | |||
1566c0ef21 | |||
03399e4b55 | |||
0066b3aef0 | |||
75f23f202f | |||
23e1a75248 | |||
f930cfbe44 | |||
72a0e2fe07 | |||
f90ffbcf86 | |||
a6992c765d | |||
1602498eab | |||
e4ace4a750 | |||
b5a13d2d66 | |||
aeb777aa49 | |||
7a552bb0fa | |||
402a4df4a0 | |||
34ef87e271 | |||
522319ac5d | |||
1ac00ce396 | |||
92f08948d3 | |||
9749f8d63e | |||
8626a07824 | |||
37ca38999c | |||
18eeac3c0c | |||
c7de492b4b | |||
901700177b | |||
88348732d9 | |||
281eea2cec | |||
28f923b1ce | |||
48bdb6a1c9 | |||
278ac22766 | |||
4fb14244db | |||
432be41b9a | |||
78c80992bc | |||
8b64f8a406 | |||
1d7d1358be | |||
5fc6a1c8ad | |||
e5a19f85ed | |||
5a8725dac1 | |||
cd66a3f987 | |||
fc4091a9fc | |||
7a49a0cfd8 | |||
de5aff68e6 | |||
5416b66116 | |||
8b94f22aca | |||
1ab81731e3 | |||
5213f12f1a | |||
c5e25bbdbc | |||
58eea3fcf6 | |||
59517cb0da | |||
e77586fc81 | |||
cafc674689 | |||
96f08cb3c8 | |||
3220bf8ae1 | |||
7236c06880 | |||
89ff113688 | |||
aa55aa76df | |||
2c0955fe57 | |||
d7e417ce3f | |||
1df2fa559c | |||
2d5d2f0486 | |||
e75ae36ba8 | |||
585450d13e | |||
bc0666be05 | |||
b14518238a | |||
04df12b581 | |||
08931de03b | |||
e6da6a185f | |||
87bf676428 | |||
4029a9bb4a | |||
a1df562785 | |||
5d57b4e214 | |||
abdf61e841 | |||
d9bfcbd6be | |||
e55d4e906b | |||
6363361e0c | |||
21f43611b6 | |||
b20f3b30c0 | |||
fcb1a2571d | |||
900c482146 | |||
d27ed8aab0 | |||
84219d381d | |||
75735fb8d8 | |||
d2b0d1e07f | |||
6f09bc7a17 | |||
ac6cae8fc4 | |||
000bef45f6 | |||
c2bba83254 | |||
5839e9d3db | |||
2ff361ab29 | |||
4f6bdc5135 | |||
65e584107c | |||
cd0f17e36d | |||
71bdc94804 | |||
648644895c | |||
2e59e44a83 | |||
3afc127750 | |||
44cdbab8dc | |||
f083815569 | |||
6f0bcbb1e6 | |||
ae46162649 | |||
64ed232d1b | |||
0e2c992d1c | |||
a10d47499b | |||
3528432c5c | |||
dd3012ac35 | |||
640144e01d | |||
dc7a59503a | |||
c5f7502397 | |||
c8f2b79015 | |||
defc230dba | |||
81308b2095 | |||
5842be3442 | |||
bca4d89f93 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
build/
|
||||
*.d
|
||||
*.o
|
||||
*.a
|
||||
|
10
.gitmodules
vendored
10
.gitmodules
vendored
@ -1,3 +1,9 @@
|
||||
[submodule "deps/plog"]
|
||||
path = deps/plog
|
||||
url = https://github.com/SergiusTheBest/plog
|
||||
[submodule "usrsctp"]
|
||||
path = usrsctp
|
||||
url = https://github.com/sctplab/usrsctp.git
|
||||
path = deps/usrsctp
|
||||
url = https://github.com/paullouisageneau/usrsctp.git
|
||||
[submodule "deps/libjuice"]
|
||||
path = deps/libjuice
|
||||
url = https://github.com/paullouisageneau/libjuice
|
||||
|
143
CMakeLists.txt
Normal file
143
CMakeLists.txt
Normal file
@ -0,0 +1,143 @@
|
||||
cmake_minimum_required (VERSION 3.7)
|
||||
project (libdatachannel
|
||||
DESCRIPTION "WebRTC Data Channels Library"
|
||||
VERSION 0.4.1
|
||||
LANGUAGES CXX)
|
||||
|
||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||
option(USE_JUICE "Use libjuice instead of libnice" OFF)
|
||||
|
||||
if(USE_GNUTLS)
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
||||
else()
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
|
||||
endif()
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||
|
||||
set(LIBDATACHANNEL_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/channel.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/configuration.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/datachannel.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
||||
)
|
||||
|
||||
set(TESTS_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
|
||||
)
|
||||
|
||||
set(TESTS_OFFERER_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/offerer.cpp
|
||||
)
|
||||
|
||||
set(TESTS_ANSWERER_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp
|
||||
)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
||||
|
||||
add_library(datachannel SHARED ${LIBDATACHANNEL_SOURCES})
|
||||
set_target_properties(datachannel PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
|
||||
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||
target_link_libraries(datachannel
|
||||
Threads::Threads
|
||||
Usrsctp::UsrsctpStatic
|
||||
)
|
||||
|
||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
|
||||
set_target_properties(datachannel-static PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
|
||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||
target_link_libraries(datachannel-static
|
||||
Threads::Threads
|
||||
Usrsctp::UsrsctpStatic
|
||||
)
|
||||
|
||||
if (USE_GNUTLS)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
if(NOT TARGET GnuTLS::GnuTLS)
|
||||
add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED)
|
||||
set_target_properties(GnuTLS::GnuTLS PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${GNUTLS_INCLUDE_DIRS}"
|
||||
INTERFACE_COMPILE_DEFINITIONS "${GNUTLS_DEFINITIONS}"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
|
||||
endif()
|
||||
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
|
||||
target_link_libraries(datachannel GnuTLS::GnuTLS)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
|
||||
target_link_libraries(datachannel-static GnuTLS::GnuTLS)
|
||||
else()
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=0)
|
||||
target_link_libraries(datachannel OpenSSL::SSL)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=0)
|
||||
target_link_libraries(datachannel-static OpenSSL::SSL)
|
||||
endif()
|
||||
|
||||
if (USE_JUICE)
|
||||
add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(datachannel PRIVATE USE_JUICE=1)
|
||||
target_link_libraries(datachannel LibJuice::LibJuiceStatic)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=1)
|
||||
target_link_libraries(datachannel-static LibJuice::LibJuiceStatic)
|
||||
else()
|
||||
find_package(LibNice REQUIRED)
|
||||
target_compile_definitions(datachannel PRIVATE USE_JUICE=0)
|
||||
target_link_libraries(datachannel LibNice::LibNice)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=0)
|
||||
target_link_libraries(datachannel-static LibNice::LibNice)
|
||||
endif()
|
||||
|
||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||
|
||||
# Main Test
|
||||
add_executable(datachannel-tests ${TESTS_SOURCES})
|
||||
set_target_properties(datachannel-tests PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_link_libraries(datachannel-tests datachannel)
|
||||
|
||||
# P2P Test: offerer
|
||||
add_executable(datachannel-offerer ${TESTS_OFFERER_SOURCES})
|
||||
set_target_properties(datachannel-offerer PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-offerer PROPERTIES OUTPUT_NAME offerer)
|
||||
target_link_libraries(datachannel-offerer datachannel)
|
||||
|
||||
# P2P Test: answerer
|
||||
add_executable(datachannel-answerer ${TESTS_ANSWERER_SOURCES})
|
||||
set_target_properties(datachannel-answerer PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME datachannel)
|
||||
target_link_libraries(datachannel-answerer datachannel)
|
||||
|
65
Jamfile
Normal file
65
Jamfile
Normal file
@ -0,0 +1,65 @@
|
||||
project libdatachannel ;
|
||||
path-constant CWD : . ;
|
||||
|
||||
lib libdatachannel
|
||||
: # sources
|
||||
[ glob ./src/*.cpp ]
|
||||
: # requirements
|
||||
<include>./include/rtc
|
||||
<define>USE_GNUTLS=0
|
||||
<define>USE_JUICE=1
|
||||
<cxxflags>"`pkg-config --cflags openssl`"
|
||||
<library>/libdatachannel//usrsctp
|
||||
<library>/libdatachannel//juice
|
||||
: # default build
|
||||
<link>static
|
||||
: # usage requirements
|
||||
<include>./include
|
||||
<library>/libdatachannel//plog
|
||||
<cxxflags>-pthread
|
||||
<linkflags>"`pkg-config --libs openssl`"
|
||||
;
|
||||
|
||||
alias plog
|
||||
: # no sources
|
||||
: # no build requirements
|
||||
: # no default build
|
||||
: # usage requirements
|
||||
<include>./deps/plog/include
|
||||
;
|
||||
|
||||
alias usrsctp
|
||||
: # no sources
|
||||
: # no build requirements
|
||||
: # no default build
|
||||
: # usage requirements
|
||||
<include>./deps/usrsctp/usrsctplib
|
||||
<library>libusrsctp.a
|
||||
;
|
||||
|
||||
alias juice
|
||||
: # no sources
|
||||
: # no build requirements
|
||||
: # no default build
|
||||
: # usage requirements
|
||||
<include>./deps/libjuice/include
|
||||
<library>libjuice.a
|
||||
;
|
||||
|
||||
make libusrsctp.a : : @make_libusrsctp ;
|
||||
actions make_libusrsctp
|
||||
{
|
||||
(cd $(CWD)/deps/usrsctp && \
|
||||
./bootstrap && \
|
||||
./configure --enable-static --disable-debug CFLAGS="-fPIC -Wno-address-of-packed-member" && \
|
||||
make)
|
||||
cp $(CWD)/deps/usrsctp/usrsctplib/.libs/libusrsctp.a $(<)
|
||||
}
|
||||
|
||||
make libjuice.a : : @make_libjuice ;
|
||||
actions make_libjuice
|
||||
{
|
||||
(cd $(CWD)/deps/libjuice && make USE_NETTLE=0)
|
||||
cp $(CWD)/deps/libjuice/libjuice.a $(<)
|
||||
}
|
||||
|
56
Makefile
56
Makefile
@ -7,11 +7,39 @@ RM=rm -f
|
||||
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
|
||||
CXXFLAGS=-std=c++17
|
||||
LDFLAGS=-pthread
|
||||
LDLIBS= -lgnutls $(shell pkg-config --libs glib-2.0 gobject-2.0 nice)
|
||||
INCLUDES=-Iinclude/rtc -I$(USRSCTP_DIR)/usrsctplib $(shell pkg-config --cflags glib-2.0 gobject-2.0 nice)
|
||||
LIBS=
|
||||
LOCALLIBS=libusrsctp.a
|
||||
USRSCTP_DIR=deps/usrsctp
|
||||
JUICE_DIR=deps/libjuice
|
||||
PLOG_DIR=deps/plog
|
||||
|
||||
USRSCTP_DIR:=usrsctp
|
||||
USRSCTP_DEFINES:=-DINET -DINET6
|
||||
INCLUDES=-Iinclude/rtc -I$(PLOG_DIR)/include -I$(USRSCTP_DIR)/usrsctplib
|
||||
LDLIBS=
|
||||
|
||||
USE_GNUTLS ?= 0
|
||||
ifneq ($(USE_GNUTLS), 0)
|
||||
CPPFLAGS+=-DUSE_GNUTLS=1
|
||||
LIBS+=gnutls
|
||||
else
|
||||
CPPFLAGS+=-DUSE_GNUTLS=0
|
||||
LIBS+=openssl
|
||||
endif
|
||||
|
||||
USE_JUICE ?= 0
|
||||
ifneq ($(USE_JUICE), 0)
|
||||
CPPFLAGS+=-DUSE_JUICE=1
|
||||
INCLUDES+=-I$(JUICE_DIR)/include
|
||||
LOCALLIBS+=libjuice.a
|
||||
ifneq ($(USE_GNUTLS), 0)
|
||||
LIBS+=nettle
|
||||
endif
|
||||
else
|
||||
CPPFLAGS+=-DUSE_JUICE=0
|
||||
LIBS+=glib-2.0 gobject-2.0 nice
|
||||
endif
|
||||
|
||||
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
|
||||
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
||||
|
||||
SRCS=$(shell printf "%s " src/*.cpp)
|
||||
OBJS=$(subst .cpp,.o,$(SRCS))
|
||||
@ -19,21 +47,21 @@ OBJS=$(subst .cpp,.o,$(SRCS))
|
||||
all: $(NAME).a $(NAME).so tests
|
||||
|
||||
src/%.o: src/%.cpp
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) $(USRSCTP_DEFINES) -MMD -MP -o $@ -c $<
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) -MMD -MP -o $@ -c $<
|
||||
|
||||
test/%.o: test/%.cpp
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -Iinclude -MMD -MP -o $@ -c $<
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) -Iinclude -Isrc -MMD -MP -o $@ -c $<
|
||||
|
||||
-include $(subst .cpp,.d,$(SRCS))
|
||||
|
||||
$(NAME).a: $(OBJS)
|
||||
$(AR) crf $@ $(OBJS)
|
||||
|
||||
$(NAME).so: libusrsctp.a $(OBJS)
|
||||
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS) libusrsctp.a
|
||||
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
||||
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
|
||||
|
||||
tests: $(NAME).a test/main.o
|
||||
$(CXX) $(LDFLAGS) -o $@ test/main.o $(LDLIBS) $(NAME).a libusrsctp.a
|
||||
$(CXX) $(LDFLAGS) -o $@ test/main.o $(NAME).a $(LDLIBS)
|
||||
|
||||
clean:
|
||||
-$(RM) include/rtc/*.d *.d
|
||||
@ -44,11 +72,13 @@ dist-clean: clean
|
||||
-$(RM) $(NAME).a
|
||||
-$(RM) $(NAME).so
|
||||
-$(RM) libusrsctp.a
|
||||
-$(RM) libjuice.a
|
||||
-$(RM) tests
|
||||
-$(RM) include/*~
|
||||
-$(RM) src/*~
|
||||
-$(RM) test/*~
|
||||
-cd $(USRSCTP_DIR) && make clean
|
||||
-cd $(JUICE_DIR) && make clean
|
||||
|
||||
libusrsctp.a:
|
||||
cd $(USRSCTP_DIR) && \
|
||||
@ -57,3 +87,11 @@ libusrsctp.a:
|
||||
make
|
||||
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
||||
|
||||
libjuice.a:
|
||||
ifneq ($(USE_GNUTLS), 0)
|
||||
cd $(JUICE_DIR) && make USE_NETTLE=1
|
||||
else
|
||||
cd $(JUICE_DIR) && make USE_NETTLE=0
|
||||
endif
|
||||
cp $(JUICE_DIR)/libjuice.a .
|
||||
|
||||
|
23
README.md
23
README.md
@ -4,27 +4,43 @@ libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 wi
|
||||
|
||||
This projet is originally inspired by [librtcdcpp](https://github.com/chadnickbok/librtcdcpp), however it is a complete rewrite from scratch, because the messy architecture of librtcdcpp made solving its implementation issues difficult.
|
||||
|
||||
The connectivity can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice). The security layer can be provided through [GnuTLS](https://www.gnutls.org/) or [OpenSSL](https://www.openssl.org/).
|
||||
|
||||
Licensed under LGPLv2, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE).
|
||||
|
||||
## Compatibility
|
||||
|
||||
This implementation has been tested to be compatible with Firefox and Chromium. It supports IPv6 and Multicast DNS candidates resolution provided the operating system also supports it.
|
||||
The library aims at fully implementing WebRTC SCTP DataChannels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13)) over DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261)) with ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)). It has been tested to be compatible with Firefox and Chromium. It supports IPv6 and Multicast DNS candidates resolution ([draft-ietf-rtcweb-mdns-ice-candidates-03](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03)) provided the operating system also supports it.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- libnice: https://github.com/libnice/libnice
|
||||
- GnuTLS: https://www.gnutls.org/
|
||||
- GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/
|
||||
|
||||
Optional:
|
||||
- libnice: https://nice.freedesktop.org/ (substituable with libjuice)
|
||||
|
||||
Submodules:
|
||||
- usrsctp: https://github.com/sctplab/usrsctp
|
||||
- libjuice: https://github.com/paullouisageneau/libjuice
|
||||
|
||||
## Building
|
||||
### Building with CMake (preferred)
|
||||
|
||||
```bash
|
||||
$ git submodule update --init --recursive
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake -DUSE_JUICE=1 -DUSE_GNUTLS=1 ..
|
||||
$ make
|
||||
```
|
||||
|
||||
### Building directly with Make
|
||||
|
||||
```bash
|
||||
$ git submodule update --init --recursive
|
||||
$ make USE_JUICE=1 USE_GNUTLS=1
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
In the following example, note the callbacks are called in another thread.
|
||||
@ -47,6 +63,7 @@ pc->onLocalDescription([](const rtc::Description &sdp) {
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const rtc::Candidate &candidate) {
|
||||
// Send the candidate to the remote peer
|
||||
MY_SEND_CANDIDATE_TO_REMOTE(candidate.candidate(), candidate.mid());
|
||||
});
|
||||
|
||||
|
123
cmake/Modules/FindGLIB.cmake
Normal file
123
cmake/Modules/FindGLIB.cmake
Normal file
@ -0,0 +1,123 @@
|
||||
# - Try to find Glib and its components (gio, gobject etc)
|
||||
# Once done, this will define
|
||||
#
|
||||
# GLIB_FOUND - system has Glib
|
||||
# GLIB_INCLUDE_DIRS - the Glib include directories
|
||||
# GLIB_LIBRARIES - link these to use Glib
|
||||
#
|
||||
# Optionally, the COMPONENTS keyword can be passed to find_package()
|
||||
# and Glib components can be looked for. Currently, the following
|
||||
# components can be used, and they define the following variables if
|
||||
# found:
|
||||
#
|
||||
# gio: GLIB_GIO_LIBRARIES
|
||||
# gobject: GLIB_GOBJECT_LIBRARIES
|
||||
# gmodule: GLIB_GMODULE_LIBRARIES
|
||||
# gthread: GLIB_GTHREAD_LIBRARIES
|
||||
#
|
||||
# Note that the respective _INCLUDE_DIR variables are not set, since
|
||||
# all headers are in the same directory as GLIB_INCLUDE_DIRS.
|
||||
#
|
||||
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
|
||||
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_GLIB QUIET glib-2.0)
|
||||
|
||||
find_library(GLIB_LIBRARIES
|
||||
NAMES glib-2.0
|
||||
HINTS ${PC_GLIB_LIBDIR}
|
||||
${PC_GLIB_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
# Files in glib's main include path may include glibconfig.h, which,
|
||||
# for some odd reason, is normally in $LIBDIR/glib-2.0/include.
|
||||
get_filename_component(_GLIB_LIBRARY_DIR ${GLIB_LIBRARIES} PATH)
|
||||
find_path(GLIBCONFIG_INCLUDE_DIR
|
||||
NAMES glibconfig.h
|
||||
HINTS ${PC_LIBDIR} ${PC_LIBRARY_DIRS} ${_GLIB_LIBRARY_DIR}
|
||||
${PC_GLIB_INCLUDEDIR} ${PC_GLIB_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES glib-2.0/include
|
||||
)
|
||||
|
||||
find_path(GLIB_INCLUDE_DIR
|
||||
NAMES glib.h
|
||||
HINTS ${PC_GLIB_INCLUDEDIR}
|
||||
${PC_GLIB_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES glib-2.0
|
||||
)
|
||||
|
||||
set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR} ${GLIBCONFIG_INCLUDE_DIR})
|
||||
|
||||
# Version detection
|
||||
if (EXISTS "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h")
|
||||
file(READ "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h" GLIBCONFIG_H_CONTENTS)
|
||||
string(REGEX MATCH "#define GLIB_MAJOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}")
|
||||
set(GLIB_VERSION_MAJOR "${CMAKE_MATCH_1}")
|
||||
string(REGEX MATCH "#define GLIB_MINOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}")
|
||||
set(GLIB_VERSION_MINOR "${CMAKE_MATCH_1}")
|
||||
string(REGEX MATCH "#define GLIB_MICRO_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}")
|
||||
set(GLIB_VERSION_MICRO "${CMAKE_MATCH_1}")
|
||||
set(GLIB_VERSION "${GLIB_VERSION_MAJOR}.${GLIB_VERSION_MINOR}.${GLIB_VERSION_MICRO}")
|
||||
endif ()
|
||||
|
||||
# Additional Glib components. We only look for libraries, as not all of them
|
||||
# have corresponding headers and all headers are installed alongside the main
|
||||
# glib ones.
|
||||
foreach (_component ${GLIB_FIND_COMPONENTS})
|
||||
if (${_component} STREQUAL "gio")
|
||||
find_library(GLIB_GIO_LIBRARIES NAMES gio-2.0 HINTS ${_GLIB_LIBRARY_DIR})
|
||||
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GIO_LIBRARIES)
|
||||
elseif (${_component} STREQUAL "gobject")
|
||||
find_library(GLIB_GOBJECT_LIBRARIES NAMES gobject-2.0 HINTS ${_GLIB_LIBRARY_DIR})
|
||||
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GOBJECT_LIBRARIES)
|
||||
elseif (${_component} STREQUAL "gmodule")
|
||||
find_library(GLIB_GMODULE_LIBRARIES NAMES gmodule-2.0 HINTS ${_GLIB_LIBRARY_DIR})
|
||||
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GMODULE_LIBRARIES)
|
||||
elseif (${_component} STREQUAL "gthread")
|
||||
find_library(GLIB_GTHREAD_LIBRARIES NAMES gthread-2.0 HINTS ${_GLIB_LIBRARY_DIR})
|
||||
set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GTHREAD_LIBRARIES)
|
||||
elseif (${_component} STREQUAL "gio-unix")
|
||||
# gio-unix is compiled as part of the gio library, but the include paths
|
||||
# are separate from the shared glib ones. Since this is currently only used
|
||||
# by WebKitGTK we don't go to extraordinary measures beyond pkg-config.
|
||||
pkg_check_modules(GIO_UNIX QUIET gio-unix-2.0)
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GLIB REQUIRED_VARS GLIB_INCLUDE_DIRS GLIB_LIBRARIES ${ADDITIONAL_REQUIRED_VARS}
|
||||
VERSION_VAR GLIB_VERSION)
|
||||
|
||||
mark_as_advanced(
|
||||
GLIBCONFIG_INCLUDE_DIR
|
||||
GLIB_GIO_LIBRARIES
|
||||
GLIB_GIO_UNIX_LIBRARIES
|
||||
GLIB_GMODULE_LIBRARIES
|
||||
GLIB_GOBJECT_LIBRARIES
|
||||
GLIB_GTHREAD_LIBRARIES
|
||||
GLIB_INCLUDE_DIR
|
||||
GLIB_INCLUDE_DIRS
|
||||
GLIB_LIBRARIES
|
||||
)
|
||||
|
35
cmake/Modules/FindLibNice.cmake
Normal file
35
cmake/Modules/FindLibNice.cmake
Normal file
@ -0,0 +1,35 @@
|
||||
if (NOT TARGET LibNice::LibNice)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_LIBNICE nice)
|
||||
set(LIBNICE_DEFINITIONS ${PC_LIBNICE_CFLAGS_OTHER})
|
||||
|
||||
find_path(LIBNICE_INCLUDE_DIR nice/agent.h
|
||||
HINTS ${PC_LIBNICE_INCLUDEDIR} ${PC_LIBNICE_INCLUDE_DIRS}
|
||||
PATH_SUFFICES libnice)
|
||||
find_library(LIBNICE_LIBRARY NAMES nice libnice
|
||||
HINTS ${PC_LIBNICE_LIBDIR} ${PC_LIBNICE_LIBRARY_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Libnice DEFAULT_MSG
|
||||
LIBNICE_LIBRARY LIBNICE_INCLUDE_DIR)
|
||||
mark_as_advanced(LIBNICE_INCLUDE_DIR LIBNICE_LIBRARY)
|
||||
|
||||
set(LIBNICE_LIBRARIES ${LIBNICE_LIBRARY})
|
||||
set(LIBNICE_INCLUDE_DIRS ${LIBNICE_INCLUDE_DIR})
|
||||
|
||||
find_package(GLIB REQUIRED COMPONENTS gio gobject gmodule gthread)
|
||||
|
||||
list(APPEND LIBNICE_INCLUDE_DIRS ${GLIB_INCLUDE_DIRS})
|
||||
list(APPEND LIBNICE_LIBRARIES ${GLIB_GOBJECT_LIBRARIES} ${GLIB_LIBRARIES})
|
||||
|
||||
if (LIBNICE_FOUND)
|
||||
add_library(LibNice::LibNice UNKNOWN IMPORTED)
|
||||
set_target_properties(LibNice::LibNice PROPERTIES
|
||||
IMPORTED_LOCATION "${LIBNICE_LIBRARY}"
|
||||
INTERFACE_COMPILE_DEFINITIONS "_REENTRANT"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LIBNICE_INCLUDE_DIRS}"
|
||||
INTERFACE_LINK_LIBRARIES "${LIBNICE_LIBRARIES}"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C")
|
||||
endif ()
|
||||
endif ()
|
||||
|
1
deps/libjuice
vendored
Submodule
1
deps/libjuice
vendored
Submodule
Submodule deps/libjuice added at 25b71f18dc
1
deps/plog
vendored
Submodule
1
deps/plog
vendored
Submodule
Submodule deps/plog added at 2931644689
1
deps/usrsctp
vendored
Submodule
1
deps/usrsctp
vendored
Submodule
Submodule deps/usrsctp added at 8768f70504
@ -29,6 +29,10 @@ class Candidate {
|
||||
public:
|
||||
Candidate(string candidate, string mid = "");
|
||||
|
||||
enum class ResolveMode { Simple, Lookup };
|
||||
bool resolve(ResolveMode mode = ResolveMode::Simple);
|
||||
bool isResolved() const;
|
||||
|
||||
string candidate() const;
|
||||
string mid() const;
|
||||
operator string() const;
|
||||
@ -36,6 +40,7 @@ public:
|
||||
private:
|
||||
string mCandidate;
|
||||
string mMid;
|
||||
bool mIsResolved;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "include.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <variant>
|
||||
|
||||
@ -28,32 +29,49 @@ namespace rtc {
|
||||
|
||||
class Channel {
|
||||
public:
|
||||
virtual void close(void) = 0;
|
||||
virtual void send(const std::variant<binary, string> &data) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual bool send(const std::variant<binary, string> &data) = 0; // returns false if buffered
|
||||
virtual std::optional<std::variant<binary, string>> receive() = 0; // only if onMessage unset
|
||||
|
||||
virtual bool isOpen(void) const = 0;
|
||||
virtual bool isClosed(void) const = 0;
|
||||
virtual bool isOpen() const = 0;
|
||||
virtual bool isClosed() const = 0;
|
||||
|
||||
virtual size_t availableAmount() const; // total size available to receive
|
||||
virtual size_t bufferedAmount() const; // total size buffered to send
|
||||
|
||||
void onOpen(std::function<void()> callback);
|
||||
void onClosed(std::function<void()> callback);
|
||||
void onError(std::function<void(const string &error)> callback);
|
||||
|
||||
void onMessage(std::function<void(const std::variant<binary, string> &data)> callback);
|
||||
void onMessage(std::function<void(const binary &data)> binaryCallback,
|
||||
std::function<void(const string &data)> stringCallback);
|
||||
|
||||
void onAvailable(std::function<void()> callback);
|
||||
void onBufferedAmountLow(std::function<void()> callback);
|
||||
|
||||
void setBufferedAmountLowThreshold(size_t amount);
|
||||
|
||||
protected:
|
||||
virtual void triggerOpen(void);
|
||||
virtual void triggerClosed(void);
|
||||
virtual void triggerOpen();
|
||||
virtual void triggerClosed();
|
||||
virtual void triggerError(const string &error);
|
||||
virtual void triggerMessage(const std::variant<binary, string> &data);
|
||||
virtual void triggerAvailable(size_t count);
|
||||
virtual void triggerBufferedAmount(size_t amount);
|
||||
|
||||
private:
|
||||
std::function<void()> mOpenCallback;
|
||||
std::function<void()> mClosedCallback;
|
||||
std::function<void(const string &)> mErrorCallback;
|
||||
std::function<void(const std::variant<binary, string> &)> mMessageCallback;
|
||||
synchronized_callback<> mOpenCallback;
|
||||
synchronized_callback<> mClosedCallback;
|
||||
synchronized_callback<const string &> mErrorCallback;
|
||||
synchronized_callback<const std::variant<binary, string> &> mMessageCallback;
|
||||
synchronized_callback<> mAvailableCallback;
|
||||
synchronized_callback<> mBufferedAmountLowCallback;
|
||||
|
||||
std::atomic<size_t> mBufferedAmount = 0;
|
||||
std::atomic<size_t> mBufferedAmountLowThreshold = 0;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif // RTC_CHANNEL_H
|
||||
|
||||
|
@ -27,16 +27,33 @@
|
||||
namespace rtc {
|
||||
|
||||
struct IceServer {
|
||||
IceServer(const string &host_);
|
||||
IceServer(const string &hostname_, uint16_t port_);
|
||||
IceServer(const string &hostname_, const string &service_);
|
||||
enum class Type { Stun, Turn };
|
||||
enum class RelayType { TurnUdp, TurnTcp, TurnTls };
|
||||
|
||||
// Any type
|
||||
IceServer(const string &url);
|
||||
|
||||
// STUN
|
||||
IceServer(string hostname_, uint16_t port_);
|
||||
IceServer(string hostname_, string service_);
|
||||
|
||||
// TURN
|
||||
IceServer(string hostname_, uint16_t port, string username_, string password_,
|
||||
RelayType relayType_ = RelayType::TurnUdp);
|
||||
IceServer(string hostname_, string service_, string username_, string password_,
|
||||
RelayType relayType_ = RelayType::TurnUdp);
|
||||
|
||||
string hostname;
|
||||
string service;
|
||||
Type type;
|
||||
string username;
|
||||
string password;
|
||||
RelayType relayType;
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
std::vector<IceServer> iceServers;
|
||||
bool enableIceTcp = false;
|
||||
uint16_t portRangeBegin = 1024;
|
||||
uint16_t portRangeEnd = 65535;
|
||||
};
|
||||
|
@ -22,10 +22,13 @@
|
||||
#include "channel.hpp"
|
||||
#include "include.hpp"
|
||||
#include "message.hpp"
|
||||
#include "queue.hpp"
|
||||
#include "reliability.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
namespace rtc {
|
||||
@ -33,41 +36,87 @@ namespace rtc {
|
||||
class SctpTransport;
|
||||
class PeerConnection;
|
||||
|
||||
class DataChannel : public Channel {
|
||||
class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel {
|
||||
public:
|
||||
DataChannel(unsigned int stream_, string label_, string protocol_, Reliability reliability_);
|
||||
DataChannel(unsigned int stream, std::shared_ptr<SctpTransport> sctpTransport);
|
||||
DataChannel(std::shared_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||
string protocol, Reliability reliability);
|
||||
DataChannel(std::shared_ptr<PeerConnection> pc, std::shared_ptr<SctpTransport> transport,
|
||||
unsigned int stream);
|
||||
~DataChannel();
|
||||
|
||||
void close(void);
|
||||
void send(const std::variant<binary, string> &data);
|
||||
void send(const byte *data, size_t size);
|
||||
void close(void) override;
|
||||
|
||||
bool send(const std::variant<binary, string> &data) override;
|
||||
bool send(const byte *data, size_t size);
|
||||
|
||||
template <typename Buffer> bool sendBuffer(const Buffer &buf);
|
||||
template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
|
||||
|
||||
std::optional<std::variant<binary, string>> receive() override;
|
||||
|
||||
bool isOpen(void) const override;
|
||||
bool isClosed(void) const override;
|
||||
size_t availableAmount() const override;
|
||||
|
||||
size_t maxMessageSize() const; // maximum message size in a call to send or sendBuffer
|
||||
|
||||
unsigned int stream() const;
|
||||
string label() const;
|
||||
string protocol() const;
|
||||
Reliability reliability() const;
|
||||
|
||||
bool isOpen(void) const;
|
||||
bool isClosed(void) const;
|
||||
|
||||
private:
|
||||
void remoteClose();
|
||||
void open(std::shared_ptr<SctpTransport> sctpTransport);
|
||||
bool outgoing(mutable_message_ptr message);
|
||||
void incoming(message_ptr message);
|
||||
void processOpenMessage(message_ptr message);
|
||||
|
||||
const unsigned int mStream;
|
||||
const std::shared_ptr<PeerConnection> mPeerConnection;
|
||||
std::shared_ptr<SctpTransport> mSctpTransport;
|
||||
|
||||
unsigned int mStream;
|
||||
string mLabel;
|
||||
string mProtocol;
|
||||
std::shared_ptr<Reliability> mReliability;
|
||||
|
||||
bool mIsOpen = false;
|
||||
bool mIsClosed = false;
|
||||
std::atomic<bool> mIsOpen = false;
|
||||
std::atomic<bool> mIsClosed = false;
|
||||
|
||||
Queue<message_ptr> mRecvQueue;
|
||||
std::atomic<size_t> mRecvAmount = 0;
|
||||
|
||||
friend class PeerConnection;
|
||||
};
|
||||
|
||||
template <typename Buffer> std::pair<const byte *, size_t> to_bytes(const Buffer &buf) {
|
||||
using T = typename std::remove_pointer<decltype(buf.data())>::type;
|
||||
using E = typename std::conditional<std::is_void<T>::value, byte, T>::type;
|
||||
return std::make_pair(static_cast<const byte *>(static_cast<const void *>(buf.data())),
|
||||
buf.size() * sizeof(E));
|
||||
}
|
||||
|
||||
template <typename Buffer> bool DataChannel::sendBuffer(const Buffer &buf) {
|
||||
auto [bytes, size] = to_bytes(buf);
|
||||
auto message = std::make_shared<Message>(size);
|
||||
std::copy(bytes, bytes + size, message->data());
|
||||
return outgoing(message);
|
||||
}
|
||||
|
||||
template <typename Iterator> bool DataChannel::sendBuffer(Iterator first, Iterator last) {
|
||||
size_t size = 0;
|
||||
for (Iterator it = first; it != last; ++it)
|
||||
size += it->size();
|
||||
|
||||
auto message = std::make_shared<Message>(size);
|
||||
auto pos = message->begin();
|
||||
for (Iterator it = first; it != last; ++it) {
|
||||
auto [bytes, size] = to_bytes(*it);
|
||||
pos = std::copy(bytes, bytes + size, pos);
|
||||
}
|
||||
return outgoing(message);
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -44,14 +44,21 @@ public:
|
||||
string mid() const;
|
||||
std::optional<string> fingerprint() const;
|
||||
std::optional<uint16_t> sctpPort() const;
|
||||
std::optional<size_t> maxMessageSize() const;
|
||||
bool trickleEnabled() const;
|
||||
|
||||
void setFingerprint(string fingerprint);
|
||||
void setSctpPort(uint16_t port);
|
||||
void setMaxMessageSize(size_t size);
|
||||
|
||||
void addCandidate(Candidate candidate);
|
||||
void endCandidates();
|
||||
std::vector<Candidate> extractCandidates();
|
||||
|
||||
operator string() const;
|
||||
|
||||
string generateSdp(const string &eol) const;
|
||||
|
||||
private:
|
||||
Type mType;
|
||||
Role mRole;
|
||||
@ -60,6 +67,7 @@ private:
|
||||
string mIceUfrag, mIcePwd;
|
||||
std::optional<string> mFingerprint;
|
||||
std::optional<uint16_t> mSctpPort;
|
||||
std::optional<size_t> mMaxMessageSize;
|
||||
std::vector<Candidate> mCandidates;
|
||||
bool mTrickle;
|
||||
|
||||
|
@ -20,10 +20,16 @@
|
||||
#define RTC_INCLUDE_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "plog/Appenders/ColorConsoleAppender.h"
|
||||
#include "plog/Log.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
using std::byte;
|
||||
@ -32,18 +38,71 @@ using binary = std::vector<byte>;
|
||||
|
||||
using std::nullopt;
|
||||
|
||||
using std::size_t;
|
||||
using std::uint16_t;
|
||||
using std::uint32_t;
|
||||
using std::uint64_t;
|
||||
using std::uint8_t;
|
||||
|
||||
// Constants
|
||||
|
||||
const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
|
||||
const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length
|
||||
|
||||
const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
|
||||
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
|
||||
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
|
||||
|
||||
// Log
|
||||
|
||||
enum class LogLevel { // Don't change, it must match plog severity
|
||||
None = 0,
|
||||
Fatal = 1,
|
||||
Error = 2,
|
||||
Warning = 3,
|
||||
Info = 4,
|
||||
Debug = 5,
|
||||
Verbose = 6
|
||||
};
|
||||
|
||||
inline void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr) {
|
||||
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
|
||||
if (!appender)
|
||||
appender = &consoleAppender;
|
||||
plog::init(severity, appender);
|
||||
PLOG_DEBUG << "Logger initialized";
|
||||
}
|
||||
|
||||
inline void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
|
||||
|
||||
// Utils
|
||||
|
||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
||||
|
||||
template <typename... P> class synchronized_callback {
|
||||
public:
|
||||
synchronized_callback() = default;
|
||||
~synchronized_callback() { *this = nullptr; }
|
||||
|
||||
synchronized_callback &operator=(std::function<void(P...)> func) {
|
||||
std::lock_guard lock(mutex);
|
||||
callback = func;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator()(P... args) const {
|
||||
std::lock_guard lock(mutex);
|
||||
if (callback)
|
||||
callback(args...);
|
||||
}
|
||||
|
||||
operator bool() const { return callback ? true : false; }
|
||||
|
||||
private:
|
||||
std::function<void(P...)> callback;
|
||||
mutable std::recursive_mutex mutex;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -30,24 +30,33 @@ namespace rtc {
|
||||
struct Message : binary {
|
||||
enum Type { Binary, String, Control };
|
||||
|
||||
Message(size_t size) : binary(size), type(Binary) {}
|
||||
|
||||
template <typename Iterator>
|
||||
Message(Iterator begin_, Iterator end_, Type type_ = Binary, unsigned int stream_ = 0,
|
||||
std::shared_ptr<Reliability> reliability_ = nullptr)
|
||||
: binary(begin_, end_), type(type_), stream(stream_), reliability(reliability_) {}
|
||||
Message(Iterator begin_, Iterator end_, Type type_ = Binary)
|
||||
: binary(begin_, end_), type(type_) {}
|
||||
|
||||
Type type;
|
||||
unsigned int stream;
|
||||
unsigned int stream = 0;
|
||||
std::shared_ptr<Reliability> reliability;
|
||||
};
|
||||
|
||||
using message_ptr = std::shared_ptr<const Message>;
|
||||
using mutable_message_ptr = std::shared_ptr<Message>;
|
||||
using message_callback = std::function<void(message_ptr message)>;
|
||||
|
||||
constexpr auto message_size_func = [](const message_ptr &m) -> size_t {
|
||||
return m->type != Message::Control ? m->size() : 0;
|
||||
};
|
||||
|
||||
template <typename Iterator>
|
||||
message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Message::Binary,
|
||||
unsigned int stream = 0,
|
||||
std::shared_ptr<Reliability> reliability = nullptr) {
|
||||
return std::make_shared<Message>(begin, end, type, stream, reliability);
|
||||
auto message = std::make_shared<Message>(begin, end, type);
|
||||
message->stream = stream;
|
||||
message->reliability = reliability;
|
||||
return message;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -30,6 +30,9 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace rtc {
|
||||
@ -39,7 +42,7 @@ class IceTransport;
|
||||
class DtlsTransport;
|
||||
class SctpTransport;
|
||||
|
||||
class PeerConnection {
|
||||
class PeerConnection : public std::enable_shared_from_this<PeerConnection> {
|
||||
public:
|
||||
enum class State : int {
|
||||
New = RTC_NEW,
|
||||
@ -47,7 +50,8 @@ public:
|
||||
Connected = RTC_CONNECTED,
|
||||
Disconnected = RTC_DISCONNECTED,
|
||||
Failed = RTC_FAILED,
|
||||
Closed = RTC_CLOSED
|
||||
Closed = RTC_CLOSED,
|
||||
Destroying = RTC_DESTROYING
|
||||
};
|
||||
|
||||
enum class GatheringState : int {
|
||||
@ -60,11 +64,15 @@ public:
|
||||
PeerConnection(const Configuration &config);
|
||||
~PeerConnection();
|
||||
|
||||
void close();
|
||||
|
||||
const Configuration *config() const;
|
||||
State state() const;
|
||||
GatheringState gatheringState() const;
|
||||
std::optional<Description> localDescription() const;
|
||||
std::optional<Description> remoteDescription() const;
|
||||
std::optional<string> localAddress() const;
|
||||
std::optional<string> remoteAddress() const;
|
||||
|
||||
void setRemoteDescription(Description description);
|
||||
void addRemoteCandidate(Candidate candidate);
|
||||
@ -79,42 +87,46 @@ public:
|
||||
void onGatheringStateChange(std::function<void(GatheringState state)> callback);
|
||||
|
||||
private:
|
||||
void initIceTransport(Description::Role role);
|
||||
void initDtlsTransport();
|
||||
void initSctpTransport();
|
||||
std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
|
||||
std::shared_ptr<DtlsTransport> initDtlsTransport();
|
||||
std::shared_ptr<SctpTransport> initSctpTransport();
|
||||
|
||||
void endLocalCandidates();
|
||||
bool checkFingerprint(const std::string &fingerprint) const;
|
||||
void forwardMessage(message_ptr message);
|
||||
void forwardBufferedAmount(uint16_t stream, size_t amount);
|
||||
void iterateDataChannels(std::function<void(std::shared_ptr<DataChannel> channel)> func);
|
||||
void openDataChannels();
|
||||
void closeDataChannels();
|
||||
void remoteCloseDataChannels();
|
||||
|
||||
void processLocalDescription(Description description);
|
||||
void processLocalCandidate(Candidate candidate);
|
||||
void triggerDataChannel(std::shared_ptr<DataChannel> dataChannel);
|
||||
void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
|
||||
void changeState(State state);
|
||||
void changeGatheringState(GatheringState state);
|
||||
|
||||
const Configuration mConfig;
|
||||
const std::shared_ptr<Certificate> mCertificate;
|
||||
|
||||
std::optional<Description> mLocalDescription;
|
||||
std::optional<Description> mRemoteDescription;
|
||||
std::optional<Description> mLocalDescription, mRemoteDescription;
|
||||
mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
||||
|
||||
std::shared_ptr<IceTransport> mIceTransport;
|
||||
std::shared_ptr<DtlsTransport> mDtlsTransport;
|
||||
std::shared_ptr<SctpTransport> mSctpTransport;
|
||||
std::recursive_mutex mInitMutex;
|
||||
|
||||
std::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels;
|
||||
|
||||
std::atomic<State> mState;
|
||||
std::atomic<GatheringState> mGatheringState;
|
||||
|
||||
std::function<void(std::shared_ptr<DataChannel> dataChannel)> mDataChannelCallback;
|
||||
std::function<void(const Description &description)> mLocalDescriptionCallback;
|
||||
std::function<void(const Candidate &candidate)> mLocalCandidateCallback;
|
||||
std::function<void(State state)> mStateChangeCallback;
|
||||
std::function<void(GatheringState state)> mGatheringStateChangeCallback;
|
||||
synchronized_callback<std::shared_ptr<DataChannel>> mDataChannelCallback;
|
||||
synchronized_callback<const Description &> mLocalDescriptionCallback;
|
||||
synchronized_callback<const Candidate &> mLocalCandidateCallback;
|
||||
synchronized_callback<State> mStateChangeCallback;
|
||||
synchronized_callback<GatheringState> mGatheringStateChangeCallback;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
138
include/rtc/queue.hpp
Normal file
138
include/rtc/queue.hpp
Normal file
@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef RTC_QUEUE_H
|
||||
#define RTC_QUEUE_H
|
||||
|
||||
#include "include.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
template <typename T> class Queue {
|
||||
public:
|
||||
using amount_function = std::function<size_t(const T &element)>;
|
||||
|
||||
Queue(size_t limit = 0, amount_function func = nullptr);
|
||||
~Queue();
|
||||
|
||||
void stop();
|
||||
bool empty() const;
|
||||
size_t size() const; // elements
|
||||
size_t amount() const; // amount
|
||||
void push(const T &element);
|
||||
void push(T &&element);
|
||||
std::optional<T> pop();
|
||||
std::optional<T> peek();
|
||||
void wait();
|
||||
void wait(const std::chrono::milliseconds &duration);
|
||||
|
||||
private:
|
||||
const size_t mLimit;
|
||||
size_t mAmount;
|
||||
std::queue<T> mQueue;
|
||||
std::condition_variable mPopCondition, mPushCondition;
|
||||
amount_function mAmountFunction;
|
||||
bool mStopping = false;
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Queue<T>::Queue(size_t limit, amount_function func) : mLimit(limit), mAmount(0) {
|
||||
mAmountFunction = func ? func : [](const T &element) -> size_t { return 1; };
|
||||
}
|
||||
|
||||
template <typename T> Queue<T>::~Queue() { stop(); }
|
||||
|
||||
template <typename T> void Queue<T>::stop() {
|
||||
std::lock_guard lock(mMutex);
|
||||
mStopping = true;
|
||||
mPopCondition.notify_all();
|
||||
mPushCondition.notify_all();
|
||||
}
|
||||
|
||||
template <typename T> bool Queue<T>::empty() const {
|
||||
std::lock_guard lock(mMutex);
|
||||
return mQueue.empty();
|
||||
}
|
||||
|
||||
template <typename T> size_t Queue<T>::size() const {
|
||||
std::lock_guard lock(mMutex);
|
||||
return mQueue.size();
|
||||
}
|
||||
|
||||
template <typename T> size_t Queue<T>::amount() const {
|
||||
std::lock_guard lock(mMutex);
|
||||
return mAmount;
|
||||
}
|
||||
|
||||
template <typename T> void Queue<T>::push(const T &element) { push(T{element}); }
|
||||
|
||||
template <typename T> void Queue<T>::push(T &&element) {
|
||||
std::unique_lock lock(mMutex);
|
||||
mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
|
||||
if (!mStopping) {
|
||||
mAmount += mAmountFunction(element);
|
||||
mQueue.emplace(std::move(element));
|
||||
mPopCondition.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> std::optional<T> Queue<T>::pop() {
|
||||
std::unique_lock lock(mMutex);
|
||||
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
||||
if (!mQueue.empty()) {
|
||||
mAmount -= mAmountFunction(mQueue.front());
|
||||
std::optional<T> element{std::move(mQueue.front())};
|
||||
mQueue.pop();
|
||||
return element;
|
||||
} else {
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> std::optional<T> Queue<T>::peek() {
|
||||
std::unique_lock lock(mMutex);
|
||||
if (!mQueue.empty()) {
|
||||
return std::optional<T>{mQueue.front()};
|
||||
} else {
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void Queue<T>::wait() {
|
||||
std::unique_lock lock(mMutex);
|
||||
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
||||
}
|
||||
|
||||
template <typename T> void Queue<T>::wait(const std::chrono::milliseconds &duration) {
|
||||
std::unique_lock lock(mMutex);
|
||||
mPopCondition.wait_for(lock, duration, [this]() { return !mQueue.empty() || mStopping; });
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -23,7 +23,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// libdatachannel rtc C API
|
||||
// libdatachannel C API
|
||||
|
||||
typedef enum {
|
||||
RTC_NEW = 0,
|
||||
@ -31,7 +31,8 @@ typedef enum {
|
||||
RTC_CONNECTED = 2,
|
||||
RTC_DISCONNECTED = 3,
|
||||
RTC_FAILED = 4,
|
||||
RTC_CLOSED = 5
|
||||
RTC_CLOSED = 5,
|
||||
RTC_DESTROYING = 6 // internal
|
||||
} rtc_state_t;
|
||||
|
||||
typedef enum {
|
||||
@ -40,6 +41,19 @@ typedef enum {
|
||||
RTC_GATHERING_COMPLETE = 2
|
||||
} rtc_gathering_state_t;
|
||||
|
||||
// Don't change, it must match plog severity
|
||||
typedef enum {
|
||||
RTC_LOG_NONE = 0,
|
||||
RTC_LOG_FATAL = 1,
|
||||
RTC_LOG_ERROR = 2,
|
||||
RTC_LOG_WARNING = 3,
|
||||
RTC_LOG_INFO = 4,
|
||||
RTC_LOG_DEBUG = 5,
|
||||
RTC_LOG_VERBOSE = 6
|
||||
} rtc_log_level_t;
|
||||
|
||||
void rtcInitLogger(rtc_log_level_t level);
|
||||
|
||||
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount);
|
||||
void rtcDeletePeerConnection(int pc);
|
||||
int rtcCreateDataChannel(int pc, const char *label);
|
||||
@ -67,4 +81,3 @@ void rtcSetUserPointer(int i, void *ptr);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -40,7 +40,7 @@ inline bool hasprefix(const string &str, const string &prefix) {
|
||||
|
||||
namespace rtc {
|
||||
|
||||
Candidate::Candidate(string candidate, string mid) {
|
||||
Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
|
||||
const std::array prefixes{"a=", "candidate:"};
|
||||
for (string prefix : prefixes)
|
||||
if (hasprefix(candidate, prefix))
|
||||
@ -48,8 +48,13 @@ Candidate::Candidate(string candidate, string mid) {
|
||||
|
||||
mCandidate = std::move(candidate);
|
||||
mMid = std::move(mid);
|
||||
}
|
||||
|
||||
// See RFC 5245 for format
|
||||
bool Candidate::resolve(ResolveMode mode) {
|
||||
if (mIsResolved)
|
||||
return true;
|
||||
|
||||
// See RFC 8445 for format
|
||||
std::stringstream ss(mCandidate);
|
||||
int component{0}, priority{0};
|
||||
string foundation, transport, node, service, typ_, type;
|
||||
@ -64,6 +69,15 @@ Candidate::Candidate(string candidate, string mid) {
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
}
|
||||
|
||||
if (transport == "TCP" || transport == "tcp") {
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
}
|
||||
|
||||
if (mode == ResolveMode::Simple)
|
||||
hints.ai_flags |= AI_NUMERICHOST;
|
||||
|
||||
struct addrinfo *result = nullptr;
|
||||
if (getaddrinfo(node.c_str(), service.c_str(), &hints, &result) == 0) {
|
||||
for (auto p = result; p; p = p->ai_next)
|
||||
@ -83,15 +97,19 @@ Candidate::Candidate(string candidate, string mid) {
|
||||
if (!left.empty())
|
||||
ss << left;
|
||||
mCandidate = ss.str();
|
||||
break;
|
||||
return mIsResolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Candidate::isResolved() const { return mIsResolved; }
|
||||
|
||||
string Candidate::candidate() const { return "candidate:" + mCandidate; }
|
||||
|
||||
string Candidate::mid() const { return mMid; }
|
||||
@ -107,4 +125,3 @@ Candidate::operator string() const {
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
|
||||
return out << std::string(candidate);
|
||||
}
|
||||
|
||||
|
@ -25,10 +25,13 @@
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <gnutls/crypto.h>
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
|
||||
#if USE_GNUTLS
|
||||
|
||||
#include <gnutls/crypto.h>
|
||||
|
||||
namespace {
|
||||
|
||||
@ -117,10 +120,10 @@ Certificate::Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey)
|
||||
"Unable to set certificate and key pair in credentials");
|
||||
}
|
||||
|
||||
string Certificate::fingerprint() const { return mFingerprint; }
|
||||
|
||||
gnutls_certificate_credentials_t Certificate::credentials() const { return *mCredentials; }
|
||||
|
||||
string Certificate::fingerprint() const { return mFingerprint; }
|
||||
|
||||
string make_fingerprint(gnutls_x509_crt_t crt) {
|
||||
const size_t size = 32;
|
||||
unsigned char buffer[size];
|
||||
@ -142,7 +145,7 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
|
||||
static std::unordered_map<string, shared_ptr<Certificate>> cache;
|
||||
static std::mutex cacheMutex;
|
||||
|
||||
std::lock_guard<std::mutex> lock(cacheMutex);
|
||||
std::lock_guard lock(cacheMutex);
|
||||
if (auto it = cache.find(commonName); it != cache.end())
|
||||
return it->second;
|
||||
|
||||
@ -177,3 +180,120 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#else
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
Certificate::Certificate(string crt_pem, string key_pem) {
|
||||
BIO *bio;
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
BIO_write(bio, crt_pem.data(), crt_pem.size());
|
||||
mX509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, 0, 0), X509_free);
|
||||
BIO_free(bio);
|
||||
if (!mX509)
|
||||
throw std::invalid_argument("Unable to import certificate PEM");
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
BIO_write(bio, key_pem.data(), key_pem.size());
|
||||
mPKey = shared_ptr<EVP_PKEY>(PEM_read_bio_PrivateKey(bio, nullptr, 0, 0), EVP_PKEY_free);
|
||||
BIO_free(bio);
|
||||
if (!mPKey)
|
||||
throw std::invalid_argument("Unable to import PEM key PEM");
|
||||
|
||||
mFingerprint = make_fingerprint(mX509.get());
|
||||
}
|
||||
|
||||
Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey) :
|
||||
mX509(std::move(x509)), mPKey(std::move(pkey))
|
||||
{
|
||||
mFingerprint = make_fingerprint(mX509.get());
|
||||
}
|
||||
|
||||
string Certificate::fingerprint() const { return mFingerprint; }
|
||||
|
||||
std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const { return {mX509.get(), mPKey.get()}; }
|
||||
|
||||
string make_fingerprint(X509 *x509) {
|
||||
const size_t size = 32;
|
||||
unsigned char buffer[size];
|
||||
unsigned int len = size;
|
||||
if (!X509_digest(x509, EVP_sha256(), buffer, &len))
|
||||
throw std::runtime_error("X509 fingerprint error");
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << std::hex << std::uppercase << std::setfill('0');
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (i)
|
||||
oss << std::setw(1) << ':';
|
||||
oss << std::setw(2) << unsigned(buffer[i]);
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
shared_ptr<Certificate> make_certificate(const string &commonName) {
|
||||
static std::unordered_map<string, shared_ptr<Certificate>> cache;
|
||||
static std::mutex cacheMutex;
|
||||
|
||||
std::lock_guard lock(cacheMutex);
|
||||
if (auto it = cache.find(commonName); it != cache.end())
|
||||
return it->second;
|
||||
|
||||
if (cache.empty()) {
|
||||
// This is the first call to OpenSSL
|
||||
OPENSSL_init_ssl(0, NULL);
|
||||
SSL_load_error_strings();
|
||||
ERR_load_crypto_strings();
|
||||
}
|
||||
|
||||
shared_ptr<X509> x509(X509_new(), X509_free);
|
||||
shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
|
||||
|
||||
unique_ptr<RSA, decltype(&RSA_free)> rsa(RSA_new(), RSA_free);
|
||||
unique_ptr<BIGNUM, decltype(&BN_free)> exponent(BN_new(), BN_free);
|
||||
unique_ptr<BIGNUM, decltype(&BN_free)> serial_number(BN_new(), BN_free);
|
||||
unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
|
||||
|
||||
if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
|
||||
throw std::runtime_error("Unable allocate structures for certificate generation");
|
||||
|
||||
const int bits = 4096;
|
||||
const unsigned int e = 65537; // 2^16 + 1
|
||||
|
||||
if (!pkey || !rsa || !exponent || !BN_set_word(exponent.get(), e) ||
|
||||
!RSA_generate_key_ex(rsa.get(), bits, exponent.get(), NULL) ||
|
||||
!EVP_PKEY_assign_RSA(pkey.get(), rsa.release())) // the key will be freed when pkey is freed
|
||||
throw std::runtime_error("Unable to generate key pair");
|
||||
|
||||
const size_t serialSize = 16;
|
||||
const auto *commonNameBytes = reinterpret_cast<const unsigned char *>(commonName.c_str());
|
||||
|
||||
if (!X509_gmtime_adj(X509_get_notBefore(x509.get()), 3600 * -1) ||
|
||||
!X509_gmtime_adj(X509_get_notAfter(x509.get()), 3600 * 24 * 365) ||
|
||||
!X509_set_version(x509.get(), 1) || !X509_set_pubkey(x509.get(), pkey.get()) ||
|
||||
!BN_pseudo_rand(serial_number.get(), serialSize, 0, 0) ||
|
||||
!BN_to_ASN1_INTEGER(serial_number.get(), X509_get_serialNumber(x509.get())) ||
|
||||
!X509_NAME_add_entry_by_NID(name.get(), NID_commonName, MBSTRING_UTF8, commonNameBytes, -1,
|
||||
-1, 0) ||
|
||||
!X509_set_subject_name(x509.get(), name.get()) ||
|
||||
!X509_set_issuer_name(x509.get(), name.get()))
|
||||
throw std::runtime_error("Unable to set certificate properties");
|
||||
|
||||
if (!X509_sign(x509.get(), pkey.get(), EVP_sha256()))
|
||||
throw std::runtime_error("Unable to auto-sign certificate");
|
||||
|
||||
auto certificate = std::make_shared<Certificate>(x509, pkey);
|
||||
cache.emplace(std::make_pair(commonName, certificate));
|
||||
return certificate;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -21,24 +21,47 @@
|
||||
|
||||
#include "include.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#if USE_GNUTLS
|
||||
#include <gnutls/x509.h>
|
||||
#else
|
||||
#include <openssl/x509.h>
|
||||
#endif
|
||||
|
||||
namespace rtc {
|
||||
|
||||
class Certificate {
|
||||
public:
|
||||
Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey);
|
||||
Certificate(string crt_pem, string key_pem);
|
||||
|
||||
string fingerprint() const;
|
||||
#if USE_GNUTLS
|
||||
Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey);
|
||||
gnutls_certificate_credentials_t credentials() const;
|
||||
#else
|
||||
Certificate(std::shared_ptr<X509> x509, std::shared_ptr<EVP_PKEY> pkey);
|
||||
std::tuple<X509 *, EVP_PKEY *> credentials() const;
|
||||
#endif
|
||||
|
||||
string fingerprint() const;
|
||||
|
||||
private:
|
||||
#if USE_GNUTLS
|
||||
std::shared_ptr<gnutls_certificate_credentials_t> mCredentials;
|
||||
#else
|
||||
std::shared_ptr<X509> mX509;
|
||||
std::shared_ptr<EVP_PKEY> mPKey;
|
||||
#endif
|
||||
|
||||
string mFingerprint;
|
||||
};
|
||||
|
||||
#if USE_GNUTLS
|
||||
string make_fingerprint(gnutls_x509_crt_t crt);
|
||||
#else
|
||||
string make_fingerprint(X509 *x509);
|
||||
#endif
|
||||
|
||||
std::shared_ptr<Certificate> make_certificate(const string &commonName);
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -18,11 +18,17 @@
|
||||
|
||||
#include "channel.hpp"
|
||||
|
||||
namespace {}
|
||||
|
||||
namespace rtc {
|
||||
|
||||
void Channel::onOpen(std::function<void()> callback) { mOpenCallback = callback; }
|
||||
void Channel::onOpen(std::function<void()> callback) {
|
||||
mOpenCallback = callback;
|
||||
}
|
||||
|
||||
void Channel::onClosed(std::function<void()> callback) { mClosedCallback = callback; }
|
||||
void Channel::onClosed(std::function<void()> callback) {
|
||||
mClosedCallback = callback;
|
||||
}
|
||||
|
||||
void Channel::onError(std::function<void(const string &error)> callback) {
|
||||
mErrorCallback = callback;
|
||||
@ -30,6 +36,10 @@ void Channel::onError(std::function<void(const string &error)> callback) {
|
||||
|
||||
void Channel::onMessage(std::function<void(const std::variant<binary, string> &data)> callback) {
|
||||
mMessageCallback = callback;
|
||||
|
||||
// Pass pending messages
|
||||
while (auto message = receive())
|
||||
mMessageCallback(*message);
|
||||
}
|
||||
|
||||
void Channel::onMessage(std::function<void(const binary &data)> binaryCallback,
|
||||
@ -39,24 +49,43 @@ void Channel::onMessage(std::function<void(const binary &data)> binaryCallback,
|
||||
});
|
||||
}
|
||||
|
||||
void Channel::triggerOpen(void) {
|
||||
if (mOpenCallback)
|
||||
mOpenCallback();
|
||||
void Channel::onAvailable(std::function<void()> callback) {
|
||||
mAvailableCallback = callback;
|
||||
}
|
||||
|
||||
void Channel::triggerClosed(void) {
|
||||
if (mClosedCallback)
|
||||
mClosedCallback();
|
||||
void Channel::onBufferedAmountLow(std::function<void()> callback) {
|
||||
mBufferedAmountLowCallback = callback;
|
||||
}
|
||||
|
||||
void Channel::triggerError(const string &error) {
|
||||
if (mErrorCallback)
|
||||
mErrorCallback(error);
|
||||
size_t Channel::availableAmount() const { return 0; }
|
||||
|
||||
size_t Channel::bufferedAmount() const { return mBufferedAmount; }
|
||||
|
||||
void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; }
|
||||
|
||||
void Channel::triggerOpen() { mOpenCallback(); }
|
||||
|
||||
void Channel::triggerClosed() { mClosedCallback(); }
|
||||
|
||||
void Channel::triggerError(const string &error) { mErrorCallback(error); }
|
||||
|
||||
void Channel::triggerAvailable(size_t count) {
|
||||
if (count == 1)
|
||||
mAvailableCallback();
|
||||
|
||||
while (mMessageCallback && count--) {
|
||||
auto message = receive();
|
||||
if (!message)
|
||||
break;
|
||||
mMessageCallback(*message);
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::triggerMessage(const std::variant<binary, string> &data) {
|
||||
if (mMessageCallback)
|
||||
mMessageCallback(data);
|
||||
void Channel::triggerBufferedAmount(size_t amount) {
|
||||
size_t previous = mBufferedAmount.exchange(amount);
|
||||
size_t threshold = mBufferedAmountLowThreshold.load();
|
||||
if (previous > threshold && amount <= threshold)
|
||||
mBufferedAmountLowCallback();
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -18,22 +18,70 @@
|
||||
|
||||
#include "configuration.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
using std::to_string;
|
||||
IceServer::IceServer(const string &url) {
|
||||
// Modified regex from RFC 3986, see https://tools.ietf.org/html/rfc3986#appendix-B
|
||||
static const char *rs =
|
||||
R"(^(([^:.@/?#]+):)?(/{0,2}((([^:@]*)(:([^@]*))?)@)?(([^:/?#]*)(:([^/?#]*))?))?([^?#]*)(\?([^#]*))?(#(.*))?)";
|
||||
static const std::regex r(rs, std::regex::extended);
|
||||
|
||||
IceServer::IceServer(const string &host) {
|
||||
if(size_t pos = host.rfind(':'); pos != string::npos) {
|
||||
hostname = host.substr(0, pos);
|
||||
service = host.substr(pos + 1);
|
||||
} else {
|
||||
hostname = host;
|
||||
service = "3478"; // STUN UDP port
|
||||
}
|
||||
std::smatch m;
|
||||
if (!std::regex_match(url, m, r) || m[10].length() == 0)
|
||||
throw std::invalid_argument("Invalid ICE server URL: " + url);
|
||||
|
||||
std::vector<std::optional<string>> opt(m.size());
|
||||
std::transform(m.begin(), m.end(), opt.begin(), [](const auto &sm) {
|
||||
return sm.length() > 0 ? std::make_optional(string(sm)) : nullopt;
|
||||
});
|
||||
|
||||
string scheme = opt[2].value_or("stun");
|
||||
if (scheme == "stun" || scheme == "STUN")
|
||||
type = Type::Stun;
|
||||
else if (scheme == "turn" || scheme == "TURN")
|
||||
type = Type::Turn;
|
||||
else if (scheme == "turns" || scheme == "TURNS")
|
||||
type = Type::Turn;
|
||||
else
|
||||
throw std::invalid_argument("Unknown ICE server protocol: " + scheme);
|
||||
|
||||
relayType = RelayType::TurnUdp;
|
||||
if (auto &query = opt[15]) {
|
||||
if (query->find("transport=udp") != string::npos)
|
||||
relayType = RelayType::TurnUdp;
|
||||
if (query->find("transport=tcp") != string::npos)
|
||||
relayType = RelayType::TurnTcp;
|
||||
if (query->find("transport=tls") != string::npos)
|
||||
relayType = RelayType::TurnTls;
|
||||
}
|
||||
|
||||
IceServer::IceServer(const string &hostname_, uint16_t port_) : IceServer(hostname_, to_string(port_)) {}
|
||||
username = opt[6].value_or("");
|
||||
password = opt[8].value_or("");
|
||||
hostname = opt[10].value();
|
||||
service = opt[12].value_or(relayType == RelayType::TurnTls ? "5349" : "3478");
|
||||
|
||||
IceServer::IceServer(const string &hostname_, const string &service_) : hostname(hostname_), service(service_) {}
|
||||
while (!hostname.empty() && hostname.front() == '[')
|
||||
hostname.erase(hostname.begin());
|
||||
while (!hostname.empty() && hostname.back() == ']')
|
||||
hostname.pop_back();
|
||||
}
|
||||
|
||||
IceServer::IceServer(string hostname_, uint16_t port_)
|
||||
: IceServer(std::move(hostname_), std::to_string(port_)) {}
|
||||
|
||||
IceServer::IceServer(string hostname_, string service_)
|
||||
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Stun) {}
|
||||
|
||||
IceServer::IceServer(string hostname_, uint16_t port_, string username_, string password_,
|
||||
RelayType relayType_)
|
||||
: IceServer(hostname_, std::to_string(port_), std::move(username_), std::move(password_),
|
||||
relayType_) {}
|
||||
|
||||
IceServer::IceServer(string hostname_, string service_, string username_, string password_,
|
||||
RelayType relayType_)
|
||||
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Turn),
|
||||
username(std::move(username_)), password(std::move(password_)), relayType(relayType_) {}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "datachannel.hpp"
|
||||
#include "include.hpp"
|
||||
#include "peerconnection.hpp"
|
||||
#include "sctptransport.hpp"
|
||||
|
||||
@ -57,48 +58,88 @@ struct CloseMessage {
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
DataChannel::DataChannel(unsigned int stream, string label, string protocol,
|
||||
Reliability reliability)
|
||||
: mStream(stream), mLabel(std::move(label)), mProtocol(std::move(protocol)),
|
||||
mReliability(std::make_shared<Reliability>(std::move(reliability))) {}
|
||||
const size_t RECV_QUEUE_LIMIT = 1024 * 1024; // 1 MiB
|
||||
|
||||
DataChannel::DataChannel(unsigned int stream, shared_ptr<SctpTransport> sctpTransport)
|
||||
: mStream(stream), mSctpTransport(sctpTransport),
|
||||
mReliability(std::make_shared<Reliability>()) {}
|
||||
DataChannel::DataChannel(shared_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||
string protocol, Reliability reliability)
|
||||
: mPeerConnection(std::move(pc)), mStream(stream), mLabel(std::move(label)),
|
||||
mProtocol(std::move(protocol)),
|
||||
mReliability(std::make_shared<Reliability>(std::move(reliability))),
|
||||
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
||||
|
||||
DataChannel::~DataChannel() { close(); }
|
||||
DataChannel::DataChannel(shared_ptr<PeerConnection> pc, shared_ptr<SctpTransport> transport,
|
||||
unsigned int stream)
|
||||
: mPeerConnection(std::move(pc)), mSctpTransport(transport), mStream(stream),
|
||||
mReliability(std::make_shared<Reliability>()),
|
||||
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
||||
|
||||
DataChannel::~DataChannel() {
|
||||
close();
|
||||
}
|
||||
|
||||
void DataChannel::close() {
|
||||
mIsOpen = false;
|
||||
if (!mIsClosed) {
|
||||
mIsClosed = true;
|
||||
if (mSctpTransport)
|
||||
if (mIsOpen.exchange(false) && mSctpTransport)
|
||||
mSctpTransport->reset(mStream);
|
||||
}
|
||||
mIsClosed = true;
|
||||
mSctpTransport.reset();
|
||||
}
|
||||
|
||||
void DataChannel::send(const std::variant<binary, string> &data) {
|
||||
if (mIsClosed || !mSctpTransport)
|
||||
return;
|
||||
void DataChannel::remoteClose() {
|
||||
mIsOpen = false;
|
||||
if (!mIsClosed.exchange(true))
|
||||
triggerClosed();
|
||||
mSctpTransport.reset();
|
||||
}
|
||||
|
||||
std::visit(
|
||||
[this](const auto &d) {
|
||||
bool DataChannel::send(const std::variant<binary, string> &data) {
|
||||
return std::visit(
|
||||
[&](const auto &d) {
|
||||
using T = std::decay_t<decltype(d)>;
|
||||
constexpr auto type = std::is_same_v<T, string> ? Message::String : Message::Binary;
|
||||
auto *b = reinterpret_cast<const byte *>(d.data());
|
||||
// Before the ACK has been received on a DataChannel, all messages must be sent ordered
|
||||
auto reliability = mIsOpen ? mReliability : nullptr;
|
||||
mSctpTransport->send(make_message(b, b + d.size(), type, mStream, reliability));
|
||||
return outgoing(std::make_shared<Message>(b, b + d.size(), type));
|
||||
},
|
||||
data);
|
||||
}
|
||||
|
||||
void DataChannel::send(const byte *data, size_t size) {
|
||||
if (mIsClosed || !mSctpTransport)
|
||||
return;
|
||||
bool DataChannel::send(const byte *data, size_t size) {
|
||||
return outgoing(std::make_shared<Message>(data, data + size, Message::Binary));
|
||||
}
|
||||
|
||||
auto reliability = mIsOpen ? mReliability : nullptr;
|
||||
mSctpTransport->send(make_message(data, data + size, Message::Binary, mStream, reliability));
|
||||
std::optional<std::variant<binary, string>> DataChannel::receive() {
|
||||
while (!mRecvQueue.empty()) {
|
||||
auto message = *mRecvQueue.pop();
|
||||
switch (message->type) {
|
||||
case Message::Control: {
|
||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||
if (raw[0] == MESSAGE_CLOSE)
|
||||
remoteClose();
|
||||
break;
|
||||
}
|
||||
case Message::String:
|
||||
return std::make_optional(
|
||||
string(reinterpret_cast<const char *>(message->data()), message->size()));
|
||||
case Message::Binary:
|
||||
return std::make_optional(std::move(*message));
|
||||
}
|
||||
}
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool DataChannel::isOpen(void) const { return mIsOpen; }
|
||||
|
||||
bool DataChannel::isClosed(void) const { return mIsClosed; }
|
||||
|
||||
size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
|
||||
|
||||
size_t DataChannel::maxMessageSize() const {
|
||||
size_t max = DEFAULT_MAX_MESSAGE_SIZE;
|
||||
if (auto description = mPeerConnection->remoteDescription())
|
||||
if (auto maxMessageSize = description->maxMessageSize())
|
||||
return *maxMessageSize > 0 ? *maxMessageSize : LOCAL_MAX_MESSAGE_SIZE;
|
||||
|
||||
return std::min(max, LOCAL_MAX_MESSAGE_SIZE);
|
||||
}
|
||||
|
||||
unsigned int DataChannel::stream() const { return mStream; }
|
||||
@ -109,10 +150,6 @@ string DataChannel::protocol() const { return mProtocol; }
|
||||
|
||||
Reliability DataChannel::reliability() const { return *mReliability; }
|
||||
|
||||
bool DataChannel::isOpen(void) const { return mIsOpen; }
|
||||
|
||||
bool DataChannel::isClosed(void) const { return mIsClosed; }
|
||||
|
||||
void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
|
||||
mSctpTransport = sctpTransport;
|
||||
|
||||
@ -144,6 +181,19 @@ void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
|
||||
mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||
}
|
||||
|
||||
bool DataChannel::outgoing(mutable_message_ptr message) {
|
||||
if (mIsClosed || !mSctpTransport)
|
||||
throw std::runtime_error("DataChannel is closed");
|
||||
|
||||
if (message->size() > maxMessageSize())
|
||||
throw std::runtime_error("Message size exceeds limit");
|
||||
|
||||
// Before the ACK has been received on a DataChannel, all messages must be sent ordered
|
||||
message->reliability = mIsOpen ? mReliability : nullptr;
|
||||
message->stream = mStream;
|
||||
return mSctpTransport->send(message);
|
||||
}
|
||||
|
||||
void DataChannel::incoming(message_ptr message) {
|
||||
switch (message->type) {
|
||||
case Message::Control: {
|
||||
@ -153,16 +203,14 @@ void DataChannel::incoming(message_ptr message) {
|
||||
processOpenMessage(message);
|
||||
break;
|
||||
case MESSAGE_ACK:
|
||||
if (!mIsOpen) {
|
||||
mIsOpen = true;
|
||||
if (!mIsOpen.exchange(true)) {
|
||||
triggerOpen();
|
||||
}
|
||||
break;
|
||||
case MESSAGE_CLOSE:
|
||||
if (mIsOpen) {
|
||||
close();
|
||||
triggerClosed();
|
||||
}
|
||||
// The close message will be processed in-order in receive()
|
||||
mRecvQueue.push(message);
|
||||
triggerAvailable(mRecvQueue.size());
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
@ -170,16 +218,16 @@ void DataChannel::incoming(message_ptr message) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Message::String: {
|
||||
triggerMessage(string(reinterpret_cast<const char *>(message->data()), message->size()));
|
||||
case Message::String:
|
||||
case Message::Binary:
|
||||
mRecvQueue.push(message);
|
||||
triggerAvailable(mRecvQueue.size());
|
||||
break;
|
||||
}
|
||||
case Message::Binary: {
|
||||
triggerMessage(*message);
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataChannel::processOpenMessage(message_ptr message) {
|
||||
if (message->size() < sizeof(OpenMessage))
|
||||
@ -220,6 +268,7 @@ void DataChannel::processOpenMessage(message_ptr message) {
|
||||
|
||||
mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||
|
||||
mIsOpen = true;
|
||||
triggerOpen();
|
||||
}
|
||||
|
||||
|
@ -81,8 +81,10 @@ Description::Description(const string &sdp, Type type, Role role)
|
||||
mIcePwd = line.substr(line.find(':') + 1);
|
||||
} else if (hasprefix(line, "a=sctp-port")) {
|
||||
mSctpPort = uint16_t(std::stoul(line.substr(line.find(':') + 1)));
|
||||
} else if (hasprefix(line, "a=max-message-size")) {
|
||||
mMaxMessageSize = size_t(std::stoul(line.substr(line.find(':') + 1)));
|
||||
} else if (hasprefix(line, "a=candidate")) {
|
||||
mCandidates.emplace_back(Candidate(line.substr(2), mMid));
|
||||
addCandidate(Candidate(line.substr(2), mMid));
|
||||
} else if (hasprefix(line, "a=end-of-candidates")) {
|
||||
mTrickle = false;
|
||||
}
|
||||
@ -103,47 +105,64 @@ std::optional<string> Description::fingerprint() const { return mFingerprint; }
|
||||
|
||||
std::optional<uint16_t> Description::sctpPort() const { return mSctpPort; }
|
||||
|
||||
std::optional<size_t> Description::maxMessageSize() const { return mMaxMessageSize; }
|
||||
|
||||
bool Description::trickleEnabled() const { return mTrickle; }
|
||||
|
||||
void Description::setFingerprint(string fingerprint) {
|
||||
mFingerprint.emplace(std::move(fingerprint));
|
||||
}
|
||||
|
||||
void Description::setSctpPort(uint16_t port) { mSctpPort.emplace(port); }
|
||||
|
||||
void Description::setMaxMessageSize(size_t size) { mMaxMessageSize.emplace(size); }
|
||||
|
||||
void Description::addCandidate(Candidate candidate) {
|
||||
mCandidates.emplace_back(std::move(candidate));
|
||||
}
|
||||
|
||||
void Description::endCandidates() { mTrickle = false; }
|
||||
|
||||
Description::operator string() const {
|
||||
std::vector<Candidate> Description::extractCandidates() {
|
||||
std::vector<Candidate> result;
|
||||
std::swap(mCandidates, result);
|
||||
mTrickle = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
Description::operator string() const { return generateSdp("\r\n"); }
|
||||
|
||||
string Description::generateSdp(const string &eol) const {
|
||||
if (!mFingerprint)
|
||||
throw std::logic_error("Fingerprint must be set to generate a SDP");
|
||||
throw std::logic_error("Fingerprint must be set to generate an SDP string");
|
||||
|
||||
std::ostringstream sdp;
|
||||
sdp << "v=0\n";
|
||||
sdp << "o=- " << mSessionId << " 0 IN IP4 0.0.0.0\n";
|
||||
sdp << "s=-\n";
|
||||
sdp << "t=0 0\n";
|
||||
sdp << "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\n";
|
||||
sdp << "c=IN IP4 0.0.0.0\n";
|
||||
sdp << "a=ice-ufrag:" << mIceUfrag << "\n";
|
||||
sdp << "a=ice-pwd:" << mIcePwd << "\n";
|
||||
sdp << "v=0" << eol;
|
||||
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "s=-" << eol;
|
||||
sdp << "t=0 0" << eol;
|
||||
sdp << "a=group:BUNDLE 0" << eol;
|
||||
sdp << "m=application 9 UDP/DTLS/SCTP webrtc-datachannel" << eol;
|
||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
||||
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
||||
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
||||
if (mTrickle)
|
||||
sdp << "a=ice-options:trickle\n";
|
||||
sdp << "a=mid:" << mMid << "\n";
|
||||
sdp << "a=setup:" << roleToString(mRole) << "\n";
|
||||
sdp << "a=dtls-id:1\n";
|
||||
sdp << "a=ice-options:trickle" << eol;
|
||||
sdp << "a=mid:" << mMid << eol;
|
||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||
sdp << "a=dtls-id:1" << eol;
|
||||
if (mFingerprint)
|
||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << "\n";
|
||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||
if (mSctpPort)
|
||||
sdp << "a=sctp-port:" << *mSctpPort << "\n";
|
||||
|
||||
sdp << "a=sctp-port:" << *mSctpPort << eol;
|
||||
if (mMaxMessageSize)
|
||||
sdp << "a=max-message-size:" << *mMaxMessageSize << eol;
|
||||
for (const auto &candidate : mCandidates) {
|
||||
sdp << string(candidate) << "\n";
|
||||
sdp << string(candidate) << eol;
|
||||
}
|
||||
|
||||
if (!mTrickle)
|
||||
sdp << "a=end-of-candidates\n";
|
||||
sdp << "a=end-of-candidates" << eol;
|
||||
|
||||
return sdp.str();
|
||||
}
|
||||
|
@ -18,23 +18,34 @@
|
||||
|
||||
#include "dtlstransport.hpp"
|
||||
#include "icetransport.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
using namespace std::chrono;
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
using std::weak_ptr;
|
||||
|
||||
#if USE_GNUTLS
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
|
||||
namespace {
|
||||
|
||||
static bool check_gnutls(int ret, const string &message = "GnuTLS error") {
|
||||
if (ret < 0) {
|
||||
if (!gnutls_error_is_fatal(ret))
|
||||
if (!gnutls_error_is_fatal(ret)) {
|
||||
PLOG_INFO << gnutls_strerror(ret);
|
||||
return false;
|
||||
}
|
||||
PLOG_ERROR << message << ": " << gnutls_strerror(ret);
|
||||
throw std::runtime_error(message + ": " + gnutls_strerror(ret));
|
||||
}
|
||||
return true;
|
||||
@ -44,90 +55,143 @@ static bool check_gnutls(int ret, const string &message = "GnuTLS error") {
|
||||
|
||||
namespace rtc {
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
|
||||
verifier_callback verifierCallback,
|
||||
state_callback stateChangeCallback)
|
||||
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
|
||||
mVerifierCallback(std::move(verifierCallback)),
|
||||
mStateChangeCallback(std::move(stateChangeCallback)) {
|
||||
|
||||
PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)";
|
||||
|
||||
gnutls_certificate_set_verify_function(mCertificate->credentials(), CertificateCallback);
|
||||
|
||||
bool active = lower->role() == Description::Role::Active;
|
||||
unsigned int flags = GNUTLS_DATAGRAM | (active ? GNUTLS_CLIENT : GNUTLS_SERVER);
|
||||
check_gnutls(gnutls_init(&mSession, flags));
|
||||
|
||||
const char *priorities = "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-ARCFOUR-128";
|
||||
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
|
||||
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL";
|
||||
const char *err_pos = NULL;
|
||||
check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
|
||||
"Unable to set TLS priorities");
|
||||
|
||||
check_gnutls(
|
||||
gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate->credentials()));
|
||||
|
||||
gnutls_dtls_set_mtu(mSession, 1280 - 40 - 8); // min MTU over UDP/IPv6 (only for handshake)
|
||||
gnutls_dtls_set_timeouts(mSession, 400, 60000);
|
||||
gnutls_handshake_set_timeout(mSession, 60000);
|
||||
|
||||
gnutls_session_set_ptr(mSession, this);
|
||||
gnutls_transport_set_ptr(mSession, this);
|
||||
gnutls_transport_set_push_function(mSession, WriteCallback);
|
||||
gnutls_transport_set_pull_function(mSession, ReadCallback);
|
||||
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
|
||||
|
||||
check_gnutls(
|
||||
gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate->credentials()));
|
||||
|
||||
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
||||
}
|
||||
|
||||
DtlsTransport::~DtlsTransport() {
|
||||
mIncomingQueue.stop();
|
||||
if (mRecvThread.joinable())
|
||||
mRecvThread.join();
|
||||
stop();
|
||||
|
||||
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
|
||||
gnutls_deinit(mSession);
|
||||
}
|
||||
|
||||
DtlsTransport::State DtlsTransport::state() const { return mState; }
|
||||
|
||||
void DtlsTransport::stop() {
|
||||
Transport::stop();
|
||||
|
||||
if (mRecvThread.joinable()) {
|
||||
PLOG_DEBUG << "Stopping DTLS recv thread";
|
||||
mIncomingQueue.stop();
|
||||
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
|
||||
mRecvThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool DtlsTransport::send(message_ptr message) {
|
||||
if (!message)
|
||||
if (!message || mState != State::Connected)
|
||||
return false;
|
||||
|
||||
while (true) {
|
||||
ssize_t ret = gnutls_record_send(mSession, message->data(), message->size());
|
||||
if (check_gnutls(ret)) {
|
||||
return ret > 0;
|
||||
}
|
||||
}
|
||||
PLOG_VERBOSE << "Send size=" << message->size();
|
||||
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = gnutls_record_send(mSession, message->data(), message->size());
|
||||
} while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
|
||||
|
||||
if (ret == GNUTLS_E_LARGE_PACKET)
|
||||
return false;
|
||||
|
||||
return check_gnutls(ret);
|
||||
}
|
||||
|
||||
void DtlsTransport::incoming(message_ptr message) { mIncomingQueue.push(message); }
|
||||
void DtlsTransport::incoming(message_ptr message) {
|
||||
if (message)
|
||||
mIncomingQueue.push(message);
|
||||
else
|
||||
mIncomingQueue.stop();
|
||||
}
|
||||
|
||||
void DtlsTransport::changeState(State state) {
|
||||
mState = state;
|
||||
if (mState.exchange(state) != state)
|
||||
mStateChangeCallback(state);
|
||||
}
|
||||
|
||||
void DtlsTransport::runRecvLoop() {
|
||||
const size_t maxMtu = 4096;
|
||||
|
||||
// Handshake loop
|
||||
try {
|
||||
changeState(State::Connecting);
|
||||
|
||||
while (!check_gnutls(gnutls_handshake(mSession), "TLS handshake failed")) {
|
||||
}
|
||||
int ret;
|
||||
do {
|
||||
ret = gnutls_handshake(mSession);
|
||||
|
||||
if (ret == GNUTLS_E_LARGE_PACKET)
|
||||
throw std::runtime_error("MTU is too low");
|
||||
|
||||
} while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN ||
|
||||
!check_gnutls(ret, "TLS handshake failed"));
|
||||
|
||||
// RFC 8261: DTLS MUST support sending messages larger than the current path MTU
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
gnutls_dtls_set_mtu(mSession, maxMtu + 1);
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "DTLS handshake: " << e.what() << std::endl;
|
||||
PLOG_ERROR << "DTLS handshake: " << e.what();
|
||||
changeState(State::Failed);
|
||||
return;
|
||||
}
|
||||
|
||||
// Receive loop
|
||||
try {
|
||||
changeState(State::Connected);
|
||||
|
||||
const size_t bufferSize = 2048;
|
||||
const size_t bufferSize = maxMtu;
|
||||
char buffer[bufferSize];
|
||||
|
||||
while (true) {
|
||||
ssize_t ret = gnutls_record_recv(mSession, buffer, bufferSize);
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = gnutls_record_recv(mSession, buffer, bufferSize);
|
||||
} while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
|
||||
|
||||
// Consider premature termination as remote closing
|
||||
if (ret == GNUTLS_E_PREMATURE_TERMINATION) {
|
||||
PLOG_DEBUG << "DTLS connection terminated";
|
||||
break;
|
||||
}
|
||||
|
||||
if (check_gnutls(ret)) {
|
||||
if (ret == 0) {
|
||||
// Closed
|
||||
PLOG_DEBUG << "DTLS connection cleanly closed";
|
||||
break;
|
||||
}
|
||||
auto *b = reinterpret_cast<byte *>(buffer);
|
||||
@ -136,9 +200,10 @@ void DtlsTransport::runRecvLoop() {
|
||||
}
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "DTLS recv: " << e.what() << std::endl;
|
||||
PLOG_ERROR << "DTLS recv: " << e.what();
|
||||
}
|
||||
|
||||
PLOG_INFO << "DTLS disconnected";
|
||||
changeState(State::Disconnected);
|
||||
recv(nullptr);
|
||||
}
|
||||
@ -183,22 +248,313 @@ ssize_t DtlsTransport::WriteCallback(gnutls_transport_ptr_t ptr, const void *dat
|
||||
|
||||
ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen) {
|
||||
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
||||
auto next = t->mIncomingQueue.pop();
|
||||
auto message = next ? *next : nullptr;
|
||||
if (!message) {
|
||||
// Closed
|
||||
gnutls_transport_set_errno(t->mSession, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (auto next = t->mIncomingQueue.pop()) {
|
||||
auto message = *next;
|
||||
ssize_t len = std::min(maxlen, message->size());
|
||||
std::memcpy(data, message->data(), len);
|
||||
gnutls_transport_set_errno(t->mSession, 0);
|
||||
return len;
|
||||
}
|
||||
// Closed
|
||||
gnutls_transport_set_errno(t->mSession, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
||||
return 1; // So ReadCallback is called
|
||||
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
||||
if (ms != GNUTLS_INDEFINITE_TIMEOUT)
|
||||
t->mIncomingQueue.wait(milliseconds(ms));
|
||||
else
|
||||
t->mIncomingQueue.wait();
|
||||
return !t->mIncomingQueue.empty() ? 1 : 0;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#else // USE_GNUTLS==0
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
namespace {
|
||||
|
||||
const int BIO_EOF = -1;
|
||||
|
||||
string openssl_error_string(unsigned long err) {
|
||||
const size_t bufferSize = 256;
|
||||
char buffer[bufferSize];
|
||||
ERR_error_string_n(err, buffer, bufferSize);
|
||||
return string(buffer);
|
||||
}
|
||||
|
||||
bool check_openssl(int success, const string &message = "OpenSSL error") {
|
||||
if (success)
|
||||
return true;
|
||||
|
||||
string str = openssl_error_string(ERR_get_error());
|
||||
PLOG_ERROR << message << ": " << str;
|
||||
throw std::runtime_error(message + ": " + str);
|
||||
}
|
||||
|
||||
bool check_openssl_ret(SSL *ssl, int ret, const string &message = "OpenSSL error") {
|
||||
if (ret == BIO_EOF)
|
||||
return true;
|
||||
|
||||
unsigned long err = SSL_get_error(ssl, ret);
|
||||
if (err == SSL_ERROR_NONE || err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
||||
return true;
|
||||
}
|
||||
if (err == SSL_ERROR_ZERO_RETURN) {
|
||||
PLOG_DEBUG << "DTLS connection cleanly closed";
|
||||
return false;
|
||||
}
|
||||
string str = openssl_error_string(err);
|
||||
PLOG_ERROR << str;
|
||||
throw std::runtime_error(message + ": " + str);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace rtc {
|
||||
|
||||
BIO_METHOD *DtlsTransport::BioMethods = NULL;
|
||||
int DtlsTransport::TransportExIndex = -1;
|
||||
std::mutex DtlsTransport::GlobalMutex;
|
||||
|
||||
void DtlsTransport::GlobalInit() {
|
||||
std::lock_guard lock(GlobalMutex);
|
||||
if (!BioMethods) {
|
||||
BioMethods = BIO_meth_new(BIO_TYPE_BIO, "DTLS writer");
|
||||
if (!BioMethods)
|
||||
throw std::runtime_error("Unable to BIO methods for DTLS writer");
|
||||
BIO_meth_set_create(BioMethods, BioMethodNew);
|
||||
BIO_meth_set_destroy(BioMethods, BioMethodFree);
|
||||
BIO_meth_set_write(BioMethods, BioMethodWrite);
|
||||
BIO_meth_set_ctrl(BioMethods, BioMethodCtrl);
|
||||
}
|
||||
if (TransportExIndex < 0) {
|
||||
TransportExIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
|
||||
verifier_callback verifierCallback, state_callback stateChangeCallback)
|
||||
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
|
||||
mVerifierCallback(std::move(verifierCallback)),
|
||||
mStateChangeCallback(std::move(stateChangeCallback)) {
|
||||
|
||||
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
|
||||
GlobalInit();
|
||||
|
||||
if (!(mCtx = SSL_CTX_new(DTLS_method())))
|
||||
throw std::runtime_error("Unable to create SSL context");
|
||||
|
||||
check_openssl(SSL_CTX_set_cipher_list(mCtx, "ALL:!LOW:!EXP:!RC4:!MD5:@STRENGTH"),
|
||||
"Unable to set SSL priorities");
|
||||
|
||||
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
|
||||
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_NO_QUERY_MTU);
|
||||
SSL_CTX_set_min_proto_version(mCtx, DTLS1_VERSION);
|
||||
SSL_CTX_set_read_ahead(mCtx, 1);
|
||||
SSL_CTX_set_quiet_shutdown(mCtx, 1);
|
||||
SSL_CTX_set_info_callback(mCtx, InfoCallback);
|
||||
SSL_CTX_set_verify(mCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||
CertificateCallback);
|
||||
SSL_CTX_set_verify_depth(mCtx, 1);
|
||||
|
||||
auto [x509, pkey] = mCertificate->credentials();
|
||||
SSL_CTX_use_certificate(mCtx, x509);
|
||||
SSL_CTX_use_PrivateKey(mCtx, pkey);
|
||||
|
||||
check_openssl(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed");
|
||||
|
||||
if (!(mSsl = SSL_new(mCtx)))
|
||||
throw std::runtime_error("Unable to create SSL instance");
|
||||
|
||||
SSL_set_ex_data(mSsl, TransportExIndex, this);
|
||||
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
|
||||
|
||||
if (lower->role() == Description::Role::Active)
|
||||
SSL_set_connect_state(mSsl);
|
||||
else
|
||||
SSL_set_accept_state(mSsl);
|
||||
|
||||
if (!(mInBio = BIO_new(BIO_s_mem())) || !(mOutBio = BIO_new(BioMethods)))
|
||||
throw std::runtime_error("Unable to create BIO");
|
||||
|
||||
BIO_set_mem_eof_return(mInBio, BIO_EOF);
|
||||
BIO_set_data(mOutBio, this);
|
||||
SSL_set_bio(mSsl, mInBio, mOutBio);
|
||||
|
||||
auto ecdh = unique_ptr<EC_KEY, decltype(&EC_KEY_free)>(
|
||||
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), EC_KEY_free);
|
||||
SSL_set_options(mSsl, SSL_OP_SINGLE_ECDH_USE);
|
||||
SSL_set_tmp_ecdh(mSsl, ecdh.get());
|
||||
|
||||
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
||||
}
|
||||
|
||||
DtlsTransport::~DtlsTransport() {
|
||||
stop();
|
||||
|
||||
SSL_free(mSsl);
|
||||
SSL_CTX_free(mCtx);
|
||||
}
|
||||
|
||||
void DtlsTransport::stop() {
|
||||
Transport::stop();
|
||||
|
||||
if (mRecvThread.joinable()) {
|
||||
PLOG_DEBUG << "Stopping DTLS recv thread";
|
||||
mIncomingQueue.stop();
|
||||
mRecvThread.join();
|
||||
|
||||
SSL_shutdown(mSsl);
|
||||
}
|
||||
}
|
||||
|
||||
DtlsTransport::State DtlsTransport::state() const { return mState; }
|
||||
|
||||
bool DtlsTransport::send(message_ptr message) {
|
||||
if (!message || mState != State::Connected)
|
||||
return false;
|
||||
|
||||
PLOG_VERBOSE << "Send size=" << message->size();
|
||||
|
||||
int ret = SSL_write(mSsl, message->data(), message->size());
|
||||
if (!check_openssl_ret(mSsl, ret))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DtlsTransport::incoming(message_ptr message) {
|
||||
if (message)
|
||||
mIncomingQueue.push(message);
|
||||
else
|
||||
mIncomingQueue.stop();
|
||||
}
|
||||
|
||||
void DtlsTransport::changeState(State state) {
|
||||
if (mState.exchange(state) != state)
|
||||
mStateChangeCallback(state);
|
||||
}
|
||||
|
||||
void DtlsTransport::runRecvLoop() {
|
||||
const size_t maxMtu = 4096;
|
||||
try {
|
||||
changeState(State::Connecting);
|
||||
|
||||
SSL_do_handshake(mSsl);
|
||||
|
||||
const size_t bufferSize = maxMtu;
|
||||
byte buffer[bufferSize];
|
||||
while (auto next = mIncomingQueue.pop()) {
|
||||
auto message = *next;
|
||||
BIO_write(mInBio, message->data(), message->size());
|
||||
int ret = SSL_read(mSsl, buffer, bufferSize);
|
||||
if (!check_openssl_ret(mSsl, ret))
|
||||
break;
|
||||
|
||||
auto decrypted = ret > 0 ? make_message(buffer, buffer + ret) : nullptr;
|
||||
|
||||
if (mState == State::Connecting) {
|
||||
if (unsigned long err = ERR_get_error())
|
||||
throw std::runtime_error("handshake failed: " + openssl_error_string(err));
|
||||
|
||||
if (SSL_is_init_finished(mSsl)) {
|
||||
changeState(State::Connected);
|
||||
|
||||
// RFC 8261: DTLS MUST support sending messages larger than the current path MTU
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
SSL_set_mtu(mSsl, maxMtu + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (decrypted)
|
||||
recv(decrypted);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "DTLS recv: " << e.what();
|
||||
}
|
||||
|
||||
if (mState == State::Connected) {
|
||||
PLOG_INFO << "DTLS disconnected";
|
||||
changeState(State::Disconnected);
|
||||
recv(nullptr);
|
||||
} else {
|
||||
PLOG_INFO << "DTLS handshake failed";
|
||||
changeState(State::Failed);
|
||||
}
|
||||
}
|
||||
|
||||
int DtlsTransport::CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx) {
|
||||
SSL *ssl =
|
||||
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
||||
DtlsTransport *t =
|
||||
static_cast<DtlsTransport *>(SSL_get_ex_data(ssl, DtlsTransport::TransportExIndex));
|
||||
|
||||
X509 *crt = X509_STORE_CTX_get_current_cert(ctx);
|
||||
std::string fingerprint = make_fingerprint(crt);
|
||||
|
||||
return t->mVerifierCallback(fingerprint) ? 1 : 0;
|
||||
}
|
||||
|
||||
void DtlsTransport::InfoCallback(const SSL *ssl, int where, int ret) {
|
||||
DtlsTransport *t =
|
||||
static_cast<DtlsTransport *>(SSL_get_ex_data(ssl, DtlsTransport::TransportExIndex));
|
||||
|
||||
if (where & SSL_CB_ALERT) {
|
||||
if (ret != 256) { // Close Notify
|
||||
PLOG_ERROR << "DTLS alert: " << SSL_alert_desc_string_long(ret);
|
||||
}
|
||||
t->mIncomingQueue.stop(); // Close the connection
|
||||
}
|
||||
}
|
||||
|
||||
int DtlsTransport::BioMethodNew(BIO *bio) {
|
||||
BIO_set_init(bio, 1);
|
||||
BIO_set_data(bio, NULL);
|
||||
BIO_set_shutdown(bio, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DtlsTransport::BioMethodFree(BIO *bio) {
|
||||
if (!bio)
|
||||
return 0;
|
||||
BIO_set_data(bio, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
|
||||
if (inl <= 0)
|
||||
return inl;
|
||||
auto transport = reinterpret_cast<DtlsTransport *>(BIO_get_data(bio));
|
||||
if (!transport)
|
||||
return -1;
|
||||
auto b = reinterpret_cast<const byte *>(in);
|
||||
return transport->outgoing(make_message(b, b + inl)) ? inl : 0;
|
||||
}
|
||||
|
||||
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) {
|
||||
switch (cmd) {
|
||||
case BIO_CTRL_FLUSH:
|
||||
return 1;
|
||||
case BIO_CTRL_DGRAM_QUERY_MTU:
|
||||
return 0; // SSL_OP_NO_QUERY_MTU must be set
|
||||
case BIO_CTRL_WPENDING:
|
||||
case BIO_CTRL_PENDING:
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -28,9 +28,14 @@
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#if USE_GNUTLS
|
||||
#include <gnutls/gnutls.h>
|
||||
#else
|
||||
#include <openssl/ssl.h>
|
||||
#endif
|
||||
|
||||
namespace rtc {
|
||||
|
||||
@ -49,16 +54,16 @@ public:
|
||||
|
||||
State state() const;
|
||||
|
||||
bool send(message_ptr message);
|
||||
void stop() override;
|
||||
bool send(message_ptr message) override; // false if dropped
|
||||
|
||||
private:
|
||||
void incoming(message_ptr message);
|
||||
void incoming(message_ptr message) override;
|
||||
void changeState(State state);
|
||||
void runRecvLoop();
|
||||
|
||||
const std::shared_ptr<Certificate> mCertificate;
|
||||
|
||||
gnutls_session_t mSession;
|
||||
Queue<message_ptr> mIncomingQueue;
|
||||
std::atomic<State> mState;
|
||||
std::thread mRecvThread;
|
||||
@ -66,10 +71,31 @@ private:
|
||||
verifier_callback mVerifierCallback;
|
||||
state_callback mStateChangeCallback;
|
||||
|
||||
#if USE_GNUTLS
|
||||
gnutls_session_t mSession;
|
||||
|
||||
static int CertificateCallback(gnutls_session_t session);
|
||||
static ssize_t WriteCallback(gnutls_transport_ptr_t ptr, const void *data, size_t len);
|
||||
static ssize_t ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen);
|
||||
static int TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms);
|
||||
#else
|
||||
SSL_CTX *mCtx;
|
||||
SSL *mSsl;
|
||||
BIO *mInBio, *mOutBio;
|
||||
|
||||
static BIO_METHOD *BioMethods;
|
||||
static int TransportExIndex;
|
||||
static std::mutex GlobalMutex;
|
||||
|
||||
static void GlobalInit();
|
||||
static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx);
|
||||
static void InfoCallback(const SSL *ssl, int where, int ret);
|
||||
|
||||
static int BioMethodNew(BIO *bio);
|
||||
static int BioMethodFree(BIO *bio);
|
||||
static int BioMethodWrite(BIO *bio, const char *in, int inl);
|
||||
static long BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
||||
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -17,40 +17,268 @@
|
||||
*/
|
||||
|
||||
#include "icetransport.hpp"
|
||||
#include "configuration.hpp"
|
||||
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
namespace rtc {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::weak_ptr;
|
||||
|
||||
#if USE_JUICE
|
||||
|
||||
namespace rtc {
|
||||
|
||||
IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
candidate_callback candidateCallback,
|
||||
state_callback stateChangeCallback,
|
||||
candidate_callback candidateCallback, state_callback stateChangeCallback,
|
||||
gathering_state_callback gatheringStateChangeCallback)
|
||||
: mRole(role), mMid("0"), mState(State::Disconnected), mGatheringState(GatheringState::New),
|
||||
mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr),
|
||||
mCandidateCallback(std::move(candidateCallback)),
|
||||
mStateChangeCallback(std::move(stateChangeCallback)),
|
||||
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)) {
|
||||
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
|
||||
mAgent(nullptr, nullptr) {
|
||||
|
||||
auto logLevelFlags = GLogLevelFlags(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION);
|
||||
g_log_set_handler(nullptr, logLevelFlags, LogCallback, this);
|
||||
nice_debug_enable(false);
|
||||
PLOG_DEBUG << "Initializing ICE transport (libjuice)";
|
||||
if (config.enableIceTcp) {
|
||||
PLOG_WARNING << "ICE-TCP is not supported with libjuice";
|
||||
}
|
||||
juice_set_log_handler(IceTransport::LogCallback);
|
||||
juice_set_log_level(JUICE_LOG_LEVEL_VERBOSE);
|
||||
|
||||
juice_config_t jconfig = {};
|
||||
jconfig.cb_state_changed = IceTransport::StateChangeCallback;
|
||||
jconfig.cb_candidate = IceTransport::CandidateCallback;
|
||||
jconfig.cb_gathering_done = IceTransport::GatheringDoneCallback;
|
||||
jconfig.cb_recv = IceTransport::RecvCallback;
|
||||
jconfig.user_ptr = this;
|
||||
|
||||
// Randomize servers order
|
||||
std::vector<IceServer> servers = config.iceServers;
|
||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
||||
|
||||
// Pick a STUN server
|
||||
for (auto &server : servers) {
|
||||
if (!server.hostname.empty() && server.type == IceServer::Type::Stun) {
|
||||
if (server.service.empty())
|
||||
server.service = "3478"; // STUN UDP port
|
||||
PLOG_DEBUG << "Using STUN server \"" << server.hostname << ":" << server.service
|
||||
<< "\"";
|
||||
mStunHostname = server.hostname;
|
||||
mStunService = server.service;
|
||||
jconfig.stun_server_host = mStunHostname.c_str();
|
||||
jconfig.stun_server_port = std::stoul(mStunService);
|
||||
}
|
||||
}
|
||||
|
||||
// TURN support is not implemented yet
|
||||
|
||||
// Create agent
|
||||
mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy);
|
||||
if (!mAgent)
|
||||
throw std::runtime_error("Failed to create the ICE agent");
|
||||
}
|
||||
|
||||
IceTransport::~IceTransport() { stop(); }
|
||||
|
||||
void IceTransport::stop() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
Description::Role IceTransport::role() const { return mRole; }
|
||||
|
||||
IceTransport::State IceTransport::state() const { return mState; }
|
||||
|
||||
Description IceTransport::getLocalDescription(Description::Type type) const {
|
||||
char sdp[JUICE_MAX_SDP_STRING_LEN];
|
||||
if (juice_get_local_description(mAgent.get(), sdp, JUICE_MAX_SDP_STRING_LEN) < 0)
|
||||
throw std::runtime_error("Failed to generate local SDP");
|
||||
|
||||
return Description(string(sdp), type, mRole);
|
||||
}
|
||||
|
||||
void IceTransport::setRemoteDescription(const Description &description) {
|
||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||
: Description::Role::Active;
|
||||
mMid = description.mid();
|
||||
// TODO
|
||||
// mTrickleTimeout = description.trickleEnabled() ? 30s : 0s;
|
||||
|
||||
if (juice_set_remote_description(mAgent.get(), string(description).c_str()) < 0)
|
||||
throw std::runtime_error("Failed to parse remote SDP");
|
||||
}
|
||||
|
||||
bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
|
||||
// Don't try to pass unresolved candidates for more safety
|
||||
if (!candidate.isResolved())
|
||||
return false;
|
||||
|
||||
return juice_add_remote_candidate(mAgent.get(), string(candidate).c_str()) >= 0;
|
||||
}
|
||||
|
||||
void IceTransport::gatherLocalCandidates() {
|
||||
// Change state now as candidates calls can be synchronous
|
||||
changeGatheringState(GatheringState::InProgress);
|
||||
|
||||
if (juice_gather_candidates(mAgent.get()) < 0) {
|
||||
throw std::runtime_error("Failed to gather local ICE candidates");
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<string> IceTransport::getLocalAddress() const {
|
||||
char str[JUICE_MAX_ADDRESS_STRING_LEN];
|
||||
if (juice_get_selected_addresses(mAgent.get(), str, JUICE_MAX_ADDRESS_STRING_LEN, NULL, 0) ==
|
||||
0) {
|
||||
return std::make_optional(string(str));
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
std::optional<string> IceTransport::getRemoteAddress() const {
|
||||
char str[JUICE_MAX_ADDRESS_STRING_LEN];
|
||||
if (juice_get_selected_addresses(mAgent.get(), NULL, 0, str, JUICE_MAX_ADDRESS_STRING_LEN) ==
|
||||
0) {
|
||||
return std::make_optional(string(str));
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool IceTransport::send(message_ptr message) {
|
||||
if (!message || (mState != State::Connected && mState != State::Completed))
|
||||
return false;
|
||||
|
||||
PLOG_VERBOSE << "Send size=" << message->size();
|
||||
return outgoing(message);
|
||||
}
|
||||
|
||||
void IceTransport::incoming(message_ptr message) { recv(message); }
|
||||
|
||||
void IceTransport::incoming(const byte *data, int size) {
|
||||
incoming(make_message(data, data + size));
|
||||
}
|
||||
|
||||
bool IceTransport::outgoing(message_ptr message) {
|
||||
return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()),
|
||||
message->size()) >= 0;
|
||||
}
|
||||
|
||||
void IceTransport::changeState(State state) {
|
||||
if (mState.exchange(state) != state)
|
||||
mStateChangeCallback(mState);
|
||||
}
|
||||
|
||||
void IceTransport::changeGatheringState(GatheringState state) {
|
||||
if (mGatheringState.exchange(state) != state)
|
||||
mGatheringStateChangeCallback(mGatheringState);
|
||||
}
|
||||
|
||||
void IceTransport::processStateChange(unsigned int state) {
|
||||
changeState(static_cast<State>(state));
|
||||
}
|
||||
|
||||
void IceTransport::processCandidate(const string &candidate) {
|
||||
mCandidateCallback(Candidate(candidate, mMid));
|
||||
}
|
||||
|
||||
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
|
||||
|
||||
void IceTransport::StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
iceTransport->processStateChange(static_cast<unsigned int>(state));
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void IceTransport::CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
iceTransport->processCandidate(sdp);
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void IceTransport::GatheringDoneCallback(juice_agent_t *agent, void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
iceTransport->processGatheringDone();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void IceTransport::RecvCallback(juice_agent_t *agent, const char *data, size_t size,
|
||||
void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
iceTransport->incoming(reinterpret_cast<const byte *>(data), size);
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
|
||||
plog::Severity severity;
|
||||
switch (level) {
|
||||
case JUICE_LOG_LEVEL_FATAL:
|
||||
severity = plog::fatal;
|
||||
break;
|
||||
case JUICE_LOG_LEVEL_ERROR:
|
||||
severity = plog::error;
|
||||
break;
|
||||
case JUICE_LOG_LEVEL_WARN:
|
||||
severity = plog::warning;
|
||||
break;
|
||||
case JUICE_LOG_LEVEL_INFO:
|
||||
severity = plog::info;
|
||||
break;
|
||||
case JUICE_LOG_LEVEL_DEBUG:
|
||||
severity = plog::debug;
|
||||
break;
|
||||
default:
|
||||
severity = plog::verbose;
|
||||
break;
|
||||
}
|
||||
PLOG(severity) << "juice: " << message;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#else // USE_JUICE == 0
|
||||
|
||||
namespace rtc {
|
||||
|
||||
IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
candidate_callback candidateCallback, state_callback stateChangeCallback,
|
||||
gathering_state_callback gatheringStateChangeCallback)
|
||||
: mRole(role), mMid("0"), mState(State::Disconnected), mGatheringState(GatheringState::New),
|
||||
mCandidateCallback(std::move(candidateCallback)),
|
||||
mStateChangeCallback(std::move(stateChangeCallback)),
|
||||
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
|
||||
mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr) {
|
||||
|
||||
PLOG_DEBUG << "Initializing ICE transport (libnice)";
|
||||
|
||||
g_log_set_handler("libnice", G_LOG_LEVEL_MASK, LogCallback, this);
|
||||
|
||||
IF_PLOG(plog::verbose) {
|
||||
nice_debug_enable(false); // do not output STUN debug messages
|
||||
}
|
||||
|
||||
mMainLoop = decltype(mMainLoop)(g_main_loop_new(nullptr, FALSE), g_main_loop_unref);
|
||||
if (!mMainLoop)
|
||||
std::runtime_error("Failed to create the main loop");
|
||||
|
||||
// RFC 5245 was obsoleted by RFC 8445 but this should be OK.
|
||||
mNiceAgent = decltype(mNiceAgent)(
|
||||
nice_agent_new(g_main_loop_get_context(mMainLoop.get()), NICE_COMPATIBILITY_RFC5245),
|
||||
g_object_unref);
|
||||
@ -59,19 +287,39 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
throw std::runtime_error("Failed to create the nice agent");
|
||||
|
||||
mMainLoopThread = std::thread(g_main_loop_run, mMainLoop.get());
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", FALSE, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-udp", TRUE, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-tcp", FALSE, nullptr);
|
||||
|
||||
mStreamId = nice_agent_add_stream(mNiceAgent.get(), 1);
|
||||
if (!mStreamId)
|
||||
throw std::runtime_error("Failed to add a stream");
|
||||
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", TRUE, nullptr); // decided later
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-udp", TRUE, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-tcp", config.enableIceTcp ? TRUE : FALSE,
|
||||
nullptr);
|
||||
|
||||
// RFC 8445: Agents MUST NOT use an RTO value smaller than 500 ms.
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-initial-timeout", 500, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-max-retransmissions", 3, nullptr);
|
||||
|
||||
// RFC 8445: ICE agents SHOULD use a default Ta value, 50 ms, but MAY use another value based on
|
||||
// the characteristics of the associated data.
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-pacing-timer", 25, nullptr);
|
||||
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr);
|
||||
|
||||
// Randomize order
|
||||
std::vector<IceServer> servers = config.iceServers;
|
||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
||||
|
||||
// Add one STUN server
|
||||
bool success = false;
|
||||
for (auto &server : servers) {
|
||||
if (server.hostname.empty())
|
||||
continue;
|
||||
if (server.type != IceServer::Type::Stun)
|
||||
continue;
|
||||
if (server.service.empty())
|
||||
server.service = "3478"; // STUN UDP port
|
||||
|
||||
@ -91,6 +339,8 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
|
||||
servbuffer, MAX_NUMERICNODE_LEN,
|
||||
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
|
||||
PLOG_DEBUG << "Using STUN server \"" << server.hostname << ":" << server.service
|
||||
<< "\"";
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server", nodebuffer, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server-port",
|
||||
std::stoul(servbuffer), nullptr);
|
||||
@ -105,6 +355,56 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
break;
|
||||
}
|
||||
|
||||
// Add TURN servers
|
||||
for (auto &server : servers) {
|
||||
if (server.hostname.empty())
|
||||
continue;
|
||||
if (server.type != IceServer::Type::Turn)
|
||||
continue;
|
||||
if (server.service.empty())
|
||||
server.service = server.relayType == IceServer::RelayType::TurnTls ? "5349" : "3478";
|
||||
|
||||
struct addrinfo hints = {};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype =
|
||||
server.relayType == IceServer::RelayType::TurnUdp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
hints.ai_protocol =
|
||||
server.relayType == IceServer::RelayType::TurnUdp ? IPPROTO_UDP : IPPROTO_TCP;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
struct addrinfo *result = nullptr;
|
||||
if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0)
|
||||
continue;
|
||||
|
||||
for (auto p = result; p; p = p->ai_next) {
|
||||
if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
|
||||
char nodebuffer[MAX_NUMERICNODE_LEN];
|
||||
char servbuffer[MAX_NUMERICSERV_LEN];
|
||||
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
|
||||
servbuffer, MAX_NUMERICNODE_LEN,
|
||||
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
|
||||
|
||||
NiceRelayType niceRelayType;
|
||||
switch (server.relayType) {
|
||||
case IceServer::RelayType::TurnTcp:
|
||||
niceRelayType = NICE_RELAY_TYPE_TURN_TCP;
|
||||
break;
|
||||
case IceServer::RelayType::TurnTls:
|
||||
niceRelayType = NICE_RELAY_TYPE_TURN_TLS;
|
||||
break;
|
||||
default:
|
||||
niceRelayType = NICE_RELAY_TYPE_TURN_UDP;
|
||||
break;
|
||||
}
|
||||
nice_agent_set_relay_info(mNiceAgent.get(), mStreamId, 1, nodebuffer,
|
||||
std::stoul(servbuffer), server.username.c_str(),
|
||||
server.password.c_str(), niceRelayType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
g_signal_connect(G_OBJECT(mNiceAgent.get()), "component-state-changed",
|
||||
G_CALLBACK(StateChangeCallback), this);
|
||||
g_signal_connect(G_OBJECT(mNiceAgent.get()), "new-candidate-full",
|
||||
@ -112,10 +412,6 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
g_signal_connect(G_OBJECT(mNiceAgent.get()), "candidate-gathering-done",
|
||||
G_CALLBACK(GatheringDoneCallback), this);
|
||||
|
||||
mStreamId = nice_agent_add_stream(mNiceAgent.get(), 1);
|
||||
if (!mStreamId)
|
||||
throw std::runtime_error("Failed to add a stream");
|
||||
|
||||
nice_agent_set_stream_name(mNiceAgent.get(), mStreamId, "application");
|
||||
nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1, config.portRangeBegin,
|
||||
config.portRangeEnd);
|
||||
@ -124,17 +420,30 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
RecvCallback, this);
|
||||
}
|
||||
|
||||
IceTransport::~IceTransport() {
|
||||
IceTransport::~IceTransport() { stop(); }
|
||||
|
||||
void IceTransport::stop() {
|
||||
if (mTimeoutId) {
|
||||
g_source_remove(mTimeoutId);
|
||||
mTimeoutId = 0;
|
||||
}
|
||||
if (mMainLoopThread.joinable()) {
|
||||
PLOG_DEBUG << "Stopping ICE thread";
|
||||
g_main_loop_quit(mMainLoop.get());
|
||||
if (mMainLoopThread.joinable())
|
||||
mMainLoopThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
Description::Role IceTransport::role() const { return mRole; }
|
||||
|
||||
IceTransport::State IceTransport::state() const { return mState; }
|
||||
|
||||
Description IceTransport::getLocalDescription(Description::Type type) const {
|
||||
// RFC 8445: The initiating agent that started the ICE processing MUST take the controlling
|
||||
// role, and the other MUST take the controlled role.
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode",
|
||||
type == Description::Type::Offer ? TRUE : FALSE, nullptr);
|
||||
|
||||
std::unique_ptr<gchar[], void (*)(void *)> sdp(nice_agent_generate_local_sdp(mNiceAgent.get()),
|
||||
g_free);
|
||||
return Description(string(sdp.get()), type, mRole);
|
||||
@ -144,21 +453,18 @@ void IceTransport::setRemoteDescription(const Description &description) {
|
||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||
: Description::Role::Active;
|
||||
mMid = description.mid();
|
||||
mTrickleTimeout = description.trickleEnabled() ? 30s : 0s;
|
||||
|
||||
if (nice_agent_parse_remote_sdp(mNiceAgent.get(), string(description).c_str()))
|
||||
// Warning: libnice expects "\n" as end of line
|
||||
if (nice_agent_parse_remote_sdp(mNiceAgent.get(), description.generateSdp("\n").c_str()) < 0)
|
||||
throw std::runtime_error("Failed to parse remote SDP");
|
||||
}
|
||||
|
||||
void IceTransport::gatherLocalCandidates() {
|
||||
// Change state now as candidates calls can be synchronous
|
||||
changeGatheringState(GatheringState::InProgress);
|
||||
|
||||
if (!nice_agent_gather_candidates(mNiceAgent.get(), mStreamId)) {
|
||||
throw std::runtime_error("Failed to gather local ICE candidates");
|
||||
}
|
||||
}
|
||||
|
||||
bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
|
||||
// Don't try to pass unresolved candidates to libnice for more safety
|
||||
if (!candidate.isResolved())
|
||||
return false;
|
||||
|
||||
// Warning: the candidate string must start with "a=candidate:" and it must not end with a
|
||||
// newline, else libnice will reject it.
|
||||
string sdp(candidate);
|
||||
@ -174,12 +480,39 @@ bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
|
||||
return ret > 0;
|
||||
}
|
||||
|
||||
void IceTransport::gatherLocalCandidates() {
|
||||
// Change state now as candidates calls can be synchronous
|
||||
changeGatheringState(GatheringState::InProgress);
|
||||
|
||||
if (!nice_agent_gather_candidates(mNiceAgent.get(), mStreamId)) {
|
||||
throw std::runtime_error("Failed to gather local ICE candidates");
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<string> IceTransport::getLocalAddress() const {
|
||||
NiceCandidate *local = nullptr;
|
||||
NiceCandidate *remote = nullptr;
|
||||
if (nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote)) {
|
||||
return std::make_optional(AddressToString(local->addr));
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::optional<string> IceTransport::getRemoteAddress() const {
|
||||
NiceCandidate *local = nullptr;
|
||||
NiceCandidate *remote = nullptr;
|
||||
if (nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote)) {
|
||||
return std::make_optional(AddressToString(remote->addr));
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool IceTransport::send(message_ptr message) {
|
||||
if (!message || !mStreamId)
|
||||
if (!message || (mState != State::Connected && mState != State::Completed))
|
||||
return false;
|
||||
|
||||
outgoing(message);
|
||||
return true;
|
||||
PLOG_VERBOSE << "Send size=" << message->size();
|
||||
return outgoing(message);
|
||||
}
|
||||
|
||||
void IceTransport::incoming(message_ptr message) { recv(message); }
|
||||
@ -188,32 +521,58 @@ void IceTransport::incoming(const byte *data, int size) {
|
||||
incoming(make_message(data, data + size));
|
||||
}
|
||||
|
||||
void IceTransport::outgoing(message_ptr message) {
|
||||
nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(),
|
||||
reinterpret_cast<const char *>(message->data()));
|
||||
bool IceTransport::outgoing(message_ptr message) {
|
||||
return nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(),
|
||||
reinterpret_cast<const char *>(message->data())) >= 0;
|
||||
}
|
||||
|
||||
void IceTransport::changeState(State state) {
|
||||
mState = state;
|
||||
if (mState.exchange(state) != state)
|
||||
mStateChangeCallback(mState);
|
||||
}
|
||||
|
||||
void IceTransport::changeGatheringState(GatheringState state) {
|
||||
mGatheringState = state;
|
||||
if (mGatheringState.exchange(state) != state)
|
||||
mGatheringStateChangeCallback(mGatheringState);
|
||||
}
|
||||
|
||||
void IceTransport::processTimeout() {
|
||||
PLOG_WARNING << "ICE timeout";
|
||||
mTimeoutId = 0;
|
||||
changeState(State::Failed);
|
||||
}
|
||||
|
||||
void IceTransport::processCandidate(const string &candidate) {
|
||||
mCandidateCallback(Candidate(candidate, mMid));
|
||||
}
|
||||
|
||||
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
|
||||
|
||||
void IceTransport::processStateChange(uint32_t state) {
|
||||
if (state != NICE_COMPONENT_STATE_GATHERING)
|
||||
void IceTransport::processStateChange(unsigned int state) {
|
||||
if (state == NICE_COMPONENT_STATE_FAILED && mTrickleTimeout.count() > 0) {
|
||||
if (mTimeoutId)
|
||||
g_source_remove(mTimeoutId);
|
||||
mTimeoutId = g_timeout_add(mTrickleTimeout.count() /* ms */, TimeoutCallback, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == NICE_COMPONENT_STATE_CONNECTED && mTimeoutId) {
|
||||
g_source_remove(mTimeoutId);
|
||||
mTimeoutId = 0;
|
||||
}
|
||||
|
||||
changeState(static_cast<State>(state));
|
||||
}
|
||||
|
||||
string IceTransport::AddressToString(const NiceAddress &addr) {
|
||||
char buffer[NICE_ADDRESS_STRING_LEN];
|
||||
nice_address_to_string(&addr, buffer);
|
||||
unsigned int port = nice_address_get_port(&addr);
|
||||
std::ostringstream ss;
|
||||
ss << buffer << ":" << port;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void IceTransport::CandidateCallback(NiceAgent *agent, NiceCandidate *candidate,
|
||||
gpointer userData) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(userData);
|
||||
@ -221,7 +580,7 @@ void IceTransport::CandidateCallback(NiceAgent *agent, NiceCandidate *candidate,
|
||||
try {
|
||||
iceTransport->processCandidate(cand);
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "ICE candidate: " << e.what() << std::endl;
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
g_free(cand);
|
||||
}
|
||||
@ -231,7 +590,7 @@ void IceTransport::GatheringDoneCallback(NiceAgent *agent, guint streamId, gpoin
|
||||
try {
|
||||
iceTransport->processGatheringDone();
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "ICE gathering done: " << e.what() << std::endl;
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,7 +600,7 @@ void IceTransport::StateChangeCallback(NiceAgent *agent, guint streamId, guint c
|
||||
try {
|
||||
iceTransport->processStateChange(state);
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "ICE change state: " << e.what() << std::endl;
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,13 +610,40 @@ void IceTransport::RecvCallback(NiceAgent *agent, guint streamId, guint componen
|
||||
try {
|
||||
iceTransport->incoming(reinterpret_cast<byte *>(buf), len);
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "ICE incoming: " << e.what() << std::endl;
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
gboolean IceTransport::TimeoutCallback(gpointer userData) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(userData);
|
||||
try {
|
||||
iceTransport->processTimeout();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void IceTransport::LogCallback(const gchar *logDomain, GLogLevelFlags logLevel,
|
||||
const gchar *message, gpointer userData) {
|
||||
std::cout << message << std::endl;
|
||||
plog::Severity severity;
|
||||
unsigned int flags = logLevel & G_LOG_LEVEL_MASK;
|
||||
if (flags & G_LOG_LEVEL_ERROR)
|
||||
severity = plog::fatal;
|
||||
else if (flags & G_LOG_LEVEL_CRITICAL)
|
||||
severity = plog::error;
|
||||
else if (flags & G_LOG_LEVEL_WARNING)
|
||||
severity = plog::warning;
|
||||
else if (flags & G_LOG_LEVEL_MESSAGE)
|
||||
severity = plog::info;
|
||||
else if (flags & G_LOG_LEVEL_INFO)
|
||||
severity = plog::info;
|
||||
else
|
||||
severity = plog::verbose; // libnice debug as verbose
|
||||
|
||||
PLOG(severity) << "nice: " << message;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
||||
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -26,25 +26,37 @@
|
||||
#include "peerconnection.hpp"
|
||||
#include "transport.hpp"
|
||||
|
||||
extern "C" {
|
||||
#if USE_JUICE
|
||||
#include <juice/juice.h>
|
||||
#else
|
||||
#include <nice/agent.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
class IceTransport : public Transport {
|
||||
public:
|
||||
enum class State : uint32_t {
|
||||
#if USE_JUICE
|
||||
enum class State : unsigned int{
|
||||
Disconnected = JUICE_STATE_DISCONNECTED,
|
||||
Connecting = JUICE_STATE_CONNECTING,
|
||||
Connected = JUICE_STATE_CONNECTED,
|
||||
Completed = JUICE_STATE_COMPLETED,
|
||||
Failed = JUICE_STATE_FAILED,
|
||||
};
|
||||
#else
|
||||
enum class State : unsigned int {
|
||||
Disconnected = NICE_COMPONENT_STATE_DISCONNECTED,
|
||||
Connecting = NICE_COMPONENT_STATE_CONNECTING,
|
||||
Connected = NICE_COMPONENT_STATE_CONNECTED,
|
||||
Ready = NICE_COMPONENT_STATE_READY,
|
||||
Failed = NICE_COMPONENT_STATE_FAILED
|
||||
Completed = NICE_COMPONENT_STATE_READY,
|
||||
Failed = NICE_COMPONENT_STATE_FAILED,
|
||||
};
|
||||
|
||||
#endif
|
||||
enum class GatheringState { New = 0, InProgress = 1, Complete = 2 };
|
||||
|
||||
using candidate_callback = std::function<void(const Candidate &candidate)>;
|
||||
@ -58,48 +70,70 @@ public:
|
||||
|
||||
Description::Role role() const;
|
||||
State state() const;
|
||||
GatheringState gyyatheringState() const;
|
||||
GatheringState gatheringState() const;
|
||||
Description getLocalDescription(Description::Type type) const;
|
||||
void setRemoteDescription(const Description &description);
|
||||
void gatherLocalCandidates();
|
||||
bool addRemoteCandidate(const Candidate &candidate);
|
||||
void gatherLocalCandidates();
|
||||
|
||||
bool send(message_ptr message);
|
||||
std::optional<string> getLocalAddress() const;
|
||||
std::optional<string> getRemoteAddress() const;
|
||||
|
||||
void stop() override;
|
||||
bool send(message_ptr message) override; // false if dropped
|
||||
|
||||
private:
|
||||
void incoming(message_ptr message);
|
||||
void incoming(message_ptr message) override;
|
||||
void incoming(const byte *data, int size);
|
||||
void outgoing(message_ptr message);
|
||||
bool outgoing(message_ptr message) override;
|
||||
|
||||
void changeState(State state);
|
||||
void changeGatheringState(GatheringState state);
|
||||
|
||||
void processStateChange(unsigned int state);
|
||||
void processCandidate(const string &candidate);
|
||||
void processGatheringDone();
|
||||
void processStateChange(uint32_t state);
|
||||
void processTimeout();
|
||||
|
||||
Description::Role mRole;
|
||||
string mMid;
|
||||
std::chrono::milliseconds mTrickleTimeout;
|
||||
std::atomic<State> mState;
|
||||
std::atomic<GatheringState> mGatheringState;
|
||||
|
||||
uint32_t mStreamId = 0;
|
||||
std::unique_ptr<NiceAgent, void (*)(gpointer)> mNiceAgent;
|
||||
std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop;
|
||||
std::thread mMainLoopThread;
|
||||
|
||||
candidate_callback mCandidateCallback;
|
||||
state_callback mStateChangeCallback;
|
||||
gathering_state_callback mGatheringStateChangeCallback;
|
||||
|
||||
#if USE_JUICE
|
||||
std::unique_ptr<juice_agent_t, void (*)(juice_agent_t *)> mAgent;
|
||||
string mStunHostname;
|
||||
string mStunService;
|
||||
|
||||
static void StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr);
|
||||
static void CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr);
|
||||
static void GatheringDoneCallback(juice_agent_t *agent, void *user_ptr);
|
||||
static void RecvCallback(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
|
||||
static void LogCallback(juice_log_level_t level, const char *message);
|
||||
#else
|
||||
uint32_t mStreamId = 0;
|
||||
std::unique_ptr<NiceAgent, void (*)(gpointer)> mNiceAgent;
|
||||
std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop;
|
||||
std::thread mMainLoopThread;
|
||||
guint mTimeoutId = 0;
|
||||
|
||||
static string AddressToString(const NiceAddress &addr);
|
||||
|
||||
static void CandidateCallback(NiceAgent *agent, NiceCandidate *candidate, gpointer userData);
|
||||
static void GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData);
|
||||
static void StateChangeCallback(NiceAgent *agent, guint streamId, guint componentId,
|
||||
guint state, gpointer userData);
|
||||
static void RecvCallback(NiceAgent *agent, guint stream_id, guint component_id, guint len,
|
||||
gchar *buf, gpointer userData);
|
||||
static gboolean TimeoutCallback(gpointer userData);
|
||||
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
|
||||
gpointer user_data);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "certificate.hpp"
|
||||
#include "dtlstransport.hpp"
|
||||
#include "icetransport.hpp"
|
||||
#include "include.hpp"
|
||||
#include "sctptransport.hpp"
|
||||
|
||||
#include <iostream>
|
||||
@ -28,15 +29,38 @@ namespace rtc {
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
using std::function;
|
||||
using std::shared_ptr;
|
||||
using std::weak_ptr;
|
||||
|
||||
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
|
||||
|
||||
PeerConnection::PeerConnection(const Configuration &config)
|
||||
: mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New) {}
|
||||
|
||||
PeerConnection::~PeerConnection() {}
|
||||
PeerConnection::~PeerConnection() {
|
||||
changeState(State::Destroying);
|
||||
close();
|
||||
mSctpTransport.reset();
|
||||
mDtlsTransport.reset();
|
||||
mIceTransport.reset();
|
||||
}
|
||||
|
||||
void PeerConnection::close() {
|
||||
// Close DataChannels
|
||||
closeDataChannels();
|
||||
mDataChannels.clear();
|
||||
|
||||
// Close Transports
|
||||
for (int i = 0; i < 2; ++i) { // Make sure a transport wasn't spawn behind our back
|
||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||
transport->stop();
|
||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
||||
transport->stop();
|
||||
if (auto transport = std::atomic_load(&mIceTransport))
|
||||
transport->stop();
|
||||
}
|
||||
changeState(State::Closed);
|
||||
}
|
||||
|
||||
const Configuration *PeerConnection::config() const { return &mConfig; }
|
||||
|
||||
@ -44,38 +68,98 @@ PeerConnection::State PeerConnection::state() const { return mState; }
|
||||
|
||||
PeerConnection::GatheringState PeerConnection::gatheringState() const { return mGatheringState; }
|
||||
|
||||
std::optional<Description> PeerConnection::localDescription() const { return mLocalDescription; }
|
||||
|
||||
std::optional<Description> PeerConnection::remoteDescription() const { return mRemoteDescription; }
|
||||
|
||||
void PeerConnection::setRemoteDescription(Description description) {
|
||||
if (!mIceTransport) {
|
||||
initIceTransport(Description::Role::ActPass);
|
||||
mIceTransport->setRemoteDescription(description);
|
||||
processLocalDescription(mIceTransport->getLocalDescription(Description::Type::Answer));
|
||||
mIceTransport->gatherLocalCandidates();
|
||||
} else {
|
||||
mIceTransport->setRemoteDescription(description);
|
||||
std::optional<Description> PeerConnection::localDescription() const {
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
return mLocalDescription;
|
||||
}
|
||||
|
||||
std::optional<Description> PeerConnection::remoteDescription() const {
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
return mRemoteDescription;
|
||||
}
|
||||
|
||||
void PeerConnection::setRemoteDescription(Description description) {
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
|
||||
auto remoteCandidates = description.extractCandidates();
|
||||
mRemoteDescription.emplace(std::move(description));
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
if (!iceTransport)
|
||||
iceTransport = initIceTransport(Description::Role::ActPass);
|
||||
|
||||
iceTransport->setRemoteDescription(*mRemoteDescription);
|
||||
|
||||
if (mRemoteDescription->type() == Description::Type::Offer) {
|
||||
// This is an offer and we are the answerer.
|
||||
processLocalDescription(iceTransport->getLocalDescription(Description::Type::Answer));
|
||||
iceTransport->gatherLocalCandidates();
|
||||
} else {
|
||||
// This is an answer and we are the offerer.
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
|
||||
// Since we assumed passive role during DataChannel creation, we need to shift the
|
||||
// stream numbers by one to shift them from odd to even.
|
||||
decltype(mDataChannels) newDataChannels;
|
||||
iterateDataChannels([&](shared_ptr<DataChannel> channel) {
|
||||
if (channel->stream() % 2 == 1)
|
||||
channel->mStream -= 1;
|
||||
newDataChannels.emplace(channel->stream(), channel);
|
||||
});
|
||||
std::swap(mDataChannels, newDataChannels);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &candidate : remoteCandidates)
|
||||
addRemoteCandidate(candidate);
|
||||
}
|
||||
|
||||
void PeerConnection::addRemoteCandidate(Candidate candidate) {
|
||||
if (!mRemoteDescription || !mIceTransport)
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
if (!mRemoteDescription || !iceTransport)
|
||||
throw std::logic_error("Remote candidate set without remote description");
|
||||
|
||||
if (mIceTransport->addRemoteCandidate(candidate))
|
||||
mRemoteDescription->addCandidate(std::move(candidate));
|
||||
mRemoteDescription->addCandidate(candidate);
|
||||
|
||||
if (candidate.resolve(Candidate::ResolveMode::Simple)) {
|
||||
iceTransport->addRemoteCandidate(candidate);
|
||||
} else {
|
||||
// OK, we might need a lookup, do it asynchronously
|
||||
weak_ptr<IceTransport> weakIceTransport{iceTransport};
|
||||
std::thread t([weakIceTransport, candidate]() mutable {
|
||||
if (candidate.resolve(Candidate::ResolveMode::Lookup))
|
||||
if (auto iceTransport = weakIceTransport.lock())
|
||||
iceTransport->addRemoteCandidate(candidate);
|
||||
});
|
||||
t.detach();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<string> PeerConnection::localAddress() const {
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
return iceTransport ? iceTransport->getLocalAddress() : nullopt;
|
||||
}
|
||||
|
||||
std::optional<string> PeerConnection::remoteAddress() const {
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
return iceTransport ? iceTransport->getRemoteAddress() : nullopt;
|
||||
}
|
||||
|
||||
shared_ptr<DataChannel> PeerConnection::createDataChannel(const string &label,
|
||||
const string &protocol,
|
||||
const Reliability &reliability) {
|
||||
// RFC 5763: The answerer MUST use either a setup attribute value of setup:active or
|
||||
// setup:passive. [...] Thus, setup:active is RECOMMENDED.
|
||||
// See https://tools.ietf.org/html/rfc5763#section-5
|
||||
// Therefore, we assume passive role when we are the offerer.
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
auto role = iceTransport ? iceTransport->role() : Description::Role::Passive;
|
||||
|
||||
// The active side must use streams with even identifiers, whereas the passive side must use
|
||||
// streams with odd identifiers.
|
||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
|
||||
auto role = mIceTransport ? mIceTransport->role() : Description::Role::Active;
|
||||
unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
|
||||
while (mDataChannels.find(stream) != mDataChannels.end()) {
|
||||
stream += 2;
|
||||
@ -83,21 +167,27 @@ shared_ptr<DataChannel> PeerConnection::createDataChannel(const string &label,
|
||||
throw std::runtime_error("Too many DataChannels");
|
||||
}
|
||||
|
||||
auto channel = std::make_shared<DataChannel>(stream, label, protocol, reliability);
|
||||
auto channel =
|
||||
std::make_shared<DataChannel>(shared_from_this(), stream, label, protocol, reliability);
|
||||
mDataChannels.insert(std::make_pair(stream, channel));
|
||||
|
||||
if (!mIceTransport) {
|
||||
initIceTransport(Description::Role::Active);
|
||||
processLocalDescription(mIceTransport->getLocalDescription(Description::Type::Offer));
|
||||
mIceTransport->gatherLocalCandidates();
|
||||
} else if (mSctpTransport && mSctpTransport->state() == SctpTransport::State::Connected) {
|
||||
channel->open(mSctpTransport);
|
||||
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);
|
||||
processLocalDescription(iceTransport->getLocalDescription(Description::Type::Offer));
|
||||
iceTransport->gatherLocalCandidates();
|
||||
} else {
|
||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||
if (transport->state() == SctpTransport::State::Connected)
|
||||
channel->open(transport);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
void PeerConnection::onDataChannel(
|
||||
std::function<void(std::shared_ptr<DataChannel> dataChannel)> callback) {
|
||||
std::function<void(shared_ptr<DataChannel> dataChannel)> callback) {
|
||||
mDataChannelCallback = callback;
|
||||
}
|
||||
|
||||
@ -118,8 +208,12 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
|
||||
mGatheringStateChangeCallback = callback;
|
||||
}
|
||||
|
||||
void PeerConnection::initIceTransport(Description::Role role) {
|
||||
mIceTransport = std::make_shared<IceTransport>(
|
||||
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mIceTransport))
|
||||
return transport;
|
||||
|
||||
auto transport = std::make_shared<IceTransport>(
|
||||
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
|
||||
[this](IceTransport::State state) {
|
||||
switch (state) {
|
||||
@ -129,9 +223,12 @@ void PeerConnection::initIceTransport(Description::Role role) {
|
||||
case IceTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case IceTransport::State::Ready:
|
||||
case IceTransport::State::Connected:
|
||||
initDtlsTransport();
|
||||
break;
|
||||
case IceTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
@ -143,8 +240,7 @@ void PeerConnection::initIceTransport(Description::Role role) {
|
||||
changeGatheringState(GatheringState::InProgress);
|
||||
break;
|
||||
case IceTransport::GatheringState::Complete:
|
||||
if (mLocalDescription)
|
||||
mLocalDescription->endCandidates();
|
||||
endLocalCandidates();
|
||||
changeGatheringState(GatheringState::Complete);
|
||||
break;
|
||||
default:
|
||||
@ -152,11 +248,18 @@ void PeerConnection::initIceTransport(Description::Role role) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mIceTransport, transport);
|
||||
return transport;
|
||||
}
|
||||
|
||||
void PeerConnection::initDtlsTransport() {
|
||||
mDtlsTransport = std::make_shared<DtlsTransport>(
|
||||
mIceTransport, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
|
||||
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
||||
return transport;
|
||||
|
||||
auto lower = std::atomic_load(&mIceTransport);
|
||||
auto transport = std::make_shared<DtlsTransport>(
|
||||
lower, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
|
||||
[this](DtlsTransport::State state) {
|
||||
switch (state) {
|
||||
case DtlsTransport::State::Connected:
|
||||
@ -165,27 +268,7 @@ void PeerConnection::initDtlsTransport() {
|
||||
case DtlsTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PeerConnection::initSctpTransport() {
|
||||
uint16_t sctpPort = mRemoteDescription->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
||||
mSctpTransport = std::make_shared<SctpTransport>(
|
||||
mDtlsTransport, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1),
|
||||
[this](SctpTransport::State state) {
|
||||
switch (state) {
|
||||
case SctpTransport::State::Connected:
|
||||
changeState(State::Connected);
|
||||
openDataChannels();
|
||||
break;
|
||||
case SctpTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case SctpTransport::State::Disconnected:
|
||||
case DtlsTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
@ -193,9 +276,51 @@ void PeerConnection::initSctpTransport() {
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mDtlsTransport, transport);
|
||||
return transport;
|
||||
}
|
||||
|
||||
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||
return transport;
|
||||
|
||||
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
||||
auto lower = std::atomic_load(&mDtlsTransport);
|
||||
auto transport = std::make_shared<SctpTransport>(
|
||||
lower, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1),
|
||||
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
|
||||
[this](SctpTransport::State state) {
|
||||
switch (state) {
|
||||
case SctpTransport::State::Connected:
|
||||
changeState(State::Connected);
|
||||
openDataChannels();
|
||||
break;
|
||||
case SctpTransport::State::Failed:
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case SctpTransport::State::Disconnected:
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mSctpTransport, transport);
|
||||
return transport;
|
||||
}
|
||||
|
||||
void PeerConnection::endLocalCandidates() {
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
if (mLocalDescription)
|
||||
mLocalDescription->endCandidates();
|
||||
}
|
||||
|
||||
bool PeerConnection::checkFingerprint(const std::string &fingerprint) const {
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
if (auto expectedFingerprint =
|
||||
mRemoteDescription ? mRemoteDescription->fingerprint() : nullopt) {
|
||||
return *expectedFingerprint == fingerprint;
|
||||
@ -204,11 +329,8 @@ bool PeerConnection::checkFingerprint(const std::string &fingerprint) const {
|
||||
}
|
||||
|
||||
void PeerConnection::forwardMessage(message_ptr message) {
|
||||
if (!mIceTransport || !mSctpTransport)
|
||||
throw std::logic_error("Got a DataChannel message without transport");
|
||||
|
||||
if (!message) {
|
||||
closeDataChannels();
|
||||
remoteCloseDataChannels();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -221,17 +343,24 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
||||
}
|
||||
}
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (!iceTransport || !sctpTransport)
|
||||
return;
|
||||
|
||||
if (!channel) {
|
||||
const byte dataChannelOpenMessage{0x03};
|
||||
unsigned int remoteParity = (mIceTransport->role() == Description::Role::Active) ? 1 : 0;
|
||||
unsigned int remoteParity = (iceTransport->role() == Description::Role::Active) ? 1 : 0;
|
||||
if (message->type == Message::Control && *message->data() == dataChannelOpenMessage &&
|
||||
message->stream % 2 == remoteParity) {
|
||||
channel = std::make_shared<DataChannel>(message->stream, mSctpTransport);
|
||||
channel->onOpen(std::bind(&PeerConnection::triggerDataChannel, this, channel));
|
||||
channel =
|
||||
std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
|
||||
channel->onOpen(std::bind(&PeerConnection::triggerDataChannel, this,
|
||||
weak_ptr<DataChannel>{channel}));
|
||||
mDataChannels.insert(std::make_pair(message->stream, channel));
|
||||
} else {
|
||||
// Invalid, close the DataChannel by resetting the stream
|
||||
mSctpTransport->reset(message->stream);
|
||||
sctpTransport->reset(message->stream);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -239,6 +368,20 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
||||
channel->incoming(message);
|
||||
}
|
||||
|
||||
void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
|
||||
shared_ptr<DataChannel> channel;
|
||||
if (auto it = mDataChannels.find(stream); it != mDataChannels.end()) {
|
||||
channel = it->second.lock();
|
||||
if (!channel || channel->isClosed()) {
|
||||
mDataChannels.erase(it);
|
||||
channel = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel)
|
||||
channel->triggerBufferedAmount(amount);
|
||||
}
|
||||
|
||||
void PeerConnection::iterateDataChannels(
|
||||
std::function<void(shared_ptr<DataChannel> channel)> func) {
|
||||
auto it = mDataChannels.begin();
|
||||
@ -254,48 +397,64 @@ void PeerConnection::iterateDataChannels(
|
||||
}
|
||||
|
||||
void PeerConnection::openDataChannels() {
|
||||
iterateDataChannels([this](shared_ptr<DataChannel> channel) { channel->open(mSctpTransport); });
|
||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||
iterateDataChannels([&](shared_ptr<DataChannel> channel) { channel->open(transport); });
|
||||
}
|
||||
|
||||
void PeerConnection::closeDataChannels() {
|
||||
iterateDataChannels([](shared_ptr<DataChannel> channel) { channel->close(); });
|
||||
iterateDataChannels([&](shared_ptr<DataChannel> channel) { channel->close(); });
|
||||
}
|
||||
|
||||
void PeerConnection::remoteCloseDataChannels() {
|
||||
iterateDataChannels([&](shared_ptr<DataChannel> channel) { channel->remoteClose(); });
|
||||
}
|
||||
|
||||
void PeerConnection::processLocalDescription(Description description) {
|
||||
auto remoteSctpPort = mRemoteDescription ? mRemoteDescription->sctpPort() : nullopt;
|
||||
std::optional<uint16_t> remoteSctpPort;
|
||||
if (auto remote = remoteDescription())
|
||||
remoteSctpPort = remote->sctpPort();
|
||||
|
||||
description.setFingerprint(mCertificate->fingerprint());
|
||||
description.setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
mLocalDescription.emplace(std::move(description));
|
||||
mLocalDescription->setFingerprint(mCertificate->fingerprint());
|
||||
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
||||
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||
|
||||
if (mLocalDescriptionCallback)
|
||||
mLocalDescriptionCallback(*mLocalDescription);
|
||||
}
|
||||
|
||||
void PeerConnection::processLocalCandidate(Candidate candidate) {
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
if (!mLocalDescription)
|
||||
throw std::logic_error("Got a local candidate without local description");
|
||||
|
||||
mLocalDescription->addCandidate(candidate);
|
||||
|
||||
if (mLocalCandidateCallback)
|
||||
mLocalCandidateCallback(candidate);
|
||||
}
|
||||
|
||||
void PeerConnection::triggerDataChannel(std::shared_ptr<DataChannel> dataChannel) {
|
||||
if (mDataChannelCallback)
|
||||
void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
|
||||
auto dataChannel = weakDataChannel.lock();
|
||||
if (!dataChannel)
|
||||
return;
|
||||
|
||||
mDataChannelCallback(dataChannel);
|
||||
}
|
||||
|
||||
void PeerConnection::changeState(State state) {
|
||||
mState = state;
|
||||
if (mStateChangeCallback)
|
||||
State current;
|
||||
do {
|
||||
current = mState.load();
|
||||
if (current == state || current == State::Destroying)
|
||||
return;
|
||||
} while (!mState.compare_exchange_weak(current, state));
|
||||
|
||||
if (state != State::Destroying)
|
||||
mStateChangeCallback(state);
|
||||
}
|
||||
|
||||
void PeerConnection::changeGatheringState(GatheringState state) {
|
||||
mGatheringState = state;
|
||||
if (mGatheringStateChangeCallback)
|
||||
if (mGatheringState.exchange(state) != state)
|
||||
mGatheringStateChangeCallback(state);
|
||||
}
|
||||
|
||||
@ -320,6 +479,12 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &st
|
||||
case State::Failed:
|
||||
str = "failed";
|
||||
break;
|
||||
case State::Closed:
|
||||
str = "closed";
|
||||
break;
|
||||
case State::Destroying:
|
||||
str = "destroying";
|
||||
break;
|
||||
default:
|
||||
str = "unknown";
|
||||
break;
|
||||
|
@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef RTC_QUEUE_H
|
||||
#define RTC_QUEUE_H
|
||||
|
||||
#include "include.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
template <typename T> class Queue {
|
||||
public:
|
||||
Queue();
|
||||
~Queue();
|
||||
|
||||
void stop();
|
||||
void push(const T &element);
|
||||
std::optional<T> pop();
|
||||
|
||||
bool empty() const;
|
||||
|
||||
private:
|
||||
std::queue<T> mQueue;
|
||||
std::condition_variable mCondition;
|
||||
std::atomic<bool> mStopping;
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
};
|
||||
|
||||
template <typename T> Queue<T>::Queue() : mStopping(false) {}
|
||||
|
||||
template <typename T> Queue<T>::~Queue() { stop(); }
|
||||
|
||||
template <typename T> void Queue<T>::stop() {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mStopping = true;
|
||||
mCondition.notify_all();
|
||||
}
|
||||
|
||||
template <typename T> void Queue<T>::push(const T &element) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mStopping)
|
||||
return;
|
||||
mQueue.push(element);
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
template <typename T> std::optional<T> Queue<T>::pop() {
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
while (mQueue.empty()) {
|
||||
if (mStopping)
|
||||
return nullopt;
|
||||
mCondition.wait(lock);
|
||||
}
|
||||
|
||||
std::optional<T> element = mQueue.front();
|
||||
mQueue.pop();
|
||||
return element;
|
||||
}
|
||||
|
||||
template <typename T> bool Queue<T>::empty() const {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mQueue.empty();
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -17,12 +17,15 @@
|
||||
*/
|
||||
|
||||
#include "datachannel.hpp"
|
||||
#include "include.hpp"
|
||||
#include "peerconnection.hpp"
|
||||
|
||||
#include <rtc.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <plog/Appenders/ColorConsoleAppender.h>
|
||||
|
||||
using namespace rtc;
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
@ -41,6 +44,8 @@ void *getUserPointer(int id) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void rtcInitLogger(rtc_log_level_t level) { InitLogger(static_cast<LogLevel>(level)); }
|
||||
|
||||
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount) {
|
||||
Configuration config;
|
||||
for (int i = 0; i < iceServersCount; ++i) {
|
||||
@ -209,4 +214,3 @@ void rtcSetUserPointer(int i, void *ptr) {
|
||||
else
|
||||
userPointerMap.erase(i);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,9 @@
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace std::chrono;
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace rtc {
|
||||
@ -33,34 +36,49 @@ std::mutex SctpTransport::GlobalMutex;
|
||||
int SctpTransport::InstancesCount = 0;
|
||||
|
||||
void SctpTransport::GlobalInit() {
|
||||
std::unique_lock<std::mutex> lock(GlobalMutex);
|
||||
std::lock_guard lock(GlobalMutex);
|
||||
if (InstancesCount++ == 0) {
|
||||
usrsctp_init(0, &SctpTransport::WriteCallback, nullptr);
|
||||
usrsctp_sysctl_set_sctp_ecn_enable(0);
|
||||
usrsctp_sysctl_set_sctp_init_rtx_max_default(5);
|
||||
usrsctp_sysctl_set_sctp_path_rtx_max_default(5);
|
||||
usrsctp_sysctl_set_sctp_assoc_rtx_max_default(5); // single path
|
||||
usrsctp_sysctl_set_sctp_rto_min_default(1 * 1000); // ms
|
||||
usrsctp_sysctl_set_sctp_rto_max_default(10 * 1000); // ms
|
||||
usrsctp_sysctl_set_sctp_rto_initial_default(1 * 1000); // ms
|
||||
usrsctp_sysctl_set_sctp_init_rto_max_default(10 * 1000); // ms
|
||||
usrsctp_sysctl_set_sctp_heartbeat_interval_default(10 * 1000); // ms
|
||||
}
|
||||
}
|
||||
|
||||
void SctpTransport::GlobalCleanup() {
|
||||
std::unique_lock<std::mutex> lock(GlobalMutex);
|
||||
if (InstancesCount-- == 0) {
|
||||
std::lock_guard lock(GlobalMutex);
|
||||
if (--InstancesCount == 0) {
|
||||
usrsctp_finish();
|
||||
}
|
||||
}
|
||||
|
||||
SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, message_callback recv,
|
||||
SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
message_callback recvCallback, amount_callback bufferedAmountCallback,
|
||||
state_callback stateChangeCallback)
|
||||
: Transport(lower), mPort(port), mState(State::Disconnected),
|
||||
mStateChangeCallback(std::move(stateChangeCallback)) {
|
||||
|
||||
onRecv(recv);
|
||||
: Transport(lower), mPort(port), mSendQueue(0, message_size_func),
|
||||
mBufferedAmountCallback(std::move(bufferedAmountCallback)),
|
||||
mStateChangeCallback(std::move(stateChangeCallback)), mState(State::Disconnected) {
|
||||
onRecv(recvCallback);
|
||||
|
||||
PLOG_DEBUG << "Initializing SCTP transport";
|
||||
GlobalInit();
|
||||
usrsctp_register_address(this);
|
||||
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::ReadCallback,
|
||||
nullptr, 0, this);
|
||||
if (!mSock)
|
||||
throw std::runtime_error("Could not create usrsctp socket, errno=" + std::to_string(errno));
|
||||
|
||||
usrsctp_register_address(this);
|
||||
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
|
||||
&SctpTransport::SendCallback, 0, this);
|
||||
if (!mSock)
|
||||
throw std::runtime_error("Could not create SCTP socket, errno=" + std::to_string(errno));
|
||||
|
||||
if (usrsctp_set_non_blocking(mSock, 1))
|
||||
throw std::runtime_error("Unable to set non-blocking mode, errno=" + std::to_string(errno));
|
||||
|
||||
// SCTP must stop sending after the lower layer is shut down, so disable linger
|
||||
struct linger sol = {};
|
||||
sol.l_onoff = 1;
|
||||
sol.l_linger = 0;
|
||||
@ -68,14 +86,6 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, me
|
||||
throw std::runtime_error("Could not set socket option SO_LINGER, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
struct sctp_paddrparams spp = {};
|
||||
spp.spp_flags = SPP_PMTUD_DISABLE;
|
||||
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &spp, sizeof(spp)))
|
||||
throw std::runtime_error("Could not set socket option SCTP_PEER_ADDR_PARAMS, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
struct sctp_assoc_value av = {};
|
||||
av.assoc_id = SCTP_ALL_ASSOC;
|
||||
av.assoc_value = 1;
|
||||
@ -83,17 +93,42 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, me
|
||||
throw std::runtime_error("Could not set socket option SCTP_ENABLE_STREAM_RESET, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
uint32_t nodelay = 1;
|
||||
struct sctp_event se = {};
|
||||
se.se_assoc_id = SCTP_ALL_ASSOC;
|
||||
se.se_on = 1;
|
||||
se.se_type = SCTP_ASSOC_CHANGE;
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_EVENT, &se, sizeof(se)))
|
||||
throw std::runtime_error("Could not subscribe to event SCTP_ASSOC_CHANGE, errno=" +
|
||||
std::to_string(errno));
|
||||
se.se_type = SCTP_SENDER_DRY_EVENT;
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_EVENT, &se, sizeof(se)))
|
||||
throw std::runtime_error("Could not subscribe to event SCTP_SENDER_DRY_EVENT, errno=" +
|
||||
std::to_string(errno));
|
||||
se.se_type = SCTP_STREAM_RESET_EVENT;
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_EVENT, &se, sizeof(se)))
|
||||
throw std::runtime_error("Could not subscribe to event SCTP_STREAM_RESET_EVENT, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
// The sender SHOULD disable the Nagle algorithm (see RFC1122) to minimize the latency.
|
||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.6
|
||||
int nodelay = 1;
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay)))
|
||||
throw std::runtime_error("Could not set socket option SCTP_NODELAY, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
struct sctp_event se = {};
|
||||
se.se_assoc_id = SCTP_ALL_ASSOC;
|
||||
se.se_on = 1;
|
||||
se.se_type = SCTP_STREAM_RESET_EVENT;
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_EVENT, &se, sizeof(se)))
|
||||
throw std::runtime_error("Could not set socket option SCTP_EVENT, errno=" +
|
||||
struct sctp_paddrparams spp = {};
|
||||
#ifdef __linux__
|
||||
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE).
|
||||
// It should be safe to enable discovery for SCTP.
|
||||
spp.spp_flags = SPP_PMTUD_ENABLE;
|
||||
#else
|
||||
// Otherwise, fall back to a safe MTU value.
|
||||
spp.spp_flags = SPP_PMTUD_DISABLE;
|
||||
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
#endif
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &spp, sizeof(spp)))
|
||||
throw std::runtime_error("Could not set socket option SCTP_PEER_ADDR_PARAMS, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
// The IETF draft recommends the number of streams negotiated during SCTP association to be
|
||||
@ -105,6 +140,46 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, me
|
||||
throw std::runtime_error("Could not set socket option SCTP_INITMSG, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
// The default send and receive window size of usrsctp is 256KiB, which is too small for
|
||||
// realistic RTTs, therefore we increase it to 1MiB for better performance.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685
|
||||
int bufSize = 1024 * 1024;
|
||||
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &bufSize, sizeof(bufSize)))
|
||||
throw std::runtime_error("Could not set SCTP recv buffer size, errno=" +
|
||||
std::to_string(errno));
|
||||
if (usrsctp_setsockopt(mSock, SOL_SOCKET, SO_SNDBUF, &bufSize, sizeof(bufSize)))
|
||||
throw std::runtime_error("Could not set SCTP send buffer size, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
connect();
|
||||
}
|
||||
|
||||
SctpTransport::~SctpTransport() {
|
||||
stop();
|
||||
|
||||
usrsctp_close(mSock);
|
||||
usrsctp_deregister_address(this);
|
||||
|
||||
GlobalCleanup();
|
||||
}
|
||||
|
||||
SctpTransport::State SctpTransport::state() const { return mState; }
|
||||
|
||||
void SctpTransport::stop() {
|
||||
Transport::stop();
|
||||
onRecv(nullptr);
|
||||
|
||||
if (!mShutdown.exchange(true)) {
|
||||
mSendQueue.stop();
|
||||
flush();
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void SctpTransport::connect() {
|
||||
PLOG_DEBUG << "SCTP connect";
|
||||
changeState(State::Connecting);
|
||||
|
||||
struct sockaddr_conn sconn = {};
|
||||
sconn.sconn_family = AF_CONN;
|
||||
sconn.sconn_port = htons(mPort);
|
||||
@ -116,33 +191,113 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, me
|
||||
if (usrsctp_bind(mSock, reinterpret_cast<struct sockaddr *>(&sconn), sizeof(sconn)))
|
||||
throw std::runtime_error("Could not bind usrsctp socket, errno=" + std::to_string(errno));
|
||||
|
||||
mConnectThread = std::thread(&SctpTransport::runConnect, this);
|
||||
// According to the IETF draft, both endpoints must initiate the SCTP association, in a
|
||||
// simultaneous-open manner, irrelevent to the SDP setup role.
|
||||
// See https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-9.3
|
||||
int ret = usrsctp_connect(mSock, reinterpret_cast<struct sockaddr *>(&sconn), sizeof(sconn));
|
||||
if (ret && errno != EINPROGRESS)
|
||||
throw std::runtime_error("Connection attempt failed, errno=" + std::to_string(errno));
|
||||
}
|
||||
|
||||
SctpTransport::~SctpTransport() {
|
||||
mStopping = true;
|
||||
mConnectCondition.notify_all();
|
||||
if (mConnectThread.joinable())
|
||||
mConnectThread.join();
|
||||
void SctpTransport::shutdown() {
|
||||
PLOG_DEBUG << "SCTP shutdown";
|
||||
|
||||
if (mSock) {
|
||||
usrsctp_shutdown(mSock, SHUT_RDWR);
|
||||
usrsctp_close(mSock);
|
||||
if (usrsctp_shutdown(mSock, SHUT_RDWR)) {
|
||||
PLOG_WARNING << "SCTP shutdown failed, errno=" << errno;
|
||||
}
|
||||
|
||||
usrsctp_deregister_address(this);
|
||||
GlobalCleanup();
|
||||
PLOG_INFO << "SCTP disconnected";
|
||||
changeState(State::Disconnected);
|
||||
mWrittenCondition.notify_all();
|
||||
}
|
||||
|
||||
SctpTransport::State SctpTransport::state() const { return mState; }
|
||||
|
||||
bool SctpTransport::send(message_ptr message) {
|
||||
std::lock_guard lock(mSendMutex);
|
||||
if (!message)
|
||||
return mSendQueue.empty();
|
||||
|
||||
PLOG_VERBOSE << "Send size=" << message->size();
|
||||
|
||||
// If nothing is pending, try to send directly
|
||||
if (mSendQueue.empty() && trySendMessage(message))
|
||||
return true;
|
||||
|
||||
mSendQueue.push(message);
|
||||
updateBufferedAmount(message->stream, message_size_func(message));
|
||||
return false;
|
||||
}
|
||||
|
||||
void SctpTransport::flush() {
|
||||
std::lock_guard lock(mSendMutex);
|
||||
trySendQueue();
|
||||
}
|
||||
|
||||
void SctpTransport::reset(unsigned int stream) {
|
||||
PLOG_DEBUG << "SCTP resetting stream " << stream;
|
||||
|
||||
std::unique_lock lock(mWriteMutex);
|
||||
mWritten = false;
|
||||
using srs_t = struct sctp_reset_streams;
|
||||
const size_t len = sizeof(srs_t) + sizeof(uint16_t);
|
||||
byte buffer[len] = {};
|
||||
srs_t &srs = *reinterpret_cast<srs_t *>(buffer);
|
||||
srs.srs_flags = SCTP_STREAM_RESET_OUTGOING;
|
||||
srs.srs_number_streams = 1;
|
||||
srs.srs_stream_list[0] = uint16_t(stream);
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &srs, len) == 0) {
|
||||
mWrittenCondition.wait_for(lock, 1000ms,
|
||||
[&]() { return mWritten || mState != State::Connected; });
|
||||
} else {
|
||||
PLOG_WARNING << "SCTP reset stream " << stream << " failed, errno=" << errno;
|
||||
}
|
||||
}
|
||||
|
||||
void SctpTransport::incoming(message_ptr message) {
|
||||
// There could be a race condition here where we receive the remote INIT before the local one is
|
||||
// sent, which would result in the connection being aborted. Therefore, we need to wait for data
|
||||
// to be sent on our side (i.e. the local INIT) before proceeding.
|
||||
{
|
||||
std::unique_lock lock(mWriteMutex);
|
||||
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || mState != State::Connected; });
|
||||
}
|
||||
|
||||
if (message) {
|
||||
usrsctp_conninput(this, message->data(), message->size(), 0);
|
||||
} else {
|
||||
PLOG_INFO << "SCTP disconnected";
|
||||
changeState(State::Disconnected);
|
||||
recv(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SctpTransport::changeState(State state) {
|
||||
if (mState.exchange(state) != state)
|
||||
mStateChangeCallback(state);
|
||||
}
|
||||
|
||||
bool SctpTransport::trySendQueue() {
|
||||
// Requires mSendMutex to be locked
|
||||
while (auto next = mSendQueue.peek()) {
|
||||
auto message = *next;
|
||||
if (!trySendMessage(message))
|
||||
return false;
|
||||
mSendQueue.pop();
|
||||
updateBufferedAmount(message->stream, -message_size_func(message));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SctpTransport::trySendMessage(message_ptr message) {
|
||||
// Requires mSendMutex to be locked
|
||||
if (mState != State::Connected)
|
||||
return false;
|
||||
|
||||
const Reliability reliability = message->reliability ? *message->reliability : Reliability();
|
||||
PLOG_VERBOSE << "SCTP try send size=" << message->size();
|
||||
|
||||
struct sctp_sendv_spa spa = {};
|
||||
// TODO: Implement SCTP ndata specification draft when supported everywhere
|
||||
// See https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-08
|
||||
|
||||
const Reliability reliability = message->reliability ? *message->reliability : Reliability();
|
||||
|
||||
uint32_t ppid;
|
||||
switch (message->type) {
|
||||
@ -157,18 +312,19 @@ bool SctpTransport::send(message_ptr message) {
|
||||
break;
|
||||
}
|
||||
|
||||
struct sctp_sendv_spa spa = {};
|
||||
|
||||
// set sndinfo
|
||||
spa.sendv_flags |= SCTP_SEND_SNDINFO_VALID;
|
||||
spa.sendv_sndinfo.snd_sid = uint16_t(message->stream);
|
||||
spa.sendv_sndinfo.snd_ppid = htonl(ppid);
|
||||
spa.sendv_sndinfo.snd_flags |= SCTP_EOR;
|
||||
spa.sendv_sndinfo.snd_flags |= SCTP_EOR; // implicit here
|
||||
|
||||
// set prinfo
|
||||
spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
|
||||
if (reliability.unordered)
|
||||
spa.sendv_sndinfo.snd_flags |= SCTP_UNORDERED;
|
||||
|
||||
using std::chrono::milliseconds;
|
||||
switch (reliability.type) {
|
||||
case Reliability::TYPE_PARTIAL_RELIABLE_REXMIT:
|
||||
spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
|
||||
@ -185,131 +341,150 @@ bool SctpTransport::send(message_ptr message) {
|
||||
break;
|
||||
}
|
||||
|
||||
ssize_t ret;
|
||||
if (!message->empty()) {
|
||||
return usrsctp_sendv(mSock, message->data(), message->size(), nullptr, 0, &spa, sizeof(spa),
|
||||
SCTP_SENDV_SPA, 0) > 0;
|
||||
ret = usrsctp_sendv(mSock, message->data(), message->size(), nullptr, 0, &spa, sizeof(spa),
|
||||
SCTP_SENDV_SPA, 0);
|
||||
} else {
|
||||
const char zero = 0;
|
||||
return usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0) > 0;
|
||||
ret = usrsctp_sendv(mSock, &zero, 1, nullptr, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0);
|
||||
}
|
||||
|
||||
if (ret >= 0) {
|
||||
PLOG_VERBOSE << "SCTP sent size=" << message->size();
|
||||
return true;
|
||||
} else if (errno == EWOULDBLOCK && errno == EAGAIN) {
|
||||
PLOG_VERBOSE << "SCTP sending not possible";
|
||||
return false;
|
||||
} else {
|
||||
PLOG_ERROR << "SCTP sending failed, errno=" << errno;
|
||||
throw std::runtime_error("Sending failed, errno=" + std::to_string(errno));
|
||||
}
|
||||
}
|
||||
|
||||
void SctpTransport::reset(unsigned int stream) {
|
||||
using srs_t = struct sctp_reset_streams;
|
||||
const size_t len = sizeof(srs_t) + sizeof(uint16_t);
|
||||
byte buffer[len] = {};
|
||||
srs_t &srs = *reinterpret_cast<srs_t *>(buffer);
|
||||
srs.srs_flags = SCTP_STREAM_RESET_OUTGOING;
|
||||
srs.srs_number_streams = 1;
|
||||
srs.srs_stream_list[0] = uint16_t(stream);
|
||||
usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &srs, len);
|
||||
void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
|
||||
// Requires mSendMutex to be locked
|
||||
auto it = mBufferedAmount.insert(std::make_pair(streamId, 0)).first;
|
||||
size_t amount = it->second;
|
||||
amount = size_t(std::max(long(amount) + delta, long(0)));
|
||||
if (amount == 0)
|
||||
mBufferedAmount.erase(it);
|
||||
mBufferedAmountCallback(streamId, amount);
|
||||
}
|
||||
|
||||
void SctpTransport::incoming(message_ptr message) {
|
||||
if (!message) {
|
||||
changeState(State::Disconnected);
|
||||
recv(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// There could be a race condition here where we receive the remote INIT before the thread in
|
||||
// usrsctp_connect sends the local one, which would result in the connection being aborted.
|
||||
// Therefore, we need to wait for data to be sent on our side (i.e. the local INIT) before
|
||||
// proceeding.
|
||||
if (!mConnectDataSent) {
|
||||
std::unique_lock<std::mutex> lock(mConnectMutex);
|
||||
mConnectCondition.wait(lock, [this] { return mConnectDataSent || mStopping; });
|
||||
}
|
||||
|
||||
if (!mStopping)
|
||||
usrsctp_conninput(this, message->data(), message->size(), 0);
|
||||
}
|
||||
|
||||
void SctpTransport::changeState(State state) {
|
||||
mState = state;
|
||||
mStateChangeCallback(state);
|
||||
}
|
||||
|
||||
void SctpTransport::runConnect() {
|
||||
int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data,
|
||||
size_t len, struct sctp_rcvinfo info, int flags) {
|
||||
try {
|
||||
changeState(State::Connecting);
|
||||
|
||||
struct sockaddr_conn sconn = {};
|
||||
sconn.sconn_family = AF_CONN;
|
||||
sconn.sconn_port = htons(mPort);
|
||||
sconn.sconn_addr = this;
|
||||
#ifdef HAVE_SCONN_LEN
|
||||
sconn.sconn_len = sizeof(sconn);
|
||||
#endif
|
||||
|
||||
// According to the IETF draft, both endpoints must initiate the SCTP association, in a
|
||||
// simultaneous-open manner, irrelevent to the SDP setup role.
|
||||
// See https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-9.3
|
||||
if (usrsctp_connect(mSock, reinterpret_cast<struct sockaddr *>(&sconn), sizeof(sconn)) !=
|
||||
0) {
|
||||
std::cerr << "SCTP connection failed, errno=" << errno << std::endl;
|
||||
changeState(State::Failed);
|
||||
mStopping = true;
|
||||
return;
|
||||
if (!len)
|
||||
return -1;
|
||||
if (flags & MSG_EOR) {
|
||||
if (!mPartialRecv.empty()) {
|
||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
||||
data = mPartialRecv.data();
|
||||
len = mPartialRecv.size();
|
||||
}
|
||||
// Message is complete, process it
|
||||
if (flags & MSG_NOTIFICATION)
|
||||
processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
|
||||
else
|
||||
processData(data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
|
||||
|
||||
if (!mStopping)
|
||||
changeState(State::Connected);
|
||||
|
||||
mPartialRecv.clear();
|
||||
} else {
|
||||
// Message is not complete
|
||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "SCTP connect: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int SctpTransport::handleWrite(void *data, size_t len, uint8_t tos, uint8_t set_df) {
|
||||
byte *b = reinterpret_cast<byte *>(data);
|
||||
outgoing(make_message(b, b + len));
|
||||
|
||||
if (!mConnectDataSent) {
|
||||
std::unique_lock<std::mutex> lock(mConnectMutex);
|
||||
mConnectDataSent = true;
|
||||
mConnectCondition.notify_all();
|
||||
PLOG_ERROR << "SCTP recv: " << e.what();
|
||||
return -1;
|
||||
}
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
int SctpTransport::process(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
||||
struct sctp_rcvinfo info, int flags) {
|
||||
if (flags & MSG_NOTIFICATION) {
|
||||
processNotification((union sctp_notification *)data, len);
|
||||
} else {
|
||||
processData((const byte *)data, len, info.rcv_sid, PayloadId(htonl(info.rcv_ppid)));
|
||||
int SctpTransport::handleSend(size_t free) {
|
||||
try {
|
||||
std::lock_guard lock(mSendMutex);
|
||||
trySendQueue();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "SCTP send: " << e.what();
|
||||
return -1;
|
||||
}
|
||||
free(data);
|
||||
return 0;
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df) {
|
||||
try {
|
||||
std::unique_lock lock(mWriteMutex);
|
||||
if (!outgoing(make_message(data, data + len)))
|
||||
return -1;
|
||||
mWritten = true;
|
||||
mWrittenOnce = true;
|
||||
mWrittenCondition.notify_all();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "SCTP write: " << e.what();
|
||||
return -1;
|
||||
}
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, PayloadId ppid) {
|
||||
Message::Type type;
|
||||
// The usage of the PPIDs "WebRTC String Partial" and "WebRTC Binary Partial" is deprecated.
|
||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.6
|
||||
// We handle them at reception for compatibility reasons but should never send them.
|
||||
switch (ppid) {
|
||||
case PPID_STRING:
|
||||
type = Message::String;
|
||||
break;
|
||||
case PPID_STRING_EMPTY:
|
||||
type = Message::String;
|
||||
len = 0;
|
||||
break;
|
||||
case PPID_BINARY:
|
||||
type = Message::Binary;
|
||||
break;
|
||||
case PPID_BINARY_EMPTY:
|
||||
type = Message::Binary;
|
||||
len = 0;
|
||||
break;
|
||||
case PPID_CONTROL:
|
||||
type = Message::Control;
|
||||
recv(make_message(data, data + len, Message::Control, sid));
|
||||
break;
|
||||
|
||||
case PPID_STRING_PARTIAL: // deprecated
|
||||
mPartialStringData.insert(mPartialStringData.end(), data, data + len);
|
||||
break;
|
||||
|
||||
case PPID_STRING:
|
||||
if (mPartialStringData.empty()) {
|
||||
recv(make_message(data, data + len, Message::String, sid));
|
||||
} else {
|
||||
mPartialStringData.insert(mPartialStringData.end(), data, data + len);
|
||||
recv(make_message(mPartialStringData.begin(), mPartialStringData.end(), Message::String,
|
||||
sid));
|
||||
mPartialStringData.clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case PPID_STRING_EMPTY:
|
||||
// This only accounts for when the partial data is empty
|
||||
recv(make_message(mPartialStringData.begin(), mPartialStringData.end(), Message::String,
|
||||
sid));
|
||||
mPartialStringData.clear();
|
||||
break;
|
||||
|
||||
case PPID_BINARY_PARTIAL: // deprecated
|
||||
mPartialBinaryData.insert(mPartialBinaryData.end(), data, data + len);
|
||||
break;
|
||||
|
||||
case PPID_BINARY:
|
||||
if (mPartialBinaryData.empty()) {
|
||||
recv(make_message(data, data + len, Message::Binary, sid));
|
||||
} else {
|
||||
mPartialBinaryData.insert(mPartialBinaryData.end(), data, data + len);
|
||||
recv(make_message(mPartialBinaryData.begin(), mPartialBinaryData.end(), Message::Binary,
|
||||
sid));
|
||||
mPartialBinaryData.clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case PPID_BINARY_EMPTY:
|
||||
// This only accounts for when the partial data is empty
|
||||
recv(make_message(mPartialBinaryData.begin(), mPartialBinaryData.end(), Message::Binary,
|
||||
sid));
|
||||
mPartialBinaryData.clear();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown
|
||||
std::cerr << "Unknown PPID: " << uint32_t(ppid) << std::endl;
|
||||
PLOG_WARNING << "Unknown PPID: " << uint32_t(ppid);
|
||||
return;
|
||||
}
|
||||
recv(make_message(data, data + len, type, sid));
|
||||
}
|
||||
|
||||
void SctpTransport::processNotification(const union sctp_notification *notify, size_t len) {
|
||||
@ -317,21 +492,43 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
|
||||
return;
|
||||
|
||||
switch (notify->sn_header.sn_type) {
|
||||
case SCTP_ASSOC_CHANGE: {
|
||||
const struct sctp_assoc_change &assoc_change = notify->sn_assoc_change;
|
||||
if (assoc_change.sac_state == SCTP_COMM_UP) {
|
||||
PLOG_INFO << "SCTP connected";
|
||||
changeState(State::Connected);
|
||||
} else {
|
||||
if (mState == State::Connecting) {
|
||||
PLOG_ERROR << "SCTP connection failed";
|
||||
changeState(State::Failed);
|
||||
} else {
|
||||
PLOG_INFO << "SCTP disconnected";
|
||||
changeState(State::Disconnected);
|
||||
}
|
||||
mWrittenCondition.notify_all();
|
||||
}
|
||||
}
|
||||
case SCTP_SENDER_DRY_EVENT: {
|
||||
// It not should be necessary since the send callback should have been called already,
|
||||
// but to be sure, let's try to send now.
|
||||
std::lock_guard lock(mSendMutex);
|
||||
trySendQueue();
|
||||
}
|
||||
case SCTP_STREAM_RESET_EVENT: {
|
||||
const struct sctp_stream_reset_event *reset_event = ¬ify->sn_strreset_event;
|
||||
const int count = (reset_event->strreset_length - sizeof(*reset_event)) / sizeof(uint16_t);
|
||||
const struct sctp_stream_reset_event &reset_event = notify->sn_strreset_event;
|
||||
const int count = (reset_event.strreset_length - sizeof(reset_event)) / sizeof(uint16_t);
|
||||
const uint16_t flags = reset_event.strreset_flags;
|
||||
|
||||
if (reset_event->strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) {
|
||||
if (flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
uint16_t streamId = reset_event->strreset_stream_list[i];
|
||||
uint16_t streamId = reset_event.strreset_stream_list[i];
|
||||
reset(streamId);
|
||||
}
|
||||
}
|
||||
|
||||
if (reset_event->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
|
||||
if (flags & SCTP_STREAM_RESET_INCOMING_SSN) {
|
||||
const byte dataChannelCloseMessage{0x04};
|
||||
for (int i = 0; i < count; ++i) {
|
||||
uint16_t streamId = reset_event->strreset_stream_list[i];
|
||||
uint16_t streamId = reset_event.strreset_stream_list[i];
|
||||
recv(make_message(&dataChannelCloseMessage, &dataChannelCloseMessage + 1,
|
||||
Message::Control, streamId));
|
||||
}
|
||||
@ -344,16 +541,29 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
|
||||
break;
|
||||
}
|
||||
}
|
||||
int SctpTransport::WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos,
|
||||
uint8_t set_df) {
|
||||
return static_cast<SctpTransport *>(sctp_ptr)->handleWrite(data, len, tos, set_df);
|
||||
|
||||
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
|
||||
size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) {
|
||||
int ret = static_cast<SctpTransport *>(ptr)->handleRecv(
|
||||
sock, addr, static_cast<const byte *>(data), len, recv_info, flags);
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SctpTransport::ReadCallback(struct socket *sock, union sctp_sockstore addr, void *data,
|
||||
size_t len, struct sctp_rcvinfo recv_info, int flags,
|
||||
void *user_data) {
|
||||
return static_cast<SctpTransport *>(user_data)->process(sock, addr, data, len, recv_info,
|
||||
flags);
|
||||
int SctpTransport::SendCallback(struct socket *sock, uint32_t sb_free) {
|
||||
struct sctp_paddrinfo paddrinfo = {};
|
||||
socklen_t len = sizeof(paddrinfo);
|
||||
if (usrsctp_getsockopt(sock, IPPROTO_SCTP, SCTP_GET_PEER_ADDR_INFO, &paddrinfo, &len))
|
||||
return -1;
|
||||
|
||||
auto sconn = reinterpret_cast<struct sockaddr_conn *>(&paddrinfo.spinfo_address);
|
||||
void *ptr = sconn->sconn_addr;
|
||||
return static_cast<SctpTransport *>(ptr)->handleSend(size_t(sb_free));
|
||||
}
|
||||
|
||||
int SctpTransport::WriteCallback(void *ptr, void *data, size_t len, uint8_t tos, uint8_t set_df) {
|
||||
return static_cast<SctpTransport *>(ptr)->handleWrite(static_cast<byte *>(data), len, tos,
|
||||
set_df);
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -21,16 +21,18 @@
|
||||
|
||||
#include "include.hpp"
|
||||
#include "peerconnection.hpp"
|
||||
#include "queue.hpp"
|
||||
#include "transport.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <usrsctp.h>
|
||||
|
||||
#include "usrsctp.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
@ -38,54 +40,74 @@ class SctpTransport : public Transport {
|
||||
public:
|
||||
enum class State { Disconnected, Connecting, Connected, Failed };
|
||||
|
||||
using amount_callback = std::function<void(uint16_t streamId, size_t amount)>;
|
||||
using state_callback = std::function<void(State state)>;
|
||||
|
||||
SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, message_callback recv,
|
||||
state_callback stateChangeCallback);
|
||||
SctpTransport(std::shared_ptr<Transport> lower, uint16_t port, message_callback recvCallback,
|
||||
amount_callback bufferedAmountCallback, state_callback stateChangeCallback);
|
||||
~SctpTransport();
|
||||
|
||||
State state() const;
|
||||
|
||||
bool send(message_ptr message);
|
||||
void stop() override;
|
||||
bool send(message_ptr message) override; // false if buffered
|
||||
void flush();
|
||||
void reset(unsigned int stream);
|
||||
|
||||
private:
|
||||
// Order seems wrong but these are the actual values
|
||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-8
|
||||
enum PayloadId : uint32_t {
|
||||
PPID_CONTROL = 50,
|
||||
PPID_STRING = 51,
|
||||
PPID_BINARY_PARTIAL = 52,
|
||||
PPID_BINARY = 53,
|
||||
PPID_STRING_PARTIAL = 54,
|
||||
PPID_STRING_EMPTY = 56,
|
||||
PPID_BINARY_EMPTY = 57
|
||||
};
|
||||
|
||||
void incoming(message_ptr message);
|
||||
void connect();
|
||||
void shutdown();
|
||||
void incoming(message_ptr message) override;
|
||||
void changeState(State state);
|
||||
void runConnect();
|
||||
|
||||
int handleWrite(void *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||
bool trySendQueue();
|
||||
bool trySendMessage(message_ptr message);
|
||||
void updateBufferedAmount(uint16_t streamId, long delta);
|
||||
|
||||
int process(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
||||
int handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data, size_t len,
|
||||
struct sctp_rcvinfo recv_info, int flags);
|
||||
int handleSend(size_t free);
|
||||
int handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||
|
||||
void processData(const byte *data, size_t len, uint16_t streamId, PayloadId ppid);
|
||||
void processNotification(const union sctp_notification *notify, size_t len);
|
||||
|
||||
const uint16_t mPort;
|
||||
struct socket *mSock;
|
||||
uint16_t mPort;
|
||||
|
||||
std::thread mConnectThread;
|
||||
std::mutex mConnectMutex;
|
||||
std::condition_variable mConnectCondition;
|
||||
std::atomic<bool> mConnectDataSent = false;
|
||||
std::atomic<bool> mStopping = false;
|
||||
std::mutex mSendMutex;
|
||||
Queue<message_ptr> mSendQueue;
|
||||
std::map<uint16_t, size_t> mBufferedAmount;
|
||||
amount_callback mBufferedAmountCallback;
|
||||
|
||||
std::atomic<State> mState;
|
||||
std::recursive_mutex mWriteMutex;
|
||||
std::condition_variable_any mWrittenCondition;
|
||||
bool mWritten = false;
|
||||
bool mWrittenOnce = false;
|
||||
|
||||
std::atomic<bool> mShutdown = false;
|
||||
|
||||
state_callback mStateChangeCallback;
|
||||
std::atomic<State> mState;
|
||||
|
||||
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||
static int ReadCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
||||
binary mPartialRecv, mPartialStringData, mPartialBinaryData;
|
||||
|
||||
static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
||||
struct sctp_rcvinfo recv_info, int flags, void *user_data);
|
||||
static int SendCallback(struct socket *sock, uint32_t sb_free);
|
||||
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||
|
||||
void GlobalInit();
|
||||
void GlobalCleanup();
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "include.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
@ -31,36 +32,35 @@ using namespace std::placeholders;
|
||||
|
||||
class Transport {
|
||||
public:
|
||||
Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(lower) { init(); }
|
||||
virtual ~Transport() {}
|
||||
|
||||
virtual bool send(message_ptr message) = 0;
|
||||
void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }
|
||||
|
||||
protected:
|
||||
void recv(message_ptr message) {
|
||||
if (mRecvCallback)
|
||||
mRecvCallback(message);
|
||||
}
|
||||
|
||||
virtual void incoming(message_ptr message) = 0;
|
||||
virtual void outgoing(message_ptr message) { getLower()->send(message); }
|
||||
|
||||
private:
|
||||
void init() {
|
||||
Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(std::move(lower)) {
|
||||
if (mLower)
|
||||
mLower->onRecv(std::bind(&Transport::incoming, this, _1));
|
||||
}
|
||||
virtual ~Transport() { stop(); }
|
||||
|
||||
std::shared_ptr<Transport> getLower() {
|
||||
virtual void stop() {
|
||||
if (mLower)
|
||||
return mLower;
|
||||
else
|
||||
throw std::logic_error("No lower transport to call");
|
||||
mLower->onRecv(nullptr);
|
||||
}
|
||||
|
||||
virtual bool send(message_ptr message) = 0;
|
||||
|
||||
void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }
|
||||
|
||||
protected:
|
||||
void recv(message_ptr message) { mRecvCallback(message); }
|
||||
|
||||
virtual void incoming(message_ptr message) = 0;
|
||||
virtual bool outgoing(message_ptr message) {
|
||||
if (mLower)
|
||||
return mLower->send(message);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Transport> mLower;
|
||||
message_callback mRecvCallback;
|
||||
synchronized_callback<message_ptr> mRecvCallback;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -26,19 +26,35 @@
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
|
||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
rtc::Configuration config;
|
||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||
InitLogger(LogLevel::Warning);
|
||||
|
||||
Configuration config;
|
||||
// config.iceServers.emplace_back("stun:stun.l.google.com:19302");
|
||||
// config.enableIceTcp = true;
|
||||
|
||||
// TURN server example
|
||||
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
|
||||
// IceServer::RelayType::TurnUdp);
|
||||
// config.iceServers.push_back(turnServer);
|
||||
|
||||
auto pc1 = std::make_shared<PeerConnection>(config);
|
||||
auto pc2 = std::make_shared<PeerConnection>(config);
|
||||
|
||||
pc1->onLocalDescription([pc2](const Description &sdp) {
|
||||
pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](const Description &sdp) {
|
||||
auto pc2 = wpc2.lock();
|
||||
if (!pc2)
|
||||
return;
|
||||
cout << "Description 1: " << sdp << endl;
|
||||
pc2->setRemoteDescription(sdp);
|
||||
});
|
||||
|
||||
pc1->onLocalCandidate([pc2](const Candidate &candidate) {
|
||||
pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](const Candidate &candidate) {
|
||||
auto pc2 = wpc2.lock();
|
||||
if (!pc2)
|
||||
return;
|
||||
cout << "Candidate 1: " << candidate << endl;
|
||||
pc2->addRemoteCandidate(candidate);
|
||||
});
|
||||
@ -48,12 +64,18 @@ int main(int argc, char **argv) {
|
||||
cout << "Gathering state 1: " << state << endl;
|
||||
});
|
||||
|
||||
pc2->onLocalDescription([pc1](const Description &sdp) {
|
||||
pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](const Description &sdp) {
|
||||
auto pc1 = wpc1.lock();
|
||||
if (!pc1)
|
||||
return;
|
||||
cout << "Description 2: " << sdp << endl;
|
||||
pc1->setRemoteDescription(sdp);
|
||||
});
|
||||
|
||||
pc2->onLocalCandidate([pc1](const Candidate &candidate) {
|
||||
pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](const Candidate &candidate) {
|
||||
auto pc1 = wpc1.lock();
|
||||
if (!pc1)
|
||||
return;
|
||||
cout << "Candidate 2: " << candidate << endl;
|
||||
pc1->addRemoteCandidate(candidate);
|
||||
});
|
||||
@ -67,19 +89,47 @@ int main(int argc, char **argv) {
|
||||
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
||||
cout << "Got a DataChannel with label: " << dc->label() << endl;
|
||||
dc2 = dc;
|
||||
dc2->send("Hello world!");
|
||||
dc2->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Received 2: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
dc2->send("Hello from 2");
|
||||
});
|
||||
|
||||
auto dc1 = pc1->createDataChannel("test");
|
||||
dc1->onOpen([dc1]() {
|
||||
dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() {
|
||||
auto dc1 = wdc1.lock();
|
||||
if (!dc1)
|
||||
return;
|
||||
cout << "DataChannel open: " << dc1->label() << endl;
|
||||
dc1->send("Hello from 1");
|
||||
});
|
||||
dc1->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Received: " << get<string>(message) << endl;
|
||||
cout << "Received 1: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
|
||||
this_thread::sleep_for(10s);
|
||||
}
|
||||
this_thread::sleep_for(3s);
|
||||
|
||||
if (auto addr = pc1->localAddress())
|
||||
cout << "Local address 1: " << *addr << endl;
|
||||
if (auto addr = pc1->remoteAddress())
|
||||
cout << "Remote address 1: " << *addr << endl;
|
||||
if (auto addr = pc2->localAddress())
|
||||
cout << "Local address 2: " << *addr << endl;
|
||||
if (auto addr = pc2->remoteAddress())
|
||||
cout << "Remote address 2: " << *addr << endl;
|
||||
|
||||
if (dc1->isOpen() && dc2->isOpen()) {
|
||||
pc1->close();
|
||||
pc2->close();
|
||||
|
||||
cout << "Success" << endl;
|
||||
return 0;
|
||||
} else {
|
||||
cout << "Failure" << endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
9
test/p2p/README.md
Normal file
9
test/p2p/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
* Execute ```offerer``` app in console
|
||||
* Execute ```answerer``` app in another console
|
||||
* Copy "Local Description" from ```offerer```
|
||||
* Enter 1 to ```answerer```
|
||||
* Paste copied description, press enter
|
||||
* Redo same procedure for ```answerer```
|
||||
* Redo same procedure for candidates
|
||||
* Wait for "DataChannel open" message
|
||||
* Send message from one peer to another
|
144
test/p2p/answerer.cpp
Normal file
144
test/p2p/answerer.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau, Murat Dogan
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
|
||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
InitLogger(LogLevel::Warning);
|
||||
|
||||
Configuration config;
|
||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||
// config.enableIceTcp = true;
|
||||
|
||||
// TURN Server example
|
||||
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
|
||||
// IceServer::RelayType::TurnUdp);
|
||||
// config.iceServers.push_back(turnServer);
|
||||
|
||||
auto pc = std::make_shared<PeerConnection>(config);
|
||||
|
||||
pc->onLocalDescription([](const Description &sdp) {
|
||||
std::string s(sdp);
|
||||
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
|
||||
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
||||
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl;
|
||||
});
|
||||
|
||||
pc->onStateChange(
|
||||
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
|
||||
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "[ Gathering State: " << state << " ]" << endl;
|
||||
});
|
||||
|
||||
shared_ptr<DataChannel> dc = nullptr;
|
||||
pc->onDataChannel([&](shared_ptr<DataChannel> _dc) {
|
||||
cout << "[ Got a DataChannel with label: " << _dc->label() << " ]" << endl;
|
||||
dc = _dc;
|
||||
|
||||
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; });
|
||||
|
||||
dc->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "[ Received: " << get<string>(message) << " ]" << endl;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
cout << endl
|
||||
<< endl
|
||||
<< "*************************************************************************" << endl
|
||||
<< "* 0: Exit /"
|
||||
<< " 1: Enter Description /"
|
||||
<< " 2: Enter Candidate /"
|
||||
<< " 3: Send Message *" << endl
|
||||
<< " [Command]: ";
|
||||
|
||||
int command;
|
||||
std::string sdp, candidate, message;
|
||||
const char *a;
|
||||
std::unique_ptr<Candidate> candidatePtr;
|
||||
std::unique_ptr<Description> descPtr;
|
||||
cin >> command;
|
||||
|
||||
switch (command) {
|
||||
case 0:
|
||||
exit = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Parse Description
|
||||
cout << "[SDP]: ";
|
||||
sdp = "";
|
||||
while (sdp.length() == 0)
|
||||
getline(cin, sdp);
|
||||
|
||||
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n');
|
||||
descPtr = std::make_unique<Description>(sdp, Description::Type::Offer,
|
||||
Description::Role::Passive);
|
||||
pc->setRemoteDescription(*descPtr);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Parse Candidate
|
||||
cout << "[Candidate]: ";
|
||||
candidate = "";
|
||||
while (candidate.length() == 0)
|
||||
getline(cin, candidate);
|
||||
|
||||
candidatePtr = std::make_unique<Candidate>(candidate);
|
||||
pc->addRemoteCandidate(*candidatePtr);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// Send Message
|
||||
if (!dc || !dc->isOpen()) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
cout << "[Message]: ";
|
||||
message = "";
|
||||
while (message.length() == 0)
|
||||
getline(cin, message);
|
||||
dc->send(message);
|
||||
break;
|
||||
|
||||
default:
|
||||
cout << "** Invalid Command ** ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dc)
|
||||
dc->close();
|
||||
if (pc)
|
||||
pc->close();
|
||||
}
|
141
test/p2p/offerer.cpp
Normal file
141
test/p2p/offerer.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau, Murat Dogan
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
|
||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
InitLogger(LogLevel::Warning);
|
||||
|
||||
Configuration config;
|
||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||
// config.enableIceTcp = true;
|
||||
|
||||
// TURN server example
|
||||
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
|
||||
// IceServer::RelayType::TurnUdp);
|
||||
// config.iceServers.push_back(turnServer);
|
||||
|
||||
auto pc = std::make_shared<PeerConnection>(config);
|
||||
|
||||
pc->onLocalDescription([](const Description &sdp) {
|
||||
std::string s(sdp);
|
||||
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
|
||||
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
||||
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl;
|
||||
});
|
||||
|
||||
pc->onStateChange(
|
||||
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
|
||||
|
||||
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "[ Gathering State: " << state << " ]" << endl;
|
||||
});
|
||||
|
||||
auto dc = pc->createDataChannel("test");
|
||||
dc->onOpen([&]() { cout << "[ DataChannel open: " << dc->label() << " ]" << endl; });
|
||||
|
||||
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; });
|
||||
|
||||
dc->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "[ Received: " << get<string>(message) << " ]" << endl;
|
||||
}
|
||||
});
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
cout << endl
|
||||
<< endl
|
||||
<< "*************************************************************************" << endl
|
||||
<< "* 0: Exit /"
|
||||
<< " 1: Enter Description /"
|
||||
<< " 2: Enter Candidate /"
|
||||
<< " 3: Send Message *" << endl
|
||||
<< " [Command]: ";
|
||||
|
||||
int command;
|
||||
std::string sdp, candidate, message;
|
||||
const char *a;
|
||||
std::unique_ptr<Candidate> candidatePtr;
|
||||
std::unique_ptr<Description> descPtr;
|
||||
cin >> command;
|
||||
|
||||
switch (command) {
|
||||
case 0:
|
||||
exit = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Parse Description
|
||||
cout << "[SDP]: ";
|
||||
sdp = "";
|
||||
while (sdp.length() == 0)
|
||||
getline(cin, sdp);
|
||||
|
||||
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n');
|
||||
descPtr = std::make_unique<Description>(sdp);
|
||||
pc->setRemoteDescription(*descPtr);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Parse Candidate
|
||||
cout << "[Candidate]: ";
|
||||
candidate = "";
|
||||
while (candidate.length() == 0)
|
||||
getline(cin, candidate);
|
||||
|
||||
candidatePtr = std::make_unique<Candidate>(candidate);
|
||||
pc->addRemoteCandidate(*candidatePtr);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// Send Message
|
||||
if (!dc->isOpen()) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
cout << "[Message]: ";
|
||||
message = "";
|
||||
while (message.length() == 0)
|
||||
getline(cin, message);
|
||||
dc->send(message);
|
||||
break;
|
||||
|
||||
default:
|
||||
cout << "** Invalid Command ** ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dc)
|
||||
dc->close();
|
||||
if (pc)
|
||||
pc->close();
|
||||
}
|
1
usrsctp
1
usrsctp
Submodule usrsctp deleted from 04d617c9c1
Reference in New Issue
Block a user