mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
158 Commits
Author | SHA1 | Date | |
---|---|---|---|
22e71bd663 | |||
9436757f73 | |||
b3bba4286b | |||
f8df667a14 | |||
c18b1738b0 | |||
96501a8a68 | |||
3be2b0427f | |||
5ae311c50a | |||
7a2e0c267d | |||
5e59186757 | |||
c5c9e24a01 | |||
2c953e77a9 | |||
ddb9f99ed6 | |||
3a737e940c | |||
258135d070 | |||
6302d995f7 | |||
6a7296d40d | |||
88c88bbaf5 | |||
c525c4b3f8 | |||
2c58fd7659 | |||
d0f91d5cf4 | |||
90ce154e15 | |||
b956e45f33 | |||
be1013fe7a | |||
d123041180 | |||
b5511f71a5 | |||
8e08ba1a29 | |||
3713b520db | |||
8c03c24e03 | |||
86b9bace53 | |||
049d339554 | |||
e86ecc2c97 | |||
de51b9adc7 | |||
0f0047729b | |||
2729b247fa | |||
32800c1c1c | |||
f153e2c795 | |||
cb79ec0023 | |||
3b98c9d1ec | |||
e075e9a7ec | |||
c89163610b | |||
c0edf3bfde | |||
c0470d813f | |||
a61c173b8c | |||
980c456de8 | |||
8530b20dbe | |||
3db8f0473b | |||
9546834605 | |||
e97efaf38d | |||
61d0f6ef73 | |||
cea564ddb3 | |||
738cbe78a0 | |||
b9102a156a | |||
306c1a3ab6 | |||
bbf7119c85 | |||
d6de29f7e0 | |||
a40a89ced8 | |||
b81eb92f96 | |||
85dd5b067e | |||
6e647e64b1 | |||
836c7c8504 | |||
b2baabd76d | |||
199db5f310 | |||
5dd8826bf9 | |||
0f934aca8c | |||
3e7ee70b7e | |||
44361714a5 | |||
56bd8c98b3 | |||
49d509f2d1 | |||
d446f49d5f | |||
070582d87a | |||
9f4a265ef0 | |||
2e33fef88d | |||
39392c52a7 | |||
cd343cd9ea | |||
9f305a6b01 | |||
dee0074270 | |||
9e36b5f4d6 | |||
17ba9af2e1 | |||
7c667cafee | |||
782efabaea | |||
011d1199a2 | |||
94561ec7e5 | |||
6173d18da4 | |||
1226d99c72 | |||
67218d8e23 | |||
20d1a03380 | |||
dffca48e69 | |||
fc595fd1bb | |||
076cf00b8f | |||
a78bc9cff3 | |||
9ed4386e0c | |||
89655ff749 | |||
c767e82d64 | |||
ed30fd9dfb | |||
c39a4ee6c5 | |||
e04113f3f1 | |||
577d048844 | |||
70cb347f3b | |||
89def5120b | |||
327085ac50 | |||
a6502c95c5 | |||
c717b65243 | |||
80e2115a7b | |||
6881e85071 | |||
e5539c02fe | |||
920189e2bb | |||
1ea4fad7c8 | |||
15e986ebfe | |||
ea8d1317ee | |||
345e7ee9b0 | |||
3b15363db8 | |||
de52f0101d | |||
a74f9419a0 | |||
9d8394eddf | |||
978d3e4d09 | |||
becdaaa25b | |||
b6f2176be8 | |||
f7f83aa519 | |||
64e8957c54 | |||
f3b3208367 | |||
ed28460e80 | |||
7b5b12617d | |||
be04d8037e | |||
56198372fd | |||
29ffb34fe8 | |||
834ea9b041 | |||
9441f78494 | |||
3367eba4fe | |||
6507542a80 | |||
fea3297a57 | |||
f322ab00ec | |||
b6374b9d07 | |||
70fd54804d | |||
ff268aee60 | |||
91a5c608d7 | |||
682be73eab | |||
fd4a6fef7f | |||
05a06f47b0 | |||
8e3de8a07a | |||
dc065add0b | |||
e64d4049a6 | |||
cb3bc85474 | |||
7af3da7872 | |||
3c77d717d2 | |||
6f399945fe | |||
c8b14b1262 | |||
35d4455c4f | |||
7d21b4b42b | |||
24e9e06c5a | |||
443a19d8e7 | |||
83de743924 | |||
1dc1de4b86 | |||
8ca7722d48 | |||
3079072e63 | |||
982d1c10e1 | |||
50b22bbf3c | |||
93e153398f |
24
.github/workflows/build-gnutls.yml
vendored
Normal file
24
.github/workflows/build-gnutls.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Build and test with GnuTLS
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=1
|
||||
- name: make
|
||||
run: (cd build; make)
|
||||
- name: test
|
||||
run: ./build/tests
|
||||
|
24
.github/workflows/build-nice.yml
vendored
Normal file
24
.github/workflows/build-nice.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Build and test with libnice
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libgnutls28-dev libnice-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_JUICE=0 -DUSE_GNUTLS=1
|
||||
- name: make
|
||||
run: (cd build; make)
|
||||
- name: test
|
||||
run: ./build/tests
|
||||
|
24
.github/workflows/build-openssl.yml
vendored
Normal file
24
.github/workflows/build-openssl.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Build and test with OpenSSL
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install packages
|
||||
run: sudo apt update && sudo apt install libssl-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_JUICE=1 -DUSE_GNUTLS=0
|
||||
- name: make
|
||||
run: (cd build; make)
|
||||
- name: test
|
||||
run: ./build/tests
|
||||
|
@ -1,7 +1,7 @@
|
||||
cmake_minimum_required (VERSION 3.7)
|
||||
project (libdatachannel
|
||||
DESCRIPTION "WebRTC DataChannels Library"
|
||||
VERSION 0.4.2
|
||||
VERSION 0.5.0
|
||||
LANGUAGES CXX)
|
||||
|
||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||
@ -16,6 +16,13 @@ endif()
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||
if (MSVC)
|
||||
add_definitions(-DNOMINMAX)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(LIBDATACHANNEL_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp
|
||||
@ -25,13 +32,35 @@ set(LIBDATACHANNEL_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/init.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/log.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
||||
)
|
||||
|
||||
set(LIBDATACHANNEL_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/include.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/init.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/log.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/message.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/peerconnection.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/queue.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
|
||||
)
|
||||
|
||||
set(TESTS_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
|
||||
)
|
||||
|
||||
set(TESTS_OFFERER_SOURCES
|
||||
@ -42,10 +71,19 @@ set(TESTS_ANSWERER_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp
|
||||
)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||
if (MSYS OR MINGW)
|
||||
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||
target_compile_definitions(usrsctp-static PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||
endif()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
|
||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
|
||||
endif()
|
||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
||||
|
||||
@ -55,13 +93,11 @@ set_target_properties(datachannel PROPERTIES
|
||||
CXX_STANDARD 17)
|
||||
|
||||
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||
target_link_libraries(datachannel
|
||||
Threads::Threads
|
||||
Usrsctp::UsrsctpStatic
|
||||
)
|
||||
target_link_libraries(datachannel PUBLIC Threads::Threads)
|
||||
target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic)
|
||||
|
||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
|
||||
set_target_properties(datachannel-static PROPERTIES
|
||||
@ -69,13 +105,16 @@ set_target_properties(datachannel-static PROPERTIES
|
||||
CXX_STANDARD 17)
|
||||
|
||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||
target_link_libraries(datachannel-static
|
||||
Threads::Threads
|
||||
Usrsctp::UsrsctpStatic
|
||||
)
|
||||
target_link_libraries(datachannel-static PUBLIC Threads::Threads)
|
||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
|
||||
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
||||
endif()
|
||||
|
||||
if (USE_GNUTLS)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
@ -88,34 +127,37 @@ if (USE_GNUTLS)
|
||||
IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
|
||||
endif()
|
||||
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
|
||||
target_link_libraries(datachannel GnuTLS::GnuTLS)
|
||||
target_link_libraries(datachannel PRIVATE GnuTLS::GnuTLS)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
|
||||
target_link_libraries(datachannel-static GnuTLS::GnuTLS)
|
||||
target_link_libraries(datachannel-static PRIVATE GnuTLS::GnuTLS)
|
||||
else()
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=0)
|
||||
target_link_libraries(datachannel OpenSSL::SSL)
|
||||
target_link_libraries(datachannel PRIVATE OpenSSL::SSL)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=0)
|
||||
target_link_libraries(datachannel-static OpenSSL::SSL)
|
||||
target_link_libraries(datachannel-static PRIVATE OpenSSL::SSL)
|
||||
endif()
|
||||
|
||||
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_link_libraries(datachannel PRIVATE LibJuice::LibJuiceStatic)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=1)
|
||||
target_link_libraries(datachannel-static LibJuice::LibJuiceStatic)
|
||||
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||
else()
|
||||
find_package(LibNice REQUIRED)
|
||||
target_compile_definitions(datachannel PRIVATE USE_JUICE=0)
|
||||
target_link_libraries(datachannel LibNice::LibNice)
|
||||
target_link_libraries(datachannel PRIVATE LibNice::LibNice)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=0)
|
||||
target_link_libraries(datachannel-static LibNice::LibNice)
|
||||
target_link_libraries(datachannel-static PRIVATE LibNice::LibNice)
|
||||
endif()
|
||||
|
||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||
|
||||
install(TARGETS datachannel LIBRARY DESTINATION lib)
|
||||
install(FILES ${LIBDATACHANNEL_HEADERS} DESTINATION include/rtc)
|
||||
|
||||
# Main Test
|
||||
add_executable(datachannel-tests ${TESTS_SOURCES})
|
||||
set_target_properties(datachannel-tests PROPERTIES
|
||||
@ -138,6 +180,6 @@ 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)
|
||||
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME answerer)
|
||||
target_link_libraries(datachannel-answerer datachannel)
|
||||
|
||||
|
27
Jamfile
27
Jamfile
@ -1,3 +1,5 @@
|
||||
import feature : feature ;
|
||||
|
||||
project libdatachannel ;
|
||||
path-constant CWD : . ;
|
||||
|
||||
@ -5,21 +7,26 @@ lib libdatachannel
|
||||
: # sources
|
||||
[ glob ./src/*.cpp ]
|
||||
: # requirements
|
||||
<cxxstd>17
|
||||
<include>./include/rtc
|
||||
<define>USE_GNUTLS=0
|
||||
<define>USE_JUICE=1
|
||||
<cxxflags>"`pkg-config --cflags openssl`"
|
||||
<library>/libdatachannel//usrsctp
|
||||
<library>/libdatachannel//juice
|
||||
<library>/libdatachannel//plog
|
||||
: # default build
|
||||
<link>static
|
||||
: # usage requirements
|
||||
<include>./include
|
||||
<library>/libdatachannel//plog
|
||||
<cxxflags>-pthread
|
||||
<linkflags>"`pkg-config --libs openssl`"
|
||||
;
|
||||
|
||||
feature crypto : openssl gnutls : composite propagated ;
|
||||
feature.compose <crypto>openssl
|
||||
: <define>USE_GNUTLS=0 ;
|
||||
feature.compose <crypto>gnutls
|
||||
: <define>USE_GNUTLS=1 ;
|
||||
|
||||
alias plog
|
||||
: # no sources
|
||||
: # no build requirements
|
||||
@ -57,9 +64,21 @@ actions make_libusrsctp
|
||||
}
|
||||
|
||||
make libjuice.a : : @make_libjuice ;
|
||||
|
||||
rule make_libjuice ( targets * : sources * : properties * )
|
||||
{
|
||||
if <crypto>gnutls in $(properties)
|
||||
{
|
||||
MAKEOPTS on $(targets) = "USE_NETTLE=1" ;
|
||||
}
|
||||
else
|
||||
{
|
||||
MAKEOPTS on $(targets) = "USE_NETTLE=0" ;
|
||||
}
|
||||
}
|
||||
actions make_libjuice
|
||||
{
|
||||
(cd $(CWD)/deps/libjuice && make USE_NETTLE=0)
|
||||
(cd $(CWD)/deps/libjuice && make $(MAKEOPTS))
|
||||
cp $(CWD)/deps/libjuice/libjuice.a $(<)
|
||||
}
|
||||
|
||||
|
11
Makefile
11
Makefile
@ -5,7 +5,7 @@ CXX=$(CROSS)g++
|
||||
AR=$(CROSS)ar
|
||||
RM=rm -f
|
||||
CXXFLAGS=-std=c++17
|
||||
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
|
||||
CPPFLAGS=-O2 -pthread -fPIC -Wall
|
||||
LDFLAGS=-pthread
|
||||
LIBS=
|
||||
LOCALLIBS=libusrsctp.a
|
||||
@ -44,6 +44,9 @@ LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
||||
SRCS=$(shell printf "%s " src/*.cpp)
|
||||
OBJS=$(subst .cpp,.o,$(SRCS))
|
||||
|
||||
TEST_SRCS=$(shell printf "%s " test/*.cpp)
|
||||
TEST_OBJS=$(subst .cpp,.o,$(TEST_SRCS))
|
||||
|
||||
all: $(NAME).a $(NAME).so tests
|
||||
|
||||
src/%.o: src/%.cpp
|
||||
@ -60,8 +63,8 @@ $(NAME).a: $(OBJS)
|
||||
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
||||
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
|
||||
|
||||
tests: $(NAME).a test/main.o
|
||||
$(CXX) $(LDFLAGS) -o $@ test/main.o $(NAME).a $(LDLIBS)
|
||||
tests: $(NAME).a $(TEST_OBJS)
|
||||
$(CXX) $(LDFLAGS) -o $@ $(TEST_OBJS) $(NAME).a $(LDLIBS)
|
||||
|
||||
clean:
|
||||
-$(RM) include/rtc/*.d *.d
|
||||
@ -83,7 +86,7 @@ dist-clean: clean
|
||||
libusrsctp.a:
|
||||
cd $(USRSCTP_DIR) && \
|
||||
./bootstrap && \
|
||||
./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS)" && \
|
||||
./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS) -Wno-error=format-truncation" && \
|
||||
make
|
||||
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
||||
|
||||
|
10
README.md
10
README.md
@ -1,6 +1,6 @@
|
||||
# libdatachannel - C/C++ WebRTC DataChannels
|
||||
|
||||
libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC API, in order to ease the design of cross-environment applications.
|
||||
libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings for POSIX platforms and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC API, in order to ease the design of cross-environment applications.
|
||||
|
||||
This projet is originally inspired by [librtcdcpp](https://github.com/chadnickbok/librtcdcpp), however it is a complete rewrite from scratch, because the messy architecture of librtcdcpp made solving its implementation issues difficult.
|
||||
|
||||
@ -79,11 +79,11 @@ MY_ON_RECV_CANDIDATE_FROM_REMOTE([pc](string candidate, string mid) {
|
||||
### Observe the PeerConnection state
|
||||
|
||||
```cpp
|
||||
pc->onStateChanged([](PeerConnection::State state) {
|
||||
pc->onStateChange([](PeerConnection::State state) {
|
||||
cout << "State: " << state << endl;
|
||||
});
|
||||
|
||||
pc->onGatheringStateChanged([](PeerConnection::GatheringState state) {
|
||||
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "Gathering state: " << state << endl;
|
||||
});
|
||||
|
||||
@ -114,5 +114,7 @@ pc->onDataChannel([&dc](shared_ptr<rtc::DataChannel> incoming) {
|
||||
|
||||
```
|
||||
|
||||
See [test/main.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/main.cpp) for a complete local connection example.
|
||||
See [test/connectivity.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/connectivity.cpp) for a complete local connection example.
|
||||
|
||||
See [test/cpai.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/capi.cpp) for a C API example.
|
||||
|
||||
|
@ -10,7 +10,7 @@ if (NOT TARGET LibNice::LibNice)
|
||||
HINTS ${PC_LIBNICE_LIBDIR} ${PC_LIBNICE_LIBRARY_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Libnice DEFAULT_MSG
|
||||
find_package_handle_standard_args(LibNice DEFAULT_MSG
|
||||
LIBNICE_LIBRARY LIBNICE_INCLUDE_DIR)
|
||||
mark_as_advanced(LIBNICE_INCLUDE_DIR LIBNICE_LIBRARY)
|
||||
|
||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: 65f492dbff...511b19f516
@ -25,6 +25,15 @@
|
||||
|
||||
namespace rtc {
|
||||
|
||||
enum class CandidateType { Host = 0, ServerReflexive, PeerReflexive, Relayed };
|
||||
enum class CandidateTransportType { Udp = 0, TcpActive, TcpPassive, TcpSo };
|
||||
struct CandidateInfo {
|
||||
string address;
|
||||
int port;
|
||||
CandidateType type;
|
||||
CandidateTransportType transportType;
|
||||
};
|
||||
|
||||
class Candidate {
|
||||
public:
|
||||
Candidate(string candidate, string mid = "");
|
||||
@ -46,6 +55,8 @@ private:
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -31,12 +31,10 @@ class Channel {
|
||||
public:
|
||||
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() const = 0;
|
||||
virtual bool isClosed() const = 0;
|
||||
|
||||
virtual size_t availableAmount() const; // total size available to receive
|
||||
virtual size_t maxMessageSize() const; // max message size in a call to send
|
||||
virtual size_t bufferedAmount() const; // total size buffered to send
|
||||
|
||||
void onOpen(std::function<void()> callback);
|
||||
@ -47,11 +45,14 @@ public:
|
||||
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);
|
||||
|
||||
// Extended API
|
||||
virtual std::optional<std::variant<binary, string>> receive() = 0; // only if onMessage unset
|
||||
virtual size_t availableAmount() const; // total size available to receive
|
||||
void onAvailable(std::function<void()> callback);
|
||||
|
||||
protected:
|
||||
virtual void triggerOpen();
|
||||
virtual void triggerClosed();
|
||||
@ -59,6 +60,8 @@ protected:
|
||||
virtual void triggerAvailable(size_t count);
|
||||
virtual void triggerBufferedAmount(size_t amount);
|
||||
|
||||
void resetCallbacks();
|
||||
|
||||
private:
|
||||
synchronized_callback<> mOpenCallback;
|
||||
synchronized_callback<> mClosedCallback;
|
||||
|
@ -51,8 +51,22 @@ struct IceServer {
|
||||
RelayType relayType;
|
||||
};
|
||||
|
||||
struct ProxyServer {
|
||||
enum class Type { None = 0, Socks5, Http, Last = Http };
|
||||
|
||||
ProxyServer(Type type_, string ip_, uint16_t port_, string username_ = "",
|
||||
string password_ = "");
|
||||
|
||||
Type type;
|
||||
string ip;
|
||||
uint16_t port;
|
||||
string username;
|
||||
string password;
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
std::vector<IceServer> iceServers;
|
||||
std::optional<ProxyServer> proxyServer;
|
||||
bool enableIceTcp = false;
|
||||
uint16_t portRangeBegin = 1024;
|
||||
uint16_t portRangeEnd = 65535;
|
||||
|
@ -38,42 +38,40 @@ class PeerConnection;
|
||||
|
||||
class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel {
|
||||
public:
|
||||
DataChannel(std::shared_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||
DataChannel(std::weak_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||
string protocol, Reliability reliability);
|
||||
DataChannel(std::shared_ptr<PeerConnection> pc, std::shared_ptr<SctpTransport> transport,
|
||||
DataChannel(std::weak_ptr<PeerConnection> pc, std::weak_ptr<SctpTransport> transport,
|
||||
unsigned int stream);
|
||||
~DataChannel();
|
||||
|
||||
void close(void) override;
|
||||
|
||||
bool send(const std::variant<binary, string> &data) override;
|
||||
bool send(const byte *data, size_t size);
|
||||
|
||||
template <typename Buffer> bool sendBuffer(const Buffer &buf);
|
||||
template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
|
||||
|
||||
std::optional<std::variant<binary, string>> receive() override;
|
||||
|
||||
bool isOpen(void) const override;
|
||||
bool isClosed(void) const override;
|
||||
size_t availableAmount() const override;
|
||||
|
||||
size_t maxMessageSize() const; // maximum message size in a call to send or sendBuffer
|
||||
|
||||
unsigned int stream() const;
|
||||
string label() const;
|
||||
string protocol() const;
|
||||
Reliability reliability() const;
|
||||
|
||||
void close(void) override;
|
||||
bool send(const std::variant<binary, string> &data) override;
|
||||
bool send(const byte *data, size_t size);
|
||||
template <typename Buffer> bool sendBuffer(const Buffer &buf);
|
||||
template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
|
||||
|
||||
bool isOpen(void) const override;
|
||||
bool isClosed(void) const override;
|
||||
size_t maxMessageSize() const override;
|
||||
|
||||
// Extended API
|
||||
size_t availableAmount() const override;
|
||||
std::optional<std::variant<binary, string>> receive() override;
|
||||
|
||||
private:
|
||||
void remoteClose();
|
||||
void open(std::shared_ptr<SctpTransport> sctpTransport);
|
||||
void open(std::shared_ptr<SctpTransport> transport);
|
||||
bool outgoing(mutable_message_ptr message);
|
||||
void incoming(message_ptr message);
|
||||
void processOpenMessage(message_ptr message);
|
||||
|
||||
const std::shared_ptr<PeerConnection> mPeerConnection;
|
||||
std::shared_ptr<SctpTransport> mSctpTransport;
|
||||
const std::weak_ptr<PeerConnection> mPeerConnection;
|
||||
std::weak_ptr<SctpTransport> mSctpTransport;
|
||||
|
||||
unsigned int mStream;
|
||||
string mLabel;
|
||||
|
@ -35,6 +35,7 @@ public:
|
||||
enum class Role { ActPass = 0, Passive = 1, Active = 2 };
|
||||
|
||||
Description(const string &sdp, const string &typeString = "");
|
||||
Description(const string &sdp, Type type);
|
||||
Description(const string &sdp, Type type, Role role);
|
||||
|
||||
Type type() const;
|
||||
@ -47,6 +48,7 @@ public:
|
||||
std::optional<size_t> maxMessageSize() const;
|
||||
bool trickleEnabled() const;
|
||||
|
||||
void hintType(Type type);
|
||||
void setFingerprint(string fingerprint);
|
||||
void setSctpPort(uint16_t port);
|
||||
void setMaxMessageSize(size_t size);
|
||||
|
@ -19,6 +19,14 @@
|
||||
#ifndef RTC_INCLUDE_H
|
||||
#define RTC_INCLUDE_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0602
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "log.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@ -27,9 +35,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "plog/Appenders/ColorConsoleAppender.h"
|
||||
#include "plog/Log.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
using std::byte;
|
||||
@ -44,8 +49,6 @@ 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
|
||||
|
||||
@ -53,29 +56,6 @@ const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
|
||||
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
|
||||
const size_t 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...>;
|
||||
@ -83,11 +63,12 @@ template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
||||
template <typename... P> class synchronized_callback {
|
||||
public:
|
||||
synchronized_callback() = default;
|
||||
synchronized_callback(std::function<void(P...)> func) { *this = std::move(func); };
|
||||
~synchronized_callback() { *this = nullptr; }
|
||||
|
||||
synchronized_callback &operator=(std::function<void(P...)> func) {
|
||||
std::lock_guard lock(mutex);
|
||||
callback = func;
|
||||
callback = std::move(func);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -97,7 +78,10 @@ public:
|
||||
callback(args...);
|
||||
}
|
||||
|
||||
operator bool() const { return callback ? true : false; }
|
||||
operator bool() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return callback ? true : false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(P...)> callback;
|
||||
|
50
include/rtc/init.hpp
Normal file
50
include/rtc/init.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef RTC_INIT_H
|
||||
#define RTC_INIT_H
|
||||
|
||||
#include "include.hpp"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
class Init;
|
||||
using init_token = std::shared_ptr<Init>;
|
||||
|
||||
class Init {
|
||||
public:
|
||||
static init_token Token();
|
||||
static void Cleanup();
|
||||
|
||||
~Init();
|
||||
|
||||
private:
|
||||
Init();
|
||||
|
||||
static std::weak_ptr<Init> Weak;
|
||||
static init_token Global;
|
||||
static std::mutex Mutex;
|
||||
};
|
||||
|
||||
inline void Cleanup() { Init::Cleanup(); }
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
40
include/rtc/log.hpp
Normal file
40
include/rtc/log.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef RTC_LOG_H
|
||||
#define RTC_LOG_H
|
||||
|
||||
#include "plog/Log.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
enum class LogLevel { // Don't change, it must match plog severity
|
||||
None = 0,
|
||||
Fatal = 1,
|
||||
Error = 2,
|
||||
Warning = 3,
|
||||
Info = 4,
|
||||
Debug = 5,
|
||||
Verbose = 6
|
||||
};
|
||||
|
||||
void InitLogger(LogLevel level);
|
||||
void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr);
|
||||
}
|
||||
|
||||
#endif
|
@ -28,9 +28,9 @@
|
||||
namespace rtc {
|
||||
|
||||
struct Message : binary {
|
||||
enum Type { Binary, String, Control };
|
||||
enum Type { Binary, String, Control, Reset };
|
||||
|
||||
Message(size_t size) : binary(size), type(Binary) {}
|
||||
Message(size_t size, Type type_ = Binary) : binary(size), type(type_) {}
|
||||
|
||||
template <typename Iterator>
|
||||
Message(Iterator begin_, Iterator end_, Type type_ = Binary)
|
||||
@ -46,7 +46,7 @@ using mutable_message_ptr = std::shared_ptr<Message>;
|
||||
using message_callback = std::function<void(message_ptr message)>;
|
||||
|
||||
constexpr auto message_size_func = [](const message_ptr &m) -> size_t {
|
||||
return m->type != Message::Control ? m->size() : 0;
|
||||
return m->type == Message::Binary || m->type == Message::String ? m->size() : 0;
|
||||
};
|
||||
|
||||
template <typename Iterator>
|
||||
@ -59,6 +59,15 @@ message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Mess
|
||||
return message;
|
||||
}
|
||||
|
||||
inline message_ptr make_message(size_t size, Message::Type type = Message::Binary,
|
||||
unsigned int stream = 0,
|
||||
std::shared_ptr<Reliability> reliability = nullptr) {
|
||||
auto message = std::make_shared<Message>(size, type);
|
||||
message->stream = stream;
|
||||
message->reliability = reliability;
|
||||
return message;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -24,14 +24,17 @@
|
||||
#include "datachannel.hpp"
|
||||
#include "description.hpp"
|
||||
#include "include.hpp"
|
||||
#include "init.hpp"
|
||||
#include "message.hpp"
|
||||
#include "reliability.hpp"
|
||||
#include "rtc.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
@ -42,6 +45,9 @@ class IceTransport;
|
||||
class DtlsTransport;
|
||||
class SctpTransport;
|
||||
|
||||
using certificate_ptr = std::shared_ptr<Certificate>;
|
||||
using future_certificate_ptr = std::shared_future<certificate_ptr>;
|
||||
|
||||
class PeerConnection : public std::enable_shared_from_this<PeerConnection> {
|
||||
public:
|
||||
enum class State : int {
|
||||
@ -50,14 +56,13 @@ public:
|
||||
Connected = RTC_CONNECTED,
|
||||
Disconnected = RTC_DISCONNECTED,
|
||||
Failed = RTC_FAILED,
|
||||
Closed = RTC_CLOSED,
|
||||
Destroying = RTC_DESTROYING
|
||||
Closed = RTC_CLOSED
|
||||
};
|
||||
|
||||
enum class GatheringState : int {
|
||||
New = RTC_GATHERING_NEW,
|
||||
InProgress = RTC_GATHERING_INPROGRESS,
|
||||
Complete = RTC_GATHERING_COMPLETE,
|
||||
Complete = RTC_GATHERING_COMPLETE
|
||||
};
|
||||
|
||||
PeerConnection(void);
|
||||
@ -86,15 +91,31 @@ public:
|
||||
void onStateChange(std::function<void(State state)> callback);
|
||||
void onGatheringStateChange(std::function<void(GatheringState state)> callback);
|
||||
|
||||
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
|
||||
|
||||
// Stats
|
||||
void clearStats();
|
||||
size_t bytesSent();
|
||||
size_t bytesReceived();
|
||||
std::optional<std::chrono::milliseconds> rtt();
|
||||
|
||||
private:
|
||||
init_token mInitToken = Init::Token();
|
||||
|
||||
std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
|
||||
std::shared_ptr<DtlsTransport> initDtlsTransport();
|
||||
std::shared_ptr<SctpTransport> initSctpTransport();
|
||||
void closeTransports();
|
||||
|
||||
void endLocalCandidates();
|
||||
bool checkFingerprint(const std::string &fingerprint) const;
|
||||
void forwardMessage(message_ptr message);
|
||||
void forwardBufferedAmount(uint16_t stream, size_t amount);
|
||||
|
||||
std::shared_ptr<DataChannel> emplaceDataChannel(Description::Role role, const string &label,
|
||||
const string &protocol,
|
||||
const Reliability &reliability);
|
||||
std::shared_ptr<DataChannel> findDataChannel(uint16_t stream);
|
||||
void iterateDataChannels(std::function<void(std::shared_ptr<DataChannel> channel)> func);
|
||||
void openDataChannels();
|
||||
void closeDataChannels();
|
||||
@ -103,11 +124,13 @@ private:
|
||||
void processLocalDescription(Description description);
|
||||
void processLocalCandidate(Candidate candidate);
|
||||
void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
|
||||
void changeState(State state);
|
||||
void changeGatheringState(GatheringState state);
|
||||
bool changeState(State state);
|
||||
bool changeGatheringState(GatheringState state);
|
||||
|
||||
void resetCallbacks();
|
||||
|
||||
const Configuration mConfig;
|
||||
const std::shared_ptr<Certificate> mCertificate;
|
||||
const future_certificate_ptr mCertificate;
|
||||
|
||||
std::optional<Description> mLocalDescription, mRemoteDescription;
|
||||
mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
||||
@ -115,9 +138,9 @@ private:
|
||||
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::shared_mutex mDataChannelsMutex;
|
||||
|
||||
std::atomic<State> mState;
|
||||
std::atomic<GatheringState> mGatheringState;
|
||||
|
@ -41,12 +41,10 @@ public:
|
||||
bool empty() const;
|
||||
size_t size() const; // elements
|
||||
size_t amount() const; // amount
|
||||
void push(const T &element);
|
||||
void push(T &&element);
|
||||
void push(T element);
|
||||
std::optional<T> pop();
|
||||
std::optional<T> peek();
|
||||
void wait();
|
||||
void wait(const std::chrono::milliseconds &duration);
|
||||
bool wait(const std::optional<std::chrono::milliseconds> &duration = nullopt);
|
||||
|
||||
private:
|
||||
const size_t mLimit;
|
||||
@ -88,9 +86,7 @@ template <typename T> size_t Queue<T>::amount() const {
|
||||
return mAmount;
|
||||
}
|
||||
|
||||
template <typename T> void Queue<T>::push(const T &element) { push(T{element}); }
|
||||
|
||||
template <typename T> void Queue<T>::push(T &&element) {
|
||||
template <typename T> void Queue<T>::push(T element) {
|
||||
std::unique_lock lock(mMutex);
|
||||
mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
|
||||
if (!mStopping) {
|
||||
@ -122,14 +118,14 @@ template <typename T> std::optional<T> Queue<T>::peek() {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void Queue<T>::wait() {
|
||||
template <typename T>
|
||||
bool Queue<T>::wait(const std::optional<std::chrono::milliseconds> &duration) {
|
||||
std::unique_lock lock(mMutex);
|
||||
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; });
|
||||
if (duration)
|
||||
mPopCondition.wait_for(lock, *duration, [this]() { return !mQueue.empty() || mStopping; });
|
||||
else
|
||||
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
||||
return !mStopping;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -23,6 +23,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// libdatachannel C API
|
||||
|
||||
typedef enum {
|
||||
@ -31,15 +33,14 @@ typedef enum {
|
||||
RTC_CONNECTED = 2,
|
||||
RTC_DISCONNECTED = 3,
|
||||
RTC_FAILED = 4,
|
||||
RTC_CLOSED = 5,
|
||||
RTC_DESTROYING = 6 // internal
|
||||
} rtc_state_t;
|
||||
RTC_CLOSED = 5
|
||||
} rtcState;
|
||||
|
||||
typedef enum {
|
||||
RTC_GATHERING_NEW = 0,
|
||||
RTC_GATHERING_INPROGRESS = 1,
|
||||
RTC_GATHERING_COMPLETE = 2
|
||||
} rtc_gathering_state_t;
|
||||
} rtcGatheringState;
|
||||
|
||||
// Don't change, it must match plog severity
|
||||
typedef enum {
|
||||
@ -50,32 +51,72 @@ typedef enum {
|
||||
RTC_LOG_INFO = 4,
|
||||
RTC_LOG_DEBUG = 5,
|
||||
RTC_LOG_VERBOSE = 6
|
||||
} rtc_log_level_t;
|
||||
} rtcLogLevel;
|
||||
|
||||
void rtcInitLogger(rtc_log_level_t level);
|
||||
typedef struct {
|
||||
const char **iceServers;
|
||||
int iceServersCount;
|
||||
uint16_t portRangeBegin;
|
||||
uint16_t portRangeEnd;
|
||||
} rtcConfiguration;
|
||||
|
||||
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount);
|
||||
void rtcDeletePeerConnection(int pc);
|
||||
int rtcCreateDataChannel(int pc, const char *label);
|
||||
void rtcDeleteDataChannel(int dc);
|
||||
void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *));
|
||||
void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *,
|
||||
void *));
|
||||
void rtcSetLocalCandidateCallback(int pc,
|
||||
void (*candidateCallback)(const char *, const char *, void *));
|
||||
void rtcSetStateChangeCallback(int pc, void (*stateCallback)(rtc_state_t state, void *));
|
||||
void rtcSetGatheringStateChangeCallback(int pc,
|
||||
void (*gatheringStateCallback)(rtc_gathering_state_t state,
|
||||
void *));
|
||||
void rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
||||
void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid);
|
||||
int rtcGetDataChannelLabel(int dc, char *data, int size);
|
||||
void rtcSetOpenCallback(int dc, void (*openCallback)(void *));
|
||||
void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *));
|
||||
void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *));
|
||||
int rtcSendMessage(int dc, const char *data, int size);
|
||||
typedef void (*dataChannelCallbackFunc)(int dc, void *ptr);
|
||||
typedef void (*descriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
||||
typedef void (*candidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
||||
typedef void (*stateChangeCallbackFunc)(rtcState state, void *ptr);
|
||||
typedef void (*gatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
||||
typedef void (*openCallbackFunc)(void *ptr);
|
||||
typedef void (*closedCallbackFunc)(void *ptr);
|
||||
typedef void (*errorCallbackFunc)(const char *error, void *ptr);
|
||||
typedef void (*messageCallbackFunc)(const char *message, int size, void *ptr);
|
||||
typedef void (*bufferedAmountLowCallbackFunc)(void *ptr);
|
||||
typedef void (*availableCallbackFunc)(void *ptr);
|
||||
|
||||
// Log
|
||||
void rtcInitLogger(rtcLogLevel level);
|
||||
|
||||
// User pointer
|
||||
void rtcSetUserPointer(int i, void *ptr);
|
||||
|
||||
// PeerConnection
|
||||
int rtcCreatePeerConnection(const rtcConfiguration *config);
|
||||
int rtcDeletePeerConnection(int pc);
|
||||
|
||||
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb);
|
||||
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb);
|
||||
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb);
|
||||
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb);
|
||||
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb);
|
||||
|
||||
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
||||
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
|
||||
|
||||
int rtcGetLocalAddress(int pc, char *buffer, int size);
|
||||
int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
||||
|
||||
// DataChannel
|
||||
int rtcCreateDataChannel(int pc, const char *label);
|
||||
int rtcDeleteDataChannel(int dc);
|
||||
|
||||
int rtcGetDataChannelLabel(int dc, char *buffer, int size);
|
||||
int rtcSetOpenCallback(int dc, openCallbackFunc cb);
|
||||
int rtcSetClosedCallback(int dc, closedCallbackFunc cb);
|
||||
int rtcSetErrorCallback(int dc, errorCallbackFunc cb);
|
||||
int rtcSetMessageCallback(int dc, messageCallbackFunc cb);
|
||||
int rtcSendMessage(int dc, const char *data, int size);
|
||||
|
||||
int rtcGetBufferedAmount(int dc); // total size buffered to send
|
||||
int rtcSetBufferedAmountLowThreshold(int dc, int amount);
|
||||
int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb);
|
||||
|
||||
// DataChannel extended API
|
||||
int rtcGetAvailableAmount(int dc); // total size available to receive
|
||||
int rtcSetAvailableCallback(int dc, availableCallbackFunc cb);
|
||||
int rtcReceiveMessage(int dc, char *buffer, int *size);
|
||||
|
||||
// Cleanup
|
||||
void rtcCleanup();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
@ -17,6 +17,10 @@
|
||||
*/
|
||||
|
||||
// C++ API
|
||||
#include "include.hpp"
|
||||
#include "init.hpp" // for rtc::Cleanup()
|
||||
#include "log.hpp"
|
||||
//
|
||||
#include "datachannel.hpp"
|
||||
#include "peerconnection.hpp"
|
||||
|
||||
|
@ -22,8 +22,14 @@
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
using std::array;
|
||||
@ -125,3 +131,33 @@ Candidate::operator string() const {
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
|
||||
return out << std::string(candidate);
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type) {
|
||||
switch (type) {
|
||||
case rtc::CandidateType::Host:
|
||||
return out << "Host";
|
||||
case rtc::CandidateType::PeerReflexive:
|
||||
return out << "PeerReflexive";
|
||||
case rtc::CandidateType::Relayed:
|
||||
return out << "Relayed";
|
||||
case rtc::CandidateType::ServerReflexive:
|
||||
return out << "ServerReflexive";
|
||||
default:
|
||||
return out << "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType) {
|
||||
switch (transportType) {
|
||||
case rtc::CandidateTransportType::TcpActive:
|
||||
return out << "TcpActive";
|
||||
case rtc::CandidateTransportType::TcpPassive:
|
||||
return out << "TcpPassive";
|
||||
case rtc::CandidateTransportType::TcpSo:
|
||||
return out << "TcpSo";
|
||||
case rtc::CandidateTransportType::Udp:
|
||||
return out << "Udp";
|
||||
default:
|
||||
return out << "Unknown";
|
||||
}
|
||||
}
|
||||
|
@ -141,14 +141,9 @@ string make_fingerprint(gnutls_x509_crt_t crt) {
|
||||
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;
|
||||
namespace {
|
||||
|
||||
certificate_ptr make_certificate_impl(string commonName) {
|
||||
std::unique_ptr<gnutls_x509_crt_t, decltype(&delete_crt)> crt(create_crt(), delete_crt);
|
||||
std::unique_ptr<gnutls_x509_privkey_t, decltype(&delete_privkey)> privkey(create_privkey(),
|
||||
delete_privkey);
|
||||
@ -174,11 +169,11 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
|
||||
check_gnutls(gnutls_x509_crt_sign2(*crt, *crt, *privkey, GNUTLS_DIG_SHA256, 0),
|
||||
"Unable to auto-sign certificate");
|
||||
|
||||
auto certificate = std::make_shared<Certificate>(*crt, *privkey);
|
||||
cache.emplace(std::make_pair(commonName, certificate));
|
||||
return certificate;
|
||||
return std::make_shared<Certificate>(*crt, *privkey);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#else
|
||||
@ -236,22 +231,9 @@ string make_fingerprint(X509 *x509) {
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
certificate_ptr make_certificate_impl(string commonName) {
|
||||
shared_ptr<X509> x509(X509_new(), X509_free);
|
||||
shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
|
||||
|
||||
@ -288,12 +270,54 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
|
||||
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;
|
||||
return std::make_shared<Certificate>(x509, pkey);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
||||
// Common for GnuTLS and OpenSSL
|
||||
|
||||
namespace rtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper function roughly equivalent to std::async with policy std::launch::async
|
||||
// since std::async might be unreliable on some platforms (e.g. Mingw32 on Windows)
|
||||
template <class F, class... Args>
|
||||
std::future<std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>> thread_call(F &&f,
|
||||
Args &&... args) {
|
||||
using R = std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>;
|
||||
std::packaged_task<R()> task(std::bind(f, std::forward<Args>(args)...));
|
||||
std::future<R> future = task.get_future();
|
||||
std::thread t(std::move(task));
|
||||
t.detach();
|
||||
return future;
|
||||
}
|
||||
|
||||
static std::unordered_map<string, future_certificate_ptr> CertificateCache;
|
||||
static std::mutex CertificateCacheMutex;
|
||||
|
||||
} // namespace
|
||||
|
||||
future_certificate_ptr make_certificate(string commonName) {
|
||||
std::lock_guard lock(CertificateCacheMutex);
|
||||
|
||||
if (auto it = CertificateCache.find(commonName); it != CertificateCache.end())
|
||||
return it->second;
|
||||
|
||||
auto future = thread_call(make_certificate_impl, commonName);
|
||||
auto shared = future.share();
|
||||
CertificateCache.emplace(std::move(commonName), shared);
|
||||
return shared;
|
||||
}
|
||||
|
||||
void CleanupCertificateCache() {
|
||||
std::lock_guard lock(CertificateCacheMutex);
|
||||
CertificateCache.clear();
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "include.hpp"
|
||||
|
||||
#include <future>
|
||||
#include <tuple>
|
||||
|
||||
#if USE_GNUTLS
|
||||
@ -62,7 +63,12 @@ string make_fingerprint(gnutls_x509_crt_t crt);
|
||||
string make_fingerprint(X509 *x509);
|
||||
#endif
|
||||
|
||||
std::shared_ptr<Certificate> make_certificate(const string &commonName);
|
||||
using certificate_ptr = std::shared_ptr<Certificate>;
|
||||
using future_certificate_ptr = std::shared_future<certificate_ptr>;
|
||||
|
||||
future_certificate_ptr make_certificate(string commonName); // cached
|
||||
|
||||
void CleanupCertificateCache();
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
|
@ -18,10 +18,14 @@
|
||||
|
||||
#include "channel.hpp"
|
||||
|
||||
namespace {}
|
||||
|
||||
namespace rtc {
|
||||
|
||||
size_t Channel::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
|
||||
|
||||
size_t Channel::bufferedAmount() const { return mBufferedAmount; }
|
||||
|
||||
size_t Channel::availableAmount() const { return 0; }
|
||||
|
||||
void Channel::onOpen(std::function<void()> callback) {
|
||||
mOpenCallback = callback;
|
||||
}
|
||||
@ -49,20 +53,16 @@ void Channel::onMessage(std::function<void(const binary &data)> binaryCallback,
|
||||
});
|
||||
}
|
||||
|
||||
void Channel::onAvailable(std::function<void()> callback) {
|
||||
mAvailableCallback = callback;
|
||||
}
|
||||
|
||||
void Channel::onBufferedAmountLow(std::function<void()> callback) {
|
||||
mBufferedAmountLowCallback = callback;
|
||||
}
|
||||
|
||||
size_t Channel::availableAmount() const { return 0; }
|
||||
|
||||
size_t Channel::bufferedAmount() const { return mBufferedAmount; }
|
||||
|
||||
void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; }
|
||||
|
||||
void Channel::onAvailable(std::function<void()> callback) {
|
||||
mAvailableCallback = callback;
|
||||
}
|
||||
|
||||
void Channel::triggerOpen() { mOpenCallback(); }
|
||||
|
||||
void Channel::triggerClosed() { mClosedCallback(); }
|
||||
@ -88,5 +88,14 @@ void Channel::triggerBufferedAmount(size_t amount) {
|
||||
mBufferedAmountLowCallback();
|
||||
}
|
||||
|
||||
void Channel::resetCallbacks() {
|
||||
mOpenCallback = nullptr;
|
||||
mClosedCallback = nullptr;
|
||||
mErrorCallback = nullptr;
|
||||
mMessageCallback = nullptr;
|
||||
mAvailableCallback = nullptr;
|
||||
mBufferedAmountLowCallback = nullptr;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
|
@ -38,16 +38,17 @@ IceServer::IceServer(const string &url) {
|
||||
});
|
||||
|
||||
string scheme = opt[2].value_or("stun");
|
||||
relayType = RelayType::TurnUdp;
|
||||
if (scheme == "stun" || scheme == "STUN")
|
||||
type = Type::Stun;
|
||||
else if (scheme == "turn" || scheme == "TURN")
|
||||
type = Type::Turn;
|
||||
else if (scheme == "turns" || scheme == "TURNS")
|
||||
else if (scheme == "turns" || scheme == "TURNS") {
|
||||
type = Type::Turn;
|
||||
else
|
||||
relayType = RelayType::TurnTls;
|
||||
} 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;
|
||||
@ -84,4 +85,7 @@ IceServer::IceServer(string hostname_, string service_, string username_, string
|
||||
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Turn),
|
||||
username(std::move(username_)), password(std::move(password_)), relayType(relayType_) {}
|
||||
|
||||
ProxyServer::ProxyServer(Type type_, string ip_, uint16_t port_, string username_, string password_)
|
||||
: type(type_), ip(ip_), port(port_), username(username_), password(password_) {}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -21,9 +21,16 @@
|
||||
#include "peerconnection.hpp"
|
||||
#include "sctptransport.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
namespace rtc {
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::weak_ptr;
|
||||
|
||||
// Messages for the DataChannel establishment protocol
|
||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09
|
||||
@ -60,16 +67,16 @@ struct CloseMessage {
|
||||
|
||||
const size_t RECV_QUEUE_LIMIT = 1024 * 1024; // 1 MiB
|
||||
|
||||
DataChannel::DataChannel(shared_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||
DataChannel::DataChannel(weak_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||
string protocol, Reliability reliability)
|
||||
: mPeerConnection(std::move(pc)), mStream(stream), mLabel(std::move(label)),
|
||||
: mPeerConnection(pc), mStream(stream), mLabel(std::move(label)),
|
||||
mProtocol(std::move(protocol)),
|
||||
mReliability(std::make_shared<Reliability>(std::move(reliability))),
|
||||
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
||||
|
||||
DataChannel::DataChannel(shared_ptr<PeerConnection> pc, shared_ptr<SctpTransport> transport,
|
||||
DataChannel::DataChannel(weak_ptr<PeerConnection> pc, weak_ptr<SctpTransport> transport,
|
||||
unsigned int stream)
|
||||
: mPeerConnection(std::move(pc)), mSctpTransport(transport), mStream(stream),
|
||||
: mPeerConnection(pc), mSctpTransport(transport), mStream(stream),
|
||||
mReliability(std::make_shared<Reliability>()),
|
||||
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
||||
|
||||
@ -77,17 +84,29 @@ DataChannel::~DataChannel() {
|
||||
close();
|
||||
}
|
||||
|
||||
unsigned int DataChannel::stream() const { return mStream; }
|
||||
|
||||
string DataChannel::label() const { return mLabel; }
|
||||
|
||||
string DataChannel::protocol() const { return mProtocol; }
|
||||
|
||||
Reliability DataChannel::reliability() const { return *mReliability; }
|
||||
|
||||
void DataChannel::close() {
|
||||
if (mIsOpen.exchange(false) && mSctpTransport)
|
||||
mSctpTransport->reset(mStream);
|
||||
mIsClosed = true;
|
||||
if (mIsOpen.exchange(false))
|
||||
if (auto transport = mSctpTransport.lock())
|
||||
transport->close(mStream);
|
||||
|
||||
mSctpTransport.reset();
|
||||
resetCallbacks();
|
||||
}
|
||||
|
||||
void DataChannel::remoteClose() {
|
||||
mIsOpen = false;
|
||||
if (!mIsClosed.exchange(true))
|
||||
triggerClosed();
|
||||
|
||||
mIsOpen = false;
|
||||
mSctpTransport.reset();
|
||||
}
|
||||
|
||||
@ -121,6 +140,9 @@ std::optional<std::variant<binary, string>> DataChannel::receive() {
|
||||
string(reinterpret_cast<const char *>(message->data()), message->size()));
|
||||
case Message::Binary:
|
||||
return std::make_optional(std::move(*message));
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,27 +153,20 @@ bool DataChannel::isOpen(void) const { return mIsOpen; }
|
||||
|
||||
bool DataChannel::isClosed(void) const { return mIsClosed; }
|
||||
|
||||
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;
|
||||
if (auto pc = mPeerConnection.lock())
|
||||
if (auto description = pc->remoteDescription())
|
||||
if (auto maxMessageSize = description->maxMessageSize())
|
||||
return *maxMessageSize > 0 ? *maxMessageSize : LOCAL_MAX_MESSAGE_SIZE;
|
||||
|
||||
return std::min(max, LOCAL_MAX_MESSAGE_SIZE);
|
||||
}
|
||||
|
||||
unsigned int DataChannel::stream() const { return mStream; }
|
||||
size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
|
||||
|
||||
string DataChannel::label() const { return mLabel; }
|
||||
|
||||
string DataChannel::protocol() const { return mProtocol; }
|
||||
|
||||
Reliability DataChannel::reliability() const { return *mReliability; }
|
||||
|
||||
void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
|
||||
mSctpTransport = sctpTransport;
|
||||
void DataChannel::open(shared_ptr<SctpTransport> transport) {
|
||||
mSctpTransport = transport;
|
||||
|
||||
uint8_t channelType = static_cast<uint8_t>(mReliability->type);
|
||||
if (mReliability->unordered)
|
||||
@ -178,20 +193,24 @@ void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
|
||||
std::copy(mLabel.begin(), mLabel.end(), end);
|
||||
std::copy(mProtocol.begin(), mProtocol.end(), end + mLabel.size());
|
||||
|
||||
mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||
transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||
}
|
||||
|
||||
bool DataChannel::outgoing(mutable_message_ptr message) {
|
||||
if (mIsClosed || !mSctpTransport)
|
||||
if (mIsClosed)
|
||||
throw std::runtime_error("DataChannel is closed");
|
||||
|
||||
if (message->size() > maxMessageSize())
|
||||
throw std::runtime_error("Message size exceeds limit");
|
||||
|
||||
auto transport = mSctpTransport.lock();
|
||||
if (!transport)
|
||||
throw std::runtime_error("DataChannel has no transport");
|
||||
|
||||
// Before the ACK has been received on a DataChannel, all messages must be sent ordered
|
||||
message->reliability = mIsOpen ? mReliability : nullptr;
|
||||
message->stream = mStream;
|
||||
return mSctpTransport->send(message);
|
||||
return transport->send(message);
|
||||
}
|
||||
|
||||
void DataChannel::incoming(message_ptr message) {
|
||||
@ -230,6 +249,10 @@ void DataChannel::incoming(message_ptr message) {
|
||||
}
|
||||
|
||||
void DataChannel::processOpenMessage(message_ptr message) {
|
||||
auto transport = mSctpTransport.lock();
|
||||
if (!transport)
|
||||
throw std::runtime_error("DataChannel has no transport");
|
||||
|
||||
if (message->size() < sizeof(OpenMessage))
|
||||
throw std::invalid_argument("DataChannel open message too small");
|
||||
|
||||
@ -266,7 +289,7 @@ void DataChannel::processOpenMessage(message_ptr message) {
|
||||
auto &ack = *reinterpret_cast<AckMessage *>(buffer.data());
|
||||
ack.type = MESSAGE_ACK;
|
||||
|
||||
mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||
transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||
|
||||
mIsOpen = true;
|
||||
triggerOpen();
|
||||
|
@ -45,12 +45,13 @@ inline void trim_end(string &str) {
|
||||
namespace rtc {
|
||||
|
||||
Description::Description(const string &sdp, const string &typeString)
|
||||
: Description(sdp, stringToType(typeString), Description::Role::ActPass) {}
|
||||
: Description(sdp, stringToType(typeString)) {}
|
||||
|
||||
Description::Description(const string &sdp, Type type) : Description(sdp, type, Role::ActPass) {}
|
||||
|
||||
Description::Description(const string &sdp, Type type, Role role)
|
||||
: mType(type), mRole(role), mMid("0"), mIceUfrag("0"), mIcePwd("0"), mTrickle(true) {
|
||||
if (mType == Type::Answer && mRole == Role::ActPass)
|
||||
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
|
||||
: mType(Type::Unspec), mRole(role), mMid("0"), mIceUfrag(""), mIcePwd(""), mTrickle(true) {
|
||||
hintType(type);
|
||||
|
||||
auto seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
std::default_random_engine generator(seed);
|
||||
@ -109,6 +110,14 @@ std::optional<size_t> Description::maxMessageSize() const { return mMaxMessageSi
|
||||
|
||||
bool Description::trickleEnabled() const { return mTrickle; }
|
||||
|
||||
void Description::hintType(Type type) {
|
||||
if (mType == Type::Unspec) {
|
||||
mType = type;
|
||||
if (mType == Type::Answer && mRole == Role::ActPass)
|
||||
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
|
||||
}
|
||||
}
|
||||
|
||||
void Description::setFingerprint(string fingerprint) {
|
||||
mFingerprint.emplace(std::move(fingerprint));
|
||||
}
|
||||
|
@ -55,17 +55,22 @@ static bool check_gnutls(int ret, const string &message = "GnuTLS error") {
|
||||
|
||||
namespace rtc {
|
||||
|
||||
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
|
||||
verifier_callback verifierCallback,
|
||||
state_callback stateChangeCallback)
|
||||
void DtlsTransport::Init() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
void DtlsTransport::Cleanup() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
|
||||
verifier_callback verifierCallback, state_callback stateChangeCallback)
|
||||
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
|
||||
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));
|
||||
@ -73,17 +78,19 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
|
||||
// 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 *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL:+COMP-NULL";
|
||||
const char *err_pos = NULL;
|
||||
check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
|
||||
"Unable to set TLS priorities");
|
||||
|
||||
gnutls_certificate_set_verify_function(mCertificate->credentials(), CertificateCallback);
|
||||
check_gnutls(
|
||||
gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate->credentials()));
|
||||
|
||||
gnutls_dtls_set_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_dtls_set_timeouts(mSession,
|
||||
1000, // 1s retransmission timeout recommended by RFC 6347
|
||||
30000); // 30s total timeout
|
||||
gnutls_handshake_set_timeout(mSession, 30000);
|
||||
|
||||
gnutls_session_set_ptr(mSession, this);
|
||||
gnutls_transport_set_ptr(mSession, this);
|
||||
@ -92,6 +99,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
|
||||
|
||||
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
||||
registerIncoming();
|
||||
}
|
||||
|
||||
DtlsTransport::~DtlsTransport() {
|
||||
@ -102,15 +110,14 @@ DtlsTransport::~DtlsTransport() {
|
||||
|
||||
DtlsTransport::State DtlsTransport::state() const { return mState; }
|
||||
|
||||
void DtlsTransport::stop() {
|
||||
Transport::stop();
|
||||
bool DtlsTransport::stop() {
|
||||
if (!Transport::stop())
|
||||
return false;
|
||||
|
||||
if (mRecvThread.joinable()) {
|
||||
PLOG_DEBUG << "Stopping DTLS recv thread";
|
||||
mIncomingQueue.stop();
|
||||
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
|
||||
mRecvThread.join();
|
||||
}
|
||||
PLOG_DEBUG << "Stopping DTLS recv thread";
|
||||
mIncomingQueue.stop();
|
||||
mRecvThread.join();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DtlsTransport::send(message_ptr message) {
|
||||
@ -131,10 +138,13 @@ bool DtlsTransport::send(message_ptr message) {
|
||||
}
|
||||
|
||||
void DtlsTransport::incoming(message_ptr message) {
|
||||
if (message)
|
||||
mIncomingQueue.push(message);
|
||||
else
|
||||
if (!message) {
|
||||
mIncomingQueue.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
PLOG_VERBOSE << "Incoming size=" << message->size();
|
||||
mIncomingQueue.push(message);
|
||||
}
|
||||
|
||||
void DtlsTransport::changeState(State state) {
|
||||
@ -148,6 +158,7 @@ void DtlsTransport::runRecvLoop() {
|
||||
// Handshake loop
|
||||
try {
|
||||
changeState(State::Connecting);
|
||||
gnutls_dtls_set_mtu(mSession, 1280 - 40 - 8); // min MTU over UDP/IPv6
|
||||
|
||||
int ret;
|
||||
do {
|
||||
@ -171,6 +182,7 @@ void DtlsTransport::runRecvLoop() {
|
||||
|
||||
// Receive loop
|
||||
try {
|
||||
PLOG_INFO << "DTLS handshake done";
|
||||
changeState(State::Connected);
|
||||
|
||||
const size_t bufferSize = maxMtu;
|
||||
@ -203,6 +215,8 @@ void DtlsTransport::runRecvLoop() {
|
||||
PLOG_ERROR << "DTLS recv: " << e.what();
|
||||
}
|
||||
|
||||
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
|
||||
|
||||
PLOG_INFO << "DTLS disconnected";
|
||||
changeState(State::Disconnected);
|
||||
recv(nullptr);
|
||||
@ -262,10 +276,8 @@ ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size
|
||||
|
||||
int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
||||
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
||||
if (ms != GNUTLS_INDEFINITE_TIMEOUT)
|
||||
t->mIncomingQueue.wait(milliseconds(ms));
|
||||
else
|
||||
t->mIncomingQueue.wait();
|
||||
t->mIncomingQueue.wait(ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms))
|
||||
: nullopt);
|
||||
return !t->mIncomingQueue.empty() ? 1 : 0;
|
||||
}
|
||||
|
||||
@ -323,7 +335,7 @@ BIO_METHOD *DtlsTransport::BioMethods = NULL;
|
||||
int DtlsTransport::TransportExIndex = -1;
|
||||
std::mutex DtlsTransport::GlobalMutex;
|
||||
|
||||
void DtlsTransport::GlobalInit() {
|
||||
void DtlsTransport::Init() {
|
||||
std::lock_guard lock(GlobalMutex);
|
||||
if (!BioMethods) {
|
||||
BioMethods = BIO_meth_new(BIO_TYPE_BIO, "DTLS writer");
|
||||
@ -339,6 +351,10 @@ void DtlsTransport::GlobalInit() {
|
||||
}
|
||||
}
|
||||
|
||||
void DtlsTransport::Cleanup() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
|
||||
verifier_callback verifierCallback, state_callback stateChangeCallback)
|
||||
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
|
||||
@ -346,7 +362,6 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
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");
|
||||
@ -376,7 +391,6 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
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);
|
||||
@ -396,6 +410,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
SSL_set_tmp_ecdh(mSsl, ecdh.get());
|
||||
|
||||
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
||||
registerIncoming();
|
||||
}
|
||||
|
||||
DtlsTransport::~DtlsTransport() {
|
||||
@ -405,16 +420,15 @@ DtlsTransport::~DtlsTransport() {
|
||||
SSL_CTX_free(mCtx);
|
||||
}
|
||||
|
||||
void DtlsTransport::stop() {
|
||||
Transport::stop();
|
||||
bool DtlsTransport::stop() {
|
||||
if (!Transport::stop())
|
||||
return false;
|
||||
|
||||
if (mRecvThread.joinable()) {
|
||||
PLOG_DEBUG << "Stopping DTLS recv thread";
|
||||
mIncomingQueue.stop();
|
||||
mRecvThread.join();
|
||||
|
||||
SSL_shutdown(mSsl);
|
||||
}
|
||||
PLOG_DEBUG << "Stopping DTLS recv thread";
|
||||
mIncomingQueue.stop();
|
||||
mRecvThread.join();
|
||||
SSL_shutdown(mSsl);
|
||||
return true;
|
||||
}
|
||||
|
||||
DtlsTransport::State DtlsTransport::state() const { return mState; }
|
||||
@ -432,10 +446,13 @@ bool DtlsTransport::send(message_ptr message) {
|
||||
}
|
||||
|
||||
void DtlsTransport::incoming(message_ptr message) {
|
||||
if (message)
|
||||
mIncomingQueue.push(message);
|
||||
else
|
||||
if (!message) {
|
||||
mIncomingQueue.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
PLOG_VERBOSE << "Incoming size=" << message->size();
|
||||
mIncomingQueue.push(message);
|
||||
}
|
||||
|
||||
void DtlsTransport::changeState(State state) {
|
||||
@ -447,35 +464,71 @@ void DtlsTransport::runRecvLoop() {
|
||||
const size_t maxMtu = 4096;
|
||||
try {
|
||||
changeState(State::Connecting);
|
||||
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
|
||||
|
||||
SSL_do_handshake(mSsl);
|
||||
// Initiate the handshake
|
||||
int ret = SSL_do_handshake(mSsl);
|
||||
check_openssl_ret(mSsl, ret, "Handshake failed");
|
||||
|
||||
const size_t bufferSize = maxMtu;
|
||||
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;
|
||||
while (true) {
|
||||
// Process pending messages
|
||||
while (!mIncomingQueue.empty()) {
|
||||
auto message = *mIncomingQueue.pop();
|
||||
BIO_write(mInBio, message->data(), message->size());
|
||||
|
||||
auto decrypted = ret > 0 ? make_message(buffer, buffer + ret) : nullptr;
|
||||
if (mState == State::Connecting) {
|
||||
// Continue the handshake
|
||||
int ret = SSL_do_handshake(mSsl);
|
||||
if (!check_openssl_ret(mSsl, ret, "Handshake failed"))
|
||||
break;
|
||||
|
||||
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)) {
|
||||
// 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 (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);
|
||||
PLOG_INFO << "DTLS handshake done";
|
||||
changeState(State::Connected);
|
||||
}
|
||||
} else {
|
||||
int ret = SSL_read(mSsl, buffer, bufferSize);
|
||||
if (!check_openssl_ret(mSsl, ret))
|
||||
break;
|
||||
if (ret > 0)
|
||||
recv(make_message(buffer, buffer + ret));
|
||||
}
|
||||
}
|
||||
|
||||
if (decrypted)
|
||||
recv(decrypted);
|
||||
// No more messages pending, retransmit and rearm timeout if connecting
|
||||
std::optional<milliseconds> duration;
|
||||
if (mState == State::Connecting) {
|
||||
// Warning: This function breaks the usual return value convention
|
||||
int ret = DTLSv1_handle_timeout(mSsl);
|
||||
if (ret < 0) {
|
||||
throw std::runtime_error("Handshake timeout"); // write BIO can't fail
|
||||
} else if (ret > 0) {
|
||||
LOG_VERBOSE << "OpenSSL did DTLS retransmit";
|
||||
}
|
||||
|
||||
struct timeval timeout = {};
|
||||
if (mState == State::Connecting && DTLSv1_get_timeout(mSsl, &timeout)) {
|
||||
duration = milliseconds(timeout.tv_sec * 1000 + timeout.tv_usec / 1000);
|
||||
// Also handle handshake timeout manually because OpenSSL actually doesn't...
|
||||
// OpenSSL backs off exponentially in base 2 starting from the recommended 1s
|
||||
// so this allows for 5 retransmissions and fails after roughly 30s.
|
||||
if (duration > 30s) {
|
||||
throw std::runtime_error("Handshake timeout");
|
||||
} else {
|
||||
LOG_VERBOSE << "OpenSSL DTLS retransmit timeout is " << duration->count()
|
||||
<< "ms";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mIncomingQueue.wait(duration))
|
||||
break; // queue is stopped
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "DTLS recv: " << e.what();
|
||||
@ -486,7 +539,7 @@ void DtlsTransport::runRecvLoop() {
|
||||
changeState(State::Disconnected);
|
||||
recv(nullptr);
|
||||
} else {
|
||||
PLOG_INFO << "DTLS handshake failed";
|
||||
PLOG_ERROR << "DTLS handshake failed";
|
||||
changeState(State::Failed);
|
||||
}
|
||||
}
|
||||
@ -536,7 +589,8 @@ int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
|
||||
if (!transport)
|
||||
return -1;
|
||||
auto b = reinterpret_cast<const byte *>(in);
|
||||
return transport->outgoing(make_message(b, b + inl)) ? inl : 0;
|
||||
transport->outgoing(make_message(b, b + inl));
|
||||
return inl; // can't fail
|
||||
}
|
||||
|
||||
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) {
|
||||
|
@ -43,18 +43,21 @@ class IceTransport;
|
||||
|
||||
class DtlsTransport : public Transport {
|
||||
public:
|
||||
static void Init();
|
||||
static void Cleanup();
|
||||
|
||||
enum class State { Disconnected, Connecting, Connected, Failed };
|
||||
|
||||
using verifier_callback = std::function<bool(const std::string &fingerprint)>;
|
||||
using state_callback = std::function<void(State state)>;
|
||||
|
||||
DtlsTransport(std::shared_ptr<IceTransport> lower, std::shared_ptr<Certificate> certificate,
|
||||
DtlsTransport(std::shared_ptr<IceTransport> lower, certificate_ptr certificate,
|
||||
verifier_callback verifierCallback, state_callback stateChangeCallback);
|
||||
~DtlsTransport();
|
||||
|
||||
State state() const;
|
||||
|
||||
void stop() override;
|
||||
bool stop() override;
|
||||
bool send(message_ptr message) override; // false if dropped
|
||||
|
||||
private:
|
||||
@ -62,7 +65,7 @@ private:
|
||||
void changeState(State state);
|
||||
void runRecvLoop();
|
||||
|
||||
const std::shared_ptr<Certificate> mCertificate;
|
||||
const certificate_ptr mCertificate;
|
||||
|
||||
Queue<message_ptr> mIncomingQueue;
|
||||
std::atomic<State> mState;
|
||||
@ -87,7 +90,6 @@ private:
|
||||
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);
|
||||
|
||||
|
@ -18,16 +18,24 @@
|
||||
|
||||
#include "icetransport.hpp"
|
||||
#include "configuration.hpp"
|
||||
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include "transport.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using std::shared_ptr;
|
||||
@ -65,7 +73,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
||||
|
||||
// Pick a STUN server
|
||||
// Pick a STUN server (TURN support is not implemented in libjuice yet)
|
||||
for (auto &server : servers) {
|
||||
if (!server.hostname.empty() && server.type == IceServer::Type::Stun) {
|
||||
if (server.service.empty())
|
||||
@ -79,7 +87,12 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
}
|
||||
}
|
||||
|
||||
// TURN support is not implemented yet
|
||||
// Port range
|
||||
if (config.portRangeBegin > 1024 ||
|
||||
(config.portRangeEnd != 0 && config.portRangeEnd != 65535)) {
|
||||
jconfig.local_port_range_begin = config.portRangeBegin;
|
||||
jconfig.local_port_range_end = config.portRangeEnd;
|
||||
}
|
||||
|
||||
// Create agent
|
||||
mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy);
|
||||
@ -89,8 +102,8 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
|
||||
IceTransport::~IceTransport() { stop(); }
|
||||
|
||||
void IceTransport::stop() {
|
||||
// Nothing to do
|
||||
bool IceTransport::stop() {
|
||||
return Transport::stop();
|
||||
}
|
||||
|
||||
Description::Role IceTransport::role() const { return mRole; }
|
||||
@ -109,9 +122,6 @@ void IceTransport::setRemoteDescription(const Description &description) {
|
||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||
: 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");
|
||||
}
|
||||
@ -158,12 +168,6 @@ bool IceTransport::send(message_ptr message) {
|
||||
return outgoing(message);
|
||||
}
|
||||
|
||||
void IceTransport::incoming(message_ptr message) { recv(message); }
|
||||
|
||||
void IceTransport::incoming(const byte *data, int size) {
|
||||
incoming(make_message(data, data + size));
|
||||
}
|
||||
|
||||
bool IceTransport::outgoing(message_ptr message) {
|
||||
return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()),
|
||||
message->size()) >= 0;
|
||||
@ -220,7 +224,9 @@ void IceTransport::RecvCallback(juice_agent_t *agent, const char *data, size_t s
|
||||
void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
iceTransport->incoming(reinterpret_cast<const byte *>(data), size);
|
||||
PLOG_VERBOSE << "Incoming size=" << size;
|
||||
auto b = reinterpret_cast<const byte *>(data);
|
||||
iceTransport->incoming(make_message(b, b + size));
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
@ -241,11 +247,8 @@ void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
|
||||
case JUICE_LOG_LEVEL_INFO:
|
||||
severity = plog::info;
|
||||
break;
|
||||
case JUICE_LOG_LEVEL_DEBUG:
|
||||
severity = plog::debug;
|
||||
break;
|
||||
default:
|
||||
severity = plog::verbose;
|
||||
severity = plog::verbose; // libjuice debug as verbose
|
||||
break;
|
||||
}
|
||||
PLOG(severity) << "juice: " << message;
|
||||
@ -308,6 +311,18 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr);
|
||||
|
||||
// Proxy
|
||||
if (config.proxyServer.has_value()) {
|
||||
ProxyServer proxyServer = config.proxyServer.value();
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-type", proxyServer.type, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-ip", proxyServer.ip.c_str(), nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-port", proxyServer.port, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-username", proxyServer.username.c_str(),
|
||||
nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-password", proxyServer.password.c_str(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
// Randomize order
|
||||
std::vector<IceServer> servers = config.iceServers;
|
||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
@ -422,16 +437,22 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
|
||||
IceTransport::~IceTransport() { stop(); }
|
||||
|
||||
void IceTransport::stop() {
|
||||
bool IceTransport::stop() {
|
||||
if (mTimeoutId) {
|
||||
g_source_remove(mTimeoutId);
|
||||
mTimeoutId = 0;
|
||||
}
|
||||
if (mMainLoopThread.joinable()) {
|
||||
PLOG_DEBUG << "Stopping ICE thread";
|
||||
g_main_loop_quit(mMainLoop.get());
|
||||
mMainLoopThread.join();
|
||||
}
|
||||
|
||||
if (!Transport::stop())
|
||||
return false;
|
||||
|
||||
PLOG_DEBUG << "Stopping ICE thread";
|
||||
nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(mMainLoop.get()),
|
||||
NULL, NULL);
|
||||
nice_agent_remove_stream(mNiceAgent.get(), mStreamId);
|
||||
g_main_loop_quit(mMainLoop.get());
|
||||
mMainLoopThread.join();
|
||||
return true;
|
||||
}
|
||||
|
||||
Description::Role IceTransport::role() const { return mRole; }
|
||||
@ -515,12 +536,6 @@ bool IceTransport::send(message_ptr message) {
|
||||
return outgoing(message);
|
||||
}
|
||||
|
||||
void IceTransport::incoming(message_ptr message) { recv(message); }
|
||||
|
||||
void IceTransport::incoming(const byte *data, int size) {
|
||||
incoming(make_message(data, data + size));
|
||||
}
|
||||
|
||||
bool IceTransport::outgoing(message_ptr message) {
|
||||
return nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(),
|
||||
reinterpret_cast<const char *>(message->data())) >= 0;
|
||||
@ -608,7 +623,9 @@ void IceTransport::RecvCallback(NiceAgent *agent, guint streamId, guint componen
|
||||
gchar *buf, gpointer userData) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(userData);
|
||||
try {
|
||||
iceTransport->incoming(reinterpret_cast<byte *>(buf), len);
|
||||
PLOG_VERBOSE << "Incoming size=" << len;
|
||||
auto b = reinterpret_cast<byte *>(buf);
|
||||
iceTransport->incoming(make_message(b, b + len));
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
@ -644,6 +661,58 @@ void IceTransport::LogCallback(const gchar *logDomain, GLogLevelFlags logLevel,
|
||||
PLOG(severity) << "nice: " << message;
|
||||
}
|
||||
|
||||
bool IceTransport::getSelectedCandidatePair(CandidateInfo *localInfo, CandidateInfo *remoteInfo) {
|
||||
NiceCandidate *local, *remote;
|
||||
gboolean result = nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote);
|
||||
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
char ipaddr[INET6_ADDRSTRLEN];
|
||||
nice_address_to_string(&local->addr, ipaddr);
|
||||
localInfo->address = std::string(ipaddr);
|
||||
localInfo->port = nice_address_get_port(&local->addr);
|
||||
localInfo->type = IceTransport::NiceTypeToCandidateType(local->type);
|
||||
localInfo->transportType =
|
||||
IceTransport::NiceTransportTypeToCandidateTransportType(local->transport);
|
||||
|
||||
nice_address_to_string(&remote->addr, ipaddr);
|
||||
remoteInfo->address = std::string(ipaddr);
|
||||
remoteInfo->port = nice_address_get_port(&remote->addr);
|
||||
remoteInfo->type = IceTransport::NiceTypeToCandidateType(remote->type);
|
||||
remoteInfo->transportType =
|
||||
IceTransport::NiceTransportTypeToCandidateTransportType(remote->transport);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const CandidateType IceTransport::NiceTypeToCandidateType(NiceCandidateType type) {
|
||||
switch (type) {
|
||||
case NiceCandidateType::NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
|
||||
return CandidateType::PeerReflexive;
|
||||
case NiceCandidateType::NICE_CANDIDATE_TYPE_RELAYED:
|
||||
return CandidateType::Relayed;
|
||||
case NiceCandidateType::NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
|
||||
return CandidateType::ServerReflexive;
|
||||
default:
|
||||
return CandidateType::Host;
|
||||
}
|
||||
}
|
||||
|
||||
const CandidateTransportType
|
||||
IceTransport::NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type) {
|
||||
switch (type) {
|
||||
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE:
|
||||
return CandidateTransportType::TcpActive;
|
||||
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE:
|
||||
return CandidateTransportType::TcpPassive;
|
||||
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_SO:
|
||||
return CandidateTransportType::TcpSo;
|
||||
default:
|
||||
return CandidateTransportType::Udp;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -56,6 +56,8 @@ public:
|
||||
Completed = NICE_COMPONENT_STATE_READY,
|
||||
Failed = NICE_COMPONENT_STATE_FAILED,
|
||||
};
|
||||
|
||||
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
|
||||
#endif
|
||||
enum class GatheringState { New = 0, InProgress = 1, Complete = 2 };
|
||||
|
||||
@ -79,12 +81,10 @@ public:
|
||||
std::optional<string> getLocalAddress() const;
|
||||
std::optional<string> getRemoteAddress() const;
|
||||
|
||||
void stop() override;
|
||||
bool stop() override;
|
||||
bool send(message_ptr message) override; // false if dropped
|
||||
|
||||
private:
|
||||
void incoming(message_ptr message) override;
|
||||
void incoming(const byte *data, int size);
|
||||
bool outgoing(message_ptr message) override;
|
||||
|
||||
void changeState(State state);
|
||||
@ -133,6 +133,8 @@ private:
|
||||
static gboolean TimeoutCallback(gpointer userData);
|
||||
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
|
||||
gpointer user_data);
|
||||
static const CandidateType NiceTypeToCandidateType(NiceCandidateType type);
|
||||
static const CandidateTransportType NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
88
src/init.cpp
Normal file
88
src/init.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "init.hpp"
|
||||
|
||||
#include "certificate.hpp"
|
||||
#include "dtlstransport.hpp"
|
||||
#include "sctptransport.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
|
||||
#if USE_GNUTLS
|
||||
// Nothing to do
|
||||
#else
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
#endif
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace rtc {
|
||||
|
||||
std::weak_ptr<Init> Init::Weak;
|
||||
init_token Init::Global;
|
||||
std::mutex Init::Mutex;
|
||||
|
||||
init_token Init::Token() {
|
||||
std::lock_guard lock(Mutex);
|
||||
|
||||
if (!Global) {
|
||||
if (auto token = Weak.lock())
|
||||
Global = token;
|
||||
else
|
||||
Global = shared_ptr<Init>(new Init());
|
||||
}
|
||||
return Global;
|
||||
}
|
||||
|
||||
void Init::Cleanup() { Global.reset(); }
|
||||
|
||||
Init::Init() {
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
|
||||
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
|
||||
#endif
|
||||
|
||||
#if USE_GNUTLS
|
||||
// Nothing to do
|
||||
#else
|
||||
OPENSSL_init_ssl(0, NULL);
|
||||
SSL_load_error_strings();
|
||||
ERR_load_crypto_strings();
|
||||
#endif
|
||||
|
||||
DtlsTransport::Init();
|
||||
SctpTransport::Init();
|
||||
}
|
||||
|
||||
Init::~Init() {
|
||||
CleanupCertificateCache();
|
||||
DtlsTransport::Cleanup();
|
||||
SctpTransport::Cleanup();
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
42
src/log.cpp
Normal file
42
src/log.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "log.hpp"
|
||||
|
||||
#include "plog/Appenders/ColorConsoleAppender.h"
|
||||
#include "plog/Log.h"
|
||||
#include "plog/Logger.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
|
||||
|
||||
void InitLogger(plog::Severity severity, plog::IAppender *appender) {
|
||||
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
|
||||
static plog::Logger<0> *logger = nullptr;
|
||||
if (!logger) {
|
||||
logger = &plog::init(severity, appender ? appender : &consoleAppender);
|
||||
PLOG_DEBUG << "Logger initialized";
|
||||
} else {
|
||||
logger->setMaxSeverity(severity);
|
||||
if (appender)
|
||||
logger->addAppender(appender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "sctptransport.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
@ -32,34 +33,34 @@ using namespace std::placeholders;
|
||||
using std::shared_ptr;
|
||||
using std::weak_ptr;
|
||||
|
||||
template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t, Args &&... _args) {
|
||||
return [bound = std::bind(f, t, _args...), weak_this = t->weak_from_this()](auto &&... args) {
|
||||
if (auto shared_this = weak_this.lock())
|
||||
bound(args...);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename F, typename T, typename... Args>
|
||||
auto weak_bind_verifier(F &&f, T *t, Args &&... _args) {
|
||||
return [bound = std::bind(f, t, _args...), weak_this = t->weak_from_this()](auto &&... args) {
|
||||
if (auto shared_this = weak_this.lock())
|
||||
return bound(args...);
|
||||
else
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
|
||||
|
||||
PeerConnection::PeerConnection(const Configuration &config)
|
||||
: mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New) {}
|
||||
: mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New),
|
||||
mGatheringState(GatheringState::New) {}
|
||||
|
||||
PeerConnection::~PeerConnection() {
|
||||
changeState(State::Destroying);
|
||||
close();
|
||||
mSctpTransport.reset();
|
||||
mDtlsTransport.reset();
|
||||
mIceTransport.reset();
|
||||
}
|
||||
PeerConnection::~PeerConnection() { close(); }
|
||||
|
||||
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);
|
||||
closeTransports();
|
||||
}
|
||||
|
||||
const Configuration *PeerConnection::config() const { return &mConfig; }
|
||||
@ -79,9 +80,10 @@ std::optional<Description> PeerConnection::remoteDescription() const {
|
||||
}
|
||||
|
||||
void PeerConnection::setRemoteDescription(Description description) {
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
|
||||
description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
|
||||
auto remoteCandidates = description.extractCandidates();
|
||||
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
mRemoteDescription.emplace(std::move(description));
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
@ -100,12 +102,16 @@ void PeerConnection::setRemoteDescription(Description description) {
|
||||
if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
|
||||
// Since we assumed passive role during DataChannel creation, we need to shift the
|
||||
// stream numbers by one to shift them from odd to even.
|
||||
std::unique_lock lock(mDataChannelsMutex); // we are going to swap the container
|
||||
decltype(mDataChannels) newDataChannels;
|
||||
iterateDataChannels([&](shared_ptr<DataChannel> channel) {
|
||||
auto it = mDataChannels.begin();
|
||||
while (it != mDataChannels.end()) {
|
||||
auto channel = it->second.lock();
|
||||
if (channel->stream() % 2 == 1)
|
||||
channel->mStream -= 1;
|
||||
newDataChannels.emplace(channel->stream(), channel);
|
||||
});
|
||||
++it;
|
||||
}
|
||||
std::swap(mDataChannels, newDataChannels);
|
||||
}
|
||||
}
|
||||
@ -157,19 +163,7 @@ shared_ptr<DataChannel> PeerConnection::createDataChannel(const string &label,
|
||||
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
|
||||
unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
|
||||
while (mDataChannels.find(stream) != mDataChannels.end()) {
|
||||
stream += 2;
|
||||
if (stream >= 65535)
|
||||
throw std::runtime_error("Too many DataChannels");
|
||||
}
|
||||
|
||||
auto channel =
|
||||
std::make_shared<DataChannel>(shared_from_this(), stream, label, protocol, reliability);
|
||||
mDataChannels.insert(std::make_pair(stream, channel));
|
||||
auto channel = emplaceDataChannel(role, label, protocol, reliability);
|
||||
|
||||
if (!iceTransport) {
|
||||
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
|
||||
@ -209,108 +203,185 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
|
||||
}
|
||||
|
||||
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mIceTransport))
|
||||
try {
|
||||
if (auto transport = std::atomic_load(&mIceTransport))
|
||||
return transport;
|
||||
|
||||
auto transport = std::make_shared<IceTransport>(
|
||||
mConfig, role, weak_bind(&PeerConnection::processLocalCandidate, this, _1),
|
||||
[this, weak_this = weak_from_this()](IceTransport::State state) {
|
||||
auto shared_this = weak_this.lock();
|
||||
if (!shared_this)
|
||||
return;
|
||||
switch (state) {
|
||||
case IceTransport::State::Connecting:
|
||||
changeState(State::Connecting);
|
||||
break;
|
||||
case IceTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case IceTransport::State::Connected:
|
||||
initDtlsTransport();
|
||||
break;
|
||||
case IceTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
},
|
||||
[this, weak_this = weak_from_this()](IceTransport::GatheringState state) {
|
||||
auto shared_this = weak_this.lock();
|
||||
if (!shared_this)
|
||||
return;
|
||||
switch (state) {
|
||||
case IceTransport::GatheringState::InProgress:
|
||||
changeGatheringState(GatheringState::InProgress);
|
||||
break;
|
||||
case IceTransport::GatheringState::Complete:
|
||||
endLocalCandidates();
|
||||
changeGatheringState(GatheringState::Complete);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
std::atomic_store(&mIceTransport, transport);
|
||||
if (mState == State::Closed) {
|
||||
mIceTransport.reset();
|
||||
transport->stop();
|
||||
throw std::runtime_error("Connection is closed");
|
||||
}
|
||||
return transport;
|
||||
|
||||
auto transport = std::make_shared<IceTransport>(
|
||||
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
|
||||
[this](IceTransport::State state) {
|
||||
switch (state) {
|
||||
case IceTransport::State::Connecting:
|
||||
changeState(State::Connecting);
|
||||
break;
|
||||
case IceTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case IceTransport::State::Connected:
|
||||
initDtlsTransport();
|
||||
break;
|
||||
case IceTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
},
|
||||
[this](IceTransport::GatheringState state) {
|
||||
switch (state) {
|
||||
case IceTransport::GatheringState::InProgress:
|
||||
changeGatheringState(GatheringState::InProgress);
|
||||
break;
|
||||
case IceTransport::GatheringState::Complete:
|
||||
endLocalCandidates();
|
||||
changeGatheringState(GatheringState::Complete);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mIceTransport, transport);
|
||||
return transport;
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << e.what();
|
||||
changeState(State::Failed);
|
||||
throw std::runtime_error("ICE transport initialization failed");
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
||||
try {
|
||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
||||
return transport;
|
||||
|
||||
auto certificate = mCertificate.get();
|
||||
auto lower = std::atomic_load(&mIceTransport);
|
||||
auto transport = std::make_shared<DtlsTransport>(
|
||||
lower, certificate, weak_bind_verifier(&PeerConnection::checkFingerprint, this, _1),
|
||||
[this, weak_this = weak_from_this()](DtlsTransport::State state) {
|
||||
auto shared_this = weak_this.lock();
|
||||
if (!shared_this)
|
||||
return;
|
||||
switch (state) {
|
||||
case DtlsTransport::State::Connected:
|
||||
initSctpTransport();
|
||||
break;
|
||||
case DtlsTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case DtlsTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
std::atomic_store(&mDtlsTransport, transport);
|
||||
if (mState == State::Closed) {
|
||||
mDtlsTransport.reset();
|
||||
transport->stop();
|
||||
throw std::runtime_error("Connection is closed");
|
||||
}
|
||||
return transport;
|
||||
|
||||
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:
|
||||
initSctpTransport();
|
||||
break;
|
||||
case DtlsTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case DtlsTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mDtlsTransport, transport);
|
||||
return transport;
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << e.what();
|
||||
changeState(State::Failed);
|
||||
throw std::runtime_error("DTLS transport initialization failed");
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||
try {
|
||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||
return transport;
|
||||
|
||||
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
||||
auto lower = std::atomic_load(&mDtlsTransport);
|
||||
auto transport = std::make_shared<SctpTransport>(
|
||||
lower, sctpPort, weak_bind(&PeerConnection::forwardMessage, this, _1),
|
||||
weak_bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
|
||||
[this, weak_this = weak_from_this()](SctpTransport::State state) {
|
||||
auto shared_this = weak_this.lock();
|
||||
if (!shared_this)
|
||||
return;
|
||||
switch (state) {
|
||||
case SctpTransport::State::Connected:
|
||||
changeState(State::Connected);
|
||||
openDataChannels();
|
||||
break;
|
||||
case SctpTransport::State::Failed:
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case SctpTransport::State::Disconnected:
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
std::atomic_store(&mSctpTransport, transport);
|
||||
if (mState == State::Closed) {
|
||||
mSctpTransport.reset();
|
||||
transport->stop();
|
||||
throw std::runtime_error("Connection is closed");
|
||||
}
|
||||
return transport;
|
||||
|
||||
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;
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << e.what();
|
||||
changeState(State::Failed);
|
||||
throw std::runtime_error("SCTP transport initialization failed");
|
||||
}
|
||||
}
|
||||
|
||||
void PeerConnection::closeTransports() {
|
||||
// Change state to sink state Closed to block init methods
|
||||
changeState(State::Closed);
|
||||
|
||||
// Reset callbacks now that state is changed
|
||||
resetCallbacks();
|
||||
|
||||
// Pass the references to a thread, allowing to terminate a transport from its own thread
|
||||
auto sctp = std::atomic_exchange(&mSctpTransport, decltype(mSctpTransport)(nullptr));
|
||||
auto dtls = std::atomic_exchange(&mDtlsTransport, decltype(mDtlsTransport)(nullptr));
|
||||
auto ice = std::atomic_exchange(&mIceTransport, decltype(mIceTransport)(nullptr));
|
||||
if (sctp || dtls || ice) {
|
||||
std::thread t([sctp, dtls, ice]() mutable {
|
||||
if (sctp)
|
||||
sctp->stop();
|
||||
if (dtls)
|
||||
dtls->stop();
|
||||
if (ice)
|
||||
ice->stop();
|
||||
|
||||
sctp.reset();
|
||||
dtls.reset();
|
||||
ice.reset();
|
||||
});
|
||||
t.detach();
|
||||
}
|
||||
}
|
||||
|
||||
void PeerConnection::endLocalCandidates() {
|
||||
@ -334,14 +405,7 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
||||
return;
|
||||
}
|
||||
|
||||
shared_ptr<DataChannel> channel;
|
||||
if (auto it = mDataChannels.find(message->stream); it != mDataChannels.end()) {
|
||||
channel = it->second.lock();
|
||||
if (!channel || channel->isClosed()) {
|
||||
mDataChannels.erase(it);
|
||||
channel = nullptr;
|
||||
}
|
||||
}
|
||||
auto channel = findDataChannel(message->stream);
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
@ -355,12 +419,12 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
||||
message->stream % 2 == remoteParity) {
|
||||
channel =
|
||||
std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
|
||||
channel->onOpen(std::bind(&PeerConnection::triggerDataChannel, this,
|
||||
channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
|
||||
weak_ptr<DataChannel>{channel}));
|
||||
mDataChannels.insert(std::make_pair(message->stream, channel));
|
||||
} else {
|
||||
// Invalid, close the DataChannel by resetting the stream
|
||||
sctpTransport->reset(message->stream);
|
||||
// Invalid, close the DataChannel
|
||||
sctpTransport->close(message->stream);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -369,30 +433,66 @@ void PeerConnection::forwardMessage(message_ptr 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)
|
||||
if (auto channel = findDataChannel(stream))
|
||||
channel->triggerBufferedAmount(amount);
|
||||
}
|
||||
|
||||
shared_ptr<DataChannel> PeerConnection::emplaceDataChannel(Description::Role role,
|
||||
const string &label,
|
||||
const string &protocol,
|
||||
const Reliability &reliability) {
|
||||
// The active side must use streams with even identifiers, whereas the passive side must use
|
||||
// streams with odd identifiers.
|
||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
|
||||
std::unique_lock lock(mDataChannelsMutex); // we are going to emplace
|
||||
unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
|
||||
while (mDataChannels.find(stream) != mDataChannels.end()) {
|
||||
stream += 2;
|
||||
if (stream >= 65535)
|
||||
throw std::runtime_error("Too many DataChannels");
|
||||
}
|
||||
auto channel =
|
||||
std::make_shared<DataChannel>(shared_from_this(), stream, label, protocol, reliability);
|
||||
mDataChannels.emplace(std::make_pair(stream, channel));
|
||||
return channel;
|
||||
}
|
||||
|
||||
shared_ptr<DataChannel> PeerConnection::findDataChannel(uint16_t stream) {
|
||||
std::shared_lock lock(mDataChannelsMutex); // read-only
|
||||
if (auto it = mDataChannels.find(stream); it != mDataChannels.end())
|
||||
if (auto channel = it->second.lock())
|
||||
return channel;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PeerConnection::iterateDataChannels(
|
||||
std::function<void(shared_ptr<DataChannel> channel)> func) {
|
||||
auto it = mDataChannels.begin();
|
||||
while (it != mDataChannels.end()) {
|
||||
auto channel = it->second.lock();
|
||||
if (!channel || channel->isClosed()) {
|
||||
it = mDataChannels.erase(it);
|
||||
continue;
|
||||
// Iterate
|
||||
{
|
||||
std::shared_lock lock(mDataChannelsMutex); // read-only
|
||||
auto it = mDataChannels.begin();
|
||||
while (it != mDataChannels.end()) {
|
||||
auto channel = it->second.lock();
|
||||
if (channel && !channel->isClosed())
|
||||
func(channel);
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
{
|
||||
std::unique_lock lock(mDataChannelsMutex); // we are going to erase
|
||||
auto it = mDataChannels.begin();
|
||||
while (it != mDataChannels.end()) {
|
||||
if (!it->second.lock()) {
|
||||
it = mDataChannels.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
func(channel);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,9 +514,11 @@ void PeerConnection::processLocalDescription(Description description) {
|
||||
if (auto remote = remoteDescription())
|
||||
remoteSctpPort = remote->sctpPort();
|
||||
|
||||
auto certificate = mCertificate.get(); // wait for certificate if not ready
|
||||
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
mLocalDescription.emplace(std::move(description));
|
||||
mLocalDescription->setFingerprint(mCertificate->fingerprint());
|
||||
mLocalDescription->setFingerprint(certificate->fingerprint());
|
||||
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
||||
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||
|
||||
@ -441,21 +543,72 @@ void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
|
||||
mDataChannelCallback(dataChannel);
|
||||
}
|
||||
|
||||
void PeerConnection::changeState(State state) {
|
||||
bool PeerConnection::changeState(State state) {
|
||||
State current;
|
||||
do {
|
||||
current = mState.load();
|
||||
if (current == state || current == State::Destroying)
|
||||
return;
|
||||
if (current == state)
|
||||
return true;
|
||||
if (current == State::Closed)
|
||||
return false;
|
||||
|
||||
} while (!mState.compare_exchange_weak(current, state));
|
||||
|
||||
if (state != State::Destroying)
|
||||
mStateChangeCallback(state);
|
||||
mStateChangeCallback(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PeerConnection::changeGatheringState(GatheringState state) {
|
||||
bool PeerConnection::changeGatheringState(GatheringState state) {
|
||||
if (mGatheringState.exchange(state) != state)
|
||||
mGatheringStateChangeCallback(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PeerConnection::resetCallbacks() {
|
||||
// Unregister all callbacks
|
||||
mDataChannelCallback = nullptr;
|
||||
mLocalDescriptionCallback = nullptr;
|
||||
mLocalCandidateCallback = nullptr;
|
||||
mStateChangeCallback = nullptr;
|
||||
mGatheringStateChangeCallback = nullptr;
|
||||
}
|
||||
|
||||
bool PeerConnection::getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote) {
|
||||
#if not USE_JUICE
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
return iceTransport->getSelectedCandidatePair(local, remote);
|
||||
#else
|
||||
PLOG_WARNING << "getSelectedCandidatePair is not implemented for libjuice";
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void PeerConnection::clearStats() {
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (sctpTransport)
|
||||
return sctpTransport->clearStats();
|
||||
}
|
||||
|
||||
size_t PeerConnection::bytesSent() {
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (sctpTransport)
|
||||
return sctpTransport->bytesSent();
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t PeerConnection::bytesReceived() {
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (sctpTransport)
|
||||
return sctpTransport->bytesReceived();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<std::chrono::milliseconds> PeerConnection::rtt() {
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (sctpTransport)
|
||||
return sctpTransport->rtt();
|
||||
PLOG_WARNING << "Could not load sctpTransport";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
@ -482,9 +635,6 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &st
|
||||
case State::Closed:
|
||||
str = "closed";
|
||||
break;
|
||||
case State::Destroying:
|
||||
str = "destroying";
|
||||
break;
|
||||
default:
|
||||
str = "unknown";
|
||||
break;
|
||||
@ -511,4 +661,3 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::Gathering
|
||||
}
|
||||
return out << str;
|
||||
}
|
||||
|
||||
|
464
src/rtc.cpp
464
src/rtc.cpp
@ -22,195 +22,433 @@
|
||||
|
||||
#include <rtc.h>
|
||||
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <plog/Appenders/ColorConsoleAppender.h>
|
||||
#include <utility>
|
||||
|
||||
using namespace rtc;
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
|
||||
#define CATCH(statement) \
|
||||
try { \
|
||||
statement; \
|
||||
} catch (const std::exception &e) { \
|
||||
PLOG_ERROR << e.what(); \
|
||||
return -1; \
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
|
||||
std::unordered_map<int, shared_ptr<DataChannel>> dataChannelMap;
|
||||
std::unordered_map<int, void *> userPointerMap;
|
||||
std::mutex mutex;
|
||||
int lastId = 0;
|
||||
|
||||
void *getUserPointer(int id) {
|
||||
std::lock_guard lock(mutex);
|
||||
auto it = userPointerMap.find(id);
|
||||
return it != userPointerMap.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
void setUserPointer(int i, void *ptr) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (ptr)
|
||||
userPointerMap.insert(std::make_pair(i, ptr));
|
||||
else
|
||||
userPointerMap.erase(i);
|
||||
}
|
||||
|
||||
void rtcInitLogger(rtc_log_level_t level) { InitLogger(static_cast<LogLevel>(level)); }
|
||||
shared_ptr<PeerConnection> getPeerConnection(int id) {
|
||||
std::lock_guard lock(mutex);
|
||||
auto it = peerConnectionMap.find(id);
|
||||
return it != peerConnectionMap.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount) {
|
||||
Configuration config;
|
||||
for (int i = 0; i < iceServersCount; ++i) {
|
||||
config.iceServers.emplace_back(IceServer(string(iceServers[i])));
|
||||
}
|
||||
shared_ptr<DataChannel> getDataChannel(int id) {
|
||||
std::lock_guard lock(mutex);
|
||||
auto it = dataChannelMap.find(id);
|
||||
return it != dataChannelMap.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
|
||||
std::lock_guard lock(mutex);
|
||||
int pc = ++lastId;
|
||||
peerConnectionMap.emplace(std::make_pair(pc, std::make_shared<PeerConnection>(config)));
|
||||
peerConnectionMap.emplace(std::make_pair(pc, ptr));
|
||||
return pc;
|
||||
}
|
||||
|
||||
void rtcDeletePeerConnection(int pc) { peerConnectionMap.erase(pc); }
|
||||
|
||||
int rtcCreateDataChannel(int pc, const char *label) {
|
||||
auto it = peerConnectionMap.find(pc);
|
||||
if (it == peerConnectionMap.end())
|
||||
return 0;
|
||||
auto dataChannel = it->second->createDataChannel(string(label));
|
||||
int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
|
||||
std::lock_guard lock(mutex);
|
||||
int dc = ++lastId;
|
||||
dataChannelMap.emplace(std::make_pair(dc, dataChannel));
|
||||
dataChannelMap.emplace(std::make_pair(dc, ptr));
|
||||
return dc;
|
||||
}
|
||||
|
||||
void rtcDeleteDataChannel(int dc) { dataChannelMap.erase(dc); }
|
||||
|
||||
void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *)) {
|
||||
auto it = peerConnectionMap.find(pc);
|
||||
if (it == peerConnectionMap.end())
|
||||
return;
|
||||
|
||||
it->second->onDataChannel([pc, dataChannelCallback](std::shared_ptr<DataChannel> dataChannel) {
|
||||
int dc = ++lastId;
|
||||
dataChannelMap.emplace(std::make_pair(dc, dataChannel));
|
||||
dataChannelCallback(dc, getUserPointer(pc));
|
||||
});
|
||||
bool erasePeerConnection(int pc) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (peerConnectionMap.erase(pc) == 0)
|
||||
return false;
|
||||
userPointerMap.erase(pc);
|
||||
return true;
|
||||
}
|
||||
|
||||
void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *,
|
||||
void *)) {
|
||||
auto it = peerConnectionMap.find(pc);
|
||||
if (it == peerConnectionMap.end())
|
||||
return;
|
||||
|
||||
it->second->onLocalDescription([pc, descriptionCallback](const Description &description) {
|
||||
descriptionCallback(string(description).c_str(), description.typeString().c_str(),
|
||||
getUserPointer(pc));
|
||||
});
|
||||
bool eraseDataChannel(int dc) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (dataChannelMap.erase(dc) == 0)
|
||||
return false;
|
||||
userPointerMap.erase(dc);
|
||||
return true;
|
||||
}
|
||||
|
||||
void rtcSetLocalCandidateCallback(int pc,
|
||||
void (*candidateCallback)(const char *, const char *, void *)) {
|
||||
auto it = peerConnectionMap.find(pc);
|
||||
if (it == peerConnectionMap.end())
|
||||
return;
|
||||
} // namespace
|
||||
|
||||
it->second->onLocalCandidate([pc, candidateCallback](const Candidate &candidate) {
|
||||
candidateCallback(candidate.candidate().c_str(), candidate.mid().c_str(),
|
||||
getUserPointer(pc));
|
||||
});
|
||||
void rtcInitLogger(rtcLogLevel level) { InitLogger(static_cast<LogLevel>(level)); }
|
||||
|
||||
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
|
||||
|
||||
int rtcCreatePeerConnection(const rtcConfiguration *config) {
|
||||
Configuration c;
|
||||
for (int i = 0; i < config->iceServersCount; ++i)
|
||||
c.iceServers.emplace_back(string(config->iceServers[i]));
|
||||
|
||||
if (config->portRangeBegin || config->portRangeEnd) {
|
||||
c.portRangeBegin = config->portRangeBegin;
|
||||
c.portRangeEnd = config->portRangeEnd;
|
||||
}
|
||||
|
||||
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
|
||||
}
|
||||
|
||||
void rtcSetStateChangeCallback(int pc, void (*stateCallback)(rtc_state_t state, void *)) {
|
||||
auto it = peerConnectionMap.find(pc);
|
||||
if (it == peerConnectionMap.end())
|
||||
return;
|
||||
int rtcDeletePeerConnection(int pc) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
it->second->onStateChange([pc, stateCallback](PeerConnection::State state) {
|
||||
stateCallback(static_cast<rtc_state_t>(state), getUserPointer(pc));
|
||||
});
|
||||
peerConnection->onDataChannel(nullptr);
|
||||
peerConnection->onLocalDescription(nullptr);
|
||||
peerConnection->onLocalCandidate(nullptr);
|
||||
peerConnection->onStateChange(nullptr);
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
|
||||
erasePeerConnection(pc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rtcSetGatheringStateChangeCallback(int pc,
|
||||
void (*gatheringStateCallback)(rtc_gathering_state_t state,
|
||||
void *)) {
|
||||
auto it = peerConnectionMap.find(pc);
|
||||
if (it == peerConnectionMap.end())
|
||||
return;
|
||||
int rtcCreateDataChannel(int pc, const char *label) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
it->second->onGatheringStateChange(
|
||||
[pc, gatheringStateCallback](PeerConnection::GatheringState state) {
|
||||
gatheringStateCallback(static_cast<rtc_gathering_state_t>(state), getUserPointer(pc));
|
||||
});
|
||||
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
|
||||
void *ptr = getUserPointer(pc);
|
||||
rtcSetUserPointer(dc, ptr);
|
||||
return dc;
|
||||
}
|
||||
|
||||
void rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
||||
auto it = peerConnectionMap.find(pc);
|
||||
if (it == peerConnectionMap.end())
|
||||
return;
|
||||
int rtcDeleteDataChannel(int dc) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
it->second->setRemoteDescription(Description(string(sdp), type ? string(type) : ""));
|
||||
dataChannel->onOpen(nullptr);
|
||||
dataChannel->onClosed(nullptr);
|
||||
dataChannel->onError(nullptr);
|
||||
dataChannel->onMessage(nullptr);
|
||||
dataChannel->onBufferedAmountLow(nullptr);
|
||||
dataChannel->onAvailable(nullptr);
|
||||
|
||||
eraseDataChannel(dc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid) {
|
||||
auto it = peerConnectionMap.find(pc);
|
||||
if (it == peerConnectionMap.end())
|
||||
return;
|
||||
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
it->second->addRemoteCandidate(Candidate(string(candidate), mid ? string(mid) : ""));
|
||||
if (cb)
|
||||
peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
|
||||
int dc = emplaceDataChannel(dataChannel);
|
||||
void *ptr = getUserPointer(pc);
|
||||
rtcSetUserPointer(dc, ptr);
|
||||
cb(dc, ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onDataChannel(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
|
||||
cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalDescription(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
|
||||
cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalCandidate(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
|
||||
cb(static_cast<rtcState>(state), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onStateChange(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
|
||||
cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
|
||||
});
|
||||
else
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
CATCH(peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}))
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (auto addr = peerConnection->localAddress()) {
|
||||
size = std::min(size_t(size - 1), addr->size());
|
||||
std::copy(addr->data(), addr->data() + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (!peerConnection)
|
||||
return -1;
|
||||
|
||||
if (auto addr = peerConnection->remoteAddress()) {
|
||||
size = std::min(size_t(size - 1), addr->size());
|
||||
std::copy(addr->data(), addr->data() + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
||||
auto it = dataChannelMap.find(dc);
|
||||
if (it == dataChannelMap.end())
|
||||
return 0;
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
if (!size)
|
||||
return 0;
|
||||
|
||||
string label = it->second->label();
|
||||
string label = dataChannel->label();
|
||||
size = std::min(size_t(size - 1), label.size());
|
||||
std::copy(label.data(), label.data() + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
void rtcSetOpenCallback(int dc, void (*openCallback)(void *)) {
|
||||
auto it = dataChannelMap.find(dc);
|
||||
if (it == dataChannelMap.end())
|
||||
return;
|
||||
int rtcSetOpenCallback(int dc, openCallbackFunc cb) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
it->second->onOpen([dc, openCallback]() { openCallback(getUserPointer(dc)); });
|
||||
if (cb)
|
||||
dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); });
|
||||
else
|
||||
dataChannel->onOpen(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *)) {
|
||||
auto it = dataChannelMap.find(dc);
|
||||
if (it == dataChannelMap.end())
|
||||
return;
|
||||
int rtcSetClosedCallback(int dc, closedCallbackFunc cb) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
it->second->onError([dc, errorCallback](const string &error) {
|
||||
errorCallback(error.c_str(), getUserPointer(dc));
|
||||
});
|
||||
if (cb)
|
||||
dataChannel->onClosed([dc, cb]() { cb(getUserPointer(dc)); });
|
||||
else
|
||||
dataChannel->onClosed(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *)) {
|
||||
auto it = dataChannelMap.find(dc);
|
||||
if (it == dataChannelMap.end())
|
||||
return;
|
||||
int rtcSetErrorCallback(int dc, errorCallbackFunc cb) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
it->second->onMessage(
|
||||
[dc, messageCallback](const binary &b) {
|
||||
messageCallback(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
|
||||
},
|
||||
[dc, messageCallback](const string &s) {
|
||||
messageCallback(s.c_str(), -1, getUserPointer(dc));
|
||||
});
|
||||
if (cb)
|
||||
dataChannel->onError(
|
||||
[dc, cb](const string &error) { cb(error.c_str(), getUserPointer(dc)); });
|
||||
else
|
||||
dataChannel->onError(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetMessageCallback(int dc, messageCallbackFunc cb) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
dataChannel->onMessage(
|
||||
[dc, cb](const binary &b) {
|
||||
cb(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
|
||||
},
|
||||
[dc, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(dc)); });
|
||||
else
|
||||
dataChannel->onMessage(nullptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSendMessage(int dc, const char *data, int size) {
|
||||
auto it = dataChannelMap.find(dc);
|
||||
if (it == dataChannelMap.end())
|
||||
return 0;
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
if (size >= 0) {
|
||||
auto b = reinterpret_cast<const byte *>(data);
|
||||
it->second->send(b, size);
|
||||
CATCH(dataChannel->send(b, size));
|
||||
return size;
|
||||
} else {
|
||||
string s(data);
|
||||
it->second->send(s);
|
||||
CATCH(dataChannel->send(s));
|
||||
return s.size();
|
||||
}
|
||||
}
|
||||
|
||||
void rtcSetUserPointer(int i, void *ptr) {
|
||||
if (ptr)
|
||||
userPointerMap.insert(std::make_pair(i, ptr));
|
||||
else
|
||||
userPointerMap.erase(i);
|
||||
int rtcGetBufferedAmount(int dc) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
CATCH(return int(dataChannel->bufferedAmount()));
|
||||
}
|
||||
|
||||
int rtcSetBufferedAmountLowThreshold(int dc, int amount) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
CATCH(dataChannel->setBufferedAmountLowThreshold(size_t(amount)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
dataChannel->onBufferedAmountLow([dc, cb]() { cb(getUserPointer(dc)); });
|
||||
else
|
||||
dataChannel->onBufferedAmountLow(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcGetAvailableAmount(int dc) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
CATCH(return int(dataChannel->availableAmount()));
|
||||
}
|
||||
|
||||
int rtcSetAvailableCallback(int dc, availableCallbackFunc cb) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
if (cb)
|
||||
dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); });
|
||||
else
|
||||
dataChannel->onOpen(nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rtcReceiveMessage(int dc, char *buffer, int *size) {
|
||||
auto dataChannel = getDataChannel(dc);
|
||||
if (!dataChannel)
|
||||
return -1;
|
||||
|
||||
if (!size)
|
||||
return -1;
|
||||
|
||||
CATCH({
|
||||
auto message = dataChannel->receive();
|
||||
if (!message)
|
||||
return 0;
|
||||
|
||||
return std::visit( //
|
||||
overloaded{ //
|
||||
[&](const binary &b) {
|
||||
*size = std::min(*size, int(b.size()));
|
||||
auto data = reinterpret_cast<const char *>(b.data());
|
||||
std::copy(data, data + *size, buffer);
|
||||
return *size;
|
||||
},
|
||||
[&](const string &s) {
|
||||
int len = std::min(*size - 1, int(s.size()));
|
||||
if (len >= 0) {
|
||||
std::copy(s.data(), s.data() + len, buffer);
|
||||
buffer[len] = '\0';
|
||||
}
|
||||
*size = -(len + 1);
|
||||
return len + 1;
|
||||
}},
|
||||
*message);
|
||||
});
|
||||
}
|
||||
|
||||
void rtcCleanup() { rtc::Cleanup(); }
|
||||
|
@ -21,9 +21,27 @@
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#ifdef USE_JUICE
|
||||
#ifndef __APPLE__
|
||||
// libjuice enables Linux path MTU discovery or sets the DF flag
|
||||
#define USE_PMTUD 1
|
||||
#else
|
||||
// Setting the DF flag is not available on Mac OS
|
||||
#define USE_PMTUD 0
|
||||
#endif
|
||||
#else
|
||||
#ifdef __linux__
|
||||
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE)
|
||||
// It should be safe to enable discovery for SCTP.
|
||||
#define USE_PMTUD 1
|
||||
#else
|
||||
// Otherwise assume fragmentation
|
||||
#define USE_PMTUD 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace std::chrono;
|
||||
@ -32,30 +50,22 @@ using std::shared_ptr;
|
||||
|
||||
namespace rtc {
|
||||
|
||||
std::mutex SctpTransport::GlobalMutex;
|
||||
int SctpTransport::InstancesCount = 0;
|
||||
|
||||
void SctpTransport::GlobalInit() {
|
||||
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::Init() {
|
||||
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::lock_guard lock(GlobalMutex);
|
||||
if (--InstancesCount == 0) {
|
||||
usrsctp_finish();
|
||||
}
|
||||
void SctpTransport::Cleanup() {
|
||||
while (usrsctp_finish() != 0)
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
|
||||
SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
@ -67,7 +77,6 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
onRecv(recvCallback);
|
||||
|
||||
PLOG_DEBUG << "Initializing SCTP transport";
|
||||
GlobalInit();
|
||||
|
||||
usrsctp_register_address(this);
|
||||
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
|
||||
@ -117,12 +126,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
std::to_string(errno));
|
||||
|
||||
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.
|
||||
#if USE_PMTUD
|
||||
// Enabled SCTP path MTU discovery
|
||||
spp.spp_flags = SPP_PMTUD_ENABLE;
|
||||
#else
|
||||
// Otherwise, fall back to a safe MTU value.
|
||||
// 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
|
||||
@ -140,6 +148,14 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
throw std::runtime_error("Could not set socket option SCTP_INITMSG, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
// Prevent fragmented interleave of messages (i.e. level 0), see RFC 6458 8.1.20.
|
||||
// Unless the user has set the fragmentation interleave level to 0, notifications
|
||||
// may also be interleaved with partially delivered messages.
|
||||
int level = 0;
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &level, sizeof(level)))
|
||||
throw std::runtime_error("Could not disable SCTP fragmented interleave, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
// The default send and receive window size of usrsctp is 256KiB, which is too small for
|
||||
// realistic RTTs, therefore we increase it to 1MiB for better performance.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685
|
||||
@ -151,32 +167,36 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
throw std::runtime_error("Could not set SCTP send buffer size, errno=" +
|
||||
std::to_string(errno));
|
||||
|
||||
registerIncoming();
|
||||
connect();
|
||||
}
|
||||
|
||||
SctpTransport::~SctpTransport() {
|
||||
stop();
|
||||
|
||||
usrsctp_close(mSock);
|
||||
usrsctp_deregister_address(this);
|
||||
if (mSock)
|
||||
usrsctp_close(mSock);
|
||||
|
||||
GlobalCleanup();
|
||||
usrsctp_deregister_address(this);
|
||||
}
|
||||
|
||||
SctpTransport::State SctpTransport::state() const { return mState; }
|
||||
|
||||
void SctpTransport::stop() {
|
||||
Transport::stop();
|
||||
onRecv(nullptr);
|
||||
bool SctpTransport::stop() {
|
||||
if (!Transport::stop())
|
||||
return false;
|
||||
|
||||
if (!mShutdown.exchange(true)) {
|
||||
mSendQueue.stop();
|
||||
flush();
|
||||
shutdown();
|
||||
}
|
||||
mSendQueue.stop();
|
||||
safeFlush();
|
||||
shutdown();
|
||||
onRecv(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SctpTransport::connect() {
|
||||
if (!mSock)
|
||||
return;
|
||||
|
||||
PLOG_DEBUG << "SCTP connect";
|
||||
changeState(State::Connecting);
|
||||
|
||||
@ -200,12 +220,19 @@ void SctpTransport::connect() {
|
||||
}
|
||||
|
||||
void SctpTransport::shutdown() {
|
||||
if (!mSock)
|
||||
return;
|
||||
|
||||
PLOG_DEBUG << "SCTP shutdown";
|
||||
|
||||
if (usrsctp_shutdown(mSock, SHUT_RDWR)) {
|
||||
if (usrsctp_shutdown(mSock, SHUT_RDWR) != 0 && errno != ENOTCONN) {
|
||||
PLOG_WARNING << "SCTP shutdown failed, errno=" << errno;
|
||||
}
|
||||
|
||||
// close() abort the connection when linger is disabled, call it now
|
||||
usrsctp_close(mSock);
|
||||
mSock = nullptr;
|
||||
|
||||
PLOG_INFO << "SCTP disconnected";
|
||||
changeState(State::Disconnected);
|
||||
mWrittenCondition.notify_all();
|
||||
@ -227,31 +254,15 @@ bool SctpTransport::send(message_ptr message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void SctpTransport::close(unsigned int stream) {
|
||||
send(make_message(0, Message::Reset, uint16_t(stream)));
|
||||
}
|
||||
|
||||
void SctpTransport::flush() {
|
||||
std::lock_guard lock(mSendMutex);
|
||||
trySendQueue();
|
||||
}
|
||||
|
||||
void SctpTransport::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
|
||||
@ -261,13 +272,15 @@ void SctpTransport::incoming(message_ptr message) {
|
||||
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || mState != State::Connected; });
|
||||
}
|
||||
|
||||
if (message) {
|
||||
usrsctp_conninput(this, message->data(), message->size(), 0);
|
||||
} else {
|
||||
if (!message) {
|
||||
PLOG_INFO << "SCTP disconnected";
|
||||
changeState(State::Disconnected);
|
||||
recv(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
PLOG_VERBOSE << "Incoming size=" << message->size();
|
||||
usrsctp_conninput(this, message->data(), message->size(), 0);
|
||||
}
|
||||
|
||||
void SctpTransport::changeState(State state) {
|
||||
@ -289,16 +302,9 @@ bool SctpTransport::trySendQueue() {
|
||||
|
||||
bool SctpTransport::trySendMessage(message_ptr message) {
|
||||
// Requires mSendMutex to be locked
|
||||
if (mState != State::Connected)
|
||||
if (!mSock || mState != State::Connected)
|
||||
return false;
|
||||
|
||||
PLOG_VERBOSE << "SCTP try send size=" << message->size();
|
||||
|
||||
// TODO: Implement SCTP ndata specification draft when supported everywhere
|
||||
// See https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-08
|
||||
|
||||
const Reliability reliability = message->reliability ? *message->reliability : Reliability();
|
||||
|
||||
uint32_t ppid;
|
||||
switch (message->type) {
|
||||
case Message::String:
|
||||
@ -307,11 +313,24 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
||||
case Message::Binary:
|
||||
ppid = !message->empty() ? PPID_BINARY : PPID_BINARY_EMPTY;
|
||||
break;
|
||||
default:
|
||||
case Message::Control:
|
||||
ppid = PPID_CONTROL;
|
||||
break;
|
||||
case Message::Reset:
|
||||
sendReset(message->stream);
|
||||
return true;
|
||||
default:
|
||||
// Ignore
|
||||
return true;
|
||||
}
|
||||
|
||||
PLOG_VERBOSE << "SCTP try send size=" << message->size();
|
||||
|
||||
// TODO: Implement SCTP ndata specification draft when supported everywhere
|
||||
// See https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-08
|
||||
|
||||
const Reliability reliability = message->reliability ? *message->reliability : Reliability();
|
||||
|
||||
struct sctp_sendv_spa spa = {};
|
||||
|
||||
// set sndinfo
|
||||
@ -352,8 +371,10 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
||||
|
||||
if (ret >= 0) {
|
||||
PLOG_VERBOSE << "SCTP sent size=" << message->size();
|
||||
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
|
||||
mBytesSent += message->size();
|
||||
return true;
|
||||
} else if (errno == EWOULDBLOCK && errno == EAGAIN) {
|
||||
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
PLOG_VERBOSE << "SCTP sending not possible";
|
||||
return false;
|
||||
} else {
|
||||
@ -365,25 +386,69 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
||||
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)));
|
||||
size_t amount = size_t(std::max(long(it->second) + delta, long(0)));
|
||||
if (amount == 0)
|
||||
mBufferedAmount.erase(it);
|
||||
else
|
||||
it->second = amount;
|
||||
|
||||
mBufferedAmountCallback(streamId, amount);
|
||||
}
|
||||
|
||||
void SctpTransport::sendReset(uint16_t streamId) {
|
||||
// Requires mSendMutex to be locked
|
||||
if (!mSock || state() != State::Connected)
|
||||
return;
|
||||
|
||||
PLOG_DEBUG << "SCTP resetting stream " << streamId;
|
||||
|
||||
using srs_t = struct sctp_reset_streams;
|
||||
const size_t len = sizeof(srs_t) + sizeof(uint16_t);
|
||||
byte buffer[len] = {};
|
||||
srs_t &srs = *reinterpret_cast<srs_t *>(buffer);
|
||||
srs.srs_flags = SCTP_STREAM_RESET_OUTGOING;
|
||||
srs.srs_number_streams = 1;
|
||||
srs.srs_stream_list[0] = streamId;
|
||||
|
||||
mWritten = false;
|
||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &srs, len) == 0) {
|
||||
std::unique_lock lock(mWriteMutex); // locking before setsockopt might deadlock usrsctp...
|
||||
mWrittenCondition.wait_for(lock, 1000ms,
|
||||
[&]() { return mWritten || mState != State::Connected; });
|
||||
} else if (errno == EINVAL) {
|
||||
PLOG_VERBOSE << "SCTP stream " << streamId << " already reset";
|
||||
} else {
|
||||
PLOG_WARNING << "SCTP reset stream " << streamId << " failed, errno=" << errno;
|
||||
}
|
||||
}
|
||||
|
||||
bool SctpTransport::safeFlush() {
|
||||
try {
|
||||
flush();
|
||||
return true;
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "SCTP flush: " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data,
|
||||
size_t len, struct sctp_rcvinfo info, int flags) {
|
||||
try {
|
||||
PLOG_VERBOSE << "Handle recv, len=" << len;
|
||||
if (!len)
|
||||
return -1;
|
||||
|
||||
// This is valid because SCTP_FRAGMENT_INTERLEAVE is set to level 0
|
||||
// so partial messages and notifications may not be interleaved.
|
||||
if (flags & MSG_EOR) {
|
||||
if (!mPartialRecv.empty()) {
|
||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
||||
data = mPartialRecv.data();
|
||||
len = mPartialRecv.size();
|
||||
}
|
||||
// Message is complete, process it
|
||||
// Message/Notification is complete, process it
|
||||
if (flags & MSG_NOTIFICATION)
|
||||
processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
|
||||
else
|
||||
@ -391,7 +456,7 @@ int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, co
|
||||
|
||||
mPartialRecv.clear();
|
||||
} else {
|
||||
// Message is not complete
|
||||
// Message/Notification is not complete
|
||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
@ -402,24 +467,21 @@ int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, co
|
||||
}
|
||||
|
||||
int SctpTransport::handleSend(size_t free) {
|
||||
try {
|
||||
std::lock_guard lock(mSendMutex);
|
||||
trySendQueue();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "SCTP send: " << e.what();
|
||||
return -1;
|
||||
}
|
||||
return 0; // success
|
||||
PLOG_VERBOSE << "Handle send, free=" << free;
|
||||
return safeFlush() ? 0 : -1;
|
||||
}
|
||||
|
||||
int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df) {
|
||||
try {
|
||||
PLOG_VERBOSE << "Handle write, len=" << len;
|
||||
|
||||
std::unique_lock lock(mWriteMutex);
|
||||
if (!outgoing(make_message(data, data + len)))
|
||||
return -1;
|
||||
mWritten = true;
|
||||
mWrittenOnce = true;
|
||||
mWrittenCondition.notify_all();
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "SCTP write: " << e.what();
|
||||
return -1;
|
||||
@ -428,6 +490,8 @@ int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_
|
||||
}
|
||||
|
||||
void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, PayloadId ppid) {
|
||||
PLOG_VERBOSE << "Process data, len=" << len;
|
||||
|
||||
// The usage of the PPIDs "WebRTC String Partial" and "WebRTC Binary Partial" is deprecated.
|
||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.6
|
||||
// We handle them at reception for compatibility reasons but should never send them.
|
||||
@ -442,9 +506,11 @@ void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, Payl
|
||||
|
||||
case PPID_STRING:
|
||||
if (mPartialStringData.empty()) {
|
||||
mBytesReceived += len;
|
||||
recv(make_message(data, data + len, Message::String, sid));
|
||||
} else {
|
||||
mPartialStringData.insert(mPartialStringData.end(), data, data + len);
|
||||
mBytesReceived += mPartialStringData.size();
|
||||
recv(make_message(mPartialStringData.begin(), mPartialStringData.end(), Message::String,
|
||||
sid));
|
||||
mPartialStringData.clear();
|
||||
@ -464,9 +530,11 @@ void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, Payl
|
||||
|
||||
case PPID_BINARY:
|
||||
if (mPartialBinaryData.empty()) {
|
||||
mBytesReceived += len;
|
||||
recv(make_message(data, data + len, Message::Binary, sid));
|
||||
} else {
|
||||
mPartialBinaryData.insert(mPartialBinaryData.end(), data, data + len);
|
||||
mBytesReceived += mPartialStringData.size();
|
||||
recv(make_message(mPartialBinaryData.begin(), mPartialBinaryData.end(), Message::Binary,
|
||||
sid));
|
||||
mPartialBinaryData.clear();
|
||||
@ -488,10 +556,15 @@ void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, Payl
|
||||
}
|
||||
|
||||
void SctpTransport::processNotification(const union sctp_notification *notify, size_t len) {
|
||||
if (len != size_t(notify->sn_header.sn_length))
|
||||
if (len != size_t(notify->sn_header.sn_length)) {
|
||||
PLOG_WARNING << "Invalid notification length";
|
||||
return;
|
||||
}
|
||||
|
||||
switch (notify->sn_header.sn_type) {
|
||||
auto type = notify->sn_header.sn_type;
|
||||
PLOG_VERBOSE << "Process notification, type=" << type;
|
||||
|
||||
switch (type) {
|
||||
case SCTP_ASSOC_CHANGE: {
|
||||
const struct sctp_assoc_change &assoc_change = notify->sn_assoc_change;
|
||||
if (assoc_change.sac_state == SCTP_COMM_UP) {
|
||||
@ -507,13 +580,16 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
|
||||
}
|
||||
mWrittenCondition.notify_all();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SCTP_SENDER_DRY_EVENT: {
|
||||
// It not should be necessary since the send callback should have been called already,
|
||||
// but to be sure, let's try to send now.
|
||||
std::lock_guard lock(mSendMutex);
|
||||
trySendQueue();
|
||||
safeFlush();
|
||||
break;
|
||||
}
|
||||
|
||||
case SCTP_STREAM_RESET_EVENT: {
|
||||
const struct sctp_stream_reset_event &reset_event = notify->sn_strreset_event;
|
||||
const int count = (reset_event.strreset_length - sizeof(reset_event)) / sizeof(uint16_t);
|
||||
@ -522,7 +598,7 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
|
||||
if (flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
uint16_t streamId = reset_event.strreset_stream_list[i];
|
||||
reset(streamId);
|
||||
close(streamId);
|
||||
}
|
||||
}
|
||||
if (flags & SCTP_STREAM_RESET_INCOMING_SSN) {
|
||||
@ -542,6 +618,28 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
|
||||
}
|
||||
}
|
||||
|
||||
void SctpTransport::clearStats() {
|
||||
mBytesReceived = 0;
|
||||
mBytesSent = 0;
|
||||
}
|
||||
|
||||
size_t SctpTransport::bytesSent() { return mBytesSent; }
|
||||
|
||||
size_t SctpTransport::bytesReceived() { return mBytesReceived; }
|
||||
|
||||
std::optional<milliseconds> SctpTransport::rtt() {
|
||||
if (!mSock || state() != State::Connected)
|
||||
return nullopt;
|
||||
|
||||
struct sctp_status status = {};
|
||||
socklen_t len = sizeof(status);
|
||||
if (usrsctp_getsockopt(mSock, IPPROTO_SCTP, SCTP_STATUS, &status, &len)) {
|
||||
PLOG_WARNING << "Could not read SCTP_STATUS";
|
||||
return nullopt;
|
||||
}
|
||||
return milliseconds(status.sstat_primary.spinfo_srtt);
|
||||
}
|
||||
|
||||
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
|
||||
size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) {
|
||||
int ret = static_cast<SctpTransport *>(ptr)->handleRecv(
|
||||
|
@ -29,15 +29,15 @@
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "usrsctp.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
class SctpTransport : public Transport {
|
||||
public:
|
||||
static void Init();
|
||||
static void Cleanup();
|
||||
|
||||
enum class State { Disconnected, Connecting, Connected, Failed };
|
||||
|
||||
using amount_callback = std::function<void(uint16_t streamId, size_t amount)>;
|
||||
@ -49,10 +49,16 @@ public:
|
||||
|
||||
State state() const;
|
||||
|
||||
void stop() override;
|
||||
bool stop() override;
|
||||
bool send(message_ptr message) override; // false if buffered
|
||||
void close(unsigned int stream);
|
||||
void flush();
|
||||
void reset(unsigned int stream);
|
||||
|
||||
// Stats
|
||||
void clearStats();
|
||||
size_t bytesSent();
|
||||
size_t bytesReceived();
|
||||
std::optional<std::chrono::milliseconds> rtt();
|
||||
|
||||
private:
|
||||
// Order seems wrong but these are the actual values
|
||||
@ -75,6 +81,8 @@ private:
|
||||
bool trySendQueue();
|
||||
bool trySendMessage(message_ptr message);
|
||||
void updateBufferedAmount(uint16_t streamId, long delta);
|
||||
void sendReset(uint16_t streamId);
|
||||
bool safeFlush();
|
||||
|
||||
int handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data, size_t len,
|
||||
struct sctp_rcvinfo recv_info, int flags);
|
||||
@ -92,28 +100,23 @@ private:
|
||||
std::map<uint16_t, size_t> mBufferedAmount;
|
||||
amount_callback mBufferedAmountCallback;
|
||||
|
||||
std::recursive_mutex mWriteMutex;
|
||||
std::condition_variable_any mWrittenCondition;
|
||||
bool mWritten = false;
|
||||
std::mutex mWriteMutex;
|
||||
std::condition_variable mWrittenCondition;
|
||||
std::atomic<bool> mWritten = false; // written outside lock
|
||||
bool mWrittenOnce = false;
|
||||
|
||||
std::atomic<bool> mShutdown = false;
|
||||
|
||||
state_callback mStateChangeCallback;
|
||||
std::atomic<State> mState;
|
||||
|
||||
// Stats
|
||||
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
|
||||
|
||||
binary mPartialRecv, mPartialStringData, mPartialBinaryData;
|
||||
|
||||
static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
||||
struct sctp_rcvinfo recv_info, int flags, void *user_data);
|
||||
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();
|
||||
|
||||
static std::mutex GlobalMutex;
|
||||
static int InstancesCount;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -32,25 +32,30 @@ using namespace std::placeholders;
|
||||
|
||||
class Transport {
|
||||
public:
|
||||
Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(std::move(lower)) {
|
||||
Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(std::move(lower)) {}
|
||||
virtual ~Transport() {
|
||||
stop();
|
||||
if (mLower)
|
||||
mLower->onRecv(nullptr); // doing it on stop could cause a deadlock
|
||||
}
|
||||
|
||||
virtual bool stop() {
|
||||
return !mShutdown.exchange(true);
|
||||
}
|
||||
|
||||
void registerIncoming() {
|
||||
if (mLower)
|
||||
mLower->onRecv(std::bind(&Transport::incoming, this, _1));
|
||||
}
|
||||
virtual ~Transport() { stop(); }
|
||||
|
||||
virtual void stop() {
|
||||
if (mLower)
|
||||
mLower->onRecv(nullptr);
|
||||
}
|
||||
|
||||
virtual bool send(message_ptr message) = 0;
|
||||
|
||||
void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }
|
||||
|
||||
virtual bool send(message_ptr message) { return outgoing(message); }
|
||||
|
||||
protected:
|
||||
void recv(message_ptr message) { mRecvCallback(message); }
|
||||
|
||||
virtual void incoming(message_ptr message) = 0;
|
||||
virtual void incoming(message_ptr message) { recv(message); }
|
||||
virtual bool outgoing(message_ptr message) {
|
||||
if (mLower)
|
||||
return mLower->send(message);
|
||||
@ -61,6 +66,7 @@ protected:
|
||||
private:
|
||||
std::shared_ptr<Transport> mLower;
|
||||
synchronized_callback<message_ptr> mRecvCallback;
|
||||
std::atomic<bool> mShutdown = false;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
220
test/capi.cpp
Normal file
220
test/capi.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <rtc/rtc.h>
|
||||
|
||||
#include <cstdbool>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#else
|
||||
#include <unistd.h> // for sleep
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
Peer *peer1 = NULL;
|
||||
Peer *peer2 = NULL;
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcSetRemoteDescription(other->pc, sdp, type);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcAddRemoteCandidate(other->pc, cand, mid);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
printf("DataChannel %d: Open\n", peer == peer1 ? 1 : 2);
|
||||
|
||||
const char *message = peer == peer1 ? "Hello from 1" : "Hello from 2";
|
||||
rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %d: %s\n", peer == peer1 ? 1 : 2, message);
|
||||
} else {
|
||||
printf("Message %d: [binary of size %d]\n", peer == peer1 ? 1 : 2, size);
|
||||
}
|
||||
}
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
rtcSetClosedCallback(dc, closedCallback);
|
||||
rtcSetMessageCallback(dc, messageCallback);
|
||||
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %d: Received with label \"%s\"\n", peer == peer1 ? 1 : 2, buffer);
|
||||
|
||||
const char *message = peer == peer1 ? "Hello from 1" : "Hello from 2";
|
||||
rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
|
||||
}
|
||||
|
||||
static Peer *createPeer(const rtcConfiguration *config) {
|
||||
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
||||
if (!peer)
|
||||
return nullptr;
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(config);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
int test_capi_main() {
|
||||
int attempts;
|
||||
|
||||
rtcInitLogger(RTC_LOG_DEBUG);
|
||||
|
||||
// Create peer 1
|
||||
rtcConfiguration config1;
|
||||
memset(&config1, 0, sizeof(config1));
|
||||
// STUN server example
|
||||
// const char *iceServers[1] = {"stun:stun.l.google.com:19302"};
|
||||
// config1.iceServers = iceServers;
|
||||
// config1.iceServersCount = 1;
|
||||
|
||||
peer1 = createPeer(&config1);
|
||||
if (!peer1)
|
||||
goto error;
|
||||
|
||||
// Create peer 2
|
||||
rtcConfiguration config2;
|
||||
memset(&config2, 0, sizeof(config2));
|
||||
// STUN server example
|
||||
// config2.iceServers = iceServers;
|
||||
// config2.iceServersCount = 1;
|
||||
// Port range example
|
||||
config2.portRangeBegin = 5000;
|
||||
config2.portRangeEnd = 6000;
|
||||
|
||||
peer2 = createPeer(&config2);
|
||||
if (!peer2)
|
||||
goto error;
|
||||
|
||||
// Peer 1: Create data channel
|
||||
peer1->dc = rtcCreateDataChannel(peer1->pc, "test");
|
||||
rtcSetOpenCallback(peer1->dc, openCallback);
|
||||
rtcSetClosedCallback(peer1->dc, closedCallback);
|
||||
rtcSetMessageCallback(peer1->dc, messageCallback);
|
||||
|
||||
attempts = 10;
|
||||
while (!peer2->connected && !peer1->connected && attempts--)
|
||||
sleep(1);
|
||||
|
||||
if (peer1->state != RTC_CONNECTED || peer2->state != RTC_CONNECTED) {
|
||||
fprintf(stderr, "PeerConnection is not connected\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!peer1->connected || !peer2->connected) {
|
||||
fprintf(stderr, "DataChannel is not connected\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer1->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer1->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
if (rtcGetLocalAddress(peer2->pc, buffer, 256) >= 0)
|
||||
printf("Local address 2: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer2->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 2: %s\n", buffer);
|
||||
|
||||
deletePeer(peer1);
|
||||
sleep(1);
|
||||
deletePeer(peer2);
|
||||
sleep(1);
|
||||
|
||||
// You may call rtcCleanup() when finished to free static resources
|
||||
rtcCleanup();
|
||||
sleep(1);
|
||||
|
||||
printf("Success\n");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
deletePeer(peer1);
|
||||
deletePeer(peer2);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
void test_capi() {
|
||||
if (test_capi_main())
|
||||
throw std::runtime_error("Connection failed");
|
||||
}
|
148
test/connectivity.cpp
Normal file
148
test/connectivity.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
|
||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
void test_connectivity() {
|
||||
InitLogger(LogLevel::Debug);
|
||||
|
||||
Configuration config1;
|
||||
// STUN server example
|
||||
// config1.iceServers.emplace_back("stun:stun.l.google.com:19302");
|
||||
|
||||
auto pc1 = std::make_shared<PeerConnection>(config1);
|
||||
|
||||
Configuration config2;
|
||||
// STUN server example
|
||||
// config2.iceServers.emplace_back("stun:stun.l.google.com:19302");
|
||||
// Port range example
|
||||
config2.portRangeBegin = 5000;
|
||||
config2.portRangeEnd = 6000;
|
||||
|
||||
auto pc2 = std::make_shared<PeerConnection>(config2);
|
||||
|
||||
pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](const Description &sdp) {
|
||||
auto pc2 = wpc2.lock();
|
||||
if (!pc2)
|
||||
return;
|
||||
cout << "Description 1: " << sdp << endl;
|
||||
pc2->setRemoteDescription(sdp);
|
||||
});
|
||||
|
||||
pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](const Candidate &candidate) {
|
||||
auto pc2 = wpc2.lock();
|
||||
if (!pc2)
|
||||
return;
|
||||
cout << "Candidate 1: " << candidate << endl;
|
||||
pc2->addRemoteCandidate(candidate);
|
||||
});
|
||||
|
||||
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
||||
pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "Gathering state 1: " << state << endl;
|
||||
});
|
||||
|
||||
pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](const Description &sdp) {
|
||||
auto pc1 = wpc1.lock();
|
||||
if (!pc1)
|
||||
return;
|
||||
cout << "Description 2: " << sdp << endl;
|
||||
pc1->setRemoteDescription(sdp);
|
||||
});
|
||||
|
||||
pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](const Candidate &candidate) {
|
||||
auto pc1 = wpc1.lock();
|
||||
if (!pc1)
|
||||
return;
|
||||
cout << "Candidate 2: " << candidate << endl;
|
||||
pc1->addRemoteCandidate(candidate);
|
||||
});
|
||||
|
||||
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
||||
pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "Gathering state 2: " << state << endl;
|
||||
});
|
||||
|
||||
shared_ptr<DataChannel> dc2;
|
||||
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
||||
cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
|
||||
dc2 = dc;
|
||||
dc2->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Message 2: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
dc2->send("Hello from 2");
|
||||
});
|
||||
|
||||
auto dc1 = pc1->createDataChannel("test");
|
||||
dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() {
|
||||
auto dc1 = wdc1.lock();
|
||||
if (!dc1)
|
||||
return;
|
||||
cout << "DataChannel 1: Open" << endl;
|
||||
dc1->send("Hello from 1");
|
||||
});
|
||||
dc1->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Message 1: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
|
||||
int attempts = 10;
|
||||
while ((!dc2 || !dc2->isOpen() || !dc1->isOpen()) && attempts--)
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
if (pc1->state() != PeerConnection::State::Connected &&
|
||||
pc2->state() != PeerConnection::State::Connected)
|
||||
throw runtime_error("PeerConnection is not connected");
|
||||
|
||||
if (!dc1->isOpen() || !dc2->isOpen())
|
||||
throw runtime_error("DataChannel is not open");
|
||||
|
||||
if (auto addr = pc1->localAddress())
|
||||
cout << "Local address 1: " << *addr << endl;
|
||||
if (auto addr = pc1->remoteAddress())
|
||||
cout << "Remote address 1: " << *addr << endl;
|
||||
if (auto addr = pc2->localAddress())
|
||||
cout << "Local address 2: " << *addr << endl;
|
||||
if (auto addr = pc2->remoteAddress())
|
||||
cout << "Remote address 2: " << *addr << endl;
|
||||
|
||||
// Delay close of peer 2 to check closing works properly
|
||||
pc1->close();
|
||||
this_thread::sleep_for(1s);
|
||||
pc2->close();
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
// You may call rtc::Cleanup() when finished to free static resources
|
||||
rtc::Cleanup();
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
cout << "Success" << endl;
|
||||
}
|
127
test/main.cpp
127
test/main.cpp
@ -16,120 +16,29 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "rtc/rtc.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
|
||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||
void test_connectivity();
|
||||
void test_capi();
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
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([wpc2 = make_weak_ptr(pc2)](const Description &sdp) {
|
||||
auto pc2 = wpc2.lock();
|
||||
if (!pc2)
|
||||
return;
|
||||
cout << "Description 1: " << sdp << endl;
|
||||
pc2->setRemoteDescription(sdp);
|
||||
});
|
||||
|
||||
pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](const Candidate &candidate) {
|
||||
auto pc2 = wpc2.lock();
|
||||
if (!pc2)
|
||||
return;
|
||||
cout << "Candidate 1: " << candidate << endl;
|
||||
pc2->addRemoteCandidate(candidate);
|
||||
});
|
||||
|
||||
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
||||
pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "Gathering state 1: " << state << endl;
|
||||
});
|
||||
|
||||
pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](const Description &sdp) {
|
||||
auto pc1 = wpc1.lock();
|
||||
if (!pc1)
|
||||
return;
|
||||
cout << "Description 2: " << sdp << endl;
|
||||
pc1->setRemoteDescription(sdp);
|
||||
});
|
||||
|
||||
pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](const Candidate &candidate) {
|
||||
auto pc1 = wpc1.lock();
|
||||
if (!pc1)
|
||||
return;
|
||||
cout << "Candidate 2: " << candidate << endl;
|
||||
pc1->addRemoteCandidate(candidate);
|
||||
});
|
||||
|
||||
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
||||
pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "Gathering state 2: " << state << endl;
|
||||
});
|
||||
|
||||
shared_ptr<DataChannel> dc2;
|
||||
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
||||
cout << "Got a DataChannel with label: " << dc->label() << endl;
|
||||
dc2 = dc;
|
||||
dc2->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Received 2: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
dc2->send("Hello from 2");
|
||||
});
|
||||
|
||||
auto dc1 = pc1->createDataChannel("test");
|
||||
dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() {
|
||||
auto dc1 = wdc1.lock();
|
||||
if (!dc1)
|
||||
return;
|
||||
cout << "DataChannel open: " << dc1->label() << endl;
|
||||
dc1->send("Hello from 1");
|
||||
});
|
||||
dc1->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Received 1: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
|
||||
this_thread::sleep_for(3s);
|
||||
|
||||
if (auto addr = pc1->localAddress())
|
||||
cout << "Local address 1: " << *addr << endl;
|
||||
if (auto addr = pc1->remoteAddress())
|
||||
cout << "Remote address 1: " << *addr << endl;
|
||||
if (auto addr = pc2->localAddress())
|
||||
cout << "Local address 2: " << *addr << endl;
|
||||
if (auto addr = pc2->remoteAddress())
|
||||
cout << "Remote address 2: " << *addr << endl;
|
||||
|
||||
if (dc1->isOpen() && dc2->isOpen()) {
|
||||
pc1->close();
|
||||
pc2->close();
|
||||
|
||||
cout << "Success" << endl;
|
||||
return 0;
|
||||
} else {
|
||||
cout << "Failure" << endl;
|
||||
return 1;
|
||||
try {
|
||||
std::cout << "*** Running connectivity test..." << std::endl;
|
||||
test_connectivity();
|
||||
std::cout << "*** Finished connectivity test" << std::endl;
|
||||
} catch (const exception &e) {
|
||||
std::cerr << "Connectivity test failed: " << e.what() << endl;
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
std::cout << "*** Running C API test..." << std::endl;
|
||||
test_capi();
|
||||
std::cout << "*** Finished C API test" << std::endl;
|
||||
} catch (const exception &e) {
|
||||
std::cerr << "C API test failed: " << e.what() << endl;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
@ -32,41 +33,36 @@ int main(int argc, char **argv) {
|
||||
|
||||
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->onLocalDescription([](const Description &description) {
|
||||
cout << "Local Description (Paste this to the other peer):" << endl;
|
||||
cout << string(description) << endl;
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
||||
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl;
|
||||
cout << "Local Candidate (Paste this to the other peer after the local description):"
|
||||
<< endl;
|
||||
cout << string(candidate) << endl << endl;
|
||||
});
|
||||
|
||||
pc->onStateChange(
|
||||
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
|
||||
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
|
||||
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "[ Gathering State: " << state << " ]" << endl;
|
||||
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;
|
||||
cout << "[Got a DataChannel with label: " << _dc->label() << "]" << endl;
|
||||
dc = _dc;
|
||||
|
||||
dc->onClosed([&]() { cout << "[ DataChannel closed: " << 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;
|
||||
cout << "[Received message: " << get<string>(message) << "]" << endl;
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -74,67 +70,86 @@ int main(int argc, char **argv) {
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
cout << endl
|
||||
<< "**********************************************************************************"
|
||||
"*****"
|
||||
<< endl
|
||||
<< "*************************************************************************" << endl
|
||||
<< "* 0: Exit /"
|
||||
<< " 1: Enter Description /"
|
||||
<< " 2: Enter Candidate /"
|
||||
<< " 3: Send Message *" << endl
|
||||
<< " [Command]: ";
|
||||
<< " 1: Enter remote description /"
|
||||
<< " 2: Enter remote candidate /"
|
||||
<< " 3: Send message /"
|
||||
<< " 4: Print Connection Info *" << endl
|
||||
<< "[Command]: ";
|
||||
|
||||
int command;
|
||||
std::string sdp, candidate, message;
|
||||
const char *a;
|
||||
std::unique_ptr<Candidate> candidatePtr;
|
||||
std::unique_ptr<Description> descPtr;
|
||||
int command = -1;
|
||||
cin >> command;
|
||||
cin.ignore();
|
||||
|
||||
switch (command) {
|
||||
case 0:
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
}
|
||||
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);
|
||||
cout << "[Description]: ";
|
||||
string sdp, line;
|
||||
while (getline(cin, line) && !line.empty()) {
|
||||
sdp += line;
|
||||
sdp += "\r\n";
|
||||
}
|
||||
std::cout << sdp;
|
||||
pc->setRemoteDescription(sdp);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
cout << "[Candidate]: ";
|
||||
candidate = "";
|
||||
while (candidate.length() == 0)
|
||||
getline(cin, candidate);
|
||||
|
||||
candidatePtr = std::make_unique<Candidate>(candidate);
|
||||
pc->addRemoteCandidate(*candidatePtr);
|
||||
string candidate;
|
||||
getline(cin, candidate);
|
||||
pc->addRemoteCandidate(candidate);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!dc || !dc->isOpen()) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
cout << "[Message]: ";
|
||||
message = "";
|
||||
while (message.length() == 0)
|
||||
getline(cin, message);
|
||||
string message;
|
||||
getline(cin, message);
|
||||
dc->send(message);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if (!dc || !dc->isOpen()) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
cout << rtt.value().count();
|
||||
else
|
||||
cout << "null";
|
||||
cout << " ms";
|
||||
} else
|
||||
cout << "Could not get Candidate Pair Info" << endl;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
cout << "** Invalid Command ** ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dc)
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
@ -32,106 +33,123 @@ int main(int argc, char **argv) {
|
||||
|
||||
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->onLocalDescription([](const Description &description) {
|
||||
cout << "Local Description (Paste this to the other peer):" << endl;
|
||||
cout << string(description) << endl;
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
||||
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl;
|
||||
cout << "Local Candidate (Paste this to the other peer after the local description):"
|
||||
<< endl;
|
||||
cout << string(candidate) << endl << endl;
|
||||
});
|
||||
|
||||
pc->onStateChange(
|
||||
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
|
||||
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
|
||||
|
||||
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "[ Gathering State: " << state << " ]" << endl;
|
||||
cout << "[Gathering State: " << state << "]" << endl;
|
||||
});
|
||||
|
||||
auto dc = pc->createDataChannel("test");
|
||||
dc->onOpen([&]() { cout << "[ DataChannel open: " << dc->label() << " ]" << endl; });
|
||||
auto dc = pc->createDataChannel("test"); // this is the offerer, so create a data channel
|
||||
|
||||
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; });
|
||||
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;
|
||||
cout << "[Received: " << get<string>(message) << "]" << endl;
|
||||
}
|
||||
});
|
||||
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
cout << endl
|
||||
<< "**********************************************************************************"
|
||||
"*****"
|
||||
<< endl
|
||||
<< "*************************************************************************" << endl
|
||||
<< "* 0: Exit /"
|
||||
<< " 1: Enter Description /"
|
||||
<< " 2: Enter Candidate /"
|
||||
<< " 3: Send Message *" << endl
|
||||
<< " [Command]: ";
|
||||
<< " 1: Enter remote description /"
|
||||
<< " 2: Enter remote candidate /"
|
||||
<< " 3: Send message /"
|
||||
<< " 4: Print Connection Info *" << endl
|
||||
<< "[Command]: ";
|
||||
|
||||
int command;
|
||||
std::string sdp, candidate, message;
|
||||
const char *a;
|
||||
std::unique_ptr<Candidate> candidatePtr;
|
||||
std::unique_ptr<Description> descPtr;
|
||||
int command = -1;
|
||||
cin >> command;
|
||||
cin.ignore();
|
||||
|
||||
switch (command) {
|
||||
case 0:
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
}
|
||||
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);
|
||||
cout << "[Description]: ";
|
||||
string sdp, line;
|
||||
while (getline(cin, line) && !line.empty()) {
|
||||
sdp += line;
|
||||
sdp += "\r\n";
|
||||
}
|
||||
pc->setRemoteDescription(sdp);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
cout << "[Candidate]: ";
|
||||
candidate = "";
|
||||
while (candidate.length() == 0)
|
||||
getline(cin, candidate);
|
||||
|
||||
candidatePtr = std::make_unique<Candidate>(candidate);
|
||||
pc->addRemoteCandidate(*candidatePtr);
|
||||
string candidate;
|
||||
getline(cin, candidate);
|
||||
pc->addRemoteCandidate(candidate);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!dc->isOpen()) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
cout << "[Message]: ";
|
||||
message = "";
|
||||
while (message.length() == 0)
|
||||
getline(cin, message);
|
||||
string message;
|
||||
getline(cin, message);
|
||||
dc->send(message);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if (!dc || !dc->isOpen()) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
cout << rtt.value().count();
|
||||
else
|
||||
cout << "null";
|
||||
cout << " ms";
|
||||
} else
|
||||
cout << "Could not get Candidate Pair Info" << endl;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
cout << "** Invalid Command ** ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dc)
|
||||
|
12
test/p2p_c_version/CMakeLists.txt
Normal file
12
test/p2p_c_version/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.5.1)
|
||||
project(offerer C)
|
||||
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_FLAGS "-Wall -g -O2")
|
||||
|
||||
add_executable(offerer offerer.c)
|
||||
target_link_libraries(offerer datachannel)
|
||||
|
||||
add_executable(answerer answerer.c)
|
||||
target_link_libraries(answerer datachannel)
|
326
test/p2p_c_version/answerer.c
Normal file
326
test/p2p_c_version/answerer.c
Normal file
@ -0,0 +1,326 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
* Copyright (c) 2020 Stevedan Ogochukwu Omodolor
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <rtc/rtc.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h> // for sleep
|
||||
#include <ctype.h>
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
Peer *peer = NULL;
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr);
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
|
||||
static void closedCallback(void *ptr);
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
int all_space(const char *str);
|
||||
char* state_print(rtcState state);
|
||||
char* rtcGatheringState_print(rtcState state);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
rtcInitLogger(RTC_LOG_DEBUG);
|
||||
|
||||
// Create peer
|
||||
rtcConfiguration config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
||||
if (!peer) {
|
||||
|
||||
printf("Error allocating memory for peer\n");
|
||||
deletePeer(peer);
|
||||
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
|
||||
rtcSetUserPointer(peer->dc, NULL);
|
||||
rtcSetDataChannelCallback(peer->pc, dataChannelCallback);
|
||||
|
||||
|
||||
|
||||
|
||||
bool exit = false;
|
||||
|
||||
while (!exit) {
|
||||
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
|
||||
// << endl
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
// int check_scan
|
||||
if (scanf("%d", &command)) {
|
||||
|
||||
}else {
|
||||
break;
|
||||
}
|
||||
while ((c = getchar()) != '\n' && c != EOF) { }
|
||||
|
||||
fflush(stdin);
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
|
||||
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char*) malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
||||
strcat(sdp, line);
|
||||
|
||||
}
|
||||
printf("%s\n",sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "offer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char* candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
|
||||
if(getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char* message = NULL;
|
||||
size_t message_size = 0;
|
||||
|
||||
if(getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "answerer", cand);
|
||||
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "answerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
|
||||
|
||||
}
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "answerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "answerer", size);
|
||||
}
|
||||
}
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
rtcSetClosedCallback(dc, closedCallback);
|
||||
rtcSetMessageCallback(dc, messageCallback);
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n", "answerer", buffer);
|
||||
|
||||
|
||||
}
|
||||
int all_space(const char *str) {
|
||||
while (*str) {
|
||||
if (!isspace(*str++)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* state_print(rtcState state) {
|
||||
char *str = NULL;
|
||||
switch (state) {
|
||||
case RTC_NEW:
|
||||
str = "RTC_NEW";
|
||||
break;
|
||||
case RTC_CONNECTING:
|
||||
str = "RTC_CONNECTING";
|
||||
break;
|
||||
case RTC_CONNECTED:
|
||||
str = "RTC_CONNECTED";
|
||||
break;
|
||||
case RTC_DISCONNECTED:
|
||||
str = "RTC_DISCONNECTED";
|
||||
break;
|
||||
case RTC_FAILED:
|
||||
str = "RTC_FAILED";
|
||||
break;
|
||||
case RTC_CLOSED:
|
||||
str = "RTC_CLOSED";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
|
||||
}
|
||||
|
||||
char* rtcGatheringState_print(rtcState state) {
|
||||
char* str = NULL;
|
||||
switch (state) {
|
||||
case RTC_GATHERING_NEW:
|
||||
str = "RTC_GATHERING_NEW";
|
||||
break;
|
||||
case RTC_GATHERING_INPROGRESS:
|
||||
str = "RTC_GATHERING_INPROGRESS";
|
||||
break;
|
||||
case RTC_GATHERING_COMPLETE:
|
||||
str = "RTC_GATHERING_COMPLETE";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
|
||||
}
|
334
test/p2p_c_version/offerer.c
Normal file
334
test/p2p_c_version/offerer.c
Normal file
@ -0,0 +1,334 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
* Copyright (c) 2020 Stevedan Ogochukwu Omodolor
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <rtc/rtc.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h> // for sleep
|
||||
#include <ctype.h>
|
||||
|
||||
char* state_print(rtcState state);
|
||||
char* rtcGatheringState_print(rtcState state);
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
Peer *peer = NULL;
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
|
||||
static void openCallback(void *ptr);
|
||||
|
||||
static void closedCallback(void *ptr);
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
int all_space(const char *str);
|
||||
|
||||
int main(int argc, char **argv){
|
||||
rtcInitLogger(RTC_LOG_DEBUG);
|
||||
|
||||
// Create peer
|
||||
rtcConfiguration config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
Peer *peer = (Peer *)malloc(sizeof(Peer));
|
||||
if (!peer) {
|
||||
|
||||
printf("Error allocating memory for peer\n");
|
||||
deletePeer(peer);
|
||||
|
||||
}
|
||||
memset(peer, 0, sizeof(Peer));
|
||||
|
||||
printf("Peer created\n");
|
||||
|
||||
// Create peer connection
|
||||
peer->pc = rtcCreatePeerConnection(&config);
|
||||
rtcSetUserPointer(peer->pc, peer);
|
||||
rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
|
||||
rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
|
||||
rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
|
||||
rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
|
||||
|
||||
// Since this is the offere, we will create a datachannel
|
||||
peer->dc = rtcCreateDataChannel(peer->pc, "test");
|
||||
|
||||
rtcSetOpenCallback(peer->dc, openCallback);
|
||||
|
||||
|
||||
rtcSetClosedCallback(peer->dc, closedCallback);
|
||||
|
||||
rtcSetMessageCallback(peer->dc, messageCallback);
|
||||
|
||||
|
||||
sleep(1);
|
||||
|
||||
bool exit = false;
|
||||
|
||||
while (!exit) {
|
||||
|
||||
printf("\n");
|
||||
printf("***************************************************************************************\n");
|
||||
|
||||
// << endl
|
||||
printf("* 0: Exit /"
|
||||
" 1: Enter remote description /"
|
||||
" 2: Enter remote candidate /"
|
||||
" 3: Send message /"
|
||||
" 4: Print Connection Info *\n"
|
||||
"[Command]: ");
|
||||
|
||||
int command = -1;
|
||||
int c;
|
||||
if (scanf("%d", &command)) {
|
||||
|
||||
}else {
|
||||
break;
|
||||
}
|
||||
while ((c = getchar()) != '\n' && c != EOF) { }
|
||||
fflush(stdin);
|
||||
|
||||
switch (command) {
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
printf("[Description]: ");
|
||||
|
||||
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
size_t read = 0;
|
||||
char *sdp = (char*) malloc(sizeof(char));
|
||||
while ((read = getline(&line, &len, stdin)) != -1 && !all_space(line)) {
|
||||
sdp = (char*) realloc (sdp,(strlen(sdp)+1) +strlen(line)+1);
|
||||
strcat(sdp, line);
|
||||
|
||||
}
|
||||
printf("%s\n",sdp);
|
||||
rtcSetRemoteDescription(peer->pc, sdp, "answer");
|
||||
free(sdp);
|
||||
free(line);
|
||||
break;
|
||||
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
printf("[Candidate]: ");
|
||||
char* candidate = NULL;
|
||||
size_t candidate_size = 0;
|
||||
if(getline(&candidate, &candidate_size, stdin)) {
|
||||
rtcAddRemoteCandidate(peer->pc, candidate, "0");
|
||||
free(candidate);
|
||||
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
printf("[Message]: ");
|
||||
char* message = NULL;
|
||||
size_t message_size = 0;
|
||||
if(getline(&message, &message_size, stdin)) {
|
||||
rtcSendMessage(peer->dc, message, -1);
|
||||
free(message);
|
||||
}else {
|
||||
printf("Error reading line\n");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Connection Info
|
||||
if(!peer->connected) {
|
||||
printf("** Channel is not Open **");
|
||||
break;
|
||||
}
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
else
|
||||
printf("Could not get Candidate Pair Info\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("** Invalid Command **");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletePeer(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "offerer", cand);
|
||||
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "offerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
char buffer[256];
|
||||
if (rtcGetDataChannelLabel(peer->dc, buffer, 256) >= 0)
|
||||
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "offerer", message);
|
||||
} else {
|
||||
printf("Message %s: [binary of size %d]\n", "offerer", size);
|
||||
}
|
||||
}
|
||||
static void deletePeer(Peer *peer) {
|
||||
if (peer) {
|
||||
if (peer->dc)
|
||||
rtcDeleteDataChannel(peer->dc);
|
||||
if (peer->pc)
|
||||
rtcDeletePeerConnection(peer->pc);
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int all_space(const char *str) {
|
||||
while (*str) {
|
||||
if (!isspace(*str++)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* state_print(rtcState state) {
|
||||
char *str = NULL;
|
||||
switch (state) {
|
||||
case RTC_NEW:
|
||||
str = "RTC_NEW";
|
||||
break;
|
||||
case RTC_CONNECTING:
|
||||
str = "RTC_CONNECTING";
|
||||
break;
|
||||
case RTC_CONNECTED:
|
||||
str = "RTC_CONNECTED";
|
||||
break;
|
||||
case RTC_DISCONNECTED:
|
||||
str = "RTC_DISCONNECTED";
|
||||
break;
|
||||
case RTC_FAILED:
|
||||
str = "RTC_FAILED";
|
||||
break;
|
||||
case RTC_CLOSED:
|
||||
str = "RTC_CLOSED";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
|
||||
}
|
||||
|
||||
char* rtcGatheringState_print(rtcState state) {
|
||||
char* str = NULL;
|
||||
switch (state) {
|
||||
case RTC_GATHERING_NEW:
|
||||
str = "RTC_GATHERING_NEW";
|
||||
break;
|
||||
case RTC_GATHERING_INPROGRESS:
|
||||
str = "RTC_GATHERING_INPROGRESS";
|
||||
break;
|
||||
case RTC_GATHERING_COMPLETE:
|
||||
str = "RTC_GATHERING_COMPLETE";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
|
||||
}
|
Reference in New Issue
Block a user