mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
dc065add0b | |||
e64d4049a6 | |||
cb3bc85474 | |||
7af3da7872 | |||
3c77d717d2 | |||
6f399945fe | |||
c8b14b1262 | |||
35d4455c4f | |||
7d21b4b42b | |||
24e9e06c5a | |||
443a19d8e7 | |||
83de743924 | |||
1dc1de4b86 | |||
8ca7722d48 | |||
3079072e63 | |||
982d1c10e1 | |||
50b22bbf3c | |||
93e153398f | |||
be6470d8bc | |||
8a92c97058 | |||
93da605230 | |||
ff0f409d80 | |||
a483e8135b | |||
36e4fdce1e | |||
ea636d1f29 | |||
472d480978 | |||
486fc373b2 | |||
2a6c10269e | |||
ed68ba5402 | |||
5f91c0c1e3 | |||
d4c618ae38 | |||
3f52aa3d56 | |||
c16ff99d83 | |||
e14b53f348 | |||
ec8847cbf8 | |||
6c4e8f0d46 | |||
e2a5d5e1fe | |||
fa8fda25c8 | |||
47c29f0ec1 | |||
a92438e94d | |||
0729ab28fd | |||
fa64b67006 | |||
b4d99158c6 | |||
0ded19992c | |||
2dece6afff | |||
0b554f988e | |||
15d29fc038 | |||
bb2c6c157d | |||
fd0f237a59 | |||
f09006f3ef | |||
06369f1f14 | |||
61354b7101 | |||
f34791b450 | |||
c3f2f6bc63 | |||
a683b76a21 | |||
e11de119be |
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -1,6 +1,9 @@
|
||||
[submodule "usrsctp"]
|
||||
path = deps/usrsctp
|
||||
url = https://github.com/sctplab/usrsctp.git
|
||||
[submodule "deps/plog"]
|
||||
path = deps/plog
|
||||
url = https://github.com/SergiusTheBest/plog
|
||||
[submodule "usrsctp"]
|
||||
path = deps/usrsctp
|
||||
url = https://github.com/sctplab/usrsctp.git
|
||||
[submodule "deps/libjuice"]
|
||||
path = deps/libjuice
|
||||
url = https://github.com/paullouisageneau/libjuice
|
||||
|
110
CMakeLists.txt
110
CMakeLists.txt
@ -1,12 +1,27 @@
|
||||
cmake_minimum_required (VERSION 3.7)
|
||||
project (libdatachannel
|
||||
DESCRIPTION "WebRTC Data Channels Library"
|
||||
VERSION 0.2.1
|
||||
DESCRIPTION "WebRTC DataChannels Library"
|
||||
VERSION 0.4.5
|
||||
LANGUAGES CXX)
|
||||
|
||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||
option(USE_JUICE "Use libjuice instead of libnice" OFF)
|
||||
|
||||
if(USE_GNUTLS)
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
||||
else()
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
|
||||
endif()
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||
|
||||
if(WIN32)
|
||||
if (MSYS OR MINGW)
|
||||
add_definitions(-DSCTP_STDINT_INCLUDE=<stdint.h>)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(LIBDATACHANNEL_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp
|
||||
@ -33,38 +48,13 @@ set(TESTS_ANSWERER_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp
|
||||
)
|
||||
|
||||
# Hack because usrsctp uses CMAKE_SOURCE_DIR instead of CMAKE_CURRENT_SOURCE_DIR
|
||||
set(CMAKE_REQUIRED_FLAGS "-I${CMAKE_CURRENT_SOURCE_DIR}/deps/usrsctp/usrsctplib")
|
||||
|
||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||
|
||||
# Set include directory and custom options to make usrsctp compile with recent g++
|
||||
target_include_directories(usrsctp-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/usrsctp/usrsctplib)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
# using regular Clang or AppleClang: Needed since they don't have -Wno-error=format-truncation
|
||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=address-of-packed-member)
|
||||
else()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0")
|
||||
# GCC version below 9.0 does not support option address-of-packed-member
|
||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
|
||||
else()
|
||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=address-of-packed-member -Wno-error=format-truncation)
|
||||
endif()
|
||||
else()
|
||||
# all other compilers
|
||||
target_compile_options(usrsctp-static PRIVATE -Wno-error=address-of-packed-member -Wno-error=format-truncation)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||
|
||||
find_package(LibNice REQUIRED)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
|
||||
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
|
||||
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
|
||||
|
||||
add_library(datachannel SHARED ${LIBDATACHANNEL_SOURCES})
|
||||
set_target_properties(datachannel PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
@ -75,10 +65,15 @@ target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inclu
|
||||
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||
target_link_libraries(datachannel
|
||||
Threads::Threads
|
||||
usrsctp-static
|
||||
LibNice::LibNice
|
||||
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})
|
||||
set_target_properties(datachannel-static PROPERTIES
|
||||
@ -91,9 +86,14 @@ target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR
|
||||
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
|
||||
target_link_libraries(datachannel-static
|
||||
Threads::Threads
|
||||
usrsctp-static
|
||||
LibNice::LibNice
|
||||
Usrsctp::UsrsctpStatic
|
||||
)
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel-static
|
||||
"wsock32" #winsock2
|
||||
"ws2_32" #winsock2
|
||||
)
|
||||
endif()
|
||||
|
||||
if (USE_GNUTLS)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
@ -117,29 +117,45 @@ else()
|
||||
target_link_libraries(datachannel-static OpenSSL::SSL)
|
||||
endif()
|
||||
|
||||
if (USE_JUICE)
|
||||
add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(datachannel PRIVATE USE_JUICE=1)
|
||||
target_link_libraries(datachannel LibJuice::LibJuiceStatic)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=1)
|
||||
target_link_libraries(datachannel-static LibJuice::LibJuiceStatic)
|
||||
else()
|
||||
find_package(LibNice REQUIRED)
|
||||
target_compile_definitions(datachannel PRIVATE USE_JUICE=0)
|
||||
target_link_libraries(datachannel LibNice::LibNice)
|
||||
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=0)
|
||||
target_link_libraries(datachannel-static LibNice::LibNice)
|
||||
endif()
|
||||
|
||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||
|
||||
# Main Test
|
||||
add_executable(tests ${TESTS_SOURCES})
|
||||
set_target_properties(tests PROPERTIES
|
||||
add_executable(datachannel-tests ${TESTS_SOURCES})
|
||||
set_target_properties(datachannel-tests PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
|
||||
target_link_libraries(tests datachannel)
|
||||
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
|
||||
target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_link_libraries(datachannel-tests datachannel)
|
||||
|
||||
# P2P Test: offerer
|
||||
add_executable(offerer ${TESTS_OFFERER_SOURCES})
|
||||
set_target_properties(offerer PROPERTIES
|
||||
add_executable(datachannel-offerer ${TESTS_OFFERER_SOURCES})
|
||||
set_target_properties(datachannel-offerer PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
|
||||
target_link_libraries(offerer datachannel)
|
||||
set_target_properties(datachannel-offerer PROPERTIES OUTPUT_NAME offerer)
|
||||
target_link_libraries(datachannel-offerer datachannel)
|
||||
|
||||
# P2P Test: answerer
|
||||
add_executable(answerer ${TESTS_ANSWERER_SOURCES})
|
||||
set_target_properties(answerer PROPERTIES
|
||||
add_executable(datachannel-answerer ${TESTS_ANSWERER_SOURCES})
|
||||
set_target_properties(datachannel-answerer PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
CXX_STANDARD 17)
|
||||
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME answerer)
|
||||
target_link_libraries(datachannel-answerer datachannel)
|
||||
|
||||
target_link_libraries(answerer datachannel)
|
||||
|
22
Jamfile
22
Jamfile
@ -7,15 +7,17 @@ lib libdatachannel
|
||||
: # requirements
|
||||
<include>./include/rtc
|
||||
<define>USE_GNUTLS=0
|
||||
<cxxflags>"`pkg-config --cflags openssl glib-2.0 gobject-2.0 nice`"
|
||||
<define>USE_JUICE=1
|
||||
<cxxflags>"`pkg-config --cflags openssl`"
|
||||
<library>/libdatachannel//usrsctp
|
||||
<library>/libdatachannel//juice
|
||||
: # default build
|
||||
<link>static
|
||||
: # usage requirements
|
||||
<include>./include
|
||||
<library>/libdatachannel//plog
|
||||
<cxxflags>-pthread
|
||||
<linkflags>"`pkg-config --libs openssl glib-2.0 gobject-2.0 nice`"
|
||||
<linkflags>"`pkg-config --libs openssl`"
|
||||
;
|
||||
|
||||
alias plog
|
||||
@ -35,6 +37,15 @@ alias usrsctp
|
||||
<library>libusrsctp.a
|
||||
;
|
||||
|
||||
alias juice
|
||||
: # no sources
|
||||
: # no build requirements
|
||||
: # no default build
|
||||
: # usage requirements
|
||||
<include>./deps/libjuice/include
|
||||
<library>libjuice.a
|
||||
;
|
||||
|
||||
make libusrsctp.a : : @make_libusrsctp ;
|
||||
actions make_libusrsctp
|
||||
{
|
||||
@ -45,3 +56,10 @@ actions make_libusrsctp
|
||||
cp $(CWD)/deps/usrsctp/usrsctplib/.libs/libusrsctp.a $(<)
|
||||
}
|
||||
|
||||
make libjuice.a : : @make_libjuice ;
|
||||
actions make_libjuice
|
||||
{
|
||||
(cd $(CWD)/deps/libjuice && make USE_NETTLE=0)
|
||||
cp $(CWD)/deps/libjuice/libjuice.a $(<)
|
||||
}
|
||||
|
||||
|
52
Makefile
52
Makefile
@ -4,24 +4,42 @@ NAME=libdatachannel
|
||||
CXX=$(CROSS)g++
|
||||
AR=$(CROSS)ar
|
||||
RM=rm -f
|
||||
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
|
||||
CXXFLAGS=-std=c++17
|
||||
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
|
||||
LDFLAGS=-pthread
|
||||
LIBS=glib-2.0 gobject-2.0 nice
|
||||
LIBS=
|
||||
LOCALLIBS=libusrsctp.a
|
||||
USRSCTP_DIR=deps/usrsctp
|
||||
JUICE_DIR=deps/libjuice
|
||||
PLOG_DIR=deps/plog
|
||||
|
||||
INCLUDES=-Iinclude/rtc -I$(PLOG_DIR)/include -I$(USRSCTP_DIR)/usrsctplib
|
||||
LDLIBS=
|
||||
|
||||
USE_GNUTLS ?= 0
|
||||
ifneq ($(USE_GNUTLS), 0)
|
||||
CPPFLAGS+= -DUSE_GNUTLS=1
|
||||
LIBS+= gnutls
|
||||
CPPFLAGS+=-DUSE_GNUTLS=1
|
||||
LIBS+=gnutls
|
||||
else
|
||||
CPPFLAGS+= -DUSE_GNUTLS=0
|
||||
LIBS+= openssl
|
||||
CPPFLAGS+=-DUSE_GNUTLS=0
|
||||
LIBS+=openssl
|
||||
endif
|
||||
|
||||
LDLIBS= $(shell pkg-config --libs $(LIBS))
|
||||
INCLUDES=-Iinclude/rtc -I$(PLOG_DIR)/include -I$(USRSCTP_DIR)/usrsctplib $(shell pkg-config --cflags $(LIBS))
|
||||
USE_JUICE ?= 0
|
||||
ifneq ($(USE_JUICE), 0)
|
||||
CPPFLAGS+=-DUSE_JUICE=1
|
||||
INCLUDES+=-I$(JUICE_DIR)/include
|
||||
LOCALLIBS+=libjuice.a
|
||||
ifneq ($(USE_GNUTLS), 0)
|
||||
LIBS+=nettle
|
||||
endif
|
||||
else
|
||||
CPPFLAGS+=-DUSE_JUICE=0
|
||||
LIBS+=glib-2.0 gobject-2.0 nice
|
||||
endif
|
||||
|
||||
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
|
||||
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
|
||||
|
||||
SRCS=$(shell printf "%s " src/*.cpp)
|
||||
OBJS=$(subst .cpp,.o,$(SRCS))
|
||||
@ -32,18 +50,18 @@ src/%.o: src/%.cpp
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) -MMD -MP -o $@ -c $<
|
||||
|
||||
test/%.o: test/%.cpp
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -Iinclude -I$(PLOG_DIR)/include -MMD -MP -o $@ -c $<
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) -Iinclude -Isrc -MMD -MP -o $@ -c $<
|
||||
|
||||
-include $(subst .cpp,.d,$(SRCS))
|
||||
|
||||
$(NAME).a: $(OBJS)
|
||||
$(AR) crf $@ $(OBJS)
|
||||
|
||||
$(NAME).so: libusrsctp.a $(OBJS)
|
||||
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS) libusrsctp.a
|
||||
$(NAME).so: $(LOCALLIBS) $(OBJS)
|
||||
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
|
||||
|
||||
tests: $(NAME).a test/main.o
|
||||
$(CXX) $(LDFLAGS) -o $@ test/main.o $(LDLIBS) $(NAME).a libusrsctp.a
|
||||
$(CXX) $(LDFLAGS) -o $@ test/main.o $(NAME).a $(LDLIBS)
|
||||
|
||||
clean:
|
||||
-$(RM) include/rtc/*.d *.d
|
||||
@ -54,11 +72,13 @@ dist-clean: clean
|
||||
-$(RM) $(NAME).a
|
||||
-$(RM) $(NAME).so
|
||||
-$(RM) libusrsctp.a
|
||||
-$(RM) libjuice.a
|
||||
-$(RM) tests
|
||||
-$(RM) include/*~
|
||||
-$(RM) src/*~
|
||||
-$(RM) test/*~
|
||||
-cd $(USRSCTP_DIR) && make clean
|
||||
-cd $(JUICE_DIR) && make clean
|
||||
|
||||
libusrsctp.a:
|
||||
cd $(USRSCTP_DIR) && \
|
||||
@ -67,3 +87,11 @@ libusrsctp.a:
|
||||
make
|
||||
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
|
||||
|
||||
libjuice.a:
|
||||
ifneq ($(USE_GNUTLS), 0)
|
||||
cd $(JUICE_DIR) && make USE_NETTLE=1
|
||||
else
|
||||
cd $(JUICE_DIR) && make USE_NETTLE=0
|
||||
endif
|
||||
cp $(JUICE_DIR)/libjuice.a .
|
||||
|
||||
|
21
README.md
21
README.md
@ -1,33 +1,46 @@
|
||||
# libdatachannel - C/C++ WebRTC DataChannels
|
||||
|
||||
libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC API, in order to ease the design of cross-environment applications.
|
||||
libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings for POSIX platforms and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC API, in order to ease the design of cross-environment applications.
|
||||
|
||||
This projet is originally inspired by [librtcdcpp](https://github.com/chadnickbok/librtcdcpp), however it is a complete rewrite from scratch, because the messy architecture of librtcdcpp made solving its implementation issues difficult.
|
||||
|
||||
The connectivity can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice). The security layer can be provided through [GnuTLS](https://www.gnutls.org/) or [OpenSSL](https://www.openssl.org/).
|
||||
|
||||
Licensed under LGPLv2, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE).
|
||||
|
||||
## Compatibility
|
||||
|
||||
The library aims at fully implementing SCTP DataChannels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13)) over DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261)) and has been tested to be compatible with Firefox and Chromium. It supports IPv6 and Multicast DNS candidates resolution ([draft-ietf-rtcweb-mdns-ice-candidates-03](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03)) provided the operating system also supports it.
|
||||
The library aims at fully implementing WebRTC SCTP DataChannels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13)) over DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261)) with ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)). It has been tested to be compatible with Firefox and Chromium. It supports IPv6 and Multicast DNS candidates resolution ([draft-ietf-rtcweb-mdns-ice-candidates-03](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03)) provided the operating system also supports it.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- libnice: https://github.com/libnice/libnice
|
||||
- GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/
|
||||
|
||||
Optional:
|
||||
- libnice: https://nice.freedesktop.org/ (substituable with libjuice)
|
||||
|
||||
Submodules:
|
||||
- usrsctp: https://github.com/sctplab/usrsctp
|
||||
- libjuice: https://github.com/paullouisageneau/libjuice
|
||||
|
||||
## Building
|
||||
### Building with CMake (preferred)
|
||||
|
||||
```bash
|
||||
$ git submodule update --init --recursive
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake -DUSE_GNUTLS=1 ..
|
||||
$ cmake -DUSE_JUICE=1 -DUSE_GNUTLS=1 ..
|
||||
$ make
|
||||
```
|
||||
|
||||
### Building directly with Make
|
||||
|
||||
```bash
|
||||
$ git submodule update --init --recursive
|
||||
$ make USE_JUICE=1 USE_GNUTLS=1
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
In the following example, note the callbacks are called in another thread.
|
||||
|
1
deps/libjuice
vendored
Submodule
1
deps/libjuice
vendored
Submodule
Submodule deps/libjuice added at 856475ebd1
2
deps/usrsctp
vendored
2
deps/usrsctp
vendored
Submodule deps/usrsctp updated: 04d617c9c1...aa10d60bc2
@ -28,15 +28,20 @@ namespace rtc {
|
||||
|
||||
struct IceServer {
|
||||
enum class Type { Stun, Turn };
|
||||
|
||||
// Don' Change It! It should be same order as enum NiceRelayType
|
||||
enum class RelayType { TurnUdp, TurnTcp, TurnTls };
|
||||
|
||||
IceServer(const string &host_);
|
||||
IceServer(const string &hostname_, uint16_t port_);
|
||||
IceServer(const string &hostname_, const string &service_);
|
||||
IceServer(const string &hostname_, const string &service_, string username_, string password_,
|
||||
RelayType relayType_);
|
||||
// Any type
|
||||
IceServer(const string &url);
|
||||
|
||||
// STUN
|
||||
IceServer(string hostname_, uint16_t port_);
|
||||
IceServer(string hostname_, string service_);
|
||||
|
||||
// TURN
|
||||
IceServer(string hostname_, uint16_t port, string username_, string password_,
|
||||
RelayType relayType_ = RelayType::TurnUdp);
|
||||
IceServer(string hostname_, string service_, string username_, string password_,
|
||||
RelayType relayType_ = RelayType::TurnUdp);
|
||||
|
||||
string hostname;
|
||||
string service;
|
||||
|
@ -35,6 +35,7 @@ public:
|
||||
enum class Role { ActPass = 0, Passive = 1, Active = 2 };
|
||||
|
||||
Description(const string &sdp, const string &typeString = "");
|
||||
Description(const string &sdp, Type type);
|
||||
Description(const string &sdp, Type type, Role role);
|
||||
|
||||
Type type() const;
|
||||
@ -47,6 +48,7 @@ public:
|
||||
std::optional<size_t> maxMessageSize() const;
|
||||
bool trickleEnabled() const;
|
||||
|
||||
void hintType(Type type);
|
||||
void setFingerprint(string fingerprint);
|
||||
void setSctpPort(uint16_t port);
|
||||
void setMaxMessageSize(size_t size);
|
||||
@ -57,6 +59,8 @@ public:
|
||||
|
||||
operator string() const;
|
||||
|
||||
string generateSdp(const string &eol) const;
|
||||
|
||||
private:
|
||||
Type mType;
|
||||
Role mRole;
|
||||
|
@ -19,6 +19,12 @@
|
||||
#ifndef RTC_INCLUDE_H
|
||||
#define RTC_INCLUDE_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0602
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@ -44,6 +50,8 @@ using std::uint32_t;
|
||||
using std::uint64_t;
|
||||
using std::uint8_t;
|
||||
|
||||
// Constants
|
||||
|
||||
const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
|
||||
const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length
|
||||
|
||||
@ -51,16 +59,9 @@ const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
|
||||
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
|
||||
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
|
||||
|
||||
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";
|
||||
}
|
||||
// Log
|
||||
|
||||
// Don't change, it must match plog severity
|
||||
enum class LogLevel {
|
||||
enum class LogLevel { // Don't change, it must match plog severity
|
||||
None = 0,
|
||||
Fatal = 1,
|
||||
Error = 2,
|
||||
@ -70,8 +71,18 @@ enum class LogLevel {
|
||||
Verbose = 6
|
||||
};
|
||||
|
||||
inline void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr) {
|
||||
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
|
||||
if (!appender)
|
||||
appender = &consoleAppender;
|
||||
plog::init(severity, appender);
|
||||
PLOG_DEBUG << "Logger initialized";
|
||||
}
|
||||
|
||||
inline void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
|
||||
|
||||
// Utils
|
||||
|
||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
||||
|
||||
|
@ -22,8 +22,14 @@
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#elif __linux__
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
using std::array;
|
||||
@ -54,7 +60,7 @@ bool Candidate::resolve(ResolveMode mode) {
|
||||
if (mIsResolved)
|
||||
return true;
|
||||
|
||||
// See RFC 5245 for format
|
||||
// See RFC 8445 for format
|
||||
std::stringstream ss(mCandidate);
|
||||
int component{0}, priority{0};
|
||||
string foundation, transport, node, service, typ_, type;
|
||||
|
@ -18,29 +18,70 @@
|
||||
|
||||
#include "configuration.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
using std::to_string;
|
||||
IceServer::IceServer(const string &url) {
|
||||
// Modified regex from RFC 3986, see https://tools.ietf.org/html/rfc3986#appendix-B
|
||||
static const char *rs =
|
||||
R"(^(([^:.@/?#]+):)?(/{0,2}((([^:@]*)(:([^@]*))?)@)?(([^:/?#]*)(:([^/?#]*))?))?([^?#]*)(\?([^#]*))?(#(.*))?)";
|
||||
static const std::regex r(rs, std::regex::extended);
|
||||
|
||||
IceServer::IceServer(const string &host) : type(Type::Stun) {
|
||||
if (size_t pos = host.rfind(':'); pos != string::npos) {
|
||||
hostname = host.substr(0, pos);
|
||||
service = host.substr(pos + 1);
|
||||
} else {
|
||||
hostname = host;
|
||||
service = "3478"; // STUN UDP port
|
||||
std::smatch m;
|
||||
if (!std::regex_match(url, m, r) || m[10].length() == 0)
|
||||
throw std::invalid_argument("Invalid ICE server URL: " + url);
|
||||
|
||||
std::vector<std::optional<string>> opt(m.size());
|
||||
std::transform(m.begin(), m.end(), opt.begin(), [](const auto &sm) {
|
||||
return sm.length() > 0 ? std::make_optional(string(sm)) : nullopt;
|
||||
});
|
||||
|
||||
string scheme = opt[2].value_or("stun");
|
||||
if (scheme == "stun" || scheme == "STUN")
|
||||
type = Type::Stun;
|
||||
else if (scheme == "turn" || scheme == "TURN")
|
||||
type = Type::Turn;
|
||||
else if (scheme == "turns" || scheme == "TURNS")
|
||||
type = Type::Turn;
|
||||
else
|
||||
throw std::invalid_argument("Unknown ICE server protocol: " + scheme);
|
||||
|
||||
relayType = RelayType::TurnUdp;
|
||||
if (auto &query = opt[15]) {
|
||||
if (query->find("transport=udp") != string::npos)
|
||||
relayType = RelayType::TurnUdp;
|
||||
if (query->find("transport=tcp") != string::npos)
|
||||
relayType = RelayType::TurnTcp;
|
||||
if (query->find("transport=tls") != string::npos)
|
||||
relayType = RelayType::TurnTls;
|
||||
}
|
||||
|
||||
username = opt[6].value_or("");
|
||||
password = opt[8].value_or("");
|
||||
hostname = opt[10].value();
|
||||
service = opt[12].value_or(relayType == RelayType::TurnTls ? "5349" : "3478");
|
||||
|
||||
while (!hostname.empty() && hostname.front() == '[')
|
||||
hostname.erase(hostname.begin());
|
||||
while (!hostname.empty() && hostname.back() == ']')
|
||||
hostname.pop_back();
|
||||
}
|
||||
|
||||
IceServer::IceServer(const string &hostname_, uint16_t port_)
|
||||
: IceServer(hostname_, to_string(port_)) {}
|
||||
IceServer::IceServer(string hostname_, uint16_t port_)
|
||||
: IceServer(std::move(hostname_), std::to_string(port_)) {}
|
||||
|
||||
IceServer::IceServer(const string &hostname_, const string &service_)
|
||||
: hostname(hostname_), service(service_), type(Type::Stun) {}
|
||||
IceServer::IceServer(string hostname_, string service_)
|
||||
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Stun) {}
|
||||
|
||||
IceServer::IceServer(const string &hostname_, const string &service_, string username_,
|
||||
string password_, RelayType relayType_)
|
||||
: hostname(hostname_), service(service_), type(Type::Turn), username(username_),
|
||||
password(password_), relayType(relayType_) {}
|
||||
IceServer::IceServer(string hostname_, uint16_t port_, string username_, string password_,
|
||||
RelayType relayType_)
|
||||
: IceServer(hostname_, std::to_string(port_), std::move(username_), std::move(password_),
|
||||
relayType_) {}
|
||||
|
||||
IceServer::IceServer(string hostname_, string service_, string username_, string password_,
|
||||
RelayType relayType_)
|
||||
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Turn),
|
||||
username(std::move(username_)), password(std::move(password_)), relayType(relayType_) {}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -45,12 +45,13 @@ inline void trim_end(string &str) {
|
||||
namespace rtc {
|
||||
|
||||
Description::Description(const string &sdp, const string &typeString)
|
||||
: Description(sdp, stringToType(typeString), Description::Role::ActPass) {}
|
||||
: Description(sdp, stringToType(typeString)) {}
|
||||
|
||||
Description::Description(const string &sdp, Type type) : Description(sdp, type, Role::ActPass) {}
|
||||
|
||||
Description::Description(const string &sdp, Type type, Role role)
|
||||
: mType(type), mRole(role), mMid("0"), mIceUfrag("0"), mIcePwd("0"), mTrickle(true) {
|
||||
if (mType == Type::Answer && mRole == Role::ActPass)
|
||||
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
|
||||
: mType(Type::Unspec), mRole(role), mMid("0"), mIceUfrag(""), mIcePwd(""), mTrickle(true) {
|
||||
hintType(type);
|
||||
|
||||
auto seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
std::default_random_engine generator(seed);
|
||||
@ -109,6 +110,14 @@ std::optional<size_t> Description::maxMessageSize() const { return mMaxMessageSi
|
||||
|
||||
bool Description::trickleEnabled() const { return mTrickle; }
|
||||
|
||||
void Description::hintType(Type type) {
|
||||
if (mType == Type::Unspec) {
|
||||
mType = type;
|
||||
if (mType == Type::Answer && mRole == Role::ActPass)
|
||||
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
|
||||
}
|
||||
}
|
||||
|
||||
void Description::setFingerprint(string fingerprint) {
|
||||
mFingerprint.emplace(std::move(fingerprint));
|
||||
}
|
||||
@ -130,37 +139,39 @@ std::vector<Candidate> Description::extractCandidates() {
|
||||
return result;
|
||||
}
|
||||
|
||||
Description::operator string() const {
|
||||
Description::operator string() const { return generateSdp("\r\n"); }
|
||||
|
||||
string Description::generateSdp(const string &eol) const {
|
||||
if (!mFingerprint)
|
||||
throw std::logic_error("Fingerprint must be set to generate a SDP");
|
||||
throw std::logic_error("Fingerprint must be set to generate an SDP string");
|
||||
|
||||
std::ostringstream sdp;
|
||||
sdp << "v=0\n";
|
||||
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1\n";
|
||||
sdp << "s=-\n";
|
||||
sdp << "t=0 0\n";
|
||||
sdp << "a=group:BUNDLE 0\n";
|
||||
sdp << "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\n";
|
||||
sdp << "c=IN IP4 0.0.0.0\n";
|
||||
sdp << "a=ice-ufrag:" << mIceUfrag << "\n";
|
||||
sdp << "a=ice-pwd:" << mIcePwd << "\n";
|
||||
sdp << "v=0" << eol;
|
||||
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "s=-" << eol;
|
||||
sdp << "t=0 0" << eol;
|
||||
sdp << "a=group:BUNDLE 0" << eol;
|
||||
sdp << "m=application 9 UDP/DTLS/SCTP webrtc-datachannel" << eol;
|
||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
||||
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
||||
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
||||
if (mTrickle)
|
||||
sdp << "a=ice-options:trickle\n";
|
||||
sdp << "a=mid:" << mMid << "\n";
|
||||
sdp << "a=setup:" << roleToString(mRole) << "\n";
|
||||
sdp << "a=dtls-id:1\n";
|
||||
sdp << "a=ice-options:trickle" << eol;
|
||||
sdp << "a=mid:" << mMid << eol;
|
||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||
sdp << "a=dtls-id:1" << eol;
|
||||
if (mFingerprint)
|
||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << "\n";
|
||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||
if (mSctpPort)
|
||||
sdp << "a=sctp-port:" << *mSctpPort << "\n";
|
||||
sdp << "a=sctp-port:" << *mSctpPort << eol;
|
||||
if (mMaxMessageSize)
|
||||
sdp << "a=max-message-size:" << *mMaxMessageSize << "\n";
|
||||
sdp << "a=max-message-size:" << *mMaxMessageSize << eol;
|
||||
for (const auto &candidate : mCandidates) {
|
||||
sdp << string(candidate) << "\n";
|
||||
sdp << string(candidate) << eol;
|
||||
}
|
||||
|
||||
if (!mTrickle)
|
||||
sdp << "a=end-of-candidates\n";
|
||||
sdp << "a=end-of-candidates" << eol;
|
||||
|
||||
return sdp.str();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include "dtlstransport.hpp"
|
||||
#include "icetransport.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
@ -72,7 +73,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
|
||||
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL";
|
||||
const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL:+COMP-NULL";
|
||||
const char *err_pos = NULL;
|
||||
check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
|
||||
"Unable to set TLS priorities");
|
||||
@ -270,7 +271,7 @@ int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms)
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#else
|
||||
#else // USE_GNUTLS==0
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/ec.h>
|
||||
@ -318,11 +319,21 @@ bool check_openssl_ret(SSL *ssl, int ret, const string &message = "OpenSSL error
|
||||
|
||||
namespace rtc {
|
||||
|
||||
BIO_METHOD *DtlsTransport::BioMethods = NULL;
|
||||
int DtlsTransport::TransportExIndex = -1;
|
||||
std::mutex DtlsTransport::GlobalMutex;
|
||||
|
||||
void DtlsTransport::GlobalInit() {
|
||||
std::lock_guard lock(GlobalMutex);
|
||||
if (!BioMethods) {
|
||||
BioMethods = BIO_meth_new(BIO_TYPE_BIO, "DTLS writer");
|
||||
if (!BioMethods)
|
||||
throw std::runtime_error("Unable to BIO methods for DTLS writer");
|
||||
BIO_meth_set_create(BioMethods, BioMethodNew);
|
||||
BIO_meth_set_destroy(BioMethods, BioMethodFree);
|
||||
BIO_meth_set_write(BioMethods, BioMethodWrite);
|
||||
BIO_meth_set_ctrl(BioMethods, BioMethodCtrl);
|
||||
}
|
||||
if (TransportExIndex < 0) {
|
||||
TransportExIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
@ -346,7 +357,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
|
||||
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION);
|
||||
SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_NO_QUERY_MTU);
|
||||
SSL_CTX_set_min_proto_version(mCtx, DTLS1_VERSION);
|
||||
SSL_CTX_set_read_ahead(mCtx, 1);
|
||||
SSL_CTX_set_quiet_shutdown(mCtx, 1);
|
||||
@ -362,7 +373,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
check_openssl(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed");
|
||||
|
||||
if (!(mSsl = SSL_new(mCtx)))
|
||||
throw std::runtime_error("Unable to create SSL instance");
|
||||
throw std::runtime_error("Unable to create SSL instance");
|
||||
|
||||
SSL_set_ex_data(mSsl, TransportExIndex, this);
|
||||
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
|
||||
@ -372,11 +383,11 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
else
|
||||
SSL_set_accept_state(mSsl);
|
||||
|
||||
if (!(mInBio = BIO_new(BIO_s_mem())) || !(mOutBio = BIO_new(BIO_s_mem())))
|
||||
throw std::runtime_error("Unable to create BIO");
|
||||
if (!(mInBio = BIO_new(BIO_s_mem())) || !(mOutBio = BIO_new(BioMethods)))
|
||||
throw std::runtime_error("Unable to create BIO");
|
||||
|
||||
BIO_set_mem_eof_return(mInBio, BIO_EOF);
|
||||
BIO_set_mem_eof_return(mOutBio, BIO_EOF);
|
||||
BIO_set_data(mOutBio, this);
|
||||
SSL_set_bio(mSsl, mInBio, mOutBio);
|
||||
|
||||
auto ecdh = unique_ptr<EC_KEY, decltype(&EC_KEY_free)>(
|
||||
@ -403,7 +414,6 @@ void DtlsTransport::stop() {
|
||||
mRecvThread.join();
|
||||
|
||||
SSL_shutdown(mSsl);
|
||||
writePending();
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,11 +426,8 @@ bool DtlsTransport::send(message_ptr message) {
|
||||
PLOG_VERBOSE << "Send size=" << message->size();
|
||||
|
||||
int ret = SSL_write(mSsl, message->data(), message->size());
|
||||
if (!check_openssl_ret(mSsl, ret)) {
|
||||
if (!check_openssl_ret(mSsl, ret))
|
||||
return false;
|
||||
}
|
||||
|
||||
writePending();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -437,15 +444,14 @@ void DtlsTransport::changeState(State state) {
|
||||
}
|
||||
|
||||
void DtlsTransport::runRecvLoop() {
|
||||
const size_t bufferSize = 4096;
|
||||
byte buffer[bufferSize];
|
||||
|
||||
const size_t maxMtu = 4096;
|
||||
try {
|
||||
changeState(State::Connecting);
|
||||
|
||||
SSL_do_handshake(mSsl);
|
||||
writePending();
|
||||
|
||||
const size_t bufferSize = maxMtu;
|
||||
byte buffer[bufferSize];
|
||||
while (auto next = mIncomingQueue.pop()) {
|
||||
auto message = *next;
|
||||
BIO_write(mInBio, message->data(), message->size());
|
||||
@ -459,9 +465,13 @@ void DtlsTransport::runRecvLoop() {
|
||||
if (unsigned long err = ERR_get_error())
|
||||
throw std::runtime_error("handshake failed: " + openssl_error_string(err));
|
||||
|
||||
writePending();
|
||||
if (SSL_is_init_finished(mSsl))
|
||||
if (SSL_is_init_finished(mSsl)) {
|
||||
changeState(State::Connected);
|
||||
|
||||
// RFC 8261: DTLS MUST support sending messages larger than the current path MTU
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
SSL_set_mtu(mSsl, maxMtu + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (decrypted)
|
||||
@ -481,16 +491,6 @@ void DtlsTransport::runRecvLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
void DtlsTransport::writePending() {
|
||||
const size_t bufferSize = 4096;
|
||||
byte buffer[bufferSize];
|
||||
while (BIO_ctrl_pending(mOutBio) > 0) {
|
||||
int ret = BIO_read(mOutBio, buffer, bufferSize);
|
||||
if (check_openssl_ret(mSsl, ret) && ret > 0)
|
||||
outgoing(make_message(buffer, buffer + ret));
|
||||
}
|
||||
}
|
||||
|
||||
int DtlsTransport::CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx) {
|
||||
SSL *ssl =
|
||||
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
||||
@ -515,6 +515,45 @@ void DtlsTransport::InfoCallback(const SSL *ssl, int where, int ret) {
|
||||
}
|
||||
}
|
||||
|
||||
int DtlsTransport::BioMethodNew(BIO *bio) {
|
||||
BIO_set_init(bio, 1);
|
||||
BIO_set_data(bio, NULL);
|
||||
BIO_set_shutdown(bio, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DtlsTransport::BioMethodFree(BIO *bio) {
|
||||
if (!bio)
|
||||
return 0;
|
||||
BIO_set_data(bio, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
|
||||
if (inl <= 0)
|
||||
return inl;
|
||||
auto transport = reinterpret_cast<DtlsTransport *>(BIO_get_data(bio));
|
||||
if (!transport)
|
||||
return -1;
|
||||
auto b = reinterpret_cast<const byte *>(in);
|
||||
return transport->outgoing(make_message(b, b + inl)) ? inl : 0;
|
||||
}
|
||||
|
||||
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) {
|
||||
switch (cmd) {
|
||||
case BIO_CTRL_FLUSH:
|
||||
return 1;
|
||||
case BIO_CTRL_DGRAM_QUERY_MTU:
|
||||
return 0; // SSL_OP_NO_QUERY_MTU must be set
|
||||
case BIO_CTRL_WPENDING:
|
||||
case BIO_CTRL_PENDING:
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -79,18 +79,22 @@ private:
|
||||
static ssize_t ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen);
|
||||
static int TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms);
|
||||
#else
|
||||
void writePending();
|
||||
|
||||
SSL_CTX *mCtx;
|
||||
SSL *mSsl;
|
||||
BIO *mInBio, *mOutBio;
|
||||
|
||||
static BIO_METHOD *BioMethods;
|
||||
static int TransportExIndex;
|
||||
static std::mutex GlobalMutex;
|
||||
|
||||
static void GlobalInit();
|
||||
static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx);
|
||||
static void InfoCallback(const SSL *ssl, int where, int ret);
|
||||
|
||||
static int BioMethodNew(BIO *bio);
|
||||
static int BioMethodFree(BIO *bio);
|
||||
static int BioMethodWrite(BIO *bio, const char *in, int inl);
|
||||
static long BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
||||
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -17,33 +17,258 @@
|
||||
*/
|
||||
|
||||
#include "icetransport.hpp"
|
||||
#include "configuration.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#elif __linux__
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::weak_ptr;
|
||||
|
||||
#if USE_JUICE
|
||||
|
||||
namespace rtc {
|
||||
|
||||
IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
candidate_callback candidateCallback, state_callback stateChangeCallback,
|
||||
gathering_state_callback gatheringStateChangeCallback)
|
||||
: mRole(role), mMid("0"), mState(State::Disconnected), mGatheringState(GatheringState::New),
|
||||
mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr),
|
||||
mCandidateCallback(std::move(candidateCallback)),
|
||||
mStateChangeCallback(std::move(stateChangeCallback)),
|
||||
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)) {
|
||||
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
|
||||
mAgent(nullptr, nullptr) {
|
||||
|
||||
PLOG_DEBUG << "Initializing ICE transport";
|
||||
PLOG_DEBUG << "Initializing ICE transport (libjuice)";
|
||||
if (config.enableIceTcp) {
|
||||
PLOG_WARNING << "ICE-TCP is not supported with libjuice";
|
||||
}
|
||||
juice_set_log_handler(IceTransport::LogCallback);
|
||||
juice_set_log_level(JUICE_LOG_LEVEL_VERBOSE);
|
||||
|
||||
juice_config_t jconfig = {};
|
||||
jconfig.cb_state_changed = IceTransport::StateChangeCallback;
|
||||
jconfig.cb_candidate = IceTransport::CandidateCallback;
|
||||
jconfig.cb_gathering_done = IceTransport::GatheringDoneCallback;
|
||||
jconfig.cb_recv = IceTransport::RecvCallback;
|
||||
jconfig.user_ptr = this;
|
||||
|
||||
// Randomize servers order
|
||||
std::vector<IceServer> servers = config.iceServers;
|
||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
||||
|
||||
// Pick a STUN server
|
||||
for (auto &server : servers) {
|
||||
if (!server.hostname.empty() && server.type == IceServer::Type::Stun) {
|
||||
if (server.service.empty())
|
||||
server.service = "3478"; // STUN UDP port
|
||||
PLOG_DEBUG << "Using STUN server \"" << server.hostname << ":" << server.service
|
||||
<< "\"";
|
||||
mStunHostname = server.hostname;
|
||||
mStunService = server.service;
|
||||
jconfig.stun_server_host = mStunHostname.c_str();
|
||||
jconfig.stun_server_port = std::stoul(mStunService);
|
||||
}
|
||||
}
|
||||
|
||||
// TURN support is not implemented yet
|
||||
|
||||
// Create agent
|
||||
mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy);
|
||||
if (!mAgent)
|
||||
throw std::runtime_error("Failed to create the ICE agent");
|
||||
}
|
||||
|
||||
IceTransport::~IceTransport() { stop(); }
|
||||
|
||||
void IceTransport::stop() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
Description::Role IceTransport::role() const { return mRole; }
|
||||
|
||||
IceTransport::State IceTransport::state() const { return mState; }
|
||||
|
||||
Description IceTransport::getLocalDescription(Description::Type type) const {
|
||||
char sdp[JUICE_MAX_SDP_STRING_LEN];
|
||||
if (juice_get_local_description(mAgent.get(), sdp, JUICE_MAX_SDP_STRING_LEN) < 0)
|
||||
throw std::runtime_error("Failed to generate local SDP");
|
||||
|
||||
return Description(string(sdp), type, mRole);
|
||||
}
|
||||
|
||||
void IceTransport::setRemoteDescription(const Description &description) {
|
||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||
: Description::Role::Active;
|
||||
mMid = description.mid();
|
||||
if (juice_set_remote_description(mAgent.get(), string(description).c_str()) < 0)
|
||||
throw std::runtime_error("Failed to parse remote SDP");
|
||||
}
|
||||
|
||||
bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
|
||||
// Don't try to pass unresolved candidates for more safety
|
||||
if (!candidate.isResolved())
|
||||
return false;
|
||||
|
||||
return juice_add_remote_candidate(mAgent.get(), string(candidate).c_str()) >= 0;
|
||||
}
|
||||
|
||||
void IceTransport::gatherLocalCandidates() {
|
||||
// Change state now as candidates calls can be synchronous
|
||||
changeGatheringState(GatheringState::InProgress);
|
||||
|
||||
if (juice_gather_candidates(mAgent.get()) < 0) {
|
||||
throw std::runtime_error("Failed to gather local ICE candidates");
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<string> IceTransport::getLocalAddress() const {
|
||||
char str[JUICE_MAX_ADDRESS_STRING_LEN];
|
||||
if (juice_get_selected_addresses(mAgent.get(), str, JUICE_MAX_ADDRESS_STRING_LEN, NULL, 0) ==
|
||||
0) {
|
||||
return std::make_optional(string(str));
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
std::optional<string> IceTransport::getRemoteAddress() const {
|
||||
char str[JUICE_MAX_ADDRESS_STRING_LEN];
|
||||
if (juice_get_selected_addresses(mAgent.get(), NULL, 0, str, JUICE_MAX_ADDRESS_STRING_LEN) ==
|
||||
0) {
|
||||
return std::make_optional(string(str));
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool IceTransport::send(message_ptr message) {
|
||||
if (!message || (mState != State::Connected && mState != State::Completed))
|
||||
return false;
|
||||
|
||||
PLOG_VERBOSE << "Send size=" << message->size();
|
||||
return outgoing(message);
|
||||
}
|
||||
|
||||
void IceTransport::incoming(message_ptr message) { recv(message); }
|
||||
|
||||
void IceTransport::incoming(const byte *data, int size) {
|
||||
incoming(make_message(data, data + size));
|
||||
}
|
||||
|
||||
bool IceTransport::outgoing(message_ptr message) {
|
||||
return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()),
|
||||
message->size()) >= 0;
|
||||
}
|
||||
|
||||
void IceTransport::changeState(State state) {
|
||||
if (mState.exchange(state) != state)
|
||||
mStateChangeCallback(mState);
|
||||
}
|
||||
|
||||
void IceTransport::changeGatheringState(GatheringState state) {
|
||||
if (mGatheringState.exchange(state) != state)
|
||||
mGatheringStateChangeCallback(mGatheringState);
|
||||
}
|
||||
|
||||
void IceTransport::processStateChange(unsigned int state) {
|
||||
changeState(static_cast<State>(state));
|
||||
}
|
||||
|
||||
void IceTransport::processCandidate(const string &candidate) {
|
||||
mCandidateCallback(Candidate(candidate, mMid));
|
||||
}
|
||||
|
||||
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
|
||||
|
||||
void IceTransport::StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
iceTransport->processStateChange(static_cast<unsigned int>(state));
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void IceTransport::CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
iceTransport->processCandidate(sdp);
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void IceTransport::GatheringDoneCallback(juice_agent_t *agent, void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
iceTransport->processGatheringDone();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void IceTransport::RecvCallback(juice_agent_t *agent, const char *data, size_t size,
|
||||
void *user_ptr) {
|
||||
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
|
||||
try {
|
||||
iceTransport->incoming(reinterpret_cast<const byte *>(data), size);
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
|
||||
plog::Severity severity;
|
||||
switch (level) {
|
||||
case JUICE_LOG_LEVEL_FATAL:
|
||||
severity = plog::fatal;
|
||||
break;
|
||||
case JUICE_LOG_LEVEL_ERROR:
|
||||
severity = plog::error;
|
||||
break;
|
||||
case JUICE_LOG_LEVEL_WARN:
|
||||
severity = plog::warning;
|
||||
break;
|
||||
case JUICE_LOG_LEVEL_INFO:
|
||||
severity = plog::info;
|
||||
break;
|
||||
case JUICE_LOG_LEVEL_DEBUG:
|
||||
severity = plog::debug;
|
||||
break;
|
||||
default:
|
||||
severity = plog::verbose;
|
||||
break;
|
||||
}
|
||||
PLOG(severity) << "juice: " << message;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#else // USE_JUICE == 0
|
||||
|
||||
namespace rtc {
|
||||
|
||||
IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
candidate_callback candidateCallback, state_callback stateChangeCallback,
|
||||
gathering_state_callback gatheringStateChangeCallback)
|
||||
: mRole(role), mMid("0"), mState(State::Disconnected), mGatheringState(GatheringState::New),
|
||||
mCandidateCallback(std::move(candidateCallback)),
|
||||
mStateChangeCallback(std::move(stateChangeCallback)),
|
||||
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
|
||||
mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr) {
|
||||
|
||||
PLOG_DEBUG << "Initializing ICE transport (libnice)";
|
||||
|
||||
g_log_set_handler("libnice", G_LOG_LEVEL_MASK, LogCallback, this);
|
||||
|
||||
@ -55,6 +280,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
if (!mMainLoop)
|
||||
std::runtime_error("Failed to create the main loop");
|
||||
|
||||
// RFC 5245 was obsoleted by RFC 8445 but this should be OK.
|
||||
mNiceAgent = decltype(mNiceAgent)(
|
||||
nice_agent_new(g_main_loop_get_context(mMainLoop.get()), NICE_COMPATIBILITY_RFC5245),
|
||||
g_object_unref);
|
||||
@ -64,25 +290,37 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
|
||||
mMainLoopThread = std::thread(g_main_loop_run, mMainLoop.get());
|
||||
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", TRUE, nullptr);
|
||||
mStreamId = nice_agent_add_stream(mNiceAgent.get(), 1);
|
||||
if (!mStreamId)
|
||||
throw std::runtime_error("Failed to add a stream");
|
||||
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", TRUE, nullptr); // decided later
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-udp", TRUE, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-tcp", config.enableIceTcp ? TRUE : FALSE,
|
||||
nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-initial-timeout", 200, nullptr);
|
||||
|
||||
// RFC 8445: Agents MUST NOT use an RTO value smaller than 500 ms.
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-initial-timeout", 500, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-max-retransmissions", 3, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-pacing-timer", 20, nullptr);
|
||||
|
||||
// RFC 8445: ICE agents SHOULD use a default Ta value, 50 ms, but MAY use another value based on
|
||||
// the characteristics of the associated data.
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-pacing-timer", 25, nullptr);
|
||||
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr);
|
||||
|
||||
// Randomize order
|
||||
std::vector<IceServer> servers = config.iceServers;
|
||||
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
|
||||
|
||||
// Add one STUN server
|
||||
bool success = false;
|
||||
for (auto &server : servers) {
|
||||
if (server.hostname.empty())
|
||||
continue;
|
||||
if (server.type == IceServer::Type::Turn)
|
||||
if (server.type != IceServer::Type::Stun)
|
||||
continue;
|
||||
if (server.service.empty())
|
||||
server.service = "3478"; // STUN UDP port
|
||||
@ -101,9 +339,10 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
char nodebuffer[MAX_NUMERICNODE_LEN];
|
||||
char servbuffer[MAX_NUMERICSERV_LEN];
|
||||
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
|
||||
|
||||
servbuffer, MAX_NUMERICNODE_LEN,
|
||||
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
|
||||
PLOG_DEBUG << "Using STUN server \"" << server.hostname << ":" << server.service
|
||||
<< "\"";
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server", nodebuffer, nullptr);
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server-port",
|
||||
std::stoul(servbuffer), nullptr);
|
||||
@ -118,35 +357,17 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
break;
|
||||
}
|
||||
|
||||
g_signal_connect(G_OBJECT(mNiceAgent.get()), "component-state-changed",
|
||||
G_CALLBACK(StateChangeCallback), this);
|
||||
g_signal_connect(G_OBJECT(mNiceAgent.get()), "new-candidate-full",
|
||||
G_CALLBACK(CandidateCallback), this);
|
||||
g_signal_connect(G_OBJECT(mNiceAgent.get()), "candidate-gathering-done",
|
||||
G_CALLBACK(GatheringDoneCallback), this);
|
||||
|
||||
mStreamId = nice_agent_add_stream(mNiceAgent.get(), 1);
|
||||
if (!mStreamId)
|
||||
throw std::runtime_error("Failed to add a stream");
|
||||
|
||||
nice_agent_set_stream_name(mNiceAgent.get(), mStreamId, "application");
|
||||
nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1, config.portRangeBegin,
|
||||
config.portRangeEnd);
|
||||
|
||||
nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(mMainLoop.get()),
|
||||
RecvCallback, this);
|
||||
|
||||
// Add TURN Servers
|
||||
// Add TURN servers
|
||||
for (auto &server : servers) {
|
||||
if (server.hostname.empty())
|
||||
continue;
|
||||
if (server.type == IceServer::Type::Stun)
|
||||
if (server.type != IceServer::Type::Turn)
|
||||
continue;
|
||||
if (server.service.empty())
|
||||
server.service = "3478"; // TURN UDP port
|
||||
server.service = server.relayType == IceServer::RelayType::TurnTls ? "5349" : "3478";
|
||||
|
||||
struct addrinfo hints = {};
|
||||
hints.ai_family = AF_INET; // IPv4
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype =
|
||||
server.relayType == IceServer::RelayType::TurnUdp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
hints.ai_protocol =
|
||||
@ -157,24 +378,48 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
continue;
|
||||
|
||||
for (auto p = result; p; p = p->ai_next) {
|
||||
if (p->ai_family == AF_INET) {
|
||||
if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
|
||||
char nodebuffer[MAX_NUMERICNODE_LEN];
|
||||
char servbuffer[MAX_NUMERICSERV_LEN];
|
||||
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
|
||||
|
||||
servbuffer, MAX_NUMERICNODE_LEN,
|
||||
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
|
||||
|
||||
NiceRelayType niceRelayType;
|
||||
switch (server.relayType) {
|
||||
case IceServer::RelayType::TurnTcp:
|
||||
niceRelayType = NICE_RELAY_TYPE_TURN_TCP;
|
||||
break;
|
||||
case IceServer::RelayType::TurnTls:
|
||||
niceRelayType = NICE_RELAY_TYPE_TURN_TLS;
|
||||
break;
|
||||
default:
|
||||
niceRelayType = NICE_RELAY_TYPE_TURN_UDP;
|
||||
break;
|
||||
}
|
||||
nice_agent_set_relay_info(mNiceAgent.get(), mStreamId, 1, nodebuffer,
|
||||
std::stoul(servbuffer), server.username.c_str(),
|
||||
server.password.c_str(),
|
||||
static_cast<NiceRelayType>(server.relayType));
|
||||
break;
|
||||
server.password.c_str(), niceRelayType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
g_signal_connect(G_OBJECT(mNiceAgent.get()), "component-state-changed",
|
||||
G_CALLBACK(StateChangeCallback), this);
|
||||
g_signal_connect(G_OBJECT(mNiceAgent.get()), "new-candidate-full",
|
||||
G_CALLBACK(CandidateCallback), this);
|
||||
g_signal_connect(G_OBJECT(mNiceAgent.get()), "candidate-gathering-done",
|
||||
G_CALLBACK(GatheringDoneCallback), this);
|
||||
|
||||
nice_agent_set_stream_name(mNiceAgent.get(), mStreamId, "application");
|
||||
nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1, config.portRangeBegin,
|
||||
config.portRangeEnd);
|
||||
|
||||
nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(mMainLoop.get()),
|
||||
RecvCallback, this);
|
||||
}
|
||||
|
||||
IceTransport::~IceTransport() { stop(); }
|
||||
@ -196,8 +441,8 @@ Description::Role IceTransport::role() const { return mRole; }
|
||||
IceTransport::State IceTransport::state() const { return mState; }
|
||||
|
||||
Description IceTransport::getLocalDescription(Description::Type type) const {
|
||||
// RFC 5245: The agent that generated the offer which started the ICE processing MUST take the
|
||||
// controlling role, and the other MUST take the controlled role.
|
||||
// RFC 8445: The initiating agent that started the ICE processing MUST take the controlling
|
||||
// role, and the other MUST take the controlled role.
|
||||
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode",
|
||||
type == Description::Type::Offer ? TRUE : FALSE, nullptr);
|
||||
|
||||
@ -212,7 +457,8 @@ void IceTransport::setRemoteDescription(const Description &description) {
|
||||
mMid = description.mid();
|
||||
mTrickleTimeout = description.trickleEnabled() ? 30s : 0s;
|
||||
|
||||
if (nice_agent_parse_remote_sdp(mNiceAgent.get(), string(description).c_str()) < 0)
|
||||
// Warning: libnice expects "\n" as end of line
|
||||
if (nice_agent_parse_remote_sdp(mNiceAgent.get(), description.generateSdp("\n").c_str()) < 0)
|
||||
throw std::runtime_error("Failed to parse remote SDP");
|
||||
}
|
||||
|
||||
@ -253,6 +499,7 @@ std::optional<string> IceTransport::getLocalAddress() const {
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::optional<string> IceTransport::getRemoteAddress() const {
|
||||
NiceCandidate *local = nullptr;
|
||||
NiceCandidate *remote = nullptr;
|
||||
@ -286,24 +533,24 @@ void IceTransport::changeState(State state) {
|
||||
mStateChangeCallback(mState);
|
||||
}
|
||||
|
||||
void IceTransport::changeGatheringState(GatheringState state) {
|
||||
if (mGatheringState.exchange(state) != state)
|
||||
mGatheringStateChangeCallback(mGatheringState);
|
||||
}
|
||||
|
||||
void IceTransport::processTimeout() {
|
||||
PLOG_WARNING << "ICE timeout";
|
||||
mTimeoutId = 0;
|
||||
changeState(State::Failed);
|
||||
}
|
||||
|
||||
void IceTransport::changeGatheringState(GatheringState state) {
|
||||
mGatheringState = state;
|
||||
mGatheringStateChangeCallback(mGatheringState);
|
||||
}
|
||||
|
||||
void IceTransport::processCandidate(const string &candidate) {
|
||||
mCandidateCallback(Candidate(candidate, mMid));
|
||||
}
|
||||
|
||||
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
|
||||
|
||||
void IceTransport::processStateChange(uint32_t state) {
|
||||
void IceTransport::processStateChange(unsigned int state) {
|
||||
if (state == NICE_COMPONENT_STATE_FAILED && mTrickleTimeout.count() > 0) {
|
||||
if (mTimeoutId)
|
||||
g_source_remove(mTimeoutId);
|
||||
@ -396,7 +643,9 @@ void IceTransport::LogCallback(const gchar *logDomain, GLogLevelFlags logLevel,
|
||||
else
|
||||
severity = plog::verbose; // libnice debug as verbose
|
||||
|
||||
PLOG(severity) << message;
|
||||
PLOG(severity) << "nice: " << message;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
||||
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -26,9 +26,11 @@
|
||||
#include "peerconnection.hpp"
|
||||
#include "transport.hpp"
|
||||
|
||||
extern "C" {
|
||||
#if USE_JUICE
|
||||
#include <juice/juice.h>
|
||||
#else
|
||||
#include <nice/agent.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@ -38,14 +40,23 @@ namespace rtc {
|
||||
|
||||
class IceTransport : public Transport {
|
||||
public:
|
||||
enum class State : uint32_t {
|
||||
#if USE_JUICE
|
||||
enum class State : unsigned int{
|
||||
Disconnected = JUICE_STATE_DISCONNECTED,
|
||||
Connecting = JUICE_STATE_CONNECTING,
|
||||
Connected = JUICE_STATE_CONNECTED,
|
||||
Completed = JUICE_STATE_COMPLETED,
|
||||
Failed = JUICE_STATE_FAILED,
|
||||
};
|
||||
#else
|
||||
enum class State : unsigned int {
|
||||
Disconnected = NICE_COMPONENT_STATE_DISCONNECTED,
|
||||
Connecting = NICE_COMPONENT_STATE_CONNECTING,
|
||||
Connected = NICE_COMPONENT_STATE_CONNECTED,
|
||||
Completed = NICE_COMPONENT_STATE_READY,
|
||||
Failed = NICE_COMPONENT_STATE_FAILED
|
||||
Failed = NICE_COMPONENT_STATE_FAILED,
|
||||
};
|
||||
|
||||
#endif
|
||||
enum class GatheringState { New = 0, InProgress = 1, Complete = 2 };
|
||||
|
||||
using candidate_callback = std::function<void(const Candidate &candidate)>;
|
||||
@ -79,9 +90,9 @@ private:
|
||||
void changeState(State state);
|
||||
void changeGatheringState(GatheringState state);
|
||||
|
||||
void processStateChange(unsigned int state);
|
||||
void processCandidate(const string &candidate);
|
||||
void processGatheringDone();
|
||||
void processStateChange(uint32_t state);
|
||||
void processTimeout();
|
||||
|
||||
Description::Role mRole;
|
||||
@ -90,27 +101,39 @@ private:
|
||||
std::atomic<State> mState;
|
||||
std::atomic<GatheringState> mGatheringState;
|
||||
|
||||
candidate_callback mCandidateCallback;
|
||||
state_callback mStateChangeCallback;
|
||||
gathering_state_callback mGatheringStateChangeCallback;
|
||||
|
||||
#if USE_JUICE
|
||||
std::unique_ptr<juice_agent_t, void (*)(juice_agent_t *)> mAgent;
|
||||
string mStunHostname;
|
||||
string mStunService;
|
||||
|
||||
static void StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr);
|
||||
static void CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr);
|
||||
static void GatheringDoneCallback(juice_agent_t *agent, void *user_ptr);
|
||||
static void RecvCallback(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
|
||||
static void LogCallback(juice_log_level_t level, const char *message);
|
||||
#else
|
||||
uint32_t mStreamId = 0;
|
||||
std::unique_ptr<NiceAgent, void (*)(gpointer)> mNiceAgent;
|
||||
std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop;
|
||||
std::thread mMainLoopThread;
|
||||
guint mTimeoutId = 0;
|
||||
|
||||
candidate_callback mCandidateCallback;
|
||||
state_callback mStateChangeCallback;
|
||||
gathering_state_callback mGatheringStateChangeCallback;
|
||||
|
||||
static string AddressToString(const NiceAddress &addr);
|
||||
|
||||
static void CandidateCallback(NiceAgent *agent, NiceCandidate *candidate, gpointer userData);
|
||||
static void GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData);
|
||||
static void StateChangeCallback(NiceAgent *agent, guint streamId, guint componentId,
|
||||
guint state, gpointer userData);
|
||||
guint state, gpointer userData);
|
||||
static void RecvCallback(NiceAgent *agent, guint stream_id, guint component_id, guint len,
|
||||
gchar *buf, gpointer userData);
|
||||
static gboolean TimeoutCallback(gpointer userData);
|
||||
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
|
||||
gpointer user_data);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -79,9 +79,10 @@ std::optional<Description> PeerConnection::remoteDescription() const {
|
||||
}
|
||||
|
||||
void PeerConnection::setRemoteDescription(Description description) {
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
|
||||
description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
|
||||
auto remoteCandidates = description.extractCandidates();
|
||||
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
mRemoteDescription.emplace(std::move(description));
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
@ -209,108 +210,126 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
|
||||
}
|
||||
|
||||
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mIceTransport))
|
||||
return transport;
|
||||
try {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mIceTransport))
|
||||
return transport;
|
||||
|
||||
auto transport = std::make_shared<IceTransport>(
|
||||
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
|
||||
[this](IceTransport::State state) {
|
||||
switch (state) {
|
||||
case IceTransport::State::Connecting:
|
||||
changeState(State::Connecting);
|
||||
break;
|
||||
case IceTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case IceTransport::State::Connected:
|
||||
initDtlsTransport();
|
||||
break;
|
||||
case IceTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
},
|
||||
[this](IceTransport::GatheringState state) {
|
||||
switch (state) {
|
||||
case IceTransport::GatheringState::InProgress:
|
||||
changeGatheringState(GatheringState::InProgress);
|
||||
break;
|
||||
case IceTransport::GatheringState::Complete:
|
||||
endLocalCandidates();
|
||||
changeGatheringState(GatheringState::Complete);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mIceTransport, transport);
|
||||
return transport;
|
||||
auto transport = std::make_shared<IceTransport>(
|
||||
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
|
||||
[this](IceTransport::State state) {
|
||||
switch (state) {
|
||||
case IceTransport::State::Connecting:
|
||||
changeState(State::Connecting);
|
||||
break;
|
||||
case IceTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case IceTransport::State::Connected:
|
||||
initDtlsTransport();
|
||||
break;
|
||||
case IceTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
},
|
||||
[this](IceTransport::GatheringState state) {
|
||||
switch (state) {
|
||||
case IceTransport::GatheringState::InProgress:
|
||||
changeGatheringState(GatheringState::InProgress);
|
||||
break;
|
||||
case IceTransport::GatheringState::Complete:
|
||||
endLocalCandidates();
|
||||
changeGatheringState(GatheringState::Complete);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mIceTransport, transport);
|
||||
return transport;
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << e.what();
|
||||
changeState(State::Failed);
|
||||
throw std::runtime_error("ICE transport initialization failed");
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
||||
return transport;
|
||||
try {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
||||
return transport;
|
||||
|
||||
auto lower = std::atomic_load(&mIceTransport);
|
||||
auto transport = std::make_shared<DtlsTransport>(
|
||||
lower, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
|
||||
[this](DtlsTransport::State state) {
|
||||
switch (state) {
|
||||
case DtlsTransport::State::Connected:
|
||||
initSctpTransport();
|
||||
break;
|
||||
case DtlsTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case DtlsTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mDtlsTransport, transport);
|
||||
return transport;
|
||||
auto lower = std::atomic_load(&mIceTransport);
|
||||
auto transport = std::make_shared<DtlsTransport>(
|
||||
lower, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
|
||||
[this](DtlsTransport::State state) {
|
||||
switch (state) {
|
||||
case DtlsTransport::State::Connected:
|
||||
initSctpTransport();
|
||||
break;
|
||||
case DtlsTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case DtlsTransport::State::Disconnected:
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mDtlsTransport, transport);
|
||||
return transport;
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << e.what();
|
||||
changeState(State::Failed);
|
||||
throw std::runtime_error("DTLS transport initialization failed");
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||
return transport;
|
||||
try {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||
return transport;
|
||||
|
||||
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
||||
auto lower = std::atomic_load(&mDtlsTransport);
|
||||
auto transport = std::make_shared<SctpTransport>(
|
||||
lower, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1),
|
||||
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
|
||||
[this](SctpTransport::State state) {
|
||||
switch (state) {
|
||||
case SctpTransport::State::Connected:
|
||||
changeState(State::Connected);
|
||||
openDataChannels();
|
||||
break;
|
||||
case SctpTransport::State::Failed:
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case SctpTransport::State::Disconnected:
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mSctpTransport, transport);
|
||||
return transport;
|
||||
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
||||
auto lower = std::atomic_load(&mDtlsTransport);
|
||||
auto transport = std::make_shared<SctpTransport>(
|
||||
lower, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1),
|
||||
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
|
||||
[this](SctpTransport::State state) {
|
||||
switch (state) {
|
||||
case SctpTransport::State::Connected:
|
||||
changeState(State::Connected);
|
||||
openDataChannels();
|
||||
break;
|
||||
case SctpTransport::State::Failed:
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Failed);
|
||||
break;
|
||||
case SctpTransport::State::Disconnected:
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Disconnected);
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
});
|
||||
std::atomic_store(&mSctpTransport, transport);
|
||||
return transport;
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << e.what();
|
||||
changeState(State::Failed);
|
||||
throw std::runtime_error("SCTP transport initialization failed");
|
||||
}
|
||||
}
|
||||
|
||||
void PeerConnection::endLocalCandidates() {
|
||||
|
@ -23,7 +23,28 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_JUICE
|
||||
#ifndef __APPLE__
|
||||
// libjuice enables Linux path MTU discovery or sets the DF flag
|
||||
#define USE_PMTUD 1
|
||||
#else
|
||||
// Setting the DF flag is not available on Mac OS
|
||||
#define USE_PMTUD 0
|
||||
#endif
|
||||
#else
|
||||
#ifdef __linux__
|
||||
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE)
|
||||
// It should be safe to enable discovery for SCTP.
|
||||
#define USE_PMTUD 1
|
||||
#else
|
||||
// Otherwise assume fragmentation
|
||||
#define USE_PMTUD 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace std::chrono;
|
||||
@ -117,12 +138,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
|
||||
std::to_string(errno));
|
||||
|
||||
struct sctp_paddrparams spp = {};
|
||||
#ifdef __linux__
|
||||
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE).
|
||||
// It should be safe to enable discovery for SCTP.
|
||||
#if USE_PMTUD
|
||||
// Enabled SCTP path MTU discovery
|
||||
spp.spp_flags = SPP_PMTUD_ENABLE;
|
||||
#else
|
||||
// Otherwise, fall back to a safe MTU value.
|
||||
// Fall back to a safe MTU value.
|
||||
spp.spp_flags = SPP_PMTUD_DISABLE;
|
||||
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
@ -353,7 +373,7 @@ bool SctpTransport::trySendMessage(message_ptr message) {
|
||||
if (ret >= 0) {
|
||||
PLOG_VERBOSE << "SCTP sent size=" << message->size();
|
||||
return true;
|
||||
} else if (errno == EWOULDBLOCK && errno == EAGAIN) {
|
||||
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
PLOG_VERBOSE << "SCTP sending not possible";
|
||||
return false;
|
||||
} else {
|
||||
|
@ -29,7 +29,12 @@
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#elif __linux__
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "usrsctp.h"
|
||||
|
@ -23,22 +23,29 @@
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
|
||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// InitLogger(LogLevel::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;
|
||||
|
||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||
// config.enableIceTcp = true;
|
||||
|
||||
// Add TURN Server Example
|
||||
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
|
||||
// IceServer::RelayType::TurnTls);
|
||||
// config.iceServers.push_back(turnServer);
|
||||
// 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);
|
||||
@ -113,14 +120,27 @@ int main(int argc, char **argv) {
|
||||
|
||||
this_thread::sleep_for(3s);
|
||||
|
||||
if (dc1->isOpen() && dc2->isOpen()) {
|
||||
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;
|
||||
return 0;
|
||||
} else {
|
||||
cout << "Failure" << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
@ -21,6 +21,11 @@
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
@ -28,44 +33,46 @@ using namespace std;
|
||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// InitLogger(LogLevel::Debug);
|
||||
InitLogger(LogLevel::Debug);
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
|
||||
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
|
||||
#endif
|
||||
|
||||
Configuration config;
|
||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||
// config.enableIceTcp = true;
|
||||
|
||||
// Add TURN Server Example
|
||||
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
|
||||
// IceServer::RelayType::TurnTls);
|
||||
// config.iceServers.push_back(turnServer);
|
||||
|
||||
auto pc = std::make_shared<PeerConnection>(config);
|
||||
|
||||
pc->onLocalDescription([](const Description &sdp) {
|
||||
std::string s(sdp);
|
||||
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
|
||||
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
|
||||
pc->onLocalDescription([](const Description &description) {
|
||||
cout << "Local Description (Paste this to the other peer):" << endl;
|
||||
cout << string(description) << endl;
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
||||
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl;
|
||||
cout << "Local Candidate (Paste this to the other peer after the local description):"
|
||||
<< endl;
|
||||
cout << string(candidate) << endl << endl;
|
||||
});
|
||||
|
||||
pc->onStateChange(
|
||||
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
|
||||
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
|
||||
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "[ Gathering State: " << state << " ]" << endl;
|
||||
cout << "[Gathering State: " << state << "]" << endl;
|
||||
});
|
||||
|
||||
shared_ptr<DataChannel> dc = nullptr;
|
||||
pc->onDataChannel([&](shared_ptr<DataChannel> _dc) {
|
||||
cout << "[ Got a DataChannel with label: " << _dc->label() << " ]" << endl;
|
||||
cout << "[Got a DataChannel with label: " << _dc->label() << "]" << endl;
|
||||
dc = _dc;
|
||||
|
||||
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; });
|
||||
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
|
||||
|
||||
dc->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "[ Received: " << get<string>(message) << " ]" << endl;
|
||||
cout << "[Received message: " << get<string>(message) << "]" << endl;
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -73,71 +80,69 @@ int main(int argc, char **argv) {
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
cout << endl
|
||||
<< "**********************************************************************************"
|
||||
"*****"
|
||||
<< endl
|
||||
<< "*************************************************************************" << endl
|
||||
<< "* 0: Exit /"
|
||||
<< " 1: Enter Description /"
|
||||
<< " 2: Enter Candidate /"
|
||||
<< " 3: Send Message *" << endl
|
||||
<< " [Command]: ";
|
||||
<< " 1: Enter remote description /"
|
||||
<< " 2: Enter remote candidate /"
|
||||
<< " 3: Send message *" << endl
|
||||
<< "[Command]: ";
|
||||
|
||||
int command;
|
||||
std::string sdp, candidate, message;
|
||||
const char *a;
|
||||
std::unique_ptr<Candidate> candidatePtr;
|
||||
std::unique_ptr<Description> descPtr;
|
||||
int command = -1;
|
||||
cin >> command;
|
||||
cin.ignore();
|
||||
|
||||
switch (command) {
|
||||
case 0:
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
cout << "[SDP]: ";
|
||||
sdp = "";
|
||||
while (sdp.length() == 0)
|
||||
getline(cin, sdp);
|
||||
|
||||
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n');
|
||||
descPtr = std::make_unique<Description>(sdp, Description::Type::Offer,
|
||||
Description::Role::Passive);
|
||||
pc->setRemoteDescription(*descPtr);
|
||||
cout << "[Description]: ";
|
||||
string sdp, line;
|
||||
while (getline(cin, line) && !line.empty()) {
|
||||
sdp += line;
|
||||
sdp += "\r\n";
|
||||
}
|
||||
std::cout << sdp;
|
||||
pc->setRemoteDescription(sdp);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
cout << "[Candidate]: ";
|
||||
candidate = "";
|
||||
while (candidate.length() == 0)
|
||||
getline(cin, candidate);
|
||||
|
||||
candidatePtr = std::make_unique<Candidate>(candidate);
|
||||
pc->addRemoteCandidate(*candidatePtr);
|
||||
string candidate;
|
||||
getline(cin, candidate);
|
||||
pc->addRemoteCandidate(candidate);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!dc || !dc->isOpen()) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
cout << "[Message]: ";
|
||||
message = "";
|
||||
while (message.length() == 0)
|
||||
getline(cin, message);
|
||||
string message;
|
||||
getline(cin, message);
|
||||
dc->send(message);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
cout << "** Invalid Command ** ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dc)
|
||||
dc->close();
|
||||
if (pc)
|
||||
pc->close();
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
@ -21,6 +21,11 @@
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
@ -28,113 +33,116 @@ using namespace std;
|
||||
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// InitLogger(LogLevel::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;
|
||||
// config.iceServers.emplace_back("stun.l.google.com:19302");
|
||||
// config.enableIceTcp = true;
|
||||
|
||||
// Add TURN Server Example
|
||||
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
|
||||
// IceServer::RelayType::TurnTls);
|
||||
// config.iceServers.push_back(turnServer);
|
||||
|
||||
auto pc = std::make_shared<PeerConnection>(config);
|
||||
|
||||
pc->onLocalDescription([](const Description &sdp) {
|
||||
std::string s(sdp);
|
||||
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94));
|
||||
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
|
||||
pc->onLocalDescription([](const Description &description) {
|
||||
cout << "Local Description (Paste this to the other peer):" << endl;
|
||||
cout << string(description) << endl;
|
||||
});
|
||||
|
||||
pc->onLocalCandidate([](const Candidate &candidate) {
|
||||
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl;
|
||||
cout << "Local Candidate (Paste this to the other peer after the local description):"
|
||||
<< endl;
|
||||
cout << string(candidate) << endl << endl;
|
||||
});
|
||||
|
||||
pc->onStateChange(
|
||||
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; });
|
||||
[](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
|
||||
|
||||
pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
|
||||
cout << "[ Gathering State: " << state << " ]" << endl;
|
||||
cout << "[Gathering State: " << state << "]" << endl;
|
||||
});
|
||||
|
||||
auto dc = pc->createDataChannel("test");
|
||||
dc->onOpen([&]() { cout << "[ DataChannel open: " << dc->label() << " ]" << endl; });
|
||||
auto dc = pc->createDataChannel("test"); // this is the offerer, so create a data channel
|
||||
|
||||
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; });
|
||||
dc->onOpen([&]() { cout << "[DataChannel open: " << dc->label() << "]" << endl; });
|
||||
|
||||
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
|
||||
|
||||
dc->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "[ Received: " << get<string>(message) << " ]" << endl;
|
||||
cout << "[Received: " << get<string>(message) << "]" << endl;
|
||||
}
|
||||
});
|
||||
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
bool exit = false;
|
||||
while (!exit) {
|
||||
cout << endl
|
||||
<< "**********************************************************************************"
|
||||
"*****"
|
||||
<< endl
|
||||
<< "*************************************************************************" << endl
|
||||
<< "* 0: Exit /"
|
||||
<< " 1: Enter Description /"
|
||||
<< " 2: Enter Candidate /"
|
||||
<< " 3: Send Message *" << endl
|
||||
<< " [Command]: ";
|
||||
<< " 1: Enter remote description /"
|
||||
<< " 2: Enter remote candidate /"
|
||||
<< " 3: Send message *" << endl
|
||||
<< "[Command]: ";
|
||||
|
||||
int command;
|
||||
std::string sdp, candidate, message;
|
||||
const char *a;
|
||||
std::unique_ptr<Candidate> candidatePtr;
|
||||
std::unique_ptr<Description> descPtr;
|
||||
int command = -1;
|
||||
cin >> command;
|
||||
cin.ignore();
|
||||
|
||||
switch (command) {
|
||||
case 0:
|
||||
case 0: {
|
||||
exit = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
}
|
||||
case 1: {
|
||||
// Parse Description
|
||||
cout << "[SDP]: ";
|
||||
sdp = "";
|
||||
while (sdp.length() == 0)
|
||||
getline(cin, sdp);
|
||||
|
||||
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n');
|
||||
descPtr = std::make_unique<Description>(sdp);
|
||||
pc->setRemoteDescription(*descPtr);
|
||||
cout << "[Description]: ";
|
||||
string sdp, line;
|
||||
while (getline(cin, line) && !line.empty()) {
|
||||
sdp += line;
|
||||
sdp += "\r\n";
|
||||
}
|
||||
pc->setRemoteDescription(sdp);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
// Parse Candidate
|
||||
cout << "[Candidate]: ";
|
||||
candidate = "";
|
||||
while (candidate.length() == 0)
|
||||
getline(cin, candidate);
|
||||
|
||||
candidatePtr = std::make_unique<Candidate>(candidate);
|
||||
pc->addRemoteCandidate(*candidatePtr);
|
||||
string candidate;
|
||||
getline(cin, candidate);
|
||||
pc->addRemoteCandidate(candidate);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
// Send Message
|
||||
if (!dc->isOpen()) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
cout << "[Message]: ";
|
||||
message = "";
|
||||
while (message.length() == 0)
|
||||
getline(cin, message);
|
||||
string message;
|
||||
getline(cin, message);
|
||||
dc->send(message);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
cout << "** Invalid Command ** ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dc)
|
||||
dc->close();
|
||||
if (pc)
|
||||
pc->close();
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
Reference in New Issue
Block a user