mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
140 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 |
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)
|
cmake_minimum_required (VERSION 3.7)
|
||||||
project (libdatachannel
|
project (libdatachannel
|
||||||
DESCRIPTION "WebRTC DataChannels Library"
|
DESCRIPTION "WebRTC DataChannels Library"
|
||||||
VERSION 0.4.5
|
VERSION 0.5.0
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
|
||||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||||
@ -17,8 +17,9 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|||||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
if (MSYS OR MINGW)
|
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||||
add_definitions(-DSCTP_STDINT_INCLUDE=<stdint.h>)
|
if (MSVC)
|
||||||
|
add_definitions(-DNOMINMAX)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -31,13 +32,35 @@ set(LIBDATACHANNEL_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/dtlstransport.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/icetransport.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/init.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/log.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/peerconnection.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/rtc.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/sctptransport.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(LIBDATACHANNEL_HEADERS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/include.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/init.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/log.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/message.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/peerconnection.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/queue.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
|
||||||
|
)
|
||||||
|
|
||||||
set(TESTS_SOURCES
|
set(TESTS_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TESTS_OFFERER_SOURCES
|
set(TESTS_OFFERER_SOURCES
|
||||||
@ -48,10 +71,19 @@ set(TESTS_ANSWERER_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||||
|
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||||
|
if (MSYS OR MINGW)
|
||||||
|
target_compile_definitions(usrsctp PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||||
|
target_compile_definitions(usrsctp-static PUBLIC -DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||||
|
endif()
|
||||||
|
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||||
|
target_compile_options(usrsctp PRIVATE -Wno-error=format-truncation)
|
||||||
|
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
|
||||||
|
endif()
|
||||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
||||||
|
|
||||||
@ -61,19 +93,11 @@ set_target_properties(datachannel PROPERTIES
|
|||||||
CXX_STANDARD 17)
|
CXX_STANDARD 17)
|
||||||
|
|
||||||
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
target_link_libraries(datachannel PUBLIC Threads::Threads)
|
||||||
target_link_libraries(datachannel
|
target_link_libraries(datachannel PRIVATE Usrsctp::UsrsctpStatic)
|
||||||
Threads::Threads
|
|
||||||
Usrsctp::UsrsctpStatic
|
|
||||||
)
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(datachannel
|
|
||||||
"wsock32" #winsock2
|
|
||||||
"ws2_32" #winsock2
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
|
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
|
||||||
set_target_properties(datachannel-static PROPERTIES
|
set_target_properties(datachannel-static PROPERTIES
|
||||||
@ -81,18 +105,15 @@ set_target_properties(datachannel-static PROPERTIES
|
|||||||
CXX_STANDARD 17)
|
CXX_STANDARD 17)
|
||||||
|
|
||||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
|
||||||
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
target_link_libraries(datachannel-static PUBLIC Threads::Threads)
|
||||||
target_link_libraries(datachannel-static
|
target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
|
||||||
Threads::Threads
|
|
||||||
Usrsctp::UsrsctpStatic
|
|
||||||
)
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(datachannel-static
|
target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
|
||||||
"wsock32" #winsock2
|
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
||||||
"ws2_32" #winsock2
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_GNUTLS)
|
if (USE_GNUTLS)
|
||||||
@ -106,34 +127,37 @@ if (USE_GNUTLS)
|
|||||||
IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
|
IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
|
||||||
endif()
|
endif()
|
||||||
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
|
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
|
||||||
target_link_libraries(datachannel GnuTLS::GnuTLS)
|
target_link_libraries(datachannel PRIVATE GnuTLS::GnuTLS)
|
||||||
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
|
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
|
||||||
target_link_libraries(datachannel-static GnuTLS::GnuTLS)
|
target_link_libraries(datachannel-static PRIVATE GnuTLS::GnuTLS)
|
||||||
else()
|
else()
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=0)
|
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=0)
|
||||||
target_link_libraries(datachannel OpenSSL::SSL)
|
target_link_libraries(datachannel PRIVATE OpenSSL::SSL)
|
||||||
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=0)
|
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=0)
|
||||||
target_link_libraries(datachannel-static OpenSSL::SSL)
|
target_link_libraries(datachannel-static PRIVATE OpenSSL::SSL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_JUICE)
|
if (USE_JUICE)
|
||||||
add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
|
add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
|
||||||
target_compile_definitions(datachannel PRIVATE USE_JUICE=1)
|
target_compile_definitions(datachannel PRIVATE USE_JUICE=1)
|
||||||
target_link_libraries(datachannel LibJuice::LibJuiceStatic)
|
target_link_libraries(datachannel PRIVATE LibJuice::LibJuiceStatic)
|
||||||
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=1)
|
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=1)
|
||||||
target_link_libraries(datachannel-static LibJuice::LibJuiceStatic)
|
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||||
else()
|
else()
|
||||||
find_package(LibNice REQUIRED)
|
find_package(LibNice REQUIRED)
|
||||||
target_compile_definitions(datachannel PRIVATE USE_JUICE=0)
|
target_compile_definitions(datachannel PRIVATE USE_JUICE=0)
|
||||||
target_link_libraries(datachannel LibNice::LibNice)
|
target_link_libraries(datachannel PRIVATE LibNice::LibNice)
|
||||||
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=0)
|
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=0)
|
||||||
target_link_libraries(datachannel-static LibNice::LibNice)
|
target_link_libraries(datachannel-static PRIVATE LibNice::LibNice)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||||
|
|
||||||
|
install(TARGETS datachannel LIBRARY DESTINATION lib)
|
||||||
|
install(FILES ${LIBDATACHANNEL_HEADERS} DESTINATION include/rtc)
|
||||||
|
|
||||||
# Main Test
|
# Main Test
|
||||||
add_executable(datachannel-tests ${TESTS_SOURCES})
|
add_executable(datachannel-tests ${TESTS_SOURCES})
|
||||||
set_target_properties(datachannel-tests PROPERTIES
|
set_target_properties(datachannel-tests PROPERTIES
|
||||||
|
27
Jamfile
27
Jamfile
@ -1,3 +1,5 @@
|
|||||||
|
import feature : feature ;
|
||||||
|
|
||||||
project libdatachannel ;
|
project libdatachannel ;
|
||||||
path-constant CWD : . ;
|
path-constant CWD : . ;
|
||||||
|
|
||||||
@ -5,21 +7,26 @@ lib libdatachannel
|
|||||||
: # sources
|
: # sources
|
||||||
[ glob ./src/*.cpp ]
|
[ glob ./src/*.cpp ]
|
||||||
: # requirements
|
: # requirements
|
||||||
|
<cxxstd>17
|
||||||
<include>./include/rtc
|
<include>./include/rtc
|
||||||
<define>USE_GNUTLS=0
|
|
||||||
<define>USE_JUICE=1
|
<define>USE_JUICE=1
|
||||||
<cxxflags>"`pkg-config --cflags openssl`"
|
|
||||||
<library>/libdatachannel//usrsctp
|
<library>/libdatachannel//usrsctp
|
||||||
<library>/libdatachannel//juice
|
<library>/libdatachannel//juice
|
||||||
|
<library>/libdatachannel//plog
|
||||||
: # default build
|
: # default build
|
||||||
<link>static
|
<link>static
|
||||||
: # usage requirements
|
: # usage requirements
|
||||||
<include>./include
|
<include>./include
|
||||||
<library>/libdatachannel//plog
|
<library>/libdatachannel//plog
|
||||||
<cxxflags>-pthread
|
<cxxflags>-pthread
|
||||||
<linkflags>"`pkg-config --libs openssl`"
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
feature crypto : openssl gnutls : composite propagated ;
|
||||||
|
feature.compose <crypto>openssl
|
||||||
|
: <define>USE_GNUTLS=0 ;
|
||||||
|
feature.compose <crypto>gnutls
|
||||||
|
: <define>USE_GNUTLS=1 ;
|
||||||
|
|
||||||
alias plog
|
alias plog
|
||||||
: # no sources
|
: # no sources
|
||||||
: # no build requirements
|
: # no build requirements
|
||||||
@ -57,9 +64,21 @@ actions make_libusrsctp
|
|||||||
}
|
}
|
||||||
|
|
||||||
make libjuice.a : : @make_libjuice ;
|
make libjuice.a : : @make_libjuice ;
|
||||||
|
|
||||||
|
rule make_libjuice ( targets * : sources * : properties * )
|
||||||
|
{
|
||||||
|
if <crypto>gnutls in $(properties)
|
||||||
|
{
|
||||||
|
MAKEOPTS on $(targets) = "USE_NETTLE=1" ;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MAKEOPTS on $(targets) = "USE_NETTLE=0" ;
|
||||||
|
}
|
||||||
|
}
|
||||||
actions make_libjuice
|
actions make_libjuice
|
||||||
{
|
{
|
||||||
(cd $(CWD)/deps/libjuice && make USE_NETTLE=0)
|
(cd $(CWD)/deps/libjuice && make $(MAKEOPTS))
|
||||||
cp $(CWD)/deps/libjuice/libjuice.a $(<)
|
cp $(CWD)/deps/libjuice/libjuice.a $(<)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
Makefile
11
Makefile
@ -5,7 +5,7 @@ CXX=$(CROSS)g++
|
|||||||
AR=$(CROSS)ar
|
AR=$(CROSS)ar
|
||||||
RM=rm -f
|
RM=rm -f
|
||||||
CXXFLAGS=-std=c++17
|
CXXFLAGS=-std=c++17
|
||||||
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
|
CPPFLAGS=-O2 -pthread -fPIC -Wall
|
||||||
LDFLAGS=-pthread
|
LDFLAGS=-pthread
|
||||||
LIBS=
|
LIBS=
|
||||||
LOCALLIBS=libusrsctp.a
|
LOCALLIBS=libusrsctp.a
|
||||||
@ -44,6 +44,9 @@ LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
|||||||
SRCS=$(shell printf "%s " src/*.cpp)
|
SRCS=$(shell printf "%s " src/*.cpp)
|
||||||
OBJS=$(subst .cpp,.o,$(SRCS))
|
OBJS=$(subst .cpp,.o,$(SRCS))
|
||||||
|
|
||||||
|
TEST_SRCS=$(shell printf "%s " test/*.cpp)
|
||||||
|
TEST_OBJS=$(subst .cpp,.o,$(TEST_SRCS))
|
||||||
|
|
||||||
all: $(NAME).a $(NAME).so tests
|
all: $(NAME).a $(NAME).so tests
|
||||||
|
|
||||||
src/%.o: src/%.cpp
|
src/%.o: src/%.cpp
|
||||||
@ -60,8 +63,8 @@ $(NAME).a: $(OBJS)
|
|||||||
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
||||||
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
|
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
|
||||||
|
|
||||||
tests: $(NAME).a test/main.o
|
tests: $(NAME).a $(TEST_OBJS)
|
||||||
$(CXX) $(LDFLAGS) -o $@ test/main.o $(NAME).a $(LDLIBS)
|
$(CXX) $(LDFLAGS) -o $@ $(TEST_OBJS) $(NAME).a $(LDLIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-$(RM) include/rtc/*.d *.d
|
-$(RM) include/rtc/*.d *.d
|
||||||
@ -83,7 +86,7 @@ dist-clean: clean
|
|||||||
libusrsctp.a:
|
libusrsctp.a:
|
||||||
cd $(USRSCTP_DIR) && \
|
cd $(USRSCTP_DIR) && \
|
||||||
./bootstrap && \
|
./bootstrap && \
|
||||||
./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS)" && \
|
./configure --enable-static --disable-debug CFLAGS="$(CPPFLAGS) -Wno-error=format-truncation" && \
|
||||||
make
|
make
|
||||||
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
||||||
|
|
||||||
|
@ -79,11 +79,11 @@ MY_ON_RECV_CANDIDATE_FROM_REMOTE([pc](string candidate, string mid) {
|
|||||||
### Observe the PeerConnection state
|
### Observe the PeerConnection state
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
pc->onStateChanged([](PeerConnection::State state) {
|
pc->onStateChange([](PeerConnection::State state) {
|
||||||
cout << "State: " << state << endl;
|
cout << "State: " << state << endl;
|
||||||
});
|
});
|
||||||
|
|
||||||
pc->onGatheringStateChanged([](PeerConnection::GatheringState state) {
|
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||||
cout << "Gathering state: " << state << endl;
|
cout << "Gathering state: " << state << endl;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -114,5 +114,7 @@ pc->onDataChannel([&dc](shared_ptr<rtc::DataChannel> incoming) {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See [test/main.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/main.cpp) for a complete local connection example.
|
See [test/connectivity.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/connectivity.cpp) for a complete local connection example.
|
||||||
|
|
||||||
|
See [test/cpai.cpp](https://github.com/paullouisageneau/libdatachannel/blob/master/test/capi.cpp) for a C API example.
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ if (NOT TARGET LibNice::LibNice)
|
|||||||
HINTS ${PC_LIBNICE_LIBDIR} ${PC_LIBNICE_LIBRARY_DIRS})
|
HINTS ${PC_LIBNICE_LIBDIR} ${PC_LIBNICE_LIBRARY_DIRS})
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
include(FindPackageHandleStandardArgs)
|
||||||
find_package_handle_standard_args(Libnice DEFAULT_MSG
|
find_package_handle_standard_args(LibNice DEFAULT_MSG
|
||||||
LIBNICE_LIBRARY LIBNICE_INCLUDE_DIR)
|
LIBNICE_LIBRARY LIBNICE_INCLUDE_DIR)
|
||||||
mark_as_advanced(LIBNICE_INCLUDE_DIR LIBNICE_LIBRARY)
|
mark_as_advanced(LIBNICE_INCLUDE_DIR LIBNICE_LIBRARY)
|
||||||
|
|
||||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: 856475ebd1...511b19f516
@ -25,6 +25,15 @@
|
|||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
|
enum class CandidateType { Host = 0, ServerReflexive, PeerReflexive, Relayed };
|
||||||
|
enum class CandidateTransportType { Udp = 0, TcpActive, TcpPassive, TcpSo };
|
||||||
|
struct CandidateInfo {
|
||||||
|
string address;
|
||||||
|
int port;
|
||||||
|
CandidateType type;
|
||||||
|
CandidateTransportType transportType;
|
||||||
|
};
|
||||||
|
|
||||||
class Candidate {
|
class Candidate {
|
||||||
public:
|
public:
|
||||||
Candidate(string candidate, string mid = "");
|
Candidate(string candidate, string mid = "");
|
||||||
@ -46,6 +55,8 @@ private:
|
|||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
|
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
|
||||||
|
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type);
|
||||||
|
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -31,12 +31,10 @@ class Channel {
|
|||||||
public:
|
public:
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
virtual bool send(const std::variant<binary, string> &data) = 0; // returns false if buffered
|
virtual bool send(const std::variant<binary, string> &data) = 0; // returns false if buffered
|
||||||
virtual std::optional<std::variant<binary, string>> receive() = 0; // only if onMessage unset
|
|
||||||
|
|
||||||
virtual bool isOpen() const = 0;
|
virtual bool isOpen() const = 0;
|
||||||
virtual bool isClosed() const = 0;
|
virtual bool isClosed() const = 0;
|
||||||
|
virtual size_t maxMessageSize() const; // max message size in a call to send
|
||||||
virtual size_t availableAmount() const; // total size available to receive
|
|
||||||
virtual size_t bufferedAmount() const; // total size buffered to send
|
virtual size_t bufferedAmount() const; // total size buffered to send
|
||||||
|
|
||||||
void onOpen(std::function<void()> callback);
|
void onOpen(std::function<void()> callback);
|
||||||
@ -47,11 +45,14 @@ public:
|
|||||||
void onMessage(std::function<void(const binary &data)> binaryCallback,
|
void onMessage(std::function<void(const binary &data)> binaryCallback,
|
||||||
std::function<void(const string &data)> stringCallback);
|
std::function<void(const string &data)> stringCallback);
|
||||||
|
|
||||||
void onAvailable(std::function<void()> callback);
|
|
||||||
void onBufferedAmountLow(std::function<void()> callback);
|
void onBufferedAmountLow(std::function<void()> callback);
|
||||||
|
|
||||||
void setBufferedAmountLowThreshold(size_t amount);
|
void setBufferedAmountLowThreshold(size_t amount);
|
||||||
|
|
||||||
|
// Extended API
|
||||||
|
virtual std::optional<std::variant<binary, string>> receive() = 0; // only if onMessage unset
|
||||||
|
virtual size_t availableAmount() const; // total size available to receive
|
||||||
|
void onAvailable(std::function<void()> callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void triggerOpen();
|
virtual void triggerOpen();
|
||||||
virtual void triggerClosed();
|
virtual void triggerClosed();
|
||||||
@ -59,6 +60,8 @@ protected:
|
|||||||
virtual void triggerAvailable(size_t count);
|
virtual void triggerAvailable(size_t count);
|
||||||
virtual void triggerBufferedAmount(size_t amount);
|
virtual void triggerBufferedAmount(size_t amount);
|
||||||
|
|
||||||
|
void resetCallbacks();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
synchronized_callback<> mOpenCallback;
|
synchronized_callback<> mOpenCallback;
|
||||||
synchronized_callback<> mClosedCallback;
|
synchronized_callback<> mClosedCallback;
|
||||||
|
@ -51,8 +51,22 @@ struct IceServer {
|
|||||||
RelayType relayType;
|
RelayType relayType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ProxyServer {
|
||||||
|
enum class Type { None = 0, Socks5, Http, Last = Http };
|
||||||
|
|
||||||
|
ProxyServer(Type type_, string ip_, uint16_t port_, string username_ = "",
|
||||||
|
string password_ = "");
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
string ip;
|
||||||
|
uint16_t port;
|
||||||
|
string username;
|
||||||
|
string password;
|
||||||
|
};
|
||||||
|
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
std::vector<IceServer> iceServers;
|
std::vector<IceServer> iceServers;
|
||||||
|
std::optional<ProxyServer> proxyServer;
|
||||||
bool enableIceTcp = false;
|
bool enableIceTcp = false;
|
||||||
uint16_t portRangeBegin = 1024;
|
uint16_t portRangeBegin = 1024;
|
||||||
uint16_t portRangeEnd = 65535;
|
uint16_t portRangeEnd = 65535;
|
||||||
|
@ -38,42 +38,40 @@ class PeerConnection;
|
|||||||
|
|
||||||
class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel {
|
class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel {
|
||||||
public:
|
public:
|
||||||
DataChannel(std::shared_ptr<PeerConnection> pc, unsigned int stream, string label,
|
DataChannel(std::weak_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||||
string protocol, Reliability reliability);
|
string protocol, Reliability reliability);
|
||||||
DataChannel(std::shared_ptr<PeerConnection> pc, std::shared_ptr<SctpTransport> transport,
|
DataChannel(std::weak_ptr<PeerConnection> pc, std::weak_ptr<SctpTransport> transport,
|
||||||
unsigned int stream);
|
unsigned int stream);
|
||||||
~DataChannel();
|
~DataChannel();
|
||||||
|
|
||||||
void close(void) override;
|
|
||||||
|
|
||||||
bool send(const std::variant<binary, string> &data) override;
|
|
||||||
bool send(const byte *data, size_t size);
|
|
||||||
|
|
||||||
template <typename Buffer> bool sendBuffer(const Buffer &buf);
|
|
||||||
template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
|
|
||||||
|
|
||||||
std::optional<std::variant<binary, string>> receive() override;
|
|
||||||
|
|
||||||
bool isOpen(void) const override;
|
|
||||||
bool isClosed(void) const override;
|
|
||||||
size_t availableAmount() const override;
|
|
||||||
|
|
||||||
size_t maxMessageSize() const; // maximum message size in a call to send or sendBuffer
|
|
||||||
|
|
||||||
unsigned int stream() const;
|
unsigned int stream() const;
|
||||||
string label() const;
|
string label() const;
|
||||||
string protocol() const;
|
string protocol() const;
|
||||||
Reliability reliability() const;
|
Reliability reliability() const;
|
||||||
|
|
||||||
|
void close(void) override;
|
||||||
|
bool send(const std::variant<binary, string> &data) override;
|
||||||
|
bool send(const byte *data, size_t size);
|
||||||
|
template <typename Buffer> bool sendBuffer(const Buffer &buf);
|
||||||
|
template <typename Iterator> bool sendBuffer(Iterator first, Iterator last);
|
||||||
|
|
||||||
|
bool isOpen(void) const override;
|
||||||
|
bool isClosed(void) const override;
|
||||||
|
size_t maxMessageSize() const override;
|
||||||
|
|
||||||
|
// Extended API
|
||||||
|
size_t availableAmount() const override;
|
||||||
|
std::optional<std::variant<binary, string>> receive() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void remoteClose();
|
void remoteClose();
|
||||||
void open(std::shared_ptr<SctpTransport> sctpTransport);
|
void open(std::shared_ptr<SctpTransport> transport);
|
||||||
bool outgoing(mutable_message_ptr message);
|
bool outgoing(mutable_message_ptr message);
|
||||||
void incoming(message_ptr message);
|
void incoming(message_ptr message);
|
||||||
void processOpenMessage(message_ptr message);
|
void processOpenMessage(message_ptr message);
|
||||||
|
|
||||||
const std::shared_ptr<PeerConnection> mPeerConnection;
|
const std::weak_ptr<PeerConnection> mPeerConnection;
|
||||||
std::shared_ptr<SctpTransport> mSctpTransport;
|
std::weak_ptr<SctpTransport> mSctpTransport;
|
||||||
|
|
||||||
unsigned int mStream;
|
unsigned int mStream;
|
||||||
string mLabel;
|
string mLabel;
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "log.hpp"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -33,9 +35,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "plog/Appenders/ColorConsoleAppender.h"
|
|
||||||
#include "plog/Log.h"
|
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
using std::byte;
|
using std::byte;
|
||||||
@ -50,8 +49,6 @@ using std::uint32_t;
|
|||||||
using std::uint64_t;
|
using std::uint64_t;
|
||||||
using std::uint8_t;
|
using std::uint8_t;
|
||||||
|
|
||||||
// Constants
|
|
||||||
|
|
||||||
const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
|
const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
|
||||||
const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length
|
const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length
|
||||||
|
|
||||||
@ -59,29 +56,6 @@ const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
|
|||||||
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
|
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
|
||||||
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
|
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
|
||||||
|
|
||||||
// Log
|
|
||||||
|
|
||||||
enum class LogLevel { // Don't change, it must match plog severity
|
|
||||||
None = 0,
|
|
||||||
Fatal = 1,
|
|
||||||
Error = 2,
|
|
||||||
Warning = 3,
|
|
||||||
Info = 4,
|
|
||||||
Debug = 5,
|
|
||||||
Verbose = 6
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr) {
|
|
||||||
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
|
|
||||||
if (!appender)
|
|
||||||
appender = &consoleAppender;
|
|
||||||
plog::init(severity, appender);
|
|
||||||
PLOG_DEBUG << "Logger initialized";
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
|
|
||||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||||
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
||||||
@ -89,11 +63,12 @@ template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
|||||||
template <typename... P> class synchronized_callback {
|
template <typename... P> class synchronized_callback {
|
||||||
public:
|
public:
|
||||||
synchronized_callback() = default;
|
synchronized_callback() = default;
|
||||||
|
synchronized_callback(std::function<void(P...)> func) { *this = std::move(func); };
|
||||||
~synchronized_callback() { *this = nullptr; }
|
~synchronized_callback() { *this = nullptr; }
|
||||||
|
|
||||||
synchronized_callback &operator=(std::function<void(P...)> func) {
|
synchronized_callback &operator=(std::function<void(P...)> func) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
callback = func;
|
callback = std::move(func);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +78,10 @@ public:
|
|||||||
callback(args...);
|
callback(args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
operator bool() const { return callback ? true : false; }
|
operator bool() const {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
return callback ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::function<void(P...)> callback;
|
std::function<void(P...)> callback;
|
||||||
|
50
include/rtc/init.hpp
Normal file
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 {
|
namespace rtc {
|
||||||
|
|
||||||
struct Message : binary {
|
struct Message : binary {
|
||||||
enum Type { Binary, String, Control };
|
enum Type { Binary, String, Control, Reset };
|
||||||
|
|
||||||
Message(size_t size) : binary(size), type(Binary) {}
|
Message(size_t size, Type type_ = Binary) : binary(size), type(type_) {}
|
||||||
|
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
Message(Iterator begin_, Iterator end_, Type type_ = Binary)
|
Message(Iterator begin_, Iterator end_, Type type_ = Binary)
|
||||||
@ -46,7 +46,7 @@ using mutable_message_ptr = std::shared_ptr<Message>;
|
|||||||
using message_callback = std::function<void(message_ptr message)>;
|
using message_callback = std::function<void(message_ptr message)>;
|
||||||
|
|
||||||
constexpr auto message_size_func = [](const message_ptr &m) -> size_t {
|
constexpr auto message_size_func = [](const message_ptr &m) -> size_t {
|
||||||
return m->type != Message::Control ? m->size() : 0;
|
return m->type == Message::Binary || m->type == Message::String ? m->size() : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
@ -59,6 +59,15 @@ message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Mess
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline message_ptr make_message(size_t size, Message::Type type = Message::Binary,
|
||||||
|
unsigned int stream = 0,
|
||||||
|
std::shared_ptr<Reliability> reliability = nullptr) {
|
||||||
|
auto message = std::make_shared<Message>(size, type);
|
||||||
|
message->stream = stream;
|
||||||
|
message->reliability = reliability;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -24,14 +24,17 @@
|
|||||||
#include "datachannel.hpp"
|
#include "datachannel.hpp"
|
||||||
#include "description.hpp"
|
#include "description.hpp"
|
||||||
#include "include.hpp"
|
#include "include.hpp"
|
||||||
|
#include "init.hpp"
|
||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "reliability.hpp"
|
#include "reliability.hpp"
|
||||||
#include "rtc.hpp"
|
#include "rtc.hpp"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
@ -42,6 +45,9 @@ class IceTransport;
|
|||||||
class DtlsTransport;
|
class DtlsTransport;
|
||||||
class SctpTransport;
|
class SctpTransport;
|
||||||
|
|
||||||
|
using certificate_ptr = std::shared_ptr<Certificate>;
|
||||||
|
using future_certificate_ptr = std::shared_future<certificate_ptr>;
|
||||||
|
|
||||||
class PeerConnection : public std::enable_shared_from_this<PeerConnection> {
|
class PeerConnection : public std::enable_shared_from_this<PeerConnection> {
|
||||||
public:
|
public:
|
||||||
enum class State : int {
|
enum class State : int {
|
||||||
@ -50,14 +56,13 @@ public:
|
|||||||
Connected = RTC_CONNECTED,
|
Connected = RTC_CONNECTED,
|
||||||
Disconnected = RTC_DISCONNECTED,
|
Disconnected = RTC_DISCONNECTED,
|
||||||
Failed = RTC_FAILED,
|
Failed = RTC_FAILED,
|
||||||
Closed = RTC_CLOSED,
|
Closed = RTC_CLOSED
|
||||||
Destroying = RTC_DESTROYING
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class GatheringState : int {
|
enum class GatheringState : int {
|
||||||
New = RTC_GATHERING_NEW,
|
New = RTC_GATHERING_NEW,
|
||||||
InProgress = RTC_GATHERING_INPROGRESS,
|
InProgress = RTC_GATHERING_INPROGRESS,
|
||||||
Complete = RTC_GATHERING_COMPLETE,
|
Complete = RTC_GATHERING_COMPLETE
|
||||||
};
|
};
|
||||||
|
|
||||||
PeerConnection(void);
|
PeerConnection(void);
|
||||||
@ -86,15 +91,31 @@ public:
|
|||||||
void onStateChange(std::function<void(State state)> callback);
|
void onStateChange(std::function<void(State state)> callback);
|
||||||
void onGatheringStateChange(std::function<void(GatheringState state)> callback);
|
void onGatheringStateChange(std::function<void(GatheringState state)> callback);
|
||||||
|
|
||||||
|
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
void clearStats();
|
||||||
|
size_t bytesSent();
|
||||||
|
size_t bytesReceived();
|
||||||
|
std::optional<std::chrono::milliseconds> rtt();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
init_token mInitToken = Init::Token();
|
||||||
|
|
||||||
std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
|
std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
|
||||||
std::shared_ptr<DtlsTransport> initDtlsTransport();
|
std::shared_ptr<DtlsTransport> initDtlsTransport();
|
||||||
std::shared_ptr<SctpTransport> initSctpTransport();
|
std::shared_ptr<SctpTransport> initSctpTransport();
|
||||||
|
void closeTransports();
|
||||||
|
|
||||||
void endLocalCandidates();
|
void endLocalCandidates();
|
||||||
bool checkFingerprint(const std::string &fingerprint) const;
|
bool checkFingerprint(const std::string &fingerprint) const;
|
||||||
void forwardMessage(message_ptr message);
|
void forwardMessage(message_ptr message);
|
||||||
void forwardBufferedAmount(uint16_t stream, size_t amount);
|
void forwardBufferedAmount(uint16_t stream, size_t amount);
|
||||||
|
|
||||||
|
std::shared_ptr<DataChannel> emplaceDataChannel(Description::Role role, const string &label,
|
||||||
|
const string &protocol,
|
||||||
|
const Reliability &reliability);
|
||||||
|
std::shared_ptr<DataChannel> findDataChannel(uint16_t stream);
|
||||||
void iterateDataChannels(std::function<void(std::shared_ptr<DataChannel> channel)> func);
|
void iterateDataChannels(std::function<void(std::shared_ptr<DataChannel> channel)> func);
|
||||||
void openDataChannels();
|
void openDataChannels();
|
||||||
void closeDataChannels();
|
void closeDataChannels();
|
||||||
@ -103,11 +124,13 @@ private:
|
|||||||
void processLocalDescription(Description description);
|
void processLocalDescription(Description description);
|
||||||
void processLocalCandidate(Candidate candidate);
|
void processLocalCandidate(Candidate candidate);
|
||||||
void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
|
void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
|
||||||
void changeState(State state);
|
bool changeState(State state);
|
||||||
void changeGatheringState(GatheringState state);
|
bool changeGatheringState(GatheringState state);
|
||||||
|
|
||||||
|
void resetCallbacks();
|
||||||
|
|
||||||
const Configuration mConfig;
|
const Configuration mConfig;
|
||||||
const std::shared_ptr<Certificate> mCertificate;
|
const future_certificate_ptr mCertificate;
|
||||||
|
|
||||||
std::optional<Description> mLocalDescription, mRemoteDescription;
|
std::optional<Description> mLocalDescription, mRemoteDescription;
|
||||||
mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
mutable std::recursive_mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
||||||
@ -115,9 +138,9 @@ private:
|
|||||||
std::shared_ptr<IceTransport> mIceTransport;
|
std::shared_ptr<IceTransport> mIceTransport;
|
||||||
std::shared_ptr<DtlsTransport> mDtlsTransport;
|
std::shared_ptr<DtlsTransport> mDtlsTransport;
|
||||||
std::shared_ptr<SctpTransport> mSctpTransport;
|
std::shared_ptr<SctpTransport> mSctpTransport;
|
||||||
std::recursive_mutex mInitMutex;
|
|
||||||
|
|
||||||
std::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels;
|
std::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels;
|
||||||
|
std::shared_mutex mDataChannelsMutex;
|
||||||
|
|
||||||
std::atomic<State> mState;
|
std::atomic<State> mState;
|
||||||
std::atomic<GatheringState> mGatheringState;
|
std::atomic<GatheringState> mGatheringState;
|
||||||
|
@ -41,12 +41,10 @@ public:
|
|||||||
bool empty() const;
|
bool empty() const;
|
||||||
size_t size() const; // elements
|
size_t size() const; // elements
|
||||||
size_t amount() const; // amount
|
size_t amount() const; // amount
|
||||||
void push(const T &element);
|
void push(T element);
|
||||||
void push(T &&element);
|
|
||||||
std::optional<T> pop();
|
std::optional<T> pop();
|
||||||
std::optional<T> peek();
|
std::optional<T> peek();
|
||||||
void wait();
|
bool wait(const std::optional<std::chrono::milliseconds> &duration = nullopt);
|
||||||
void wait(const std::chrono::milliseconds &duration);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const size_t mLimit;
|
const size_t mLimit;
|
||||||
@ -88,9 +86,7 @@ template <typename T> size_t Queue<T>::amount() const {
|
|||||||
return mAmount;
|
return mAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> void Queue<T>::push(const T &element) { push(T{element}); }
|
template <typename T> void Queue<T>::push(T element) {
|
||||||
|
|
||||||
template <typename T> void Queue<T>::push(T &&element) {
|
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
|
mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
|
||||||
if (!mStopping) {
|
if (!mStopping) {
|
||||||
@ -122,14 +118,14 @@ template <typename T> std::optional<T> Queue<T>::peek() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> void Queue<T>::wait() {
|
template <typename T>
|
||||||
|
bool Queue<T>::wait(const std::optional<std::chrono::milliseconds> &duration) {
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
|
if (duration)
|
||||||
|
mPopCondition.wait_for(lock, *duration, [this]() { return !mQueue.empty() || mStopping; });
|
||||||
|
else
|
||||||
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
||||||
}
|
return !mStopping;
|
||||||
|
|
||||||
template <typename T> void Queue<T>::wait(const std::chrono::milliseconds &duration) {
|
|
||||||
std::unique_lock lock(mMutex);
|
|
||||||
mPopCondition.wait_for(lock, duration, [this]() { return !mQueue.empty() || mStopping; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
// libdatachannel C API
|
// libdatachannel C API
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -31,15 +33,14 @@ typedef enum {
|
|||||||
RTC_CONNECTED = 2,
|
RTC_CONNECTED = 2,
|
||||||
RTC_DISCONNECTED = 3,
|
RTC_DISCONNECTED = 3,
|
||||||
RTC_FAILED = 4,
|
RTC_FAILED = 4,
|
||||||
RTC_CLOSED = 5,
|
RTC_CLOSED = 5
|
||||||
RTC_DESTROYING = 6 // internal
|
} rtcState;
|
||||||
} rtc_state_t;
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
RTC_GATHERING_NEW = 0,
|
RTC_GATHERING_NEW = 0,
|
||||||
RTC_GATHERING_INPROGRESS = 1,
|
RTC_GATHERING_INPROGRESS = 1,
|
||||||
RTC_GATHERING_COMPLETE = 2
|
RTC_GATHERING_COMPLETE = 2
|
||||||
} rtc_gathering_state_t;
|
} rtcGatheringState;
|
||||||
|
|
||||||
// Don't change, it must match plog severity
|
// Don't change, it must match plog severity
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -50,32 +51,72 @@ typedef enum {
|
|||||||
RTC_LOG_INFO = 4,
|
RTC_LOG_INFO = 4,
|
||||||
RTC_LOG_DEBUG = 5,
|
RTC_LOG_DEBUG = 5,
|
||||||
RTC_LOG_VERBOSE = 6
|
RTC_LOG_VERBOSE = 6
|
||||||
} rtc_log_level_t;
|
} rtcLogLevel;
|
||||||
|
|
||||||
void rtcInitLogger(rtc_log_level_t level);
|
typedef struct {
|
||||||
|
const char **iceServers;
|
||||||
|
int iceServersCount;
|
||||||
|
uint16_t portRangeBegin;
|
||||||
|
uint16_t portRangeEnd;
|
||||||
|
} rtcConfiguration;
|
||||||
|
|
||||||
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount);
|
typedef void (*dataChannelCallbackFunc)(int dc, void *ptr);
|
||||||
void rtcDeletePeerConnection(int pc);
|
typedef void (*descriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
||||||
int rtcCreateDataChannel(int pc, const char *label);
|
typedef void (*candidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
||||||
void rtcDeleteDataChannel(int dc);
|
typedef void (*stateChangeCallbackFunc)(rtcState state, void *ptr);
|
||||||
void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *));
|
typedef void (*gatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
||||||
void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *,
|
typedef void (*openCallbackFunc)(void *ptr);
|
||||||
void *));
|
typedef void (*closedCallbackFunc)(void *ptr);
|
||||||
void rtcSetLocalCandidateCallback(int pc,
|
typedef void (*errorCallbackFunc)(const char *error, void *ptr);
|
||||||
void (*candidateCallback)(const char *, const char *, void *));
|
typedef void (*messageCallbackFunc)(const char *message, int size, void *ptr);
|
||||||
void rtcSetStateChangeCallback(int pc, void (*stateCallback)(rtc_state_t state, void *));
|
typedef void (*bufferedAmountLowCallbackFunc)(void *ptr);
|
||||||
void rtcSetGatheringStateChangeCallback(int pc,
|
typedef void (*availableCallbackFunc)(void *ptr);
|
||||||
void (*gatheringStateCallback)(rtc_gathering_state_t state,
|
|
||||||
void *));
|
// Log
|
||||||
void rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
void rtcInitLogger(rtcLogLevel level);
|
||||||
void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid);
|
|
||||||
int rtcGetDataChannelLabel(int dc, char *data, int size);
|
// User pointer
|
||||||
void rtcSetOpenCallback(int dc, void (*openCallback)(void *));
|
|
||||||
void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *));
|
|
||||||
void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *));
|
|
||||||
int rtcSendMessage(int dc, const char *data, int size);
|
|
||||||
void rtcSetUserPointer(int i, void *ptr);
|
void rtcSetUserPointer(int i, void *ptr);
|
||||||
|
|
||||||
|
// PeerConnection
|
||||||
|
int rtcCreatePeerConnection(const rtcConfiguration *config);
|
||||||
|
int rtcDeletePeerConnection(int pc);
|
||||||
|
|
||||||
|
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb);
|
||||||
|
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb);
|
||||||
|
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb);
|
||||||
|
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb);
|
||||||
|
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb);
|
||||||
|
|
||||||
|
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
||||||
|
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
|
||||||
|
|
||||||
|
int rtcGetLocalAddress(int pc, char *buffer, int size);
|
||||||
|
int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
||||||
|
|
||||||
|
// DataChannel
|
||||||
|
int rtcCreateDataChannel(int pc, const char *label);
|
||||||
|
int rtcDeleteDataChannel(int dc);
|
||||||
|
|
||||||
|
int rtcGetDataChannelLabel(int dc, char *buffer, int size);
|
||||||
|
int rtcSetOpenCallback(int dc, openCallbackFunc cb);
|
||||||
|
int rtcSetClosedCallback(int dc, closedCallbackFunc cb);
|
||||||
|
int rtcSetErrorCallback(int dc, errorCallbackFunc cb);
|
||||||
|
int rtcSetMessageCallback(int dc, messageCallbackFunc cb);
|
||||||
|
int rtcSendMessage(int dc, const char *data, int size);
|
||||||
|
|
||||||
|
int rtcGetBufferedAmount(int dc); // total size buffered to send
|
||||||
|
int rtcSetBufferedAmountLowThreshold(int dc, int amount);
|
||||||
|
int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb);
|
||||||
|
|
||||||
|
// DataChannel extended API
|
||||||
|
int rtcGetAvailableAmount(int dc); // total size available to receive
|
||||||
|
int rtcSetAvailableCallback(int dc, availableCallbackFunc cb);
|
||||||
|
int rtcReceiveMessage(int dc, char *buffer, int *size);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
void rtcCleanup();
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
@ -17,6 +17,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// C++ API
|
// C++ API
|
||||||
|
#include "include.hpp"
|
||||||
|
#include "init.hpp" // for rtc::Cleanup()
|
||||||
|
#include "log.hpp"
|
||||||
|
//
|
||||||
#include "datachannel.hpp"
|
#include "datachannel.hpp"
|
||||||
#include "peerconnection.hpp"
|
#include "peerconnection.hpp"
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#elif __linux__
|
#else
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#endif
|
#endif
|
||||||
@ -131,3 +131,33 @@ Candidate::operator string() const {
|
|||||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
|
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
|
||||||
return out << std::string(candidate);
|
return out << std::string(candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type) {
|
||||||
|
switch (type) {
|
||||||
|
case rtc::CandidateType::Host:
|
||||||
|
return out << "Host";
|
||||||
|
case rtc::CandidateType::PeerReflexive:
|
||||||
|
return out << "PeerReflexive";
|
||||||
|
case rtc::CandidateType::Relayed:
|
||||||
|
return out << "Relayed";
|
||||||
|
case rtc::CandidateType::ServerReflexive:
|
||||||
|
return out << "ServerReflexive";
|
||||||
|
default:
|
||||||
|
return out << "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType) {
|
||||||
|
switch (transportType) {
|
||||||
|
case rtc::CandidateTransportType::TcpActive:
|
||||||
|
return out << "TcpActive";
|
||||||
|
case rtc::CandidateTransportType::TcpPassive:
|
||||||
|
return out << "TcpPassive";
|
||||||
|
case rtc::CandidateTransportType::TcpSo:
|
||||||
|
return out << "TcpSo";
|
||||||
|
case rtc::CandidateTransportType::Udp:
|
||||||
|
return out << "Udp";
|
||||||
|
default:
|
||||||
|
return out << "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -141,14 +141,9 @@ string make_fingerprint(gnutls_x509_crt_t crt) {
|
|||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Certificate> make_certificate(const string &commonName) {
|
namespace {
|
||||||
static std::unordered_map<string, shared_ptr<Certificate>> cache;
|
|
||||||
static std::mutex cacheMutex;
|
|
||||||
|
|
||||||
std::lock_guard lock(cacheMutex);
|
|
||||||
if (auto it = cache.find(commonName); it != cache.end())
|
|
||||||
return it->second;
|
|
||||||
|
|
||||||
|
certificate_ptr make_certificate_impl(string commonName) {
|
||||||
std::unique_ptr<gnutls_x509_crt_t, decltype(&delete_crt)> crt(create_crt(), delete_crt);
|
std::unique_ptr<gnutls_x509_crt_t, decltype(&delete_crt)> crt(create_crt(), delete_crt);
|
||||||
std::unique_ptr<gnutls_x509_privkey_t, decltype(&delete_privkey)> privkey(create_privkey(),
|
std::unique_ptr<gnutls_x509_privkey_t, decltype(&delete_privkey)> privkey(create_privkey(),
|
||||||
delete_privkey);
|
delete_privkey);
|
||||||
@ -174,11 +169,11 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
|
|||||||
check_gnutls(gnutls_x509_crt_sign2(*crt, *crt, *privkey, GNUTLS_DIG_SHA256, 0),
|
check_gnutls(gnutls_x509_crt_sign2(*crt, *crt, *privkey, GNUTLS_DIG_SHA256, 0),
|
||||||
"Unable to auto-sign certificate");
|
"Unable to auto-sign certificate");
|
||||||
|
|
||||||
auto certificate = std::make_shared<Certificate>(*crt, *privkey);
|
return std::make_shared<Certificate>(*crt, *privkey);
|
||||||
cache.emplace(std::make_pair(commonName, certificate));
|
|
||||||
return certificate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
#else
|
#else
|
||||||
@ -236,22 +231,9 @@ string make_fingerprint(X509 *x509) {
|
|||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
shared_ptr<Certificate> make_certificate(const string &commonName) {
|
certificate_ptr make_certificate_impl(string commonName) {
|
||||||
static std::unordered_map<string, shared_ptr<Certificate>> cache;
|
|
||||||
static std::mutex cacheMutex;
|
|
||||||
|
|
||||||
std::lock_guard lock(cacheMutex);
|
|
||||||
if (auto it = cache.find(commonName); it != cache.end())
|
|
||||||
return it->second;
|
|
||||||
|
|
||||||
if (cache.empty()) {
|
|
||||||
// This is the first call to OpenSSL
|
|
||||||
OPENSSL_init_ssl(0, NULL);
|
|
||||||
SSL_load_error_strings();
|
|
||||||
ERR_load_crypto_strings();
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<X509> x509(X509_new(), X509_free);
|
shared_ptr<X509> x509(X509_new(), X509_free);
|
||||||
shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
|
shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
|
||||||
|
|
||||||
@ -288,12 +270,54 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
|
|||||||
if (!X509_sign(x509.get(), pkey.get(), EVP_sha256()))
|
if (!X509_sign(x509.get(), pkey.get(), EVP_sha256()))
|
||||||
throw std::runtime_error("Unable to auto-sign certificate");
|
throw std::runtime_error("Unable to auto-sign certificate");
|
||||||
|
|
||||||
auto certificate = std::make_shared<Certificate>(x509, pkey);
|
return std::make_shared<Certificate>(x509, pkey);
|
||||||
cache.emplace(std::make_pair(commonName, certificate));
|
|
||||||
return certificate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Common for GnuTLS and OpenSSL
|
||||||
|
|
||||||
|
namespace rtc {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Helper function roughly equivalent to std::async with policy std::launch::async
|
||||||
|
// since std::async might be unreliable on some platforms (e.g. Mingw32 on Windows)
|
||||||
|
template <class F, class... Args>
|
||||||
|
std::future<std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>> thread_call(F &&f,
|
||||||
|
Args &&... args) {
|
||||||
|
using R = std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>;
|
||||||
|
std::packaged_task<R()> task(std::bind(f, std::forward<Args>(args)...));
|
||||||
|
std::future<R> future = task.get_future();
|
||||||
|
std::thread t(std::move(task));
|
||||||
|
t.detach();
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unordered_map<string, future_certificate_ptr> CertificateCache;
|
||||||
|
static std::mutex CertificateCacheMutex;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
future_certificate_ptr make_certificate(string commonName) {
|
||||||
|
std::lock_guard lock(CertificateCacheMutex);
|
||||||
|
|
||||||
|
if (auto it = CertificateCache.find(commonName); it != CertificateCache.end())
|
||||||
|
return it->second;
|
||||||
|
|
||||||
|
auto future = thread_call(make_certificate_impl, commonName);
|
||||||
|
auto shared = future.share();
|
||||||
|
CertificateCache.emplace(std::move(commonName), shared);
|
||||||
|
return shared;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CleanupCertificateCache() {
|
||||||
|
std::lock_guard lock(CertificateCacheMutex);
|
||||||
|
CertificateCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rtc
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "include.hpp"
|
#include "include.hpp"
|
||||||
|
|
||||||
|
#include <future>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
#if USE_GNUTLS
|
#if USE_GNUTLS
|
||||||
@ -62,7 +63,12 @@ string make_fingerprint(gnutls_x509_crt_t crt);
|
|||||||
string make_fingerprint(X509 *x509);
|
string make_fingerprint(X509 *x509);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::shared_ptr<Certificate> make_certificate(const string &commonName);
|
using certificate_ptr = std::shared_ptr<Certificate>;
|
||||||
|
using future_certificate_ptr = std::shared_future<certificate_ptr>;
|
||||||
|
|
||||||
|
future_certificate_ptr make_certificate(string commonName); // cached
|
||||||
|
|
||||||
|
void CleanupCertificateCache();
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
|
@ -18,10 +18,14 @@
|
|||||||
|
|
||||||
#include "channel.hpp"
|
#include "channel.hpp"
|
||||||
|
|
||||||
namespace {}
|
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
|
size_t Channel::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
|
||||||
|
|
||||||
|
size_t Channel::bufferedAmount() const { return mBufferedAmount; }
|
||||||
|
|
||||||
|
size_t Channel::availableAmount() const { return 0; }
|
||||||
|
|
||||||
void Channel::onOpen(std::function<void()> callback) {
|
void Channel::onOpen(std::function<void()> callback) {
|
||||||
mOpenCallback = callback;
|
mOpenCallback = callback;
|
||||||
}
|
}
|
||||||
@ -49,20 +53,16 @@ void Channel::onMessage(std::function<void(const binary &data)> binaryCallback,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Channel::onAvailable(std::function<void()> callback) {
|
|
||||||
mAvailableCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Channel::onBufferedAmountLow(std::function<void()> callback) {
|
void Channel::onBufferedAmountLow(std::function<void()> callback) {
|
||||||
mBufferedAmountLowCallback = callback;
|
mBufferedAmountLowCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Channel::availableAmount() const { return 0; }
|
|
||||||
|
|
||||||
size_t Channel::bufferedAmount() const { return mBufferedAmount; }
|
|
||||||
|
|
||||||
void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; }
|
void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; }
|
||||||
|
|
||||||
|
void Channel::onAvailable(std::function<void()> callback) {
|
||||||
|
mAvailableCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
void Channel::triggerOpen() { mOpenCallback(); }
|
void Channel::triggerOpen() { mOpenCallback(); }
|
||||||
|
|
||||||
void Channel::triggerClosed() { mClosedCallback(); }
|
void Channel::triggerClosed() { mClosedCallback(); }
|
||||||
@ -88,5 +88,14 @@ void Channel::triggerBufferedAmount(size_t amount) {
|
|||||||
mBufferedAmountLowCallback();
|
mBufferedAmountLowCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Channel::resetCallbacks() {
|
||||||
|
mOpenCallback = nullptr;
|
||||||
|
mClosedCallback = nullptr;
|
||||||
|
mErrorCallback = nullptr;
|
||||||
|
mMessageCallback = nullptr;
|
||||||
|
mAvailableCallback = nullptr;
|
||||||
|
mBufferedAmountLowCallback = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
|
@ -38,16 +38,17 @@ IceServer::IceServer(const string &url) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
string scheme = opt[2].value_or("stun");
|
string scheme = opt[2].value_or("stun");
|
||||||
|
relayType = RelayType::TurnUdp;
|
||||||
if (scheme == "stun" || scheme == "STUN")
|
if (scheme == "stun" || scheme == "STUN")
|
||||||
type = Type::Stun;
|
type = Type::Stun;
|
||||||
else if (scheme == "turn" || scheme == "TURN")
|
else if (scheme == "turn" || scheme == "TURN")
|
||||||
type = Type::Turn;
|
type = Type::Turn;
|
||||||
else if (scheme == "turns" || scheme == "TURNS")
|
else if (scheme == "turns" || scheme == "TURNS") {
|
||||||
type = Type::Turn;
|
type = Type::Turn;
|
||||||
else
|
relayType = RelayType::TurnTls;
|
||||||
|
} else
|
||||||
throw std::invalid_argument("Unknown ICE server protocol: " + scheme);
|
throw std::invalid_argument("Unknown ICE server protocol: " + scheme);
|
||||||
|
|
||||||
relayType = RelayType::TurnUdp;
|
|
||||||
if (auto &query = opt[15]) {
|
if (auto &query = opt[15]) {
|
||||||
if (query->find("transport=udp") != string::npos)
|
if (query->find("transport=udp") != string::npos)
|
||||||
relayType = RelayType::TurnUdp;
|
relayType = RelayType::TurnUdp;
|
||||||
@ -84,4 +85,7 @@ IceServer::IceServer(string hostname_, string service_, string username_, string
|
|||||||
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Turn),
|
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Turn),
|
||||||
username(std::move(username_)), password(std::move(password_)), relayType(relayType_) {}
|
username(std::move(username_)), password(std::move(password_)), relayType(relayType_) {}
|
||||||
|
|
||||||
|
ProxyServer::ProxyServer(Type type_, string ip_, uint16_t port_, string username_, string password_)
|
||||||
|
: type(type_), ip(ip_), port(port_), username(username_), password(password_) {}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
@ -21,9 +21,16 @@
|
|||||||
#include "peerconnection.hpp"
|
#include "peerconnection.hpp"
|
||||||
#include "sctptransport.hpp"
|
#include "sctptransport.hpp"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
|
using std::weak_ptr;
|
||||||
|
|
||||||
// Messages for the DataChannel establishment protocol
|
// Messages for the DataChannel establishment protocol
|
||||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09
|
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09
|
||||||
@ -60,16 +67,16 @@ struct CloseMessage {
|
|||||||
|
|
||||||
const size_t RECV_QUEUE_LIMIT = 1024 * 1024; // 1 MiB
|
const size_t RECV_QUEUE_LIMIT = 1024 * 1024; // 1 MiB
|
||||||
|
|
||||||
DataChannel::DataChannel(shared_ptr<PeerConnection> pc, unsigned int stream, string label,
|
DataChannel::DataChannel(weak_ptr<PeerConnection> pc, unsigned int stream, string label,
|
||||||
string protocol, Reliability reliability)
|
string protocol, Reliability reliability)
|
||||||
: mPeerConnection(std::move(pc)), mStream(stream), mLabel(std::move(label)),
|
: mPeerConnection(pc), mStream(stream), mLabel(std::move(label)),
|
||||||
mProtocol(std::move(protocol)),
|
mProtocol(std::move(protocol)),
|
||||||
mReliability(std::make_shared<Reliability>(std::move(reliability))),
|
mReliability(std::make_shared<Reliability>(std::move(reliability))),
|
||||||
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
||||||
|
|
||||||
DataChannel::DataChannel(shared_ptr<PeerConnection> pc, shared_ptr<SctpTransport> transport,
|
DataChannel::DataChannel(weak_ptr<PeerConnection> pc, weak_ptr<SctpTransport> transport,
|
||||||
unsigned int stream)
|
unsigned int stream)
|
||||||
: mPeerConnection(std::move(pc)), mSctpTransport(transport), mStream(stream),
|
: mPeerConnection(pc), mSctpTransport(transport), mStream(stream),
|
||||||
mReliability(std::make_shared<Reliability>()),
|
mReliability(std::make_shared<Reliability>()),
|
||||||
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
|
||||||
|
|
||||||
@ -77,17 +84,29 @@ DataChannel::~DataChannel() {
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned int DataChannel::stream() const { return mStream; }
|
||||||
|
|
||||||
|
string DataChannel::label() const { return mLabel; }
|
||||||
|
|
||||||
|
string DataChannel::protocol() const { return mProtocol; }
|
||||||
|
|
||||||
|
Reliability DataChannel::reliability() const { return *mReliability; }
|
||||||
|
|
||||||
void DataChannel::close() {
|
void DataChannel::close() {
|
||||||
if (mIsOpen.exchange(false) && mSctpTransport)
|
|
||||||
mSctpTransport->reset(mStream);
|
|
||||||
mIsClosed = true;
|
mIsClosed = true;
|
||||||
|
if (mIsOpen.exchange(false))
|
||||||
|
if (auto transport = mSctpTransport.lock())
|
||||||
|
transport->close(mStream);
|
||||||
|
|
||||||
mSctpTransport.reset();
|
mSctpTransport.reset();
|
||||||
|
resetCallbacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataChannel::remoteClose() {
|
void DataChannel::remoteClose() {
|
||||||
mIsOpen = false;
|
|
||||||
if (!mIsClosed.exchange(true))
|
if (!mIsClosed.exchange(true))
|
||||||
triggerClosed();
|
triggerClosed();
|
||||||
|
|
||||||
|
mIsOpen = false;
|
||||||
mSctpTransport.reset();
|
mSctpTransport.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +140,9 @@ std::optional<std::variant<binary, string>> DataChannel::receive() {
|
|||||||
string(reinterpret_cast<const char *>(message->data()), message->size()));
|
string(reinterpret_cast<const char *>(message->data()), message->size()));
|
||||||
case Message::Binary:
|
case Message::Binary:
|
||||||
return std::make_optional(std::move(*message));
|
return std::make_optional(std::move(*message));
|
||||||
|
default:
|
||||||
|
// Ignore
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,27 +153,20 @@ bool DataChannel::isOpen(void) const { return mIsOpen; }
|
|||||||
|
|
||||||
bool DataChannel::isClosed(void) const { return mIsClosed; }
|
bool DataChannel::isClosed(void) const { return mIsClosed; }
|
||||||
|
|
||||||
size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
|
|
||||||
|
|
||||||
size_t DataChannel::maxMessageSize() const {
|
size_t DataChannel::maxMessageSize() const {
|
||||||
size_t max = DEFAULT_MAX_MESSAGE_SIZE;
|
size_t max = DEFAULT_MAX_MESSAGE_SIZE;
|
||||||
if (auto description = mPeerConnection->remoteDescription())
|
if (auto pc = mPeerConnection.lock())
|
||||||
|
if (auto description = pc->remoteDescription())
|
||||||
if (auto maxMessageSize = description->maxMessageSize())
|
if (auto maxMessageSize = description->maxMessageSize())
|
||||||
return *maxMessageSize > 0 ? *maxMessageSize : LOCAL_MAX_MESSAGE_SIZE;
|
return *maxMessageSize > 0 ? *maxMessageSize : LOCAL_MAX_MESSAGE_SIZE;
|
||||||
|
|
||||||
return std::min(max, LOCAL_MAX_MESSAGE_SIZE);
|
return std::min(max, LOCAL_MAX_MESSAGE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int DataChannel::stream() const { return mStream; }
|
size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
|
||||||
|
|
||||||
string DataChannel::label() const { return mLabel; }
|
void DataChannel::open(shared_ptr<SctpTransport> transport) {
|
||||||
|
mSctpTransport = transport;
|
||||||
string DataChannel::protocol() const { return mProtocol; }
|
|
||||||
|
|
||||||
Reliability DataChannel::reliability() const { return *mReliability; }
|
|
||||||
|
|
||||||
void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
|
|
||||||
mSctpTransport = sctpTransport;
|
|
||||||
|
|
||||||
uint8_t channelType = static_cast<uint8_t>(mReliability->type);
|
uint8_t channelType = static_cast<uint8_t>(mReliability->type);
|
||||||
if (mReliability->unordered)
|
if (mReliability->unordered)
|
||||||
@ -178,20 +193,24 @@ void DataChannel::open(shared_ptr<SctpTransport> sctpTransport) {
|
|||||||
std::copy(mLabel.begin(), mLabel.end(), end);
|
std::copy(mLabel.begin(), mLabel.end(), end);
|
||||||
std::copy(mProtocol.begin(), mProtocol.end(), end + mLabel.size());
|
std::copy(mProtocol.begin(), mProtocol.end(), end + mLabel.size());
|
||||||
|
|
||||||
mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DataChannel::outgoing(mutable_message_ptr message) {
|
bool DataChannel::outgoing(mutable_message_ptr message) {
|
||||||
if (mIsClosed || !mSctpTransport)
|
if (mIsClosed)
|
||||||
throw std::runtime_error("DataChannel is closed");
|
throw std::runtime_error("DataChannel is closed");
|
||||||
|
|
||||||
if (message->size() > maxMessageSize())
|
if (message->size() > maxMessageSize())
|
||||||
throw std::runtime_error("Message size exceeds limit");
|
throw std::runtime_error("Message size exceeds limit");
|
||||||
|
|
||||||
|
auto transport = mSctpTransport.lock();
|
||||||
|
if (!transport)
|
||||||
|
throw std::runtime_error("DataChannel has no transport");
|
||||||
|
|
||||||
// Before the ACK has been received on a DataChannel, all messages must be sent ordered
|
// Before the ACK has been received on a DataChannel, all messages must be sent ordered
|
||||||
message->reliability = mIsOpen ? mReliability : nullptr;
|
message->reliability = mIsOpen ? mReliability : nullptr;
|
||||||
message->stream = mStream;
|
message->stream = mStream;
|
||||||
return mSctpTransport->send(message);
|
return transport->send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataChannel::incoming(message_ptr message) {
|
void DataChannel::incoming(message_ptr message) {
|
||||||
@ -230,6 +249,10 @@ void DataChannel::incoming(message_ptr message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DataChannel::processOpenMessage(message_ptr message) {
|
void DataChannel::processOpenMessage(message_ptr message) {
|
||||||
|
auto transport = mSctpTransport.lock();
|
||||||
|
if (!transport)
|
||||||
|
throw std::runtime_error("DataChannel has no transport");
|
||||||
|
|
||||||
if (message->size() < sizeof(OpenMessage))
|
if (message->size() < sizeof(OpenMessage))
|
||||||
throw std::invalid_argument("DataChannel open message too small");
|
throw std::invalid_argument("DataChannel open message too small");
|
||||||
|
|
||||||
@ -266,7 +289,7 @@ void DataChannel::processOpenMessage(message_ptr message) {
|
|||||||
auto &ack = *reinterpret_cast<AckMessage *>(buffer.data());
|
auto &ack = *reinterpret_cast<AckMessage *>(buffer.data());
|
||||||
ack.type = MESSAGE_ACK;
|
ack.type = MESSAGE_ACK;
|
||||||
|
|
||||||
mSctpTransport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
|
||||||
|
|
||||||
mIsOpen = true;
|
mIsOpen = true;
|
||||||
triggerOpen();
|
triggerOpen();
|
||||||
|
@ -55,17 +55,22 @@ static bool check_gnutls(int ret, const string &message = "GnuTLS error") {
|
|||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
|
void DtlsTransport::Init() {
|
||||||
verifier_callback verifierCallback,
|
// Nothing to do
|
||||||
state_callback stateChangeCallback)
|
}
|
||||||
|
|
||||||
|
void DtlsTransport::Cleanup() {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
|
||||||
|
verifier_callback verifierCallback, state_callback stateChangeCallback)
|
||||||
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
|
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
|
||||||
mVerifierCallback(std::move(verifierCallback)),
|
mVerifierCallback(std::move(verifierCallback)),
|
||||||
mStateChangeCallback(std::move(stateChangeCallback)) {
|
mStateChangeCallback(std::move(stateChangeCallback)) {
|
||||||
|
|
||||||
PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)";
|
PLOG_DEBUG << "Initializing DTLS transport (GnuTLS)";
|
||||||
|
|
||||||
gnutls_certificate_set_verify_function(mCertificate->credentials(), CertificateCallback);
|
|
||||||
|
|
||||||
bool active = lower->role() == Description::Role::Active;
|
bool active = lower->role() == Description::Role::Active;
|
||||||
unsigned int flags = GNUTLS_DATAGRAM | (active ? GNUTLS_CLIENT : GNUTLS_SERVER);
|
unsigned int flags = GNUTLS_DATAGRAM | (active ? GNUTLS_CLIENT : GNUTLS_SERVER);
|
||||||
check_gnutls(gnutls_init(&mSession, flags));
|
check_gnutls(gnutls_init(&mSession, flags));
|
||||||
@ -78,12 +83,14 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
|||||||
check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
|
check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
|
||||||
"Unable to set TLS priorities");
|
"Unable to set TLS priorities");
|
||||||
|
|
||||||
|
gnutls_certificate_set_verify_function(mCertificate->credentials(), CertificateCallback);
|
||||||
check_gnutls(
|
check_gnutls(
|
||||||
gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate->credentials()));
|
gnutls_credentials_set(mSession, GNUTLS_CRD_CERTIFICATE, mCertificate->credentials()));
|
||||||
|
|
||||||
gnutls_dtls_set_mtu(mSession, 1280 - 40 - 8); // min MTU over UDP/IPv6 (only for handshake)
|
gnutls_dtls_set_timeouts(mSession,
|
||||||
gnutls_dtls_set_timeouts(mSession, 400, 60000);
|
1000, // 1s retransmission timeout recommended by RFC 6347
|
||||||
gnutls_handshake_set_timeout(mSession, 60000);
|
30000); // 30s total timeout
|
||||||
|
gnutls_handshake_set_timeout(mSession, 30000);
|
||||||
|
|
||||||
gnutls_session_set_ptr(mSession, this);
|
gnutls_session_set_ptr(mSession, this);
|
||||||
gnutls_transport_set_ptr(mSession, this);
|
gnutls_transport_set_ptr(mSession, this);
|
||||||
@ -92,6 +99,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
|||||||
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
|
gnutls_transport_set_pull_timeout_function(mSession, TimeoutCallback);
|
||||||
|
|
||||||
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
||||||
|
registerIncoming();
|
||||||
}
|
}
|
||||||
|
|
||||||
DtlsTransport::~DtlsTransport() {
|
DtlsTransport::~DtlsTransport() {
|
||||||
@ -102,15 +110,14 @@ DtlsTransport::~DtlsTransport() {
|
|||||||
|
|
||||||
DtlsTransport::State DtlsTransport::state() const { return mState; }
|
DtlsTransport::State DtlsTransport::state() const { return mState; }
|
||||||
|
|
||||||
void DtlsTransport::stop() {
|
bool DtlsTransport::stop() {
|
||||||
Transport::stop();
|
if (!Transport::stop())
|
||||||
|
return false;
|
||||||
|
|
||||||
if (mRecvThread.joinable()) {
|
|
||||||
PLOG_DEBUG << "Stopping DTLS recv thread";
|
PLOG_DEBUG << "Stopping DTLS recv thread";
|
||||||
mIncomingQueue.stop();
|
mIncomingQueue.stop();
|
||||||
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
|
|
||||||
mRecvThread.join();
|
mRecvThread.join();
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DtlsTransport::send(message_ptr message) {
|
bool DtlsTransport::send(message_ptr message) {
|
||||||
@ -131,10 +138,13 @@ bool DtlsTransport::send(message_ptr message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DtlsTransport::incoming(message_ptr message) {
|
void DtlsTransport::incoming(message_ptr message) {
|
||||||
if (message)
|
if (!message) {
|
||||||
mIncomingQueue.push(message);
|
|
||||||
else
|
|
||||||
mIncomingQueue.stop();
|
mIncomingQueue.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PLOG_VERBOSE << "Incoming size=" << message->size();
|
||||||
|
mIncomingQueue.push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtlsTransport::changeState(State state) {
|
void DtlsTransport::changeState(State state) {
|
||||||
@ -148,6 +158,7 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
// Handshake loop
|
// Handshake loop
|
||||||
try {
|
try {
|
||||||
changeState(State::Connecting);
|
changeState(State::Connecting);
|
||||||
|
gnutls_dtls_set_mtu(mSession, 1280 - 40 - 8); // min MTU over UDP/IPv6
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
do {
|
do {
|
||||||
@ -171,6 +182,7 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
|
|
||||||
// Receive loop
|
// Receive loop
|
||||||
try {
|
try {
|
||||||
|
PLOG_INFO << "DTLS handshake done";
|
||||||
changeState(State::Connected);
|
changeState(State::Connected);
|
||||||
|
|
||||||
const size_t bufferSize = maxMtu;
|
const size_t bufferSize = maxMtu;
|
||||||
@ -203,6 +215,8 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
PLOG_ERROR << "DTLS recv: " << e.what();
|
PLOG_ERROR << "DTLS recv: " << e.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gnutls_bye(mSession, GNUTLS_SHUT_RDWR);
|
||||||
|
|
||||||
PLOG_INFO << "DTLS disconnected";
|
PLOG_INFO << "DTLS disconnected";
|
||||||
changeState(State::Disconnected);
|
changeState(State::Disconnected);
|
||||||
recv(nullptr);
|
recv(nullptr);
|
||||||
@ -262,10 +276,8 @@ ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size
|
|||||||
|
|
||||||
int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
||||||
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
||||||
if (ms != GNUTLS_INDEFINITE_TIMEOUT)
|
t->mIncomingQueue.wait(ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms))
|
||||||
t->mIncomingQueue.wait(milliseconds(ms));
|
: nullopt);
|
||||||
else
|
|
||||||
t->mIncomingQueue.wait();
|
|
||||||
return !t->mIncomingQueue.empty() ? 1 : 0;
|
return !t->mIncomingQueue.empty() ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +335,7 @@ BIO_METHOD *DtlsTransport::BioMethods = NULL;
|
|||||||
int DtlsTransport::TransportExIndex = -1;
|
int DtlsTransport::TransportExIndex = -1;
|
||||||
std::mutex DtlsTransport::GlobalMutex;
|
std::mutex DtlsTransport::GlobalMutex;
|
||||||
|
|
||||||
void DtlsTransport::GlobalInit() {
|
void DtlsTransport::Init() {
|
||||||
std::lock_guard lock(GlobalMutex);
|
std::lock_guard lock(GlobalMutex);
|
||||||
if (!BioMethods) {
|
if (!BioMethods) {
|
||||||
BioMethods = BIO_meth_new(BIO_TYPE_BIO, "DTLS writer");
|
BioMethods = BIO_meth_new(BIO_TYPE_BIO, "DTLS writer");
|
||||||
@ -339,6 +351,10 @@ void DtlsTransport::GlobalInit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DtlsTransport::Cleanup() {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
|
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
|
||||||
verifier_callback verifierCallback, state_callback stateChangeCallback)
|
verifier_callback verifierCallback, state_callback stateChangeCallback)
|
||||||
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
|
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
|
||||||
@ -346,7 +362,6 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
|||||||
mStateChangeCallback(std::move(stateChangeCallback)) {
|
mStateChangeCallback(std::move(stateChangeCallback)) {
|
||||||
|
|
||||||
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
|
PLOG_DEBUG << "Initializing DTLS transport (OpenSSL)";
|
||||||
GlobalInit();
|
|
||||||
|
|
||||||
if (!(mCtx = SSL_CTX_new(DTLS_method())))
|
if (!(mCtx = SSL_CTX_new(DTLS_method())))
|
||||||
throw std::runtime_error("Unable to create SSL context");
|
throw std::runtime_error("Unable to create SSL context");
|
||||||
@ -376,7 +391,6 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
|||||||
throw std::runtime_error("Unable to create SSL instance");
|
throw std::runtime_error("Unable to create SSL instance");
|
||||||
|
|
||||||
SSL_set_ex_data(mSsl, TransportExIndex, this);
|
SSL_set_ex_data(mSsl, TransportExIndex, this);
|
||||||
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
|
|
||||||
|
|
||||||
if (lower->role() == Description::Role::Active)
|
if (lower->role() == Description::Role::Active)
|
||||||
SSL_set_connect_state(mSsl);
|
SSL_set_connect_state(mSsl);
|
||||||
@ -396,6 +410,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
|||||||
SSL_set_tmp_ecdh(mSsl, ecdh.get());
|
SSL_set_tmp_ecdh(mSsl, ecdh.get());
|
||||||
|
|
||||||
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
|
||||||
|
registerIncoming();
|
||||||
}
|
}
|
||||||
|
|
||||||
DtlsTransport::~DtlsTransport() {
|
DtlsTransport::~DtlsTransport() {
|
||||||
@ -405,16 +420,15 @@ DtlsTransport::~DtlsTransport() {
|
|||||||
SSL_CTX_free(mCtx);
|
SSL_CTX_free(mCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtlsTransport::stop() {
|
bool DtlsTransport::stop() {
|
||||||
Transport::stop();
|
if (!Transport::stop())
|
||||||
|
return false;
|
||||||
|
|
||||||
if (mRecvThread.joinable()) {
|
|
||||||
PLOG_DEBUG << "Stopping DTLS recv thread";
|
PLOG_DEBUG << "Stopping DTLS recv thread";
|
||||||
mIncomingQueue.stop();
|
mIncomingQueue.stop();
|
||||||
mRecvThread.join();
|
mRecvThread.join();
|
||||||
|
|
||||||
SSL_shutdown(mSsl);
|
SSL_shutdown(mSsl);
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
DtlsTransport::State DtlsTransport::state() const { return mState; }
|
DtlsTransport::State DtlsTransport::state() const { return mState; }
|
||||||
@ -432,10 +446,13 @@ bool DtlsTransport::send(message_ptr message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DtlsTransport::incoming(message_ptr message) {
|
void DtlsTransport::incoming(message_ptr message) {
|
||||||
if (message)
|
if (!message) {
|
||||||
mIncomingQueue.push(message);
|
|
||||||
else
|
|
||||||
mIncomingQueue.stop();
|
mIncomingQueue.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PLOG_VERBOSE << "Incoming size=" << message->size();
|
||||||
|
mIncomingQueue.push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtlsTransport::changeState(State state) {
|
void DtlsTransport::changeState(State state) {
|
||||||
@ -447,35 +464,71 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
const size_t maxMtu = 4096;
|
const size_t maxMtu = 4096;
|
||||||
try {
|
try {
|
||||||
changeState(State::Connecting);
|
changeState(State::Connecting);
|
||||||
|
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
|
||||||
|
|
||||||
SSL_do_handshake(mSsl);
|
// Initiate the handshake
|
||||||
|
int ret = SSL_do_handshake(mSsl);
|
||||||
|
check_openssl_ret(mSsl, ret, "Handshake failed");
|
||||||
|
|
||||||
const size_t bufferSize = maxMtu;
|
const size_t bufferSize = maxMtu;
|
||||||
byte buffer[bufferSize];
|
byte buffer[bufferSize];
|
||||||
while (auto next = mIncomingQueue.pop()) {
|
while (true) {
|
||||||
auto message = *next;
|
// Process pending messages
|
||||||
|
while (!mIncomingQueue.empty()) {
|
||||||
|
auto message = *mIncomingQueue.pop();
|
||||||
BIO_write(mInBio, message->data(), message->size());
|
BIO_write(mInBio, message->data(), message->size());
|
||||||
|
|
||||||
|
if (mState == State::Connecting) {
|
||||||
|
// Continue the handshake
|
||||||
|
int ret = SSL_do_handshake(mSsl);
|
||||||
|
if (!check_openssl_ret(mSsl, ret, "Handshake failed"))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (SSL_is_init_finished(mSsl)) {
|
||||||
|
// RFC 8261: DTLS MUST support sending messages larger than the current path
|
||||||
|
// MTU See https://tools.ietf.org/html/rfc8261#section-5
|
||||||
|
SSL_set_mtu(mSsl, maxMtu + 1);
|
||||||
|
|
||||||
|
PLOG_INFO << "DTLS handshake done";
|
||||||
|
changeState(State::Connected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
int ret = SSL_read(mSsl, buffer, bufferSize);
|
int ret = SSL_read(mSsl, buffer, bufferSize);
|
||||||
if (!check_openssl_ret(mSsl, ret))
|
if (!check_openssl_ret(mSsl, ret))
|
||||||
break;
|
break;
|
||||||
|
if (ret > 0)
|
||||||
|
recv(make_message(buffer, buffer + ret));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto decrypted = ret > 0 ? make_message(buffer, buffer + ret) : nullptr;
|
// No more messages pending, retransmit and rearm timeout if connecting
|
||||||
|
std::optional<milliseconds> duration;
|
||||||
if (mState == State::Connecting) {
|
if (mState == State::Connecting) {
|
||||||
if (unsigned long err = ERR_get_error())
|
// Warning: This function breaks the usual return value convention
|
||||||
throw std::runtime_error("handshake failed: " + openssl_error_string(err));
|
int ret = DTLSv1_handle_timeout(mSsl);
|
||||||
|
if (ret < 0) {
|
||||||
|
throw std::runtime_error("Handshake timeout"); // write BIO can't fail
|
||||||
|
} else if (ret > 0) {
|
||||||
|
LOG_VERBOSE << "OpenSSL did DTLS retransmit";
|
||||||
|
}
|
||||||
|
|
||||||
if (SSL_is_init_finished(mSsl)) {
|
struct timeval timeout = {};
|
||||||
changeState(State::Connected);
|
if (mState == State::Connecting && DTLSv1_get_timeout(mSsl, &timeout)) {
|
||||||
|
duration = milliseconds(timeout.tv_sec * 1000 + timeout.tv_usec / 1000);
|
||||||
// RFC 8261: DTLS MUST support sending messages larger than the current path MTU
|
// Also handle handshake timeout manually because OpenSSL actually doesn't...
|
||||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
// OpenSSL backs off exponentially in base 2 starting from the recommended 1s
|
||||||
SSL_set_mtu(mSsl, maxMtu + 1);
|
// so this allows for 5 retransmissions and fails after roughly 30s.
|
||||||
|
if (duration > 30s) {
|
||||||
|
throw std::runtime_error("Handshake timeout");
|
||||||
|
} else {
|
||||||
|
LOG_VERBOSE << "OpenSSL DTLS retransmit timeout is " << duration->count()
|
||||||
|
<< "ms";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decrypted)
|
if (!mIncomingQueue.wait(duration))
|
||||||
recv(decrypted);
|
break; // queue is stopped
|
||||||
}
|
}
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_ERROR << "DTLS recv: " << e.what();
|
PLOG_ERROR << "DTLS recv: " << e.what();
|
||||||
@ -486,7 +539,7 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
changeState(State::Disconnected);
|
changeState(State::Disconnected);
|
||||||
recv(nullptr);
|
recv(nullptr);
|
||||||
} else {
|
} else {
|
||||||
PLOG_INFO << "DTLS handshake failed";
|
PLOG_ERROR << "DTLS handshake failed";
|
||||||
changeState(State::Failed);
|
changeState(State::Failed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -536,7 +589,8 @@ int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
|
|||||||
if (!transport)
|
if (!transport)
|
||||||
return -1;
|
return -1;
|
||||||
auto b = reinterpret_cast<const byte *>(in);
|
auto b = reinterpret_cast<const byte *>(in);
|
||||||
return transport->outgoing(make_message(b, b + inl)) ? inl : 0;
|
transport->outgoing(make_message(b, b + inl));
|
||||||
|
return inl; // can't fail
|
||||||
}
|
}
|
||||||
|
|
||||||
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) {
|
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) {
|
||||||
|
@ -43,18 +43,21 @@ class IceTransport;
|
|||||||
|
|
||||||
class DtlsTransport : public Transport {
|
class DtlsTransport : public Transport {
|
||||||
public:
|
public:
|
||||||
|
static void Init();
|
||||||
|
static void Cleanup();
|
||||||
|
|
||||||
enum class State { Disconnected, Connecting, Connected, Failed };
|
enum class State { Disconnected, Connecting, Connected, Failed };
|
||||||
|
|
||||||
using verifier_callback = std::function<bool(const std::string &fingerprint)>;
|
using verifier_callback = std::function<bool(const std::string &fingerprint)>;
|
||||||
using state_callback = std::function<void(State state)>;
|
using state_callback = std::function<void(State state)>;
|
||||||
|
|
||||||
DtlsTransport(std::shared_ptr<IceTransport> lower, std::shared_ptr<Certificate> certificate,
|
DtlsTransport(std::shared_ptr<IceTransport> lower, certificate_ptr certificate,
|
||||||
verifier_callback verifierCallback, state_callback stateChangeCallback);
|
verifier_callback verifierCallback, state_callback stateChangeCallback);
|
||||||
~DtlsTransport();
|
~DtlsTransport();
|
||||||
|
|
||||||
State state() const;
|
State state() const;
|
||||||
|
|
||||||
void stop() override;
|
bool stop() override;
|
||||||
bool send(message_ptr message) override; // false if dropped
|
bool send(message_ptr message) override; // false if dropped
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -62,7 +65,7 @@ private:
|
|||||||
void changeState(State state);
|
void changeState(State state);
|
||||||
void runRecvLoop();
|
void runRecvLoop();
|
||||||
|
|
||||||
const std::shared_ptr<Certificate> mCertificate;
|
const certificate_ptr mCertificate;
|
||||||
|
|
||||||
Queue<message_ptr> mIncomingQueue;
|
Queue<message_ptr> mIncomingQueue;
|
||||||
std::atomic<State> mState;
|
std::atomic<State> mState;
|
||||||
@ -87,7 +90,6 @@ private:
|
|||||||
static int TransportExIndex;
|
static int TransportExIndex;
|
||||||
static std::mutex GlobalMutex;
|
static std::mutex GlobalMutex;
|
||||||
|
|
||||||
static void GlobalInit();
|
|
||||||
static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx);
|
static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx);
|
||||||
static void InfoCallback(const SSL *ssl, int where, int ret);
|
static void InfoCallback(const SSL *ssl, int where, int ret);
|
||||||
|
|
||||||
|
@ -18,10 +18,17 @@
|
|||||||
|
|
||||||
#include "icetransport.hpp"
|
#include "icetransport.hpp"
|
||||||
#include "configuration.hpp"
|
#include "configuration.hpp"
|
||||||
|
#include "transport.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#elif __linux__
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
@ -29,10 +36,6 @@
|
|||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <random>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
@ -70,7 +73,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||||
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
||||||
|
|
||||||
// Pick a STUN server
|
// Pick a STUN server (TURN support is not implemented in libjuice yet)
|
||||||
for (auto &server : servers) {
|
for (auto &server : servers) {
|
||||||
if (!server.hostname.empty() && server.type == IceServer::Type::Stun) {
|
if (!server.hostname.empty() && server.type == IceServer::Type::Stun) {
|
||||||
if (server.service.empty())
|
if (server.service.empty())
|
||||||
@ -84,7 +87,12 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TURN support is not implemented yet
|
// Port range
|
||||||
|
if (config.portRangeBegin > 1024 ||
|
||||||
|
(config.portRangeEnd != 0 && config.portRangeEnd != 65535)) {
|
||||||
|
jconfig.local_port_range_begin = config.portRangeBegin;
|
||||||
|
jconfig.local_port_range_end = config.portRangeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
// Create agent
|
// Create agent
|
||||||
mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy);
|
mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy);
|
||||||
@ -94,8 +102,8 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
|
|
||||||
IceTransport::~IceTransport() { stop(); }
|
IceTransport::~IceTransport() { stop(); }
|
||||||
|
|
||||||
void IceTransport::stop() {
|
bool IceTransport::stop() {
|
||||||
// Nothing to do
|
return Transport::stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
Description::Role IceTransport::role() const { return mRole; }
|
Description::Role IceTransport::role() const { return mRole; }
|
||||||
@ -160,12 +168,6 @@ bool IceTransport::send(message_ptr message) {
|
|||||||
return outgoing(message);
|
return outgoing(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IceTransport::incoming(message_ptr message) { recv(message); }
|
|
||||||
|
|
||||||
void IceTransport::incoming(const byte *data, int size) {
|
|
||||||
incoming(make_message(data, data + size));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IceTransport::outgoing(message_ptr message) {
|
bool IceTransport::outgoing(message_ptr message) {
|
||||||
return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()),
|
return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()),
|
||||||
message->size()) >= 0;
|
message->size()) >= 0;
|
||||||
@ -222,7 +224,9 @@ void IceTransport::RecvCallback(juice_agent_t *agent, const char *data, size_t s
|
|||||||
void *user_ptr) {
|
void *user_ptr) {
|
||||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||||
try {
|
try {
|
||||||
iceTransport->incoming(reinterpret_cast<const byte *>(data), size);
|
PLOG_VERBOSE << "Incoming size=" << size;
|
||||||
|
auto b = reinterpret_cast<const byte *>(data);
|
||||||
|
iceTransport->incoming(make_message(b, b + size));
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_WARNING << e.what();
|
PLOG_WARNING << e.what();
|
||||||
}
|
}
|
||||||
@ -243,11 +247,8 @@ void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
|
|||||||
case JUICE_LOG_LEVEL_INFO:
|
case JUICE_LOG_LEVEL_INFO:
|
||||||
severity = plog::info;
|
severity = plog::info;
|
||||||
break;
|
break;
|
||||||
case JUICE_LOG_LEVEL_DEBUG:
|
|
||||||
severity = plog::debug;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
severity = plog::verbose;
|
severity = plog::verbose; // libjuice debug as verbose
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
PLOG(severity) << "juice: " << message;
|
PLOG(severity) << "juice: " << message;
|
||||||
@ -310,6 +311,18 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
|
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
|
||||||
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr);
|
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr);
|
||||||
|
|
||||||
|
// Proxy
|
||||||
|
if (config.proxyServer.has_value()) {
|
||||||
|
ProxyServer proxyServer = config.proxyServer.value();
|
||||||
|
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-type", proxyServer.type, nullptr);
|
||||||
|
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-ip", proxyServer.ip.c_str(), nullptr);
|
||||||
|
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-port", proxyServer.port, nullptr);
|
||||||
|
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-username", proxyServer.username.c_str(),
|
||||||
|
nullptr);
|
||||||
|
g_object_set(G_OBJECT(mNiceAgent.get()), "proxy-password", proxyServer.password.c_str(),
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
// Randomize order
|
// Randomize order
|
||||||
std::vector<IceServer> servers = config.iceServers;
|
std::vector<IceServer> servers = config.iceServers;
|
||||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||||
@ -424,16 +437,22 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
|
|
||||||
IceTransport::~IceTransport() { stop(); }
|
IceTransport::~IceTransport() { stop(); }
|
||||||
|
|
||||||
void IceTransport::stop() {
|
bool IceTransport::stop() {
|
||||||
if (mTimeoutId) {
|
if (mTimeoutId) {
|
||||||
g_source_remove(mTimeoutId);
|
g_source_remove(mTimeoutId);
|
||||||
mTimeoutId = 0;
|
mTimeoutId = 0;
|
||||||
}
|
}
|
||||||
if (mMainLoopThread.joinable()) {
|
|
||||||
|
if (!Transport::stop())
|
||||||
|
return false;
|
||||||
|
|
||||||
PLOG_DEBUG << "Stopping ICE thread";
|
PLOG_DEBUG << "Stopping ICE thread";
|
||||||
|
nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(mMainLoop.get()),
|
||||||
|
NULL, NULL);
|
||||||
|
nice_agent_remove_stream(mNiceAgent.get(), mStreamId);
|
||||||
g_main_loop_quit(mMainLoop.get());
|
g_main_loop_quit(mMainLoop.get());
|
||||||
mMainLoopThread.join();
|
mMainLoopThread.join();
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Description::Role IceTransport::role() const { return mRole; }
|
Description::Role IceTransport::role() const { return mRole; }
|
||||||
@ -517,12 +536,6 @@ bool IceTransport::send(message_ptr message) {
|
|||||||
return outgoing(message);
|
return outgoing(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IceTransport::incoming(message_ptr message) { recv(message); }
|
|
||||||
|
|
||||||
void IceTransport::incoming(const byte *data, int size) {
|
|
||||||
incoming(make_message(data, data + size));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IceTransport::outgoing(message_ptr message) {
|
bool IceTransport::outgoing(message_ptr message) {
|
||||||
return nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(),
|
return nice_agent_send(mNiceAgent.get(), mStreamId, 1, message->size(),
|
||||||
reinterpret_cast<const char *>(message->data())) >= 0;
|
reinterpret_cast<const char *>(message->data())) >= 0;
|
||||||
@ -610,7 +623,9 @@ void IceTransport::RecvCallback(NiceAgent *agent, guint streamId, guint componen
|
|||||||
gchar *buf, gpointer userData) {
|
gchar *buf, gpointer userData) {
|
||||||
auto iceTransport = static_cast<rtc::IceTransport *>(userData);
|
auto iceTransport = static_cast<rtc::IceTransport *>(userData);
|
||||||
try {
|
try {
|
||||||
iceTransport->incoming(reinterpret_cast<byte *>(buf), len);
|
PLOG_VERBOSE << "Incoming size=" << len;
|
||||||
|
auto b = reinterpret_cast<byte *>(buf);
|
||||||
|
iceTransport->incoming(make_message(b, b + len));
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_WARNING << e.what();
|
PLOG_WARNING << e.what();
|
||||||
}
|
}
|
||||||
@ -646,6 +661,58 @@ void IceTransport::LogCallback(const gchar *logDomain, GLogLevelFlags logLevel,
|
|||||||
PLOG(severity) << "nice: " << message;
|
PLOG(severity) << "nice: " << message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IceTransport::getSelectedCandidatePair(CandidateInfo *localInfo, CandidateInfo *remoteInfo) {
|
||||||
|
NiceCandidate *local, *remote;
|
||||||
|
gboolean result = nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char ipaddr[INET6_ADDRSTRLEN];
|
||||||
|
nice_address_to_string(&local->addr, ipaddr);
|
||||||
|
localInfo->address = std::string(ipaddr);
|
||||||
|
localInfo->port = nice_address_get_port(&local->addr);
|
||||||
|
localInfo->type = IceTransport::NiceTypeToCandidateType(local->type);
|
||||||
|
localInfo->transportType =
|
||||||
|
IceTransport::NiceTransportTypeToCandidateTransportType(local->transport);
|
||||||
|
|
||||||
|
nice_address_to_string(&remote->addr, ipaddr);
|
||||||
|
remoteInfo->address = std::string(ipaddr);
|
||||||
|
remoteInfo->port = nice_address_get_port(&remote->addr);
|
||||||
|
remoteInfo->type = IceTransport::NiceTypeToCandidateType(remote->type);
|
||||||
|
remoteInfo->transportType =
|
||||||
|
IceTransport::NiceTransportTypeToCandidateTransportType(remote->transport);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CandidateType IceTransport::NiceTypeToCandidateType(NiceCandidateType type) {
|
||||||
|
switch (type) {
|
||||||
|
case NiceCandidateType::NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
|
||||||
|
return CandidateType::PeerReflexive;
|
||||||
|
case NiceCandidateType::NICE_CANDIDATE_TYPE_RELAYED:
|
||||||
|
return CandidateType::Relayed;
|
||||||
|
case NiceCandidateType::NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
|
||||||
|
return CandidateType::ServerReflexive;
|
||||||
|
default:
|
||||||
|
return CandidateType::Host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CandidateTransportType
|
||||||
|
IceTransport::NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type) {
|
||||||
|
switch (type) {
|
||||||
|
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE:
|
||||||
|
return CandidateTransportType::TcpActive;
|
||||||
|
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE:
|
||||||
|
return CandidateTransportType::TcpPassive;
|
||||||
|
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_SO:
|
||||||
|
return CandidateTransportType::TcpSo;
|
||||||
|
default:
|
||||||
|
return CandidateTransportType::Udp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -56,6 +56,8 @@ public:
|
|||||||
Completed = NICE_COMPONENT_STATE_READY,
|
Completed = NICE_COMPONENT_STATE_READY,
|
||||||
Failed = NICE_COMPONENT_STATE_FAILED,
|
Failed = NICE_COMPONENT_STATE_FAILED,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
|
||||||
#endif
|
#endif
|
||||||
enum class GatheringState { New = 0, InProgress = 1, Complete = 2 };
|
enum class GatheringState { New = 0, InProgress = 1, Complete = 2 };
|
||||||
|
|
||||||
@ -79,12 +81,10 @@ public:
|
|||||||
std::optional<string> getLocalAddress() const;
|
std::optional<string> getLocalAddress() const;
|
||||||
std::optional<string> getRemoteAddress() const;
|
std::optional<string> getRemoteAddress() const;
|
||||||
|
|
||||||
void stop() override;
|
bool stop() override;
|
||||||
bool send(message_ptr message) override; // false if dropped
|
bool send(message_ptr message) override; // false if dropped
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void incoming(message_ptr message) override;
|
|
||||||
void incoming(const byte *data, int size);
|
|
||||||
bool outgoing(message_ptr message) override;
|
bool outgoing(message_ptr message) override;
|
||||||
|
|
||||||
void changeState(State state);
|
void changeState(State state);
|
||||||
@ -133,6 +133,8 @@ private:
|
|||||||
static gboolean TimeoutCallback(gpointer userData);
|
static gboolean TimeoutCallback(gpointer userData);
|
||||||
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
|
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
|
static const CandidateType NiceTypeToCandidateType(NiceCandidateType type);
|
||||||
|
static const CandidateTransportType NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type);
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
88
src/init.cpp
Normal file
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 "sctptransport.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
@ -32,34 +33,34 @@ using namespace std::placeholders;
|
|||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
using std::weak_ptr;
|
using std::weak_ptr;
|
||||||
|
|
||||||
|
template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t, Args &&... _args) {
|
||||||
|
return [bound = std::bind(f, t, _args...), weak_this = t->weak_from_this()](auto &&... args) {
|
||||||
|
if (auto shared_this = weak_this.lock())
|
||||||
|
bound(args...);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F, typename T, typename... Args>
|
||||||
|
auto weak_bind_verifier(F &&f, T *t, Args &&... _args) {
|
||||||
|
return [bound = std::bind(f, t, _args...), weak_this = t->weak_from_this()](auto &&... args) {
|
||||||
|
if (auto shared_this = weak_this.lock())
|
||||||
|
return bound(args...);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
|
PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
|
||||||
|
|
||||||
PeerConnection::PeerConnection(const Configuration &config)
|
PeerConnection::PeerConnection(const Configuration &config)
|
||||||
: mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New) {}
|
: mConfig(config), mCertificate(make_certificate("libdatachannel")), mState(State::New),
|
||||||
|
mGatheringState(GatheringState::New) {}
|
||||||
|
|
||||||
PeerConnection::~PeerConnection() {
|
PeerConnection::~PeerConnection() { close(); }
|
||||||
changeState(State::Destroying);
|
|
||||||
close();
|
|
||||||
mSctpTransport.reset();
|
|
||||||
mDtlsTransport.reset();
|
|
||||||
mIceTransport.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeerConnection::close() {
|
void PeerConnection::close() {
|
||||||
// Close DataChannels
|
|
||||||
closeDataChannels();
|
closeDataChannels();
|
||||||
mDataChannels.clear();
|
closeTransports();
|
||||||
|
|
||||||
// Close Transports
|
|
||||||
for (int i = 0; i < 2; ++i) { // Make sure a transport wasn't spawn behind our back
|
|
||||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
|
||||||
transport->stop();
|
|
||||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
|
||||||
transport->stop();
|
|
||||||
if (auto transport = std::atomic_load(&mIceTransport))
|
|
||||||
transport->stop();
|
|
||||||
}
|
|
||||||
changeState(State::Closed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Configuration *PeerConnection::config() const { return &mConfig; }
|
const Configuration *PeerConnection::config() const { return &mConfig; }
|
||||||
@ -101,12 +102,16 @@ void PeerConnection::setRemoteDescription(Description description) {
|
|||||||
if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
|
if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
|
||||||
// Since we assumed passive role during DataChannel creation, we need to shift the
|
// Since we assumed passive role during DataChannel creation, we need to shift the
|
||||||
// stream numbers by one to shift them from odd to even.
|
// stream numbers by one to shift them from odd to even.
|
||||||
|
std::unique_lock lock(mDataChannelsMutex); // we are going to swap the container
|
||||||
decltype(mDataChannels) newDataChannels;
|
decltype(mDataChannels) newDataChannels;
|
||||||
iterateDataChannels([&](shared_ptr<DataChannel> channel) {
|
auto it = mDataChannels.begin();
|
||||||
|
while (it != mDataChannels.end()) {
|
||||||
|
auto channel = it->second.lock();
|
||||||
if (channel->stream() % 2 == 1)
|
if (channel->stream() % 2 == 1)
|
||||||
channel->mStream -= 1;
|
channel->mStream -= 1;
|
||||||
newDataChannels.emplace(channel->stream(), channel);
|
newDataChannels.emplace(channel->stream(), channel);
|
||||||
});
|
++it;
|
||||||
|
}
|
||||||
std::swap(mDataChannels, newDataChannels);
|
std::swap(mDataChannels, newDataChannels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,19 +163,7 @@ shared_ptr<DataChannel> PeerConnection::createDataChannel(const string &label,
|
|||||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
auto role = iceTransport ? iceTransport->role() : Description::Role::Passive;
|
auto role = iceTransport ? iceTransport->role() : Description::Role::Passive;
|
||||||
|
|
||||||
// The active side must use streams with even identifiers, whereas the passive side must use
|
auto channel = emplaceDataChannel(role, label, protocol, reliability);
|
||||||
// streams with odd identifiers.
|
|
||||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
|
|
||||||
unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
|
|
||||||
while (mDataChannels.find(stream) != mDataChannels.end()) {
|
|
||||||
stream += 2;
|
|
||||||
if (stream >= 65535)
|
|
||||||
throw std::runtime_error("Too many DataChannels");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto channel =
|
|
||||||
std::make_shared<DataChannel>(shared_from_this(), stream, label, protocol, reliability);
|
|
||||||
mDataChannels.insert(std::make_pair(stream, channel));
|
|
||||||
|
|
||||||
if (!iceTransport) {
|
if (!iceTransport) {
|
||||||
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
|
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
|
||||||
@ -211,13 +204,15 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
|
|||||||
|
|
||||||
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
|
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
|
||||||
try {
|
try {
|
||||||
std::lock_guard lock(mInitMutex);
|
|
||||||
if (auto transport = std::atomic_load(&mIceTransport))
|
if (auto transport = std::atomic_load(&mIceTransport))
|
||||||
return transport;
|
return transport;
|
||||||
|
|
||||||
auto transport = std::make_shared<IceTransport>(
|
auto transport = std::make_shared<IceTransport>(
|
||||||
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
|
mConfig, role, weak_bind(&PeerConnection::processLocalCandidate, this, _1),
|
||||||
[this](IceTransport::State state) {
|
[this, weak_this = weak_from_this()](IceTransport::State state) {
|
||||||
|
auto shared_this = weak_this.lock();
|
||||||
|
if (!shared_this)
|
||||||
|
return;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case IceTransport::State::Connecting:
|
case IceTransport::State::Connecting:
|
||||||
changeState(State::Connecting);
|
changeState(State::Connecting);
|
||||||
@ -236,7 +231,10 @@ shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[this](IceTransport::GatheringState state) {
|
[this, weak_this = weak_from_this()](IceTransport::GatheringState state) {
|
||||||
|
auto shared_this = weak_this.lock();
|
||||||
|
if (!shared_this)
|
||||||
|
return;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case IceTransport::GatheringState::InProgress:
|
case IceTransport::GatheringState::InProgress:
|
||||||
changeGatheringState(GatheringState::InProgress);
|
changeGatheringState(GatheringState::InProgress);
|
||||||
@ -250,8 +248,15 @@ shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
std::atomic_store(&mIceTransport, transport);
|
std::atomic_store(&mIceTransport, transport);
|
||||||
|
if (mState == State::Closed) {
|
||||||
|
mIceTransport.reset();
|
||||||
|
transport->stop();
|
||||||
|
throw std::runtime_error("Connection is closed");
|
||||||
|
}
|
||||||
return transport;
|
return transport;
|
||||||
|
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_ERROR << e.what();
|
PLOG_ERROR << e.what();
|
||||||
changeState(State::Failed);
|
changeState(State::Failed);
|
||||||
@ -261,14 +266,17 @@ shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role
|
|||||||
|
|
||||||
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
||||||
try {
|
try {
|
||||||
std::lock_guard lock(mInitMutex);
|
|
||||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
if (auto transport = std::atomic_load(&mDtlsTransport))
|
||||||
return transport;
|
return transport;
|
||||||
|
|
||||||
|
auto certificate = mCertificate.get();
|
||||||
auto lower = std::atomic_load(&mIceTransport);
|
auto lower = std::atomic_load(&mIceTransport);
|
||||||
auto transport = std::make_shared<DtlsTransport>(
|
auto transport = std::make_shared<DtlsTransport>(
|
||||||
lower, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
|
lower, certificate, weak_bind_verifier(&PeerConnection::checkFingerprint, this, _1),
|
||||||
[this](DtlsTransport::State state) {
|
[this, weak_this = weak_from_this()](DtlsTransport::State state) {
|
||||||
|
auto shared_this = weak_this.lock();
|
||||||
|
if (!shared_this)
|
||||||
|
return;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case DtlsTransport::State::Connected:
|
case DtlsTransport::State::Connected:
|
||||||
initSctpTransport();
|
initSctpTransport();
|
||||||
@ -284,8 +292,15 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
std::atomic_store(&mDtlsTransport, transport);
|
std::atomic_store(&mDtlsTransport, transport);
|
||||||
|
if (mState == State::Closed) {
|
||||||
|
mDtlsTransport.reset();
|
||||||
|
transport->stop();
|
||||||
|
throw std::runtime_error("Connection is closed");
|
||||||
|
}
|
||||||
return transport;
|
return transport;
|
||||||
|
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_ERROR << e.what();
|
PLOG_ERROR << e.what();
|
||||||
changeState(State::Failed);
|
changeState(State::Failed);
|
||||||
@ -295,16 +310,18 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
|||||||
|
|
||||||
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
||||||
try {
|
try {
|
||||||
std::lock_guard lock(mInitMutex);
|
|
||||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||||
return transport;
|
return transport;
|
||||||
|
|
||||||
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
||||||
auto lower = std::atomic_load(&mDtlsTransport);
|
auto lower = std::atomic_load(&mDtlsTransport);
|
||||||
auto transport = std::make_shared<SctpTransport>(
|
auto transport = std::make_shared<SctpTransport>(
|
||||||
lower, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1),
|
lower, sctpPort, weak_bind(&PeerConnection::forwardMessage, this, _1),
|
||||||
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
|
weak_bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
|
||||||
[this](SctpTransport::State state) {
|
[this, weak_this = weak_from_this()](SctpTransport::State state) {
|
||||||
|
auto shared_this = weak_this.lock();
|
||||||
|
if (!shared_this)
|
||||||
|
return;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case SctpTransport::State::Connected:
|
case SctpTransport::State::Connected:
|
||||||
changeState(State::Connected);
|
changeState(State::Connected);
|
||||||
@ -323,8 +340,15 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
std::atomic_store(&mSctpTransport, transport);
|
std::atomic_store(&mSctpTransport, transport);
|
||||||
|
if (mState == State::Closed) {
|
||||||
|
mSctpTransport.reset();
|
||||||
|
transport->stop();
|
||||||
|
throw std::runtime_error("Connection is closed");
|
||||||
|
}
|
||||||
return transport;
|
return transport;
|
||||||
|
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_ERROR << e.what();
|
PLOG_ERROR << e.what();
|
||||||
changeState(State::Failed);
|
changeState(State::Failed);
|
||||||
@ -332,6 +356,34 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PeerConnection::closeTransports() {
|
||||||
|
// Change state to sink state Closed to block init methods
|
||||||
|
changeState(State::Closed);
|
||||||
|
|
||||||
|
// Reset callbacks now that state is changed
|
||||||
|
resetCallbacks();
|
||||||
|
|
||||||
|
// Pass the references to a thread, allowing to terminate a transport from its own thread
|
||||||
|
auto sctp = std::atomic_exchange(&mSctpTransport, decltype(mSctpTransport)(nullptr));
|
||||||
|
auto dtls = std::atomic_exchange(&mDtlsTransport, decltype(mDtlsTransport)(nullptr));
|
||||||
|
auto ice = std::atomic_exchange(&mIceTransport, decltype(mIceTransport)(nullptr));
|
||||||
|
if (sctp || dtls || ice) {
|
||||||
|
std::thread t([sctp, dtls, ice]() mutable {
|
||||||
|
if (sctp)
|
||||||
|
sctp->stop();
|
||||||
|
if (dtls)
|
||||||
|
dtls->stop();
|
||||||
|
if (ice)
|
||||||
|
ice->stop();
|
||||||
|
|
||||||
|
sctp.reset();
|
||||||
|
dtls.reset();
|
||||||
|
ice.reset();
|
||||||
|
});
|
||||||
|
t.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PeerConnection::endLocalCandidates() {
|
void PeerConnection::endLocalCandidates() {
|
||||||
std::lock_guard lock(mLocalDescriptionMutex);
|
std::lock_guard lock(mLocalDescriptionMutex);
|
||||||
if (mLocalDescription)
|
if (mLocalDescription)
|
||||||
@ -353,14 +405,7 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<DataChannel> channel;
|
auto channel = findDataChannel(message->stream);
|
||||||
if (auto it = mDataChannels.find(message->stream); it != mDataChannels.end()) {
|
|
||||||
channel = it->second.lock();
|
|
||||||
if (!channel || channel->isClosed()) {
|
|
||||||
mDataChannels.erase(it);
|
|
||||||
channel = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||||
@ -374,12 +419,12 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
|||||||
message->stream % 2 == remoteParity) {
|
message->stream % 2 == remoteParity) {
|
||||||
channel =
|
channel =
|
||||||
std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
|
std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
|
||||||
channel->onOpen(std::bind(&PeerConnection::triggerDataChannel, this,
|
channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
|
||||||
weak_ptr<DataChannel>{channel}));
|
weak_ptr<DataChannel>{channel}));
|
||||||
mDataChannels.insert(std::make_pair(message->stream, channel));
|
mDataChannels.insert(std::make_pair(message->stream, channel));
|
||||||
} else {
|
} else {
|
||||||
// Invalid, close the DataChannel by resetting the stream
|
// Invalid, close the DataChannel
|
||||||
sctpTransport->reset(message->stream);
|
sctpTransport->close(message->stream);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -388,32 +433,68 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
|
void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
|
||||||
shared_ptr<DataChannel> channel;
|
if (auto channel = findDataChannel(stream))
|
||||||
if (auto it = mDataChannels.find(stream); it != mDataChannels.end()) {
|
channel->triggerBufferedAmount(amount);
|
||||||
channel = it->second.lock();
|
|
||||||
if (!channel || channel->isClosed()) {
|
|
||||||
mDataChannels.erase(it);
|
|
||||||
channel = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel)
|
shared_ptr<DataChannel> PeerConnection::emplaceDataChannel(Description::Role role,
|
||||||
channel->triggerBufferedAmount(amount);
|
const string &label,
|
||||||
|
const string &protocol,
|
||||||
|
const Reliability &reliability) {
|
||||||
|
// The active side must use streams with even identifiers, whereas the passive side must use
|
||||||
|
// streams with odd identifiers.
|
||||||
|
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
|
||||||
|
std::unique_lock lock(mDataChannelsMutex); // we are going to emplace
|
||||||
|
unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
|
||||||
|
while (mDataChannels.find(stream) != mDataChannels.end()) {
|
||||||
|
stream += 2;
|
||||||
|
if (stream >= 65535)
|
||||||
|
throw std::runtime_error("Too many DataChannels");
|
||||||
|
}
|
||||||
|
auto channel =
|
||||||
|
std::make_shared<DataChannel>(shared_from_this(), stream, label, protocol, reliability);
|
||||||
|
mDataChannels.emplace(std::make_pair(stream, channel));
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<DataChannel> PeerConnection::findDataChannel(uint16_t stream) {
|
||||||
|
std::shared_lock lock(mDataChannelsMutex); // read-only
|
||||||
|
if (auto it = mDataChannels.find(stream); it != mDataChannels.end())
|
||||||
|
if (auto channel = it->second.lock())
|
||||||
|
return channel;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::iterateDataChannels(
|
void PeerConnection::iterateDataChannels(
|
||||||
std::function<void(shared_ptr<DataChannel> channel)> func) {
|
std::function<void(shared_ptr<DataChannel> channel)> func) {
|
||||||
|
// Iterate
|
||||||
|
{
|
||||||
|
std::shared_lock lock(mDataChannelsMutex); // read-only
|
||||||
auto it = mDataChannels.begin();
|
auto it = mDataChannels.begin();
|
||||||
while (it != mDataChannels.end()) {
|
while (it != mDataChannels.end()) {
|
||||||
auto channel = it->second.lock();
|
auto channel = it->second.lock();
|
||||||
if (!channel || channel->isClosed()) {
|
if (channel && !channel->isClosed())
|
||||||
|
func(channel);
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
{
|
||||||
|
std::unique_lock lock(mDataChannelsMutex); // we are going to erase
|
||||||
|
auto it = mDataChannels.begin();
|
||||||
|
while (it != mDataChannels.end()) {
|
||||||
|
if (!it->second.lock()) {
|
||||||
it = mDataChannels.erase(it);
|
it = mDataChannels.erase(it);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
func(channel);
|
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PeerConnection::openDataChannels() {
|
void PeerConnection::openDataChannels() {
|
||||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||||
@ -433,9 +514,11 @@ void PeerConnection::processLocalDescription(Description description) {
|
|||||||
if (auto remote = remoteDescription())
|
if (auto remote = remoteDescription())
|
||||||
remoteSctpPort = remote->sctpPort();
|
remoteSctpPort = remote->sctpPort();
|
||||||
|
|
||||||
|
auto certificate = mCertificate.get(); // wait for certificate if not ready
|
||||||
|
|
||||||
std::lock_guard lock(mLocalDescriptionMutex);
|
std::lock_guard lock(mLocalDescriptionMutex);
|
||||||
mLocalDescription.emplace(std::move(description));
|
mLocalDescription.emplace(std::move(description));
|
||||||
mLocalDescription->setFingerprint(mCertificate->fingerprint());
|
mLocalDescription->setFingerprint(certificate->fingerprint());
|
||||||
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
mLocalDescription->setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
||||||
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
mLocalDescription->setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||||
|
|
||||||
@ -460,21 +543,72 @@ void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
|
|||||||
mDataChannelCallback(dataChannel);
|
mDataChannelCallback(dataChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::changeState(State state) {
|
bool PeerConnection::changeState(State state) {
|
||||||
State current;
|
State current;
|
||||||
do {
|
do {
|
||||||
current = mState.load();
|
current = mState.load();
|
||||||
if (current == state || current == State::Destroying)
|
if (current == state)
|
||||||
return;
|
return true;
|
||||||
|
if (current == State::Closed)
|
||||||
|
return false;
|
||||||
|
|
||||||
} while (!mState.compare_exchange_weak(current, state));
|
} while (!mState.compare_exchange_weak(current, state));
|
||||||
|
|
||||||
if (state != State::Destroying)
|
|
||||||
mStateChangeCallback(state);
|
mStateChangeCallback(state);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::changeGatheringState(GatheringState state) {
|
bool PeerConnection::changeGatheringState(GatheringState state) {
|
||||||
if (mGatheringState.exchange(state) != state)
|
if (mGatheringState.exchange(state) != state)
|
||||||
mGatheringStateChangeCallback(state);
|
mGatheringStateChangeCallback(state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeerConnection::resetCallbacks() {
|
||||||
|
// Unregister all callbacks
|
||||||
|
mDataChannelCallback = nullptr;
|
||||||
|
mLocalDescriptionCallback = nullptr;
|
||||||
|
mLocalCandidateCallback = nullptr;
|
||||||
|
mStateChangeCallback = nullptr;
|
||||||
|
mGatheringStateChangeCallback = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PeerConnection::getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote) {
|
||||||
|
#if not USE_JUICE
|
||||||
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
|
return iceTransport->getSelectedCandidatePair(local, remote);
|
||||||
|
#else
|
||||||
|
PLOG_WARNING << "getSelectedCandidatePair is not implemented for libjuice";
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeerConnection::clearStats() {
|
||||||
|
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||||
|
if (sctpTransport)
|
||||||
|
return sctpTransport->clearStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PeerConnection::bytesSent() {
|
||||||
|
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||||
|
if (sctpTransport)
|
||||||
|
return sctpTransport->bytesSent();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PeerConnection::bytesReceived() {
|
||||||
|
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||||
|
if (sctpTransport)
|
||||||
|
return sctpTransport->bytesReceived();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::chrono::milliseconds> PeerConnection::rtt() {
|
||||||
|
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||||
|
if (sctpTransport)
|
||||||
|
return sctpTransport->rtt();
|
||||||
|
PLOG_WARNING << "Could not load sctpTransport";
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
@ -501,9 +635,6 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &st
|
|||||||
case State::Closed:
|
case State::Closed:
|
||||||
str = "closed";
|
str = "closed";
|
||||||
break;
|
break;
|
||||||
case State::Destroying:
|
|
||||||
str = "destroying";
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
str = "unknown";
|
str = "unknown";
|
||||||
break;
|
break;
|
||||||
@ -530,4 +661,3 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::Gathering
|
|||||||
}
|
}
|
||||||
return out << str;
|
return out << str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
448
src/rtc.cpp
448
src/rtc.cpp
@ -22,195 +22,433 @@
|
|||||||
|
|
||||||
#include <rtc.h>
|
#include <rtc.h>
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <mutex>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
#include <plog/Appenders/ColorConsoleAppender.h>
|
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
using std::string;
|
using std::string;
|
||||||
|
|
||||||
|
#define CATCH(statement) \
|
||||||
|
try { \
|
||||||
|
statement; \
|
||||||
|
} catch (const std::exception &e) { \
|
||||||
|
PLOG_ERROR << e.what(); \
|
||||||
|
return -1; \
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
|
std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
|
||||||
std::unordered_map<int, shared_ptr<DataChannel>> dataChannelMap;
|
std::unordered_map<int, shared_ptr<DataChannel>> dataChannelMap;
|
||||||
std::unordered_map<int, void *> userPointerMap;
|
std::unordered_map<int, void *> userPointerMap;
|
||||||
|
std::mutex mutex;
|
||||||
int lastId = 0;
|
int lastId = 0;
|
||||||
|
|
||||||
void *getUserPointer(int id) {
|
void *getUserPointer(int id) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
auto it = userPointerMap.find(id);
|
auto it = userPointerMap.find(id);
|
||||||
return it != userPointerMap.end() ? it->second : nullptr;
|
return it != userPointerMap.end() ? it->second : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
void setUserPointer(int i, void *ptr) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
void rtcInitLogger(rtc_log_level_t level) { InitLogger(static_cast<LogLevel>(level)); }
|
if (ptr)
|
||||||
|
userPointerMap.insert(std::make_pair(i, ptr));
|
||||||
int rtcCreatePeerConnection(const char **iceServers, int iceServersCount) {
|
else
|
||||||
Configuration config;
|
userPointerMap.erase(i);
|
||||||
for (int i = 0; i < iceServersCount; ++i) {
|
|
||||||
config.iceServers.emplace_back(IceServer(string(iceServers[i])));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shared_ptr<PeerConnection> getPeerConnection(int id) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
auto it = peerConnectionMap.find(id);
|
||||||
|
return it != peerConnectionMap.end() ? it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<DataChannel> getDataChannel(int id) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
auto it = dataChannelMap.find(id);
|
||||||
|
return it != dataChannelMap.end() ? it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
int pc = ++lastId;
|
int pc = ++lastId;
|
||||||
peerConnectionMap.emplace(std::make_pair(pc, std::make_shared<PeerConnection>(config)));
|
peerConnectionMap.emplace(std::make_pair(pc, ptr));
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcDeletePeerConnection(int pc) { peerConnectionMap.erase(pc); }
|
int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
int rtcCreateDataChannel(int pc, const char *label) {
|
|
||||||
auto it = peerConnectionMap.find(pc);
|
|
||||||
if (it == peerConnectionMap.end())
|
|
||||||
return 0;
|
|
||||||
auto dataChannel = it->second->createDataChannel(string(label));
|
|
||||||
int dc = ++lastId;
|
int dc = ++lastId;
|
||||||
dataChannelMap.emplace(std::make_pair(dc, dataChannel));
|
dataChannelMap.emplace(std::make_pair(dc, ptr));
|
||||||
return dc;
|
return dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcDeleteDataChannel(int dc) { dataChannelMap.erase(dc); }
|
bool erasePeerConnection(int pc) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
if (peerConnectionMap.erase(pc) == 0)
|
||||||
|
return false;
|
||||||
|
userPointerMap.erase(pc);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *)) {
|
bool eraseDataChannel(int dc) {
|
||||||
auto it = peerConnectionMap.find(pc);
|
std::lock_guard lock(mutex);
|
||||||
if (it == peerConnectionMap.end())
|
if (dataChannelMap.erase(dc) == 0)
|
||||||
return;
|
return false;
|
||||||
|
userPointerMap.erase(dc);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
it->second->onDataChannel([pc, dataChannelCallback](std::shared_ptr<DataChannel> dataChannel) {
|
} // namespace
|
||||||
int dc = ++lastId;
|
|
||||||
dataChannelMap.emplace(std::make_pair(dc, dataChannel));
|
void rtcInitLogger(rtcLogLevel level) { InitLogger(static_cast<LogLevel>(level)); }
|
||||||
dataChannelCallback(dc, getUserPointer(pc));
|
|
||||||
|
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
|
||||||
|
|
||||||
|
int rtcCreatePeerConnection(const rtcConfiguration *config) {
|
||||||
|
Configuration c;
|
||||||
|
for (int i = 0; i < config->iceServersCount; ++i)
|
||||||
|
c.iceServers.emplace_back(string(config->iceServers[i]));
|
||||||
|
|
||||||
|
if (config->portRangeBegin || config->portRangeEnd) {
|
||||||
|
c.portRangeBegin = config->portRangeBegin;
|
||||||
|
c.portRangeEnd = config->portRangeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return emplacePeerConnection(std::make_shared<PeerConnection>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcDeletePeerConnection(int pc) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
peerConnection->onDataChannel(nullptr);
|
||||||
|
peerConnection->onLocalDescription(nullptr);
|
||||||
|
peerConnection->onLocalCandidate(nullptr);
|
||||||
|
peerConnection->onStateChange(nullptr);
|
||||||
|
peerConnection->onGatheringStateChange(nullptr);
|
||||||
|
|
||||||
|
erasePeerConnection(pc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcCreateDataChannel(int pc, const char *label) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int dc = emplaceDataChannel(peerConnection->createDataChannel(string(label)));
|
||||||
|
void *ptr = getUserPointer(pc);
|
||||||
|
rtcSetUserPointer(dc, ptr);
|
||||||
|
return dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcDeleteDataChannel(int dc) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
dataChannel->onOpen(nullptr);
|
||||||
|
dataChannel->onClosed(nullptr);
|
||||||
|
dataChannel->onError(nullptr);
|
||||||
|
dataChannel->onMessage(nullptr);
|
||||||
|
dataChannel->onBufferedAmountLow(nullptr);
|
||||||
|
dataChannel->onAvailable(nullptr);
|
||||||
|
|
||||||
|
eraseDataChannel(dc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetDataChannelCallback(int pc, dataChannelCallbackFunc cb) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
|
||||||
|
int dc = emplaceDataChannel(dataChannel);
|
||||||
|
void *ptr = getUserPointer(pc);
|
||||||
|
rtcSetUserPointer(dc, ptr);
|
||||||
|
cb(dc, ptr);
|
||||||
});
|
});
|
||||||
|
else
|
||||||
|
peerConnection->onDataChannel(nullptr);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetLocalDescriptionCallback(int pc, void (*descriptionCallback)(const char *, const char *,
|
int rtcSetLocalDescriptionCallback(int pc, descriptionCallbackFunc cb) {
|
||||||
void *)) {
|
auto peerConnection = getPeerConnection(pc);
|
||||||
auto it = peerConnectionMap.find(pc);
|
if (!peerConnection)
|
||||||
if (it == peerConnectionMap.end())
|
return -1;
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->onLocalDescription([pc, descriptionCallback](const Description &description) {
|
if (cb)
|
||||||
descriptionCallback(string(description).c_str(), description.typeString().c_str(),
|
peerConnection->onLocalDescription([pc, cb](const Description &desc) {
|
||||||
getUserPointer(pc));
|
cb(string(desc).c_str(), desc.typeString().c_str(), getUserPointer(pc));
|
||||||
});
|
});
|
||||||
|
else
|
||||||
|
peerConnection->onLocalDescription(nullptr);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetLocalCandidateCallback(int pc,
|
int rtcSetLocalCandidateCallback(int pc, candidateCallbackFunc cb) {
|
||||||
void (*candidateCallback)(const char *, const char *, void *)) {
|
auto peerConnection = getPeerConnection(pc);
|
||||||
auto it = peerConnectionMap.find(pc);
|
if (!peerConnection)
|
||||||
if (it == peerConnectionMap.end())
|
return -1;
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->onLocalCandidate([pc, candidateCallback](const Candidate &candidate) {
|
if (cb)
|
||||||
candidateCallback(candidate.candidate().c_str(), candidate.mid().c_str(),
|
peerConnection->onLocalCandidate([pc, cb](const Candidate &cand) {
|
||||||
getUserPointer(pc));
|
cb(cand.candidate().c_str(), cand.mid().c_str(), getUserPointer(pc));
|
||||||
});
|
});
|
||||||
|
else
|
||||||
|
peerConnection->onLocalCandidate(nullptr);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetStateChangeCallback(int pc, void (*stateCallback)(rtc_state_t state, void *)) {
|
int rtcSetStateChangeCallback(int pc, stateChangeCallbackFunc cb) {
|
||||||
auto it = peerConnectionMap.find(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (it == peerConnectionMap.end())
|
if (!peerConnection)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
it->second->onStateChange([pc, stateCallback](PeerConnection::State state) {
|
if (cb)
|
||||||
stateCallback(static_cast<rtc_state_t>(state), getUserPointer(pc));
|
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
|
||||||
|
cb(static_cast<rtcState>(state), getUserPointer(pc));
|
||||||
});
|
});
|
||||||
|
else
|
||||||
|
peerConnection->onStateChange(nullptr);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetGatheringStateChangeCallback(int pc,
|
int rtcSetGatheringStateChangeCallback(int pc, gatheringStateCallbackFunc cb) {
|
||||||
void (*gatheringStateCallback)(rtc_gathering_state_t state,
|
auto peerConnection = getPeerConnection(pc);
|
||||||
void *)) {
|
if (!peerConnection)
|
||||||
auto it = peerConnectionMap.find(pc);
|
return -1;
|
||||||
if (it == peerConnectionMap.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
it->second->onGatheringStateChange(
|
if (cb)
|
||||||
[pc, gatheringStateCallback](PeerConnection::GatheringState state) {
|
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
|
||||||
gatheringStateCallback(static_cast<rtc_gathering_state_t>(state), getUserPointer(pc));
|
cb(static_cast<rtcGatheringState>(state), getUserPointer(pc));
|
||||||
});
|
});
|
||||||
|
else
|
||||||
|
peerConnection->onGatheringStateChange(nullptr);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
|
||||||
auto it = peerConnectionMap.find(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (it == peerConnectionMap.end())
|
if (!peerConnection)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
it->second->setRemoteDescription(Description(string(sdp), type ? string(type) : ""));
|
CATCH(peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}));
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid) {
|
int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
|
||||||
auto it = peerConnectionMap.find(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
if (it == peerConnectionMap.end())
|
if (!peerConnection)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
it->second->addRemoteCandidate(Candidate(string(candidate), mid ? string(mid) : ""));
|
CATCH(peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (auto addr = peerConnection->localAddress()) {
|
||||||
|
size = std::min(size_t(size - 1), addr->size());
|
||||||
|
std::copy(addr->data(), addr->data() + size, buffer);
|
||||||
|
buffer[size] = '\0';
|
||||||
|
return size + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
||||||
|
auto peerConnection = getPeerConnection(pc);
|
||||||
|
if (!peerConnection)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (auto addr = peerConnection->remoteAddress()) {
|
||||||
|
size = std::min(size_t(size - 1), addr->size());
|
||||||
|
std::copy(addr->data(), addr->data() + size, buffer);
|
||||||
|
buffer[size] = '\0';
|
||||||
|
return size + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
|
||||||
auto it = dataChannelMap.find(dc);
|
auto dataChannel = getDataChannel(dc);
|
||||||
if (it == dataChannelMap.end())
|
if (!dataChannel)
|
||||||
return 0;
|
return -1;
|
||||||
|
|
||||||
if (!size)
|
if (!size)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
string label = it->second->label();
|
string label = dataChannel->label();
|
||||||
size = std::min(size_t(size - 1), label.size());
|
size = std::min(size_t(size - 1), label.size());
|
||||||
std::copy(label.data(), label.data() + size, buffer);
|
std::copy(label.data(), label.data() + size, buffer);
|
||||||
buffer[size] = '\0';
|
buffer[size] = '\0';
|
||||||
return size + 1;
|
return size + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetOpenCallback(int dc, void (*openCallback)(void *)) {
|
int rtcSetOpenCallback(int dc, openCallbackFunc cb) {
|
||||||
auto it = dataChannelMap.find(dc);
|
auto dataChannel = getDataChannel(dc);
|
||||||
if (it == dataChannelMap.end())
|
if (!dataChannel)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
it->second->onOpen([dc, openCallback]() { openCallback(getUserPointer(dc)); });
|
if (cb)
|
||||||
|
dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); });
|
||||||
|
else
|
||||||
|
dataChannel->onOpen(nullptr);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *)) {
|
int rtcSetClosedCallback(int dc, closedCallbackFunc cb) {
|
||||||
auto it = dataChannelMap.find(dc);
|
auto dataChannel = getDataChannel(dc);
|
||||||
if (it == dataChannelMap.end())
|
if (!dataChannel)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
it->second->onError([dc, errorCallback](const string &error) {
|
if (cb)
|
||||||
errorCallback(error.c_str(), getUserPointer(dc));
|
dataChannel->onClosed([dc, cb]() { cb(getUserPointer(dc)); });
|
||||||
});
|
else
|
||||||
|
dataChannel->onClosed(nullptr);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *)) {
|
int rtcSetErrorCallback(int dc, errorCallbackFunc cb) {
|
||||||
auto it = dataChannelMap.find(dc);
|
auto dataChannel = getDataChannel(dc);
|
||||||
if (it == dataChannelMap.end())
|
if (!dataChannel)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
it->second->onMessage(
|
if (cb)
|
||||||
[dc, messageCallback](const binary &b) {
|
dataChannel->onError(
|
||||||
messageCallback(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
|
[dc, cb](const string &error) { cb(error.c_str(), getUserPointer(dc)); });
|
||||||
|
else
|
||||||
|
dataChannel->onError(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetMessageCallback(int dc, messageCallbackFunc cb) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
dataChannel->onMessage(
|
||||||
|
[dc, cb](const binary &b) {
|
||||||
|
cb(reinterpret_cast<const char *>(b.data()), b.size(), getUserPointer(dc));
|
||||||
},
|
},
|
||||||
[dc, messageCallback](const string &s) {
|
[dc, cb](const string &s) { cb(s.c_str(), -1, getUserPointer(dc)); });
|
||||||
messageCallback(s.c_str(), -1, getUserPointer(dc));
|
else
|
||||||
});
|
dataChannel->onMessage(nullptr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcSendMessage(int dc, const char *data, int size) {
|
int rtcSendMessage(int dc, const char *data, int size) {
|
||||||
auto it = dataChannelMap.find(dc);
|
auto dataChannel = getDataChannel(dc);
|
||||||
if (it == dataChannelMap.end())
|
if (!dataChannel)
|
||||||
return 0;
|
return -1;
|
||||||
|
|
||||||
if (size >= 0) {
|
if (size >= 0) {
|
||||||
auto b = reinterpret_cast<const byte *>(data);
|
auto b = reinterpret_cast<const byte *>(data);
|
||||||
it->second->send(b, size);
|
CATCH(dataChannel->send(b, size));
|
||||||
return size;
|
return size;
|
||||||
} else {
|
} else {
|
||||||
string s(data);
|
string s(data);
|
||||||
it->second->send(s);
|
CATCH(dataChannel->send(s));
|
||||||
return s.size();
|
return s.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtcSetUserPointer(int i, void *ptr) {
|
int rtcGetBufferedAmount(int dc) {
|
||||||
if (ptr)
|
auto dataChannel = getDataChannel(dc);
|
||||||
userPointerMap.insert(std::make_pair(i, ptr));
|
if (!dataChannel)
|
||||||
else
|
return -1;
|
||||||
userPointerMap.erase(i);
|
|
||||||
|
CATCH(return int(dataChannel->bufferedAmount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int rtcSetBufferedAmountLowThreshold(int dc, int amount) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
CATCH(dataChannel->setBufferedAmountLowThreshold(size_t(amount)));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetBufferedAmountLowCallback(int dc, bufferedAmountLowCallbackFunc cb) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
dataChannel->onBufferedAmountLow([dc, cb]() { cb(getUserPointer(dc)); });
|
||||||
|
else
|
||||||
|
dataChannel->onBufferedAmountLow(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcGetAvailableAmount(int dc) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
CATCH(return int(dataChannel->availableAmount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcSetAvailableCallback(int dc, availableCallbackFunc cb) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cb)
|
||||||
|
dataChannel->onOpen([dc, cb]() { cb(getUserPointer(dc)); });
|
||||||
|
else
|
||||||
|
dataChannel->onOpen(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtcReceiveMessage(int dc, char *buffer, int *size) {
|
||||||
|
auto dataChannel = getDataChannel(dc);
|
||||||
|
if (!dataChannel)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!size)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
CATCH({
|
||||||
|
auto message = dataChannel->receive();
|
||||||
|
if (!message)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return std::visit( //
|
||||||
|
overloaded{ //
|
||||||
|
[&](const binary &b) {
|
||||||
|
*size = std::min(*size, int(b.size()));
|
||||||
|
auto data = reinterpret_cast<const char *>(b.data());
|
||||||
|
std::copy(data, data + *size, buffer);
|
||||||
|
return *size;
|
||||||
|
},
|
||||||
|
[&](const string &s) {
|
||||||
|
int len = std::min(*size - 1, int(s.size()));
|
||||||
|
if (len >= 0) {
|
||||||
|
std::copy(s.data(), s.data() + len, buffer);
|
||||||
|
buffer[len] = '\0';
|
||||||
|
}
|
||||||
|
*size = -(len + 1);
|
||||||
|
return len + 1;
|
||||||
|
}},
|
||||||
|
*message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void rtcCleanup() { rtc::Cleanup(); }
|
||||||
|
@ -21,12 +21,9 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_JUICE
|
#ifdef USE_JUICE
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
// libjuice enables Linux path MTU discovery or sets the DF flag
|
// libjuice enables Linux path MTU discovery or sets the DF flag
|
||||||
@ -53,12 +50,7 @@ using std::shared_ptr;
|
|||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
std::mutex SctpTransport::GlobalMutex;
|
void SctpTransport::Init() {
|
||||||
int SctpTransport::InstancesCount = 0;
|
|
||||||
|
|
||||||
void SctpTransport::GlobalInit() {
|
|
||||||
std::lock_guard lock(GlobalMutex);
|
|
||||||
if (InstancesCount++ == 0) {
|
|
||||||
usrsctp_init(0, &SctpTransport::WriteCallback, nullptr);
|
usrsctp_init(0, &SctpTransport::WriteCallback, nullptr);
|
||||||
usrsctp_sysctl_set_sctp_ecn_enable(0);
|
usrsctp_sysctl_set_sctp_ecn_enable(0);
|
||||||
usrsctp_sysctl_set_sctp_init_rtx_max_default(5);
|
usrsctp_sysctl_set_sctp_init_rtx_max_default(5);
|
||||||
@ -70,13 +62,10 @@ void SctpTransport::GlobalInit() {
|
|||||||
usrsctp_sysctl_set_sctp_init_rto_max_default(10 * 1000); // ms
|
usrsctp_sysctl_set_sctp_init_rto_max_default(10 * 1000); // ms
|
||||||
usrsctp_sysctl_set_sctp_heartbeat_interval_default(10 * 1000); // ms
|
usrsctp_sysctl_set_sctp_heartbeat_interval_default(10 * 1000); // ms
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void SctpTransport::GlobalCleanup() {
|
void SctpTransport::Cleanup() {
|
||||||
std::lock_guard lock(GlobalMutex);
|
while (usrsctp_finish() != 0)
|
||||||
if (--InstancesCount == 0) {
|
std::this_thread::sleep_for(100ms);
|
||||||
usrsctp_finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||||
@ -88,7 +77,6 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
|||||||
onRecv(recvCallback);
|
onRecv(recvCallback);
|
||||||
|
|
||||||
PLOG_DEBUG << "Initializing SCTP transport";
|
PLOG_DEBUG << "Initializing SCTP transport";
|
||||||
GlobalInit();
|
|
||||||
|
|
||||||
usrsctp_register_address(this);
|
usrsctp_register_address(this);
|
||||||
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
|
mSock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &SctpTransport::RecvCallback,
|
||||||
@ -160,6 +148,14 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
|||||||
throw std::runtime_error("Could not set socket option SCTP_INITMSG, errno=" +
|
throw std::runtime_error("Could not set socket option SCTP_INITMSG, errno=" +
|
||||||
std::to_string(errno));
|
std::to_string(errno));
|
||||||
|
|
||||||
|
// Prevent fragmented interleave of messages (i.e. level 0), see RFC 6458 8.1.20.
|
||||||
|
// Unless the user has set the fragmentation interleave level to 0, notifications
|
||||||
|
// may also be interleaved with partially delivered messages.
|
||||||
|
int level = 0;
|
||||||
|
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE, &level, sizeof(level)))
|
||||||
|
throw std::runtime_error("Could not disable SCTP fragmented interleave, errno=" +
|
||||||
|
std::to_string(errno));
|
||||||
|
|
||||||
// The default send and receive window size of usrsctp is 256KiB, which is too small for
|
// The default send and receive window size of usrsctp is 256KiB, which is too small for
|
||||||
// realistic RTTs, therefore we increase it to 1MiB for better performance.
|
// realistic RTTs, therefore we increase it to 1MiB for better performance.
|
||||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1051685
|
||||||
@ -171,32 +167,36 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
|||||||
throw std::runtime_error("Could not set SCTP send buffer size, errno=" +
|
throw std::runtime_error("Could not set SCTP send buffer size, errno=" +
|
||||||
std::to_string(errno));
|
std::to_string(errno));
|
||||||
|
|
||||||
|
registerIncoming();
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
SctpTransport::~SctpTransport() {
|
SctpTransport::~SctpTransport() {
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
|
if (mSock)
|
||||||
usrsctp_close(mSock);
|
usrsctp_close(mSock);
|
||||||
usrsctp_deregister_address(this);
|
|
||||||
|
|
||||||
GlobalCleanup();
|
usrsctp_deregister_address(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
SctpTransport::State SctpTransport::state() const { return mState; }
|
SctpTransport::State SctpTransport::state() const { return mState; }
|
||||||
|
|
||||||
void SctpTransport::stop() {
|
bool SctpTransport::stop() {
|
||||||
Transport::stop();
|
if (!Transport::stop())
|
||||||
onRecv(nullptr);
|
return false;
|
||||||
|
|
||||||
if (!mShutdown.exchange(true)) {
|
|
||||||
mSendQueue.stop();
|
mSendQueue.stop();
|
||||||
flush();
|
safeFlush();
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
onRecv(nullptr);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::connect() {
|
void SctpTransport::connect() {
|
||||||
|
if (!mSock)
|
||||||
|
return;
|
||||||
|
|
||||||
PLOG_DEBUG << "SCTP connect";
|
PLOG_DEBUG << "SCTP connect";
|
||||||
changeState(State::Connecting);
|
changeState(State::Connecting);
|
||||||
|
|
||||||
@ -220,12 +220,19 @@ void SctpTransport::connect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::shutdown() {
|
void SctpTransport::shutdown() {
|
||||||
|
if (!mSock)
|
||||||
|
return;
|
||||||
|
|
||||||
PLOG_DEBUG << "SCTP shutdown";
|
PLOG_DEBUG << "SCTP shutdown";
|
||||||
|
|
||||||
if (usrsctp_shutdown(mSock, SHUT_RDWR)) {
|
if (usrsctp_shutdown(mSock, SHUT_RDWR) != 0 && errno != ENOTCONN) {
|
||||||
PLOG_WARNING << "SCTP shutdown failed, errno=" << errno;
|
PLOG_WARNING << "SCTP shutdown failed, errno=" << errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// close() abort the connection when linger is disabled, call it now
|
||||||
|
usrsctp_close(mSock);
|
||||||
|
mSock = nullptr;
|
||||||
|
|
||||||
PLOG_INFO << "SCTP disconnected";
|
PLOG_INFO << "SCTP disconnected";
|
||||||
changeState(State::Disconnected);
|
changeState(State::Disconnected);
|
||||||
mWrittenCondition.notify_all();
|
mWrittenCondition.notify_all();
|
||||||
@ -247,31 +254,15 @@ bool SctpTransport::send(message_ptr message) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SctpTransport::close(unsigned int stream) {
|
||||||
|
send(make_message(0, Message::Reset, uint16_t(stream)));
|
||||||
|
}
|
||||||
|
|
||||||
void SctpTransport::flush() {
|
void SctpTransport::flush() {
|
||||||
std::lock_guard lock(mSendMutex);
|
std::lock_guard lock(mSendMutex);
|
||||||
trySendQueue();
|
trySendQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::reset(unsigned int stream) {
|
|
||||||
PLOG_DEBUG << "SCTP resetting stream " << stream;
|
|
||||||
|
|
||||||
std::unique_lock lock(mWriteMutex);
|
|
||||||
mWritten = false;
|
|
||||||
using srs_t = struct sctp_reset_streams;
|
|
||||||
const size_t len = sizeof(srs_t) + sizeof(uint16_t);
|
|
||||||
byte buffer[len] = {};
|
|
||||||
srs_t &srs = *reinterpret_cast<srs_t *>(buffer);
|
|
||||||
srs.srs_flags = SCTP_STREAM_RESET_OUTGOING;
|
|
||||||
srs.srs_number_streams = 1;
|
|
||||||
srs.srs_stream_list[0] = uint16_t(stream);
|
|
||||||
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &srs, len) == 0) {
|
|
||||||
mWrittenCondition.wait_for(lock, 1000ms,
|
|
||||||
[&]() { return mWritten || mState != State::Connected; });
|
|
||||||
} else {
|
|
||||||
PLOG_WARNING << "SCTP reset stream " << stream << " failed, errno=" << errno;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SctpTransport::incoming(message_ptr message) {
|
void SctpTransport::incoming(message_ptr message) {
|
||||||
// There could be a race condition here where we receive the remote INIT before the local one is
|
// There could be a race condition here where we receive the remote INIT before the local one is
|
||||||
// sent, which would result in the connection being aborted. Therefore, we need to wait for data
|
// sent, which would result in the connection being aborted. Therefore, we need to wait for data
|
||||||
@ -281,13 +272,15 @@ void SctpTransport::incoming(message_ptr message) {
|
|||||||
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || mState != State::Connected; });
|
mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || mState != State::Connected; });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message) {
|
if (!message) {
|
||||||
usrsctp_conninput(this, message->data(), message->size(), 0);
|
|
||||||
} else {
|
|
||||||
PLOG_INFO << "SCTP disconnected";
|
PLOG_INFO << "SCTP disconnected";
|
||||||
changeState(State::Disconnected);
|
changeState(State::Disconnected);
|
||||||
recv(nullptr);
|
recv(nullptr);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PLOG_VERBOSE << "Incoming size=" << message->size();
|
||||||
|
usrsctp_conninput(this, message->data(), message->size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::changeState(State state) {
|
void SctpTransport::changeState(State state) {
|
||||||
@ -309,16 +302,9 @@ bool SctpTransport::trySendQueue() {
|
|||||||
|
|
||||||
bool SctpTransport::trySendMessage(message_ptr message) {
|
bool SctpTransport::trySendMessage(message_ptr message) {
|
||||||
// Requires mSendMutex to be locked
|
// Requires mSendMutex to be locked
|
||||||
if (mState != State::Connected)
|
if (!mSock || mState != State::Connected)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
PLOG_VERBOSE << "SCTP try send size=" << message->size();
|
|
||||||
|
|
||||||
// TODO: Implement SCTP ndata specification draft when supported everywhere
|
|
||||||
// See https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-08
|
|
||||||
|
|
||||||
const Reliability reliability = message->reliability ? *message->reliability : Reliability();
|
|
||||||
|
|
||||||
uint32_t ppid;
|
uint32_t ppid;
|
||||||
switch (message->type) {
|
switch (message->type) {
|
||||||
case Message::String:
|
case Message::String:
|
||||||
@ -327,11 +313,24 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
|||||||
case Message::Binary:
|
case Message::Binary:
|
||||||
ppid = !message->empty() ? PPID_BINARY : PPID_BINARY_EMPTY;
|
ppid = !message->empty() ? PPID_BINARY : PPID_BINARY_EMPTY;
|
||||||
break;
|
break;
|
||||||
default:
|
case Message::Control:
|
||||||
ppid = PPID_CONTROL;
|
ppid = PPID_CONTROL;
|
||||||
break;
|
break;
|
||||||
|
case Message::Reset:
|
||||||
|
sendReset(message->stream);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
// Ignore
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PLOG_VERBOSE << "SCTP try send size=" << message->size();
|
||||||
|
|
||||||
|
// TODO: Implement SCTP ndata specification draft when supported everywhere
|
||||||
|
// See https://tools.ietf.org/html/draft-ietf-tsvwg-sctp-ndata-08
|
||||||
|
|
||||||
|
const Reliability reliability = message->reliability ? *message->reliability : Reliability();
|
||||||
|
|
||||||
struct sctp_sendv_spa spa = {};
|
struct sctp_sendv_spa spa = {};
|
||||||
|
|
||||||
// set sndinfo
|
// set sndinfo
|
||||||
@ -372,6 +371,8 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
|||||||
|
|
||||||
if (ret >= 0) {
|
if (ret >= 0) {
|
||||||
PLOG_VERBOSE << "SCTP sent size=" << message->size();
|
PLOG_VERBOSE << "SCTP sent size=" << message->size();
|
||||||
|
if (message->type == Message::Type::Binary || message->type == Message::Type::String)
|
||||||
|
mBytesSent += message->size();
|
||||||
return true;
|
return true;
|
||||||
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||||
PLOG_VERBOSE << "SCTP sending not possible";
|
PLOG_VERBOSE << "SCTP sending not possible";
|
||||||
@ -385,25 +386,69 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
|||||||
void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
|
void SctpTransport::updateBufferedAmount(uint16_t streamId, long delta) {
|
||||||
// Requires mSendMutex to be locked
|
// Requires mSendMutex to be locked
|
||||||
auto it = mBufferedAmount.insert(std::make_pair(streamId, 0)).first;
|
auto it = mBufferedAmount.insert(std::make_pair(streamId, 0)).first;
|
||||||
size_t amount = it->second;
|
size_t amount = size_t(std::max(long(it->second) + delta, long(0)));
|
||||||
amount = size_t(std::max(long(amount) + delta, long(0)));
|
|
||||||
if (amount == 0)
|
if (amount == 0)
|
||||||
mBufferedAmount.erase(it);
|
mBufferedAmount.erase(it);
|
||||||
|
else
|
||||||
|
it->second = amount;
|
||||||
|
|
||||||
mBufferedAmountCallback(streamId, amount);
|
mBufferedAmountCallback(streamId, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SctpTransport::sendReset(uint16_t streamId) {
|
||||||
|
// Requires mSendMutex to be locked
|
||||||
|
if (!mSock || state() != State::Connected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PLOG_DEBUG << "SCTP resetting stream " << streamId;
|
||||||
|
|
||||||
|
using srs_t = struct sctp_reset_streams;
|
||||||
|
const size_t len = sizeof(srs_t) + sizeof(uint16_t);
|
||||||
|
byte buffer[len] = {};
|
||||||
|
srs_t &srs = *reinterpret_cast<srs_t *>(buffer);
|
||||||
|
srs.srs_flags = SCTP_STREAM_RESET_OUTGOING;
|
||||||
|
srs.srs_number_streams = 1;
|
||||||
|
srs.srs_stream_list[0] = streamId;
|
||||||
|
|
||||||
|
mWritten = false;
|
||||||
|
if (usrsctp_setsockopt(mSock, IPPROTO_SCTP, SCTP_RESET_STREAMS, &srs, len) == 0) {
|
||||||
|
std::unique_lock lock(mWriteMutex); // locking before setsockopt might deadlock usrsctp...
|
||||||
|
mWrittenCondition.wait_for(lock, 1000ms,
|
||||||
|
[&]() { return mWritten || mState != State::Connected; });
|
||||||
|
} else if (errno == EINVAL) {
|
||||||
|
PLOG_VERBOSE << "SCTP stream " << streamId << " already reset";
|
||||||
|
} else {
|
||||||
|
PLOG_WARNING << "SCTP reset stream " << streamId << " failed, errno=" << errno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SctpTransport::safeFlush() {
|
||||||
|
try {
|
||||||
|
flush();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
PLOG_ERROR << "SCTP flush: " << e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data,
|
int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data,
|
||||||
size_t len, struct sctp_rcvinfo info, int flags) {
|
size_t len, struct sctp_rcvinfo info, int flags) {
|
||||||
try {
|
try {
|
||||||
|
PLOG_VERBOSE << "Handle recv, len=" << len;
|
||||||
if (!len)
|
if (!len)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
// This is valid because SCTP_FRAGMENT_INTERLEAVE is set to level 0
|
||||||
|
// so partial messages and notifications may not be interleaved.
|
||||||
if (flags & MSG_EOR) {
|
if (flags & MSG_EOR) {
|
||||||
if (!mPartialRecv.empty()) {
|
if (!mPartialRecv.empty()) {
|
||||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
||||||
data = mPartialRecv.data();
|
data = mPartialRecv.data();
|
||||||
len = mPartialRecv.size();
|
len = mPartialRecv.size();
|
||||||
}
|
}
|
||||||
// Message is complete, process it
|
// Message/Notification is complete, process it
|
||||||
if (flags & MSG_NOTIFICATION)
|
if (flags & MSG_NOTIFICATION)
|
||||||
processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
|
processNotification(reinterpret_cast<const union sctp_notification *>(data), len);
|
||||||
else
|
else
|
||||||
@ -411,7 +456,7 @@ int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, co
|
|||||||
|
|
||||||
mPartialRecv.clear();
|
mPartialRecv.clear();
|
||||||
} else {
|
} else {
|
||||||
// Message is not complete
|
// Message/Notification is not complete
|
||||||
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
mPartialRecv.insert(mPartialRecv.end(), data, data + len);
|
||||||
}
|
}
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
@ -422,24 +467,21 @@ int SctpTransport::handleRecv(struct socket *sock, union sctp_sockstore addr, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
int SctpTransport::handleSend(size_t free) {
|
int SctpTransport::handleSend(size_t free) {
|
||||||
try {
|
PLOG_VERBOSE << "Handle send, free=" << free;
|
||||||
std::lock_guard lock(mSendMutex);
|
return safeFlush() ? 0 : -1;
|
||||||
trySendQueue();
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
PLOG_ERROR << "SCTP send: " << e.what();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0; // success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df) {
|
int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_df) {
|
||||||
try {
|
try {
|
||||||
|
PLOG_VERBOSE << "Handle write, len=" << len;
|
||||||
|
|
||||||
std::unique_lock lock(mWriteMutex);
|
std::unique_lock lock(mWriteMutex);
|
||||||
if (!outgoing(make_message(data, data + len)))
|
if (!outgoing(make_message(data, data + len)))
|
||||||
return -1;
|
return -1;
|
||||||
mWritten = true;
|
mWritten = true;
|
||||||
mWrittenOnce = true;
|
mWrittenOnce = true;
|
||||||
mWrittenCondition.notify_all();
|
mWrittenCondition.notify_all();
|
||||||
|
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_ERROR << "SCTP write: " << e.what();
|
PLOG_ERROR << "SCTP write: " << e.what();
|
||||||
return -1;
|
return -1;
|
||||||
@ -448,6 +490,8 @@ int SctpTransport::handleWrite(byte *data, size_t len, uint8_t tos, uint8_t set_
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, PayloadId ppid) {
|
void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, PayloadId ppid) {
|
||||||
|
PLOG_VERBOSE << "Process data, len=" << len;
|
||||||
|
|
||||||
// The usage of the PPIDs "WebRTC String Partial" and "WebRTC Binary Partial" is deprecated.
|
// The usage of the PPIDs "WebRTC String Partial" and "WebRTC Binary Partial" is deprecated.
|
||||||
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.6
|
// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.6
|
||||||
// We handle them at reception for compatibility reasons but should never send them.
|
// We handle them at reception for compatibility reasons but should never send them.
|
||||||
@ -462,9 +506,11 @@ void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, Payl
|
|||||||
|
|
||||||
case PPID_STRING:
|
case PPID_STRING:
|
||||||
if (mPartialStringData.empty()) {
|
if (mPartialStringData.empty()) {
|
||||||
|
mBytesReceived += len;
|
||||||
recv(make_message(data, data + len, Message::String, sid));
|
recv(make_message(data, data + len, Message::String, sid));
|
||||||
} else {
|
} else {
|
||||||
mPartialStringData.insert(mPartialStringData.end(), data, data + len);
|
mPartialStringData.insert(mPartialStringData.end(), data, data + len);
|
||||||
|
mBytesReceived += mPartialStringData.size();
|
||||||
recv(make_message(mPartialStringData.begin(), mPartialStringData.end(), Message::String,
|
recv(make_message(mPartialStringData.begin(), mPartialStringData.end(), Message::String,
|
||||||
sid));
|
sid));
|
||||||
mPartialStringData.clear();
|
mPartialStringData.clear();
|
||||||
@ -484,9 +530,11 @@ void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, Payl
|
|||||||
|
|
||||||
case PPID_BINARY:
|
case PPID_BINARY:
|
||||||
if (mPartialBinaryData.empty()) {
|
if (mPartialBinaryData.empty()) {
|
||||||
|
mBytesReceived += len;
|
||||||
recv(make_message(data, data + len, Message::Binary, sid));
|
recv(make_message(data, data + len, Message::Binary, sid));
|
||||||
} else {
|
} else {
|
||||||
mPartialBinaryData.insert(mPartialBinaryData.end(), data, data + len);
|
mPartialBinaryData.insert(mPartialBinaryData.end(), data, data + len);
|
||||||
|
mBytesReceived += mPartialStringData.size();
|
||||||
recv(make_message(mPartialBinaryData.begin(), mPartialBinaryData.end(), Message::Binary,
|
recv(make_message(mPartialBinaryData.begin(), mPartialBinaryData.end(), Message::Binary,
|
||||||
sid));
|
sid));
|
||||||
mPartialBinaryData.clear();
|
mPartialBinaryData.clear();
|
||||||
@ -508,10 +556,15 @@ void SctpTransport::processData(const byte *data, size_t len, uint16_t sid, Payl
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SctpTransport::processNotification(const union sctp_notification *notify, size_t len) {
|
void SctpTransport::processNotification(const union sctp_notification *notify, size_t len) {
|
||||||
if (len != size_t(notify->sn_header.sn_length))
|
if (len != size_t(notify->sn_header.sn_length)) {
|
||||||
|
PLOG_WARNING << "Invalid notification length";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (notify->sn_header.sn_type) {
|
auto type = notify->sn_header.sn_type;
|
||||||
|
PLOG_VERBOSE << "Process notification, type=" << type;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
case SCTP_ASSOC_CHANGE: {
|
case SCTP_ASSOC_CHANGE: {
|
||||||
const struct sctp_assoc_change &assoc_change = notify->sn_assoc_change;
|
const struct sctp_assoc_change &assoc_change = notify->sn_assoc_change;
|
||||||
if (assoc_change.sac_state == SCTP_COMM_UP) {
|
if (assoc_change.sac_state == SCTP_COMM_UP) {
|
||||||
@ -527,13 +580,16 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
|
|||||||
}
|
}
|
||||||
mWrittenCondition.notify_all();
|
mWrittenCondition.notify_all();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SCTP_SENDER_DRY_EVENT: {
|
case SCTP_SENDER_DRY_EVENT: {
|
||||||
// It not should be necessary since the send callback should have been called already,
|
// It not should be necessary since the send callback should have been called already,
|
||||||
// but to be sure, let's try to send now.
|
// but to be sure, let's try to send now.
|
||||||
std::lock_guard lock(mSendMutex);
|
safeFlush();
|
||||||
trySendQueue();
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SCTP_STREAM_RESET_EVENT: {
|
case SCTP_STREAM_RESET_EVENT: {
|
||||||
const struct sctp_stream_reset_event &reset_event = notify->sn_strreset_event;
|
const struct sctp_stream_reset_event &reset_event = notify->sn_strreset_event;
|
||||||
const int count = (reset_event.strreset_length - sizeof(reset_event)) / sizeof(uint16_t);
|
const int count = (reset_event.strreset_length - sizeof(reset_event)) / sizeof(uint16_t);
|
||||||
@ -542,7 +598,7 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
|
|||||||
if (flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
|
if (flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
uint16_t streamId = reset_event.strreset_stream_list[i];
|
uint16_t streamId = reset_event.strreset_stream_list[i];
|
||||||
reset(streamId);
|
close(streamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (flags & SCTP_STREAM_RESET_INCOMING_SSN) {
|
if (flags & SCTP_STREAM_RESET_INCOMING_SSN) {
|
||||||
@ -562,6 +618,28 @@ void SctpTransport::processNotification(const union sctp_notification *notify, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SctpTransport::clearStats() {
|
||||||
|
mBytesReceived = 0;
|
||||||
|
mBytesSent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SctpTransport::bytesSent() { return mBytesSent; }
|
||||||
|
|
||||||
|
size_t SctpTransport::bytesReceived() { return mBytesReceived; }
|
||||||
|
|
||||||
|
std::optional<milliseconds> SctpTransport::rtt() {
|
||||||
|
if (!mSock || state() != State::Connected)
|
||||||
|
return nullopt;
|
||||||
|
|
||||||
|
struct sctp_status status = {};
|
||||||
|
socklen_t len = sizeof(status);
|
||||||
|
if (usrsctp_getsockopt(mSock, IPPROTO_SCTP, SCTP_STATUS, &status, &len)) {
|
||||||
|
PLOG_WARNING << "Could not read SCTP_STATUS";
|
||||||
|
return nullopt;
|
||||||
|
}
|
||||||
|
return milliseconds(status.sstat_primary.spinfo_srtt);
|
||||||
|
}
|
||||||
|
|
||||||
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
|
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
|
||||||
size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) {
|
size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) {
|
||||||
int ret = static_cast<SctpTransport *>(ptr)->handleRecv(
|
int ret = static_cast<SctpTransport *>(ptr)->handleRecv(
|
||||||
|
@ -29,20 +29,15 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <winsock2.h>
|
|
||||||
#elif __linux__
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "usrsctp.h"
|
#include "usrsctp.h"
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
class SctpTransport : public Transport {
|
class SctpTransport : public Transport {
|
||||||
public:
|
public:
|
||||||
|
static void Init();
|
||||||
|
static void Cleanup();
|
||||||
|
|
||||||
enum class State { Disconnected, Connecting, Connected, Failed };
|
enum class State { Disconnected, Connecting, Connected, Failed };
|
||||||
|
|
||||||
using amount_callback = std::function<void(uint16_t streamId, size_t amount)>;
|
using amount_callback = std::function<void(uint16_t streamId, size_t amount)>;
|
||||||
@ -54,10 +49,16 @@ public:
|
|||||||
|
|
||||||
State state() const;
|
State state() const;
|
||||||
|
|
||||||
void stop() override;
|
bool stop() override;
|
||||||
bool send(message_ptr message) override; // false if buffered
|
bool send(message_ptr message) override; // false if buffered
|
||||||
|
void close(unsigned int stream);
|
||||||
void flush();
|
void flush();
|
||||||
void reset(unsigned int stream);
|
|
||||||
|
// Stats
|
||||||
|
void clearStats();
|
||||||
|
size_t bytesSent();
|
||||||
|
size_t bytesReceived();
|
||||||
|
std::optional<std::chrono::milliseconds> rtt();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Order seems wrong but these are the actual values
|
// Order seems wrong but these are the actual values
|
||||||
@ -80,6 +81,8 @@ private:
|
|||||||
bool trySendQueue();
|
bool trySendQueue();
|
||||||
bool trySendMessage(message_ptr message);
|
bool trySendMessage(message_ptr message);
|
||||||
void updateBufferedAmount(uint16_t streamId, long delta);
|
void updateBufferedAmount(uint16_t streamId, long delta);
|
||||||
|
void sendReset(uint16_t streamId);
|
||||||
|
bool safeFlush();
|
||||||
|
|
||||||
int handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data, size_t len,
|
int handleRecv(struct socket *sock, union sctp_sockstore addr, const byte *data, size_t len,
|
||||||
struct sctp_rcvinfo recv_info, int flags);
|
struct sctp_rcvinfo recv_info, int flags);
|
||||||
@ -97,28 +100,23 @@ private:
|
|||||||
std::map<uint16_t, size_t> mBufferedAmount;
|
std::map<uint16_t, size_t> mBufferedAmount;
|
||||||
amount_callback mBufferedAmountCallback;
|
amount_callback mBufferedAmountCallback;
|
||||||
|
|
||||||
std::recursive_mutex mWriteMutex;
|
std::mutex mWriteMutex;
|
||||||
std::condition_variable_any mWrittenCondition;
|
std::condition_variable mWrittenCondition;
|
||||||
bool mWritten = false;
|
std::atomic<bool> mWritten = false; // written outside lock
|
||||||
bool mWrittenOnce = false;
|
bool mWrittenOnce = false;
|
||||||
|
|
||||||
std::atomic<bool> mShutdown = false;
|
|
||||||
|
|
||||||
state_callback mStateChangeCallback;
|
state_callback mStateChangeCallback;
|
||||||
std::atomic<State> mState;
|
std::atomic<State> mState;
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
|
||||||
|
|
||||||
binary mPartialRecv, mPartialStringData, mPartialBinaryData;
|
binary mPartialRecv, mPartialStringData, mPartialBinaryData;
|
||||||
|
|
||||||
static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
||||||
struct sctp_rcvinfo recv_info, int flags, void *user_data);
|
struct sctp_rcvinfo recv_info, int flags, void *user_data);
|
||||||
static int SendCallback(struct socket *sock, uint32_t sb_free);
|
static int SendCallback(struct socket *sock, uint32_t sb_free);
|
||||||
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
|
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||||
|
|
||||||
void GlobalInit();
|
|
||||||
void GlobalCleanup();
|
|
||||||
|
|
||||||
static std::mutex GlobalMutex;
|
|
||||||
static int InstancesCount;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
@ -32,25 +32,30 @@ using namespace std::placeholders;
|
|||||||
|
|
||||||
class Transport {
|
class Transport {
|
||||||
public:
|
public:
|
||||||
Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(std::move(lower)) {
|
Transport(std::shared_ptr<Transport> lower = nullptr) : mLower(std::move(lower)) {}
|
||||||
|
virtual ~Transport() {
|
||||||
|
stop();
|
||||||
|
if (mLower)
|
||||||
|
mLower->onRecv(nullptr); // doing it on stop could cause a deadlock
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool stop() {
|
||||||
|
return !mShutdown.exchange(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerIncoming() {
|
||||||
if (mLower)
|
if (mLower)
|
||||||
mLower->onRecv(std::bind(&Transport::incoming, this, _1));
|
mLower->onRecv(std::bind(&Transport::incoming, this, _1));
|
||||||
}
|
}
|
||||||
virtual ~Transport() { stop(); }
|
|
||||||
|
|
||||||
virtual void stop() {
|
|
||||||
if (mLower)
|
|
||||||
mLower->onRecv(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool send(message_ptr message) = 0;
|
|
||||||
|
|
||||||
void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }
|
void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }
|
||||||
|
|
||||||
|
virtual bool send(message_ptr message) { return outgoing(message); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void recv(message_ptr message) { mRecvCallback(message); }
|
void recv(message_ptr message) { mRecvCallback(message); }
|
||||||
|
|
||||||
virtual void incoming(message_ptr message) = 0;
|
virtual void incoming(message_ptr message) { recv(message); }
|
||||||
virtual bool outgoing(message_ptr message) {
|
virtual bool outgoing(message_ptr message) {
|
||||||
if (mLower)
|
if (mLower)
|
||||||
return mLower->send(message);
|
return mLower->send(message);
|
||||||
@ -61,6 +66,7 @@ protected:
|
|||||||
private:
|
private:
|
||||||
std::shared_ptr<Transport> mLower;
|
std::shared_ptr<Transport> mLower;
|
||||||
synchronized_callback<message_ptr> mRecvCallback;
|
synchronized_callback<message_ptr> mRecvCallback;
|
||||||
|
std::atomic<bool> mShutdown = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
220
test/capi.cpp
Normal file
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;
|
||||||
|
}
|
136
test/main.cpp
136
test/main.cpp
@ -16,131 +16,29 @@
|
|||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "rtc/rtc.hpp"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <winsock2.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace rtc;
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
void test_connectivity();
|
||||||
|
void test_capi();
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
InitLogger(LogLevel::Warning);
|
try {
|
||||||
|
std::cout << "*** Running connectivity test..." << std::endl;
|
||||||
#ifdef _WIN32
|
test_connectivity();
|
||||||
WSADATA wsaData;
|
std::cout << "*** Finished connectivity test" << std::endl;
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
|
} catch (const exception &e) {
|
||||||
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
|
std::cerr << "Connectivity test failed: " << e.what() << endl;
|
||||||
#endif
|
return -1;
|
||||||
|
|
||||||
Configuration config;
|
|
||||||
// config.iceServers.emplace_back("stun:stun.l.google.com:19302");
|
|
||||||
// config.iceServers.emplace_back(IceServer("TURN_SERVER_URL", "PORT", "USERNAME", "PASSWORD",
|
|
||||||
// IceServer::RelayType::TurnUdp)); // libnice only
|
|
||||||
// config.enableIceTcp = true; // libnice only
|
|
||||||
|
|
||||||
auto pc1 = std::make_shared<PeerConnection>(config);
|
|
||||||
auto pc2 = std::make_shared<PeerConnection>(config);
|
|
||||||
|
|
||||||
pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](const Description &sdp) {
|
|
||||||
auto pc2 = wpc2.lock();
|
|
||||||
if (!pc2)
|
|
||||||
return;
|
|
||||||
cout << "Description 1: " << sdp << endl;
|
|
||||||
pc2->setRemoteDescription(sdp);
|
|
||||||
});
|
|
||||||
|
|
||||||
pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](const Candidate &candidate) {
|
|
||||||
auto pc2 = wpc2.lock();
|
|
||||||
if (!pc2)
|
|
||||||
return;
|
|
||||||
cout << "Candidate 1: " << candidate << endl;
|
|
||||||
pc2->addRemoteCandidate(candidate);
|
|
||||||
});
|
|
||||||
|
|
||||||
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
|
||||||
pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
|
||||||
cout << "Gathering state 1: " << state << endl;
|
|
||||||
});
|
|
||||||
|
|
||||||
pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](const Description &sdp) {
|
|
||||||
auto pc1 = wpc1.lock();
|
|
||||||
if (!pc1)
|
|
||||||
return;
|
|
||||||
cout << "Description 2: " << sdp << endl;
|
|
||||||
pc1->setRemoteDescription(sdp);
|
|
||||||
});
|
|
||||||
|
|
||||||
pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](const Candidate &candidate) {
|
|
||||||
auto pc1 = wpc1.lock();
|
|
||||||
if (!pc1)
|
|
||||||
return;
|
|
||||||
cout << "Candidate 2: " << candidate << endl;
|
|
||||||
pc1->addRemoteCandidate(candidate);
|
|
||||||
});
|
|
||||||
|
|
||||||
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
|
||||||
pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
|
||||||
cout << "Gathering state 2: " << state << endl;
|
|
||||||
});
|
|
||||||
|
|
||||||
shared_ptr<DataChannel> dc2;
|
|
||||||
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
|
||||||
cout << "Got a DataChannel with label: " << dc->label() << endl;
|
|
||||||
dc2 = dc;
|
|
||||||
dc2->onMessage([](const variant<binary, string> &message) {
|
|
||||||
if (holds_alternative<string>(message)) {
|
|
||||||
cout << "Received 2: " << get<string>(message) << endl;
|
|
||||||
}
|
}
|
||||||
});
|
try {
|
||||||
dc2->send("Hello from 2");
|
std::cout << "*** Running C API test..." << std::endl;
|
||||||
});
|
test_capi();
|
||||||
|
std::cout << "*** Finished C API test" << std::endl;
|
||||||
auto dc1 = pc1->createDataChannel("test");
|
} catch (const exception &e) {
|
||||||
dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() {
|
std::cerr << "C API test failed: " << e.what() << endl;
|
||||||
auto dc1 = wdc1.lock();
|
return -1;
|
||||||
if (!dc1)
|
|
||||||
return;
|
|
||||||
cout << "DataChannel open: " << dc1->label() << endl;
|
|
||||||
dc1->send("Hello from 1");
|
|
||||||
});
|
|
||||||
dc1->onMessage([](const variant<binary, string> &message) {
|
|
||||||
if (holds_alternative<string>(message)) {
|
|
||||||
cout << "Received 1: " << get<string>(message) << endl;
|
|
||||||
}
|
}
|
||||||
});
|
return 0;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
bool success;
|
|
||||||
if ((success = dc1->isOpen() && dc2->isOpen())) {
|
|
||||||
pc1->close();
|
|
||||||
pc2->close();
|
|
||||||
cout << "Success" << endl;
|
|
||||||
} else {
|
|
||||||
cout << "Failure" << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSACleanup();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return success ? 0 : 1;
|
|
||||||
}
|
}
|
||||||
|
@ -23,23 +23,13 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <winsock2.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
InitLogger(LogLevel::Debug);
|
InitLogger(LogLevel::Warning);
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSADATA wsaData;
|
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
|
|
||||||
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Configuration config;
|
Configuration config;
|
||||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||||
@ -86,7 +76,8 @@ int main(int argc, char **argv) {
|
|||||||
<< "* 0: Exit /"
|
<< "* 0: Exit /"
|
||||||
<< " 1: Enter remote description /"
|
<< " 1: Enter remote description /"
|
||||||
<< " 2: Enter remote candidate /"
|
<< " 2: Enter remote candidate /"
|
||||||
<< " 3: Send message *" << endl
|
<< " 3: Send message /"
|
||||||
|
<< " 4: Print Connection Info *" << endl
|
||||||
<< "[Command]: ";
|
<< "[Command]: ";
|
||||||
|
|
||||||
int command = -1;
|
int command = -1;
|
||||||
@ -130,6 +121,30 @@ int main(int argc, char **argv) {
|
|||||||
dc->send(message);
|
dc->send(message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 4: {
|
||||||
|
// Connection Info
|
||||||
|
if (!dc || !dc->isOpen()) {
|
||||||
|
cout << "** Channel is not Open ** ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CandidateInfo local, remote;
|
||||||
|
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||||
|
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||||
|
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||||
|
<< local.transportType << endl;
|
||||||
|
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||||
|
<< " " << remote.transportType << endl;
|
||||||
|
cout << "Bytes Sent:" << pc->bytesSent()
|
||||||
|
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||||
|
if (rtt.has_value())
|
||||||
|
cout << rtt.value().count();
|
||||||
|
else
|
||||||
|
cout << "null";
|
||||||
|
cout << " ms";
|
||||||
|
} else
|
||||||
|
cout << "Could not get Candidate Pair Info" << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
cout << "** Invalid Command ** ";
|
cout << "** Invalid Command ** ";
|
||||||
break;
|
break;
|
||||||
@ -141,8 +156,4 @@ int main(int argc, char **argv) {
|
|||||||
dc->close();
|
dc->close();
|
||||||
if (pc)
|
if (pc)
|
||||||
pc->close();
|
pc->close();
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSACleanup();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <winsock2.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@ -35,12 +31,6 @@ template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
|||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
InitLogger(LogLevel::Warning);
|
InitLogger(LogLevel::Warning);
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSADATA wsaData;
|
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
|
|
||||||
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Configuration config;
|
Configuration config;
|
||||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||||
|
|
||||||
@ -87,7 +77,8 @@ int main(int argc, char **argv) {
|
|||||||
<< "* 0: Exit /"
|
<< "* 0: Exit /"
|
||||||
<< " 1: Enter remote description /"
|
<< " 1: Enter remote description /"
|
||||||
<< " 2: Enter remote candidate /"
|
<< " 2: Enter remote candidate /"
|
||||||
<< " 3: Send message *" << endl
|
<< " 3: Send message /"
|
||||||
|
<< " 4: Print Connection Info *" << endl
|
||||||
<< "[Command]: ";
|
<< "[Command]: ";
|
||||||
|
|
||||||
int command = -1;
|
int command = -1;
|
||||||
@ -130,6 +121,30 @@ int main(int argc, char **argv) {
|
|||||||
dc->send(message);
|
dc->send(message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 4: {
|
||||||
|
// Connection Info
|
||||||
|
if (!dc || !dc->isOpen()) {
|
||||||
|
cout << "** Channel is not Open ** ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CandidateInfo local, remote;
|
||||||
|
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||||
|
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||||
|
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||||
|
<< local.transportType << endl;
|
||||||
|
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||||
|
<< " " << remote.transportType << endl;
|
||||||
|
cout << "Bytes Sent:" << pc->bytesSent()
|
||||||
|
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||||
|
if (rtt.has_value())
|
||||||
|
cout << rtt.value().count();
|
||||||
|
else
|
||||||
|
cout << "null";
|
||||||
|
cout << " ms";
|
||||||
|
} else
|
||||||
|
cout << "Could not get Candidate Pair Info" << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
cout << "** Invalid Command ** ";
|
cout << "** Invalid Command ** ";
|
||||||
break;
|
break;
|
||||||
@ -141,8 +156,4 @@ int main(int argc, char **argv) {
|
|||||||
dc->close();
|
dc->close();
|
||||||
if (pc)
|
if (pc)
|
||||||
pc->close();
|
pc->close();
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSACleanup();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
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